Преглед изворни кода

feat(server): 预习作业结构确定

Go пре 5 година
родитељ
комит
6a9a91e5a2
100 измењених фајлова са 2683 додато и 1128 уклоњено
  1. 3 5
      front/project/Constant.js
  2. 3 2
      front/project/admin/components/Association/index.js
  3. 1 1
      front/project/admin/routes/course/ask/page.js
  4. 9 2
      front/project/admin/routes/course/data/page.js
  5. 1 1
      front/project/admin/routes/course/dataHistory/page.js
  6. 1 1
      front/project/admin/routes/course/experience/page.js
  7. 1 1
      front/project/admin/routes/course/invoice/page.js
  8. 1 1
      front/project/admin/routes/course/list/page.js
  9. 1 1
      front/project/admin/routes/course/package/page.js
  10. 41 35
      front/project/admin/routes/course/student/page.js
  11. 9 1
      front/project/admin/routes/course/study/page.js
  12. 1 1
      front/project/admin/routes/setting/comment/page.js
  13. 1 1
      front/project/admin/routes/setting/faqConsult/page.js
  14. 1 1
      front/project/admin/routes/setting/faqSystem/page.js
  15. 1 1
      front/project/admin/routes/setting/message/page.js
  16. 1 1
      front/project/admin/routes/setting/rank/page.js
  17. 1 1
      front/project/admin/routes/setting/tips/page.js
  18. 1 1
      front/project/admin/routes/subject/examination/page.js
  19. 60 62
      front/project/admin/routes/subject/exercise/page.js
  20. 78 71
      front/project/admin/routes/subject/preview/page.js
  21. 185 68
      front/project/admin/routes/subject/previewDetail/page.js
  22. 1 1
      front/project/admin/routes/subject/sentence/page.js
  23. 1 1
      front/project/admin/routes/subject/textbook/page.js
  24. 1 1
      front/project/admin/routes/subject/textbookLibrary/page.js
  25. 1 1
      front/project/admin/routes/system/manager/list/page.js
  26. 1 1
      front/project/admin/routes/user/ask/page.js
  27. 116 38
      front/project/admin/routes/user/detail/page.js
  28. 1 1
      front/project/admin/routes/user/feedback/page.js
  29. 3 2
      front/project/admin/routes/user/index.js
  30. 83 71
      front/project/admin/routes/user/list/page.js
  31. 1 1
      front/project/admin/routes/user/preview/page.js
  32. 15 0
      front/project/admin/routes/user/recordAll/index.js
  33. 3 0
      front/project/admin/routes/user/recordAll/index.less
  34. 1 7
      front/project/admin/routes/user/service/page.js
  35. 3 3
      front/project/admin/routes/user/service/index.js
  36. 1 1
      front/project/admin/routes/user/service/index.less
  37. 134 0
      front/project/admin/routes/user/recordBuy/page.js
  38. 1 1
      front/project/admin/routes/user/student/page.js
  39. 21 1
      front/project/admin/stores/preview.js
  40. 27 7
      front/project/admin/stores/user.js
  41. 3 3
      front/project/www/routes/examination/main/page.js
  42. 10 0
      front/src/containers/Page.js
  43. 6 6
      front/src/services/Tools.js
  44. 1 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/trade/RecordSource.java
  45. 47 3
      server/data/src/main/java/com/qxgmat/data/dao/entity/Comment.java
  46. 42 7
      server/data/src/main/java/com/qxgmat/data/dao/entity/Faq.java
  47. 156 16
      server/data/src/main/java/com/qxgmat/data/dao/entity/PreviewAssign.java
  48. 35 35
      server/data/src/main/java/com/qxgmat/data/dao/entity/User.java
  49. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrderRecord.java
  50. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CommentMapper.xml
  51. 3 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/FaqMapper.xml
  52. 7 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/PreviewAssignMapper.xml
  53. 3 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserMapper.xml
  54. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderRecordMapper.xml
  55. 18 0
      server/data/src/main/java/com/qxgmat/data/relation/PreviewPaperRelationMapper.java
  56. 0 15
      server/data/src/main/java/com/qxgmat/data/relation/UserPaperRelationMapper.java
  57. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/CourseExperienceRelationMapper.xml
  58. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/CoursePackageRelationMapper.xml
  59. 3 3
      server/data/src/main/java/com/qxgmat/data/relation/mapping/CourseRelationMapper.xml
  60. 31 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/PreviewPaperRelationMapper.xml
  61. 0 58
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserPaperRelationMapper.xml
  62. 2 2
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserRelationMapper.xml
  63. 4 1
      server/data/src/main/resources/mybatis-generator.xml
  64. 2 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/AdController.java
  65. 18 2
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java
  66. 2 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/ExaminationController.java
  67. 2 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/ExerciseController.java
  68. 86 51
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/PreviewController.java
  69. 4 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SentenceController.java
  70. 9 3
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  71. 143 61
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  72. 55 71
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  73. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/UserExtendDto.java
  74. 99 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/PreviewAssignDto.java
  75. 30 23
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/PreviewDto.java
  76. 37 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserAbnormalHandlerDto.java
  77. 27 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserMoneyDto.java
  78. 67 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserRealDto.java
  79. 0 90
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/PreviewListDto.java
  80. 112 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/PreviewPaperListDto.java
  81. 91 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserAbnormalInfoDto.java
  82. 301 1
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserDetailDto.java
  83. 40 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserOrderListDto.java
  84. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserServiceRecordInfoDto.java
  85. 0 1
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserTextbookFeedbackInfoDto.java
  86. 0 69
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserPreviewPaperExtendDto.java
  87. 4 4
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseDetailDto.java
  88. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserQuestionDetailDto.java
  89. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserTextbookInfoDto.java
  90. 2 1
      server/gateway-api/src/main/java/com/qxgmat/help/AiHelp.java
  91. 16 4
      server/gateway-api/src/main/java/com/qxgmat/service/UsersService.java
  92. 2 1
      server/gateway-api/src/main/java/com/qxgmat/service/annotation/InitRecord.java
  93. 9 0
      server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java
  94. 39 14
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  95. 0 134
      server/gateway-api/src/main/java/com/qxgmat/service/extend/PreviewService.java
  96. 65 18
      server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java
  97. 9 0
      server/gateway-api/src/main/java/com/qxgmat/service/extend/SentenceService.java
  98. 99 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewAssignService.java
  99. 34 22
      server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewPaperService.java
  100. 0 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserAbnormalService.java

+ 3 - 5
front/project/Constant.js

@@ -16,10 +16,6 @@ export const MoneyRange = [{ label: '0', value: 0 }, { label: '1-1000', value: 1
 
 export const AskTarget = [{ label: '题目', value: 'question', title: '题目', key: 'question' }, { label: '官方解析', value: 'official', title: '官方解析', key: 'official' }, { label: '千行解析', value: 'qx', title: '千行解析', key: 'qx' }, { label: '题源联想', value: 'association', title: '题源联想', key: 'association' }, { label: '相关问答', value: 'qa', title: '相关问答', key: 'qa' }];
 
-export const PreviewStatus = [{ label: '全部', value: 0 }, { label: '未开始', value: 1 }, { label: '进行中', value: 2 }, { label: '已结束', value: 3 }];
-
-export const PreviewMode = [{ label: '指定作业', value: 0 }, { label: '系统作业', value: 1 }];
-
 export const ServiceKey = [{ label: 'VIP', value: 'vip' }, { label: '机经', value: 'textbook' }, { label: '千行CAT', value: 'qx_cat' }];
 
 export const ServiceParamMap = {
@@ -32,6 +28,8 @@ export const CourseSource = [{ label: '网上报名', value: 'bank' }, { label:
 
 export const SwitchSelect = [{ value: 0, label: '否' }, { value: 1, label: '是' }];
 
+export const PassSelect = [{ value: 0, label: '未认证' }, { value: 1, label: '通过' }];
+
 export const AskStatus = [{ value: 0, label: '新增' }, { value: 1, label: '已回答' }, { value: 2, label: '忽略' }];
 
 export const FeedbackStatus = [{ value: 0, label: '新增' }, { value: 1, label: '已处理' }, { value: 2, label: '忽略' }, { value: 3, label: '无需修改' }];
@@ -42,7 +40,7 @@ export const PrepareStatus = [{ label: '学生-Domestic', value: 'student_domest
 
 export const PrepareExaminationTime = [{ label: '近1个月', value: 'one_month' }, { label: '近2个月', value: 'two_month' }, { label: '近3个月', value: 'three_month' }, { label: '半年内', value: 'six_month' }];
 
-export const PayModule = [{ label: '服务', value: 'service' }, { label: '课程', value: 'class' }, { label: '资料', value: 'data' }];
+export const ProductType = [{ label: '服务', value: 'service' }, { label: '课程', value: 'course' }, { label: '资料', value: 'data' }, { label: '课程-套餐', value: 'course-package' }];
 
 export const QuestionStyleType = [{ label: '单选', value: 'single' }, { label: '横纵向单选', value: 'double' }, { label: '题目内选择', value: 'inline' }];
 

+ 3 - 2
front/project/admin/components/Association/index.js

@@ -29,7 +29,7 @@ class Association extends Component {
       if (err) {
         return;
       }
-      if (this.props.onConfirm && this.props.modal) {
+      if (this.props.onConfirm) {
         this.setState({ loading: true });
         this.props
           .onConfirm(fieldsValue)
@@ -73,6 +73,7 @@ class Association extends Component {
       const map = getMap(result, 'id');
       const questionNos = values.map(row => map[row]).filter(row => row);
       this.setState({ questionNos, loading: false });
+      if (!this.props.modal) this.onConfirm();
     });
   }
 
@@ -128,7 +129,7 @@ class Association extends Component {
         {err && <Alert type="error" showIcon message={err} closable onClose={() => this.setState({ err: '' })} />}
         {this.renderForm()}
       </Modal>
-    ) : (<div>{this.renderForm()}</div>);
+    ) : (<div><h1>{title}</h1>{this.renderForm()}</div>);
   }
 }
 export default Form.create()(Association);

+ 1 - 1
front/project/admin/routes/course/ask/page.js

@@ -197,7 +197,7 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       /> */}
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 9 - 2
front/project/admin/routes/course/data/page.js

@@ -7,7 +7,7 @@ import Block from '@src/components/Block';
 import FilterLayout from '@src/layouts/FilterLayout';
 import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { getMap, formatTreeData } from '@src/services/Tools';
+import { getMap, formatTreeData, formatDate } from '@src/services/Tools';
 // import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
 import { DataType, SwitchSelect } from '../../../../Constant';
 // import { User } from '../../../stores/user';
@@ -82,6 +82,13 @@ export default class extends Page {
       title: '购买人数',
       dataIndex: 'saleNumber',
     }, {
+      title: '更新时间',
+      sorter: true,
+      dataIndex: 'updateTime',
+      render: (text) => {
+        return formatDate(text);
+      },
+    }, {
       title: '操作',
       dataIndex: 'handler',
       render: (text, record) => {
@@ -130,7 +137,7 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/course/dataHistory/page.js

@@ -199,7 +199,7 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={false}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/course/experience/page.js

@@ -190,7 +190,7 @@ export default class extends Page {
       />
       <TableLayout
         select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/course/invoice/page.js

@@ -141,7 +141,7 @@ export default class extends Page {
       />
       <TableLayout
         select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/course/list/page.js

@@ -170,7 +170,7 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/course/package/page.js

@@ -177,7 +177,7 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 41 - 35
front/project/admin/routes/course/student/page.js

@@ -1,5 +1,4 @@
 import React from 'react';
-import moment from 'moment';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
@@ -100,7 +99,7 @@ export default class extends Page {
       key: 'addVsStudent',
       name: '添加学员',
     }];
-    this.vsList = [{
+    this.vsAddList = [{
       key: 'id',
       type: 'hidden',
     }, {
@@ -110,10 +109,6 @@ export default class extends Page {
       select: [],
       number: true,
       placeholder: '请输入',
-      // }, {
-      //   key: 'time',
-      //   type: 'daterange',
-      //   name: '有效期',
     }, {
       key: 'teacherId',
       type: 'select',
@@ -128,9 +123,31 @@ export default class extends Page {
       type: 'input',
       name: 'CCTalk用户名',
     }];
+    this.vsChangeList = [{
+      key: 'id',
+      type: 'hidden',
+    }, {
+      key: 'teacherId',
+      type: 'select',
+      select: [],
+      name: '老师',
+    }, {
+      key: 'cctalkName',
+      type: 'input',
+      name: 'CCTalk用户名',
+    }];
     this.vsColumns = [{
+      title: '学员id',
+      dataIndex: 'user.id',
+    }, {
       title: '学员名称',
       dataIndex: 'user.nickname',
+      render: (text, record) => {
+        return `${text}(${record.mobile})`;
+      },
+    }, {
+      title: '课时数',
+      dataIndex: 'number',
     }, {
       title: '有效期',
       dataIndex: 'time',
@@ -273,68 +290,57 @@ export default class extends Page {
     });
   }
 
-  changeVs(record) {
+  addVsStudentAction() {
     const { id } = this.params;
     const { data } = this.state;
-    record.time = [moment(record.useStartTime), moment(record.useEndTime)];
-    this.vsList[4].type = data.vsType === 'novice' ? 'hidden' : 'number';
-    asyncForm('修改', this.vsList, record, info => {
+    this.vsAddList[4].type = data.vsType === 'novice' ? 'hidden' : 'number';
+    asyncForm('添加', this.vsAddList, {}, info => {
+      if (data.vsType === 'novice') {
+        // 写死:新手每次1课时
+        info.number = 1;
+      }
       // ([info.useStartTime, info.useEndTime] = info.time);
-      return Course.editStudentVs(Object.assign({ courseId: id }, info)).then(() => {
+      return Course.addStudentVs(Object.assign({ courseId: id }, info)).then(() => {
         asyncSMessage('添加成功!');
         this.refresh();
       });
     }).then(component => {
-      bindSearch(this.vsList, 'userId', component, (search) => {
+      bindSearch(this.vsAddList, 'userId', component, (search) => {
         return User.list(search);
       }, (row) => {
         return {
           title: `${row.nickname}(${row.mobile})`,
           value: row.id,
         };
-      }, record.userId, null);
-      bindSearch(this.vsList, 'teacherId', component, (search) => {
+      }, null, null);
+      bindSearch(this.vsAddList, 'teacherId', component, (search) => {
         return Course.listTeacher(Object.assign({ courseId: id }, search));
       }, (row) => {
         return {
           title: row.realname,
           value: row.id,
         };
-      }, record.teacherId, null);
+      }, null, null);
     });
   }
 
-  addVsStudentAction() {
+  changeVs(record) {
     const { id } = this.params;
-    const { data } = this.state;
-    this.vsList[4].type = data.vsType === 'novice' ? 'hidden' : 'number';
-    asyncForm('添加', this.vsList, {}, info => {
-      if (data.vsType === 'novice') {
-        // 写死:新手每次1课时
-        info.number = 1;
-      }
+    asyncForm('修改', this.vsChangeList, record, info => {
       // ([info.useStartTime, info.useEndTime] = info.time);
-      return Course.addStudentVs(Object.assign({ courseId: id }, info)).then(() => {
-        asyncSMessage('添加成功!');
+      return Course.editStudentVs(Object.assign({ courseId: id }, info)).then(() => {
+        asyncSMessage('编辑成功!');
         this.refresh();
       });
     }).then(component => {
-      bindSearch(this.vsList, 'userId', component, (search) => {
-        return User.list(search);
-      }, (row) => {
-        return {
-          title: `${row.nickname}(${row.mobile})`,
-          value: row.id,
-        };
-      }, null, null);
-      bindSearch(this.vsList, 'teacherId', component, (search) => {
+      bindSearch(this.vsChangeList, 'teacherId', component, (search) => {
         return Course.listTeacher(Object.assign({ courseId: id }, search));
       }, (row) => {
         return {
           title: row.realname,
           value: row.id,
         };
-      }, null, null);
+      }, record.teacherId, null);
     });
   }
 

+ 9 - 1
front/project/admin/routes/course/study/page.js

@@ -137,6 +137,14 @@ export default class extends Page {
         value: row.id,
       };
     }, this.state.search.userId ? Number(this.state.search.userId) : null, null);
+    bindSearch(this.filterForm, 'courseId', this, (search) => {
+      return Course.list(search);
+    }, (row) => {
+      return {
+        title: row.title,
+        value: row.id,
+      };
+    }, this.state.search.courseId ? Number(this.state.search.courseId) : null, null);
   }
 
   init() {
@@ -172,7 +180,7 @@ export default class extends Page {
           this.search(data);
         }} />}
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/setting/comment/page.js

@@ -227,7 +227,7 @@ export default class extends Page {
       />
       <TableLayout
         select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/setting/faqConsult/page.js

@@ -214,7 +214,7 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/setting/faqSystem/page.js

@@ -170,7 +170,7 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/setting/message/page.js

@@ -213,7 +213,7 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={false}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/setting/rank/page.js

@@ -132,7 +132,7 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={false}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/setting/tips/page.js

@@ -65,7 +65,7 @@ export default class extends Page {
   renderView() {
     return <Block flex >
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={locations}
         pagination={false}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/subject/examination/page.js

@@ -197,7 +197,7 @@ export default class extends Page {
       />
       <TableLayout
         select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 60 - 62
front/project/admin/routes/subject/exercise/page.js

@@ -18,67 +18,65 @@ import { Slient } from '../../../stores/slient';
 
 const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
 
-const filterForm = [
-  {
-    key: 'questionType',
-    type: 'select',
-    allowClear: true,
-    name: '题型',
-    select: QuestionType,
-    placeholder: '请选择',
-    number: true,
-  },
-  {
-    key: 'structId',
-    type: 'tree',
-    allowClear: true,
-    name: '分册',
-    select: [],
-    placeholder: '请选择',
-    number: true,
-  },
-  {
-    key: 'paperId',
-    type: 'select',
-    allowClear: true,
-    name: '练习册',
-    select: [],
-    placeholder: '请选择',
-    number: true,
-  },
-  {
-    key: 'place',
-    type: 'select',
-    allowClear: true,
-    name: '考点',
-    select: [],
-    placeholder: '请选择',
-    number: true,
-  },
-  {
-    key: 'difficult',
-    type: 'select',
-    allowClear: true,
-    name: '难度',
-    select: QuestionDifficult,
-    placeholder: '请选择',
-    number: true,
-  },
-  {
-    key: 'time',
-    type: 'daterange',
-    name: '修改时间',
-  },
-  {
-    key: 'questionNoId',
-    type: 'select',
-    allowClear: true,
-    name: '题目ID',
-    select: [],
-    number: true,
-    placeholder: '请输入',
-  },
-];
+const filterForm = [{
+  key: 'questionType',
+  type: 'select',
+  allowClear: true,
+  name: '题型',
+  select: QuestionType,
+  placeholder: '请选择',
+  number: true,
+},
+{
+  key: 'structId',
+  type: 'tree',
+  allowClear: true,
+  name: '分册',
+  select: [],
+  placeholder: '请选择',
+  number: true,
+},
+{
+  key: 'paperId',
+  type: 'select',
+  allowClear: true,
+  name: '练习册',
+  select: [],
+  placeholder: '请选择',
+  number: true,
+},
+{
+  key: 'place',
+  type: 'select',
+  allowClear: true,
+  name: '考点',
+  select: [],
+  placeholder: '请选择',
+  number: true,
+},
+{
+  key: 'difficult',
+  type: 'select',
+  allowClear: true,
+  name: '难度',
+  select: QuestionDifficult,
+  placeholder: '请选择',
+  number: true,
+},
+{
+  key: 'time',
+  type: 'daterange',
+  name: '修改时间',
+},
+{
+  key: 'questionNoId',
+  type: 'select',
+  allowClear: true,
+  name: '题目ID',
+  select: [],
+  number: true,
+  placeholder: '请输入',
+}];
 export default class extends Page {
   constructor(props) {
     super(props);
@@ -285,7 +283,7 @@ export default class extends Page {
       />
       <TableLayout
         select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 78 - 71
front/project/admin/routes/subject/preview/page.js

@@ -7,72 +7,82 @@ import Block from '@src/components/Block';
 import FilterLayout from '@src/layouts/FilterLayout';
 import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { getMap, formatDate } from '@src/services/Tools';
+import { getMap, formatTreeData, bindSearch } from '@src/services/Tools';
 import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
-import { PreviewStatus } from '../../../../Constant';
+import { CourseModule } from '../../../../Constant';
 import { Exercise } from '../../../stores/exercise';
+import { Course } from '../../../stores/course';
 import { Preview } from '../../../stores/preview';
 
-const filterForm = [
-  {
-    key: 'category',
-    type: 'select',
-    allowClear: true,
-    name: '课程',
-    placeholder: '请选择',
-    number: true,
-  },
-  {
-    key: 'status',
-    type: 'select',
-    name: '状态',
-    select: PreviewStatus,
-    number: true,
-    placeholder: '请选择',
-  },
-];
+const CourseModuleMap = getMap(CourseModule, 'value', 'label');
+
 export default class extends Page {
-  constructor(props) {
-    super(props);
+  init() {
+    this.exerciseMap = {};
     this.actionList = [{
-      key: 'add',
-      name: '新建作业',
+      key: 'addVideo',
+      type: 'primary',
+      name: '创建视频作业',
+      render: (item) => {
+        return <Link to='/subject/preview/detail?module=video'><Button type='primary'>{item.name}</Button></Link>;
+      },
+    }, {
+      key: 'addOnline',
+      type: 'primary',
+      name: '创建小班作业',
+      render: (item) => {
+        return <Link to='/subject/preview/detail?module=online'><Button type='primary'>{item.name}</Button></Link>;
+      },
+    }, {
+      key: 'addVs',
+      type: 'primary',
+      name: '创建1vs1作业',
       render: (item) => {
-        // return <Link to='/subject/preview/add'><Button>{item.name}</Button></Link>;
-        return <Button onClick={() => {
-          linkTo('/subject/preview/detail');
-        }}>{item.name}</Button>;
+        return <Link to='/subject/preview/detail?module=vs'><Button type='primary'>{item.name}</Button></Link>;
       },
     }];
-    this.categoryMap = {};
+
+    this.filterForm = [{
+      key: 'courseModule',
+      type: 'select',
+      allowClear: true,
+      name: '课程类型',
+      placeholder: '请选择',
+      select: CourseModule,
+    }, {
+      key: 'structId',
+      type: 'tree',
+      allowClear: true,
+      name: '单项',
+      placeholder: '请选择',
+      select: [],
+    }, {
+      key: 'courseId',
+      type: 'select',
+      name: '课程名称',
+      select: [],
+      number: true,
+      placeholder: '请选择',
+    }];
     this.columns = [{
-      title: '课程',
-      dataIndex: 'category',
+      title: '课程类型',
+      dataIndex: 'courseModule',
       render: (text) => {
-        return this.categoryMap[text] || text;
+        return CourseModuleMap[text] || text;
       },
     }, {
-      title: '开始时间',
-      dataIndex: 'startTime',
-      render: (text) => {
-        return formatDate(text);
-      },
+      title: '课程名称',
+      dataIndex: 'course.title',
     }, {
-      title: '结束时间',
-      dataIndex: 'endTime',
+      title: '单项',
+      dataIndex: 'course.structId',
       render: (text) => {
-        return formatDate(text);
+        return this.exerciseMap[text] || text;
       },
     }, {
       title: '作业标题',
       dataIndex: 'title',
     }, {
-      title: '完成进度',
-      dataIndex: 'finish',
-      render: (text, record) => {
-        return `${text}/${record.userIds.length}`;
-      },
-    }, {
       title: '操作',
       dataIndex: 'handler',
       render: (text, record) => {
@@ -80,31 +90,28 @@ export default class extends Page {
           {(
             <Link to={`/subject/preview/detail/${record.id}`}>编辑</Link>
           )}
-          {(
-            <Link to={`/user/previewList?previewId=${record.id}&startTime=${record.startTime}&endTime=${record.endTime}`}>查看</Link>
-          )}
-          {(
-            <Button
-              size="small"
-              type="link"
-              onClick={() => {
-
-              }}
-            >
-              复制
-          </Button>
-          )}
         </div>;
       },
     }];
-  }
+    Exercise.courseStruct().then((result) => {
+      const list = result.map(row => { row.title = `${row.titleZh}`; row.value = row.id; return row; });
+      this.filterForm[1].tree = formatTreeData(list, 'id', 'title', 'parentId');
+      this.exerciseMap = getMap(result.map(row => {
+        row.title = `${row.titleZh}`;
+        row.value = row.id;
+        return row;
+      }), 'id', 'title');
 
-  init() {
-    Exercise.allStruct().then(result => {
-      filterForm[0].select = result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; });
-      this.categoryMap = getMap(filterForm[0].select, 'id', 'title');
       this.setState({ exercise: result });
     });
+    bindSearch(this.filterForm, 'courseId', this, (search) => {
+      return Course.list(search);
+    }, (row) => {
+      return {
+        title: row.title,
+        value: row.id,
+      };
+    }, this.state.search.courseId ? Number(this.state.search.courseId) : null, null);
   }
 
   initData() {
@@ -116,7 +123,7 @@ export default class extends Page {
   deleteAction() {
     const { selectedKeys } = this.state;
     asyncDelConfirm('删除确认', '是否删除选中作业?', () => {
-      return Promise.all(selectedKeys.map(row => Preview.delStruct({ id: row }))).then(() => {
+      return Promise.all(selectedKeys.map(row => Preview.del({ id: row }))).then(() => {
         asyncSMessage('删除成功!');
         this.refresh();
       });
@@ -124,22 +131,22 @@ export default class extends Page {
   }
 
   renderView() {
+    const { exercise } = this.state;
     return <Block flex>
-      <FilterLayout
+      {exercise && <FilterLayout
         show
-        itemList={filterForm}
+        itemList={this.filterForm}
         data={this.state.search}
         onChange={data => {
           this.search(data);
-        }} />
+        }} />}
       <ActionLayout
         itemList={this.actionList}
         selectedKeys={this.state.selectedKeys}
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 185 - 68
front/project/admin/routes/subject/previewDetail/page.js

@@ -1,63 +1,129 @@
 import React from 'react';
-import { Form, Input, Button, Row, Col, DatePicker } from 'antd';
+import { Form, Input, Button, Row, Col } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import Select from '@src/components/Select';
-// import FileUpload from '@src/components/FileUpload';
-import { formatFormError, generateSearch } from '@src/services/Tools';
-import { asyncSMessage } from '@src/services/AsyncTools';
+import { formatFormError, bindSearch, formatDate, generateSearch } from '@src/services/Tools';
+import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
 // import { PreviewMode } from '../../../../Constant';
-import QuestionNoList from '../../../components/QuestionNoList';
+import Association from '../../../components/Association';
 import { Preview } from '../../../stores/preview';
+import { Course } from '../../../stores/course';
 import { User } from '../../../stores/user';
-// import { Question } from '../../../stores/question';
-import { Exercise } from '../../../stores/exercise';
-import config from './index';
 
 export default class extends Page {
-  constructor(props) {
-    super(props);
-    const { id } = this.params;
-
-    if (id) {
-      config.title = '编辑预习作业';
-    } else {
-      config.title = '添加预习作业';
-    }
-  }
-
   init() {
-    Exercise.allStruct().then(result => {
-      result = result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; });
-      this.setState({ exercise: result });
-    });
+    this.onlineList = [{
+      key: 'courseTime',
+      type: 'select',
+      select: [],
+      name: '时间段',
+    }, {
+      key: 'time',
+      type: 'daterange',
+      name: '完成时间',
+    }];
+
+    this.vsList = [{
+      key: 'userId',
+      type: 'select',
+      select: [],
+      name: '学生',
+    }, {
+      key: 'time',
+      type: 'daterange',
+      name: '完成时间',
+    }, {
+      key: 'title',
+      type: 'input',
+      name: '作业标题',
+    }];
   }
 
   initData() {
     const { id } = this.params;
     const { form } = this.props;
+    const { module } = this.state.search;
     let handler;
     if (id) {
       handler = Preview.get({ id });
     } else {
-      handler = Promise.resolve({ questionNoIds: [] });
+      handler = Promise.resolve({ questionNoIds: [], courseModule: module });
     }
     handler
       .then(result => {
         const { questionNoIds } = result;
-        result.time = [result.startTime, result.endTime];
+        generateSearch('courseId', {}, this, (search) => {
+          return Course.list(Object.assign({ courseModule: result.courseModule }, search));
+        }, (row) => {
+          return {
+            title: row.title,
+            value: row.id,
+          };
+        }, result.courseId, null);
         form.setFieldsValue(result);
-        generateSearch('userIds', { mode: 'multiple' }, this, (search) => {
+        this.setState({ module: result.courseModule, data: result, questionNoIds });
+        this.refresh();
+      });
+  }
+
+  refresh() {
+    const { data } = this.state;
+    switch (data.courseModule) {
+      case 'video':
+        this.refreshNo(data.courseId);
+        break;
+      case 'online':
+        bindSearch(this.onlineList, 'courseTime', this, (search) => {
+          return Course.listTime(Object.assign({ courseId: data.courseId }, search));
+        }, (row) => {
+          return {
+            title: `${formatDate(row.startTime, 'YYYY-MM-DD')}~${formatDate(row.endTime, 'YYYY-MM-DD')}`,
+            value: row.id,
+          };
+        }, null, null);
+        break;
+      case 'vs':
+        bindSearch(this.vsList, 'userId', this, (search) => {
           return User.list(search);
         }, (row) => {
           return {
             title: `${row.nickname}(${row.mobile})`,
             value: row.id,
           };
-        }, result.userIds, null);
-        this.setState({ questionNoIds });
+        }, [], null);
+        break;
+      default:
+    }
+  }
+
+  refreshCourse(id) {
+    if (!id) return;
+    Course.get({ id }).then((info) => {
+      const { data } = this.state;
+      // 设置长难句paperModule信息
+      data.paperModule = info.extend === 'sentence' ? 'sentence' : 'exercise';
+      data.courseId = id;
+      this.setState({ data });
+      if (data.courseModule === 'video') {
+        this.refreshNo(id);
+      }
+    });
+  }
+
+  refreshNo(id) {
+    if (!id) return;
+    Course.allNo({ courseId: id }).then(result => {
+      this.setState({
+        courseNo: result.map((row) => {
+          return {
+            title: `${row.title}(${row.no})`,
+            value: row.id,
+          };
+        }),
       });
+    });
   }
 
   submit() {
@@ -65,16 +131,22 @@ export default class extends Page {
     form.validateFields((err) => {
       if (!err) {
         const data = form.getFieldsValue();
-        [data.startTime, data.endTime] = data.time;
         let handler;
-        data.questionNoIds = this.state.questionNos.map(row => row.id);
+        data.questionNoIds = this.state.questionNoIds;
+        data.paperModule = this.state.data.paperModule;
+        data.courseModule = this.state.data.courseModule;
         if (!data.id) {
           handler = Preview.add(data);
         } else {
           handler = Preview.edit(data);
         }
-        handler.then(() => {
+        handler.then((result) => {
           asyncSMessage('保存成功');
+          if (data.id) {
+            linkTo(`/subject/preview/detail/${data.id}`);
+          } else {
+            linkTo(`/subject/preview/detail/${result.id}`);
+          }
         }).catch((e) => {
           if (e.result) form.setFields(formatFormError(data, e.result));
         });
@@ -98,29 +170,72 @@ export default class extends Page {
     this.props.form.setFieldsValue({ questionNos: questionNos.map(row => row.no) });
   }
 
+  addOnlineAction() {
+    const { data } = this.state;
+    asyncForm('添加', this.onlineList, {}, info => {
+      if (info.time.length > 0) {
+        info.startTime = info.time[0].format('YYYY-MM-DD HH:mm:ss');
+        info.endTime = info.time[1].format('YYYY-MM-DD HH:mm:ss');
+      }
+      return Preview.addAssign(Object.assign({ paperModule: data.paperModule, paperId: data.id, courseModule: data.courseModule }, info)).then(() => {
+        asyncSMessage('添加成功!');
+        this.refresh();
+      });
+    }).catch(err => {
+      console.log(err);
+    });
+  }
+
+  addVsAction() {
+    const { data } = this.state;
+    asyncForm('添加', this.vsList, {}, info => {
+      if (info.time.length > 0) {
+        info.startTime = info.time[0].format('YYYY-MM-DD HH:mm:ss');
+        info.endTime = info.time[1].format('YYYY-MM-DD HH:mm:ss');
+      }
+      return User.listCourseAppointment({ courseId: data.courseId, userId: info.userId, startTime: info.startTime, endTime: info.endTime }).then(result => {
+        if (result.total === 0) {
+          return Promise.reject(new Error('没有找到相关的预约记录'));
+        } if (result.total > 1) {
+          return Promise.reject(new Error('时间段内存在多条预约记录'));
+        }
+        return Preview.addAssign(Object.assign({ paperModule: data.paperModule, paperId: data.id, courseModule: data.courseModule }, info)).then(() => {
+          asyncSMessage('添加成功!');
+          this.refresh();
+        });
+      });
+    }).catch(err => {
+      console.log(err);
+    });
+  }
+
+
   renderBase() {
+    const { data = {}, questionNoIds } = this.state;
     const { getFieldDecorator } = this.props.form;
     return <Block>
       <Form>
         {getFieldDecorator('id')(<input hidden />)}
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='选择课程'>
-          {getFieldDecorator('category', {
+          {getFieldDecorator('courseId', {
             rules: [
               { required: true, message: '请选择课程' },
             ],
           })(
-            <Select select={this.state.exercise} placeholder='请选择课程' />,
+            <Select {...this.state.courseId} disabled={data.id} placeholder='请选择课程' onChange={(v) => {
+              this.refreshCourse(v);
+            }} />,
           )}
         </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='起止时间'>
-          {getFieldDecorator('time', {
+        {data.courseModule === 'video' && <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='选择课时'>
+          {getFieldDecorator('courseNo', {
             rules: [
-              { required: true, message: '请输入起止时间' },
+              { required: true, message: '请选择课时' },
             ],
           })(
-            <DatePicker.RangePicker />,
+            <Select select={this.state.courseNo} disabled={data.id || !data.courseId} placeholder='请选择课时' />,
           )}
-        </Form.Item>
+        </Form.Item>}
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='作业标题'>
           {getFieldDecorator('title', {
             rules: [
@@ -130,47 +245,48 @@ export default class extends Page {
             <Input placeholder='请输入作业标题' />,
           )}
         </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='指定做题人'>
-          {getFieldDecorator('userIds', {
-            rules: [
-              { required: true, message: '请指定做题人' },
-            ],
-          })(
-            <Select {...this.state.userIds} placeholder='请指定做题人' />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='选择作业题'>
-          {getFieldDecorator('questionNos', {
-            rules: [
-              { required: true, message: '请选择作业题' },
-            ],
-          })(
-            <Select mode='multiple' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} {...this.state.questionNos} onChange={(values) => {
-              // todo 查找
-              this.setState({ questionNos: values });
-            }} />,
-          )}
-        </Form.Item>
       </Form>
+      {data.paperModule && <Association title='选择题目' ids={questionNoIds} field={'questionNoIds'} module={data.paperModule} modal={false} onConfirm={(info) => {
+        this.setState({ questionNoIds: info.questionNoIds });
+        return Promise.resolve();
+      }} />}
     </Block>;
   }
 
-  renderQuestionList() {
-    const { getFieldDecorator, setFieldsValue } = this.props.form;
+  renderOnline() {
     return <Block>
-      <h1>题目预览</h1>
-      <QuestionNoList loading={false} questionNos={this.state.questionNos} onChange={(nos) => {
-        getFieldDecorator('questionNos');
-        setFieldsValue({ questionNos: nos.map(row => row.id) });
-      }} />
+      <h1><Button onClick={() => {
+        this.addOnlineAction();
+      }}>添加指定做题人</Button></h1>
     </Block>;
   }
 
+  renderVs() {
+    return <Block>
+      <h1><Button onClick={() => {
+        this.addVsAction();
+      }}>添加指定做题人</Button></h1>
+    </Block>;
+  }
+
+  renderContent() {
+    const { data } = this.state;
+    switch (data.courseModule) {
+      case 'video':
+        return [];
+      case 'online':
+        return [this.renderOnline()];
+      case 'vs':
+        return [this.renderVs()];
+      default:
+        return <div />;
+    }
+  }
+
   renderView() {
+    const { data = {} } = this.state;
     return <div flex>
       {this.renderBase()}
-      {this.renderQuestionList()}
-
       <Row type="flex" justify="center">
         <Col>
           <Button type="primary" onClick={() => {
@@ -178,6 +294,7 @@ export default class extends Page {
           }}>保存</Button>
         </Col>
       </Row>
+      {data.id > 0 && this.renderContent()}
     </div>;
   }
 }

+ 1 - 1
front/project/admin/routes/subject/sentence/page.js

@@ -272,7 +272,7 @@ export default class extends Page {
       />
       <TableLayout
         select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/subject/textbook/page.js

@@ -135,7 +135,7 @@ export default class extends Page {
       />
       <TableLayout
         select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/subject/textbookLibrary/page.js

@@ -126,7 +126,7 @@ export default class extends Page {
         }}>新增换库</Button></Form.Item>
       </Form>
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/system/manager/list/page.js

@@ -102,7 +102,7 @@ export default class extends Page {
         />
         <TableLayout
           // select
-          columns={this.columns}
+          columns={this.tableSort(this.columns)}
           list={this.state.list}
           pagination={this.state.page}
           loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/user/ask/page.js

@@ -183,7 +183,7 @@ export default class extends Page {
       />
       <TableLayout
         select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 116 - 38
front/project/admin/routes/user/detail/page.js

@@ -1,30 +1,31 @@
 import React from 'react';
-import { Form, Row, Col, Avatar, Button } from 'antd';
+import { Form, Row, Col, Avatar, Button, Modal, Input, Upload } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import TableLayout from '@src/layouts/TableLayout';
-import { formatDate, getMap, formatMoney } from '@src/services/Tools';
-import { UserUrl, PrepareStatus, PrepareExaminationTime, ServiceKey, PayModule } from '../../../../Constant';
+import { formatDate, getMap, formatMoney, formatSeconds } from '@src/services/Tools';
+import { asyncSMessage, asyncForm, asyncDelConfirm } from '@src/services/AsyncTools';
+import { UserUrl, PrepareStatus, PrepareExaminationTime, ServiceKey } from '../../../../Constant';
 import { User } from '../../../stores/user';
+import { System } from '../../../stores/system';
 import { Exercise } from '../../../stores/exercise';
 
 const PrepareStatusMap = getMap(PrepareStatus, 'value', 'label');
 const PrepareExaminationTimeMap = getMap(PrepareExaminationTime, 'value', 'label');
 const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
-const PayModuleMap = getMap(PayModule, 'value', 'label');
 
 export default class extends Page {
-  constructor(props) {
-    super(props);
-    this.categoryMap = {};
-  }
-
   init() {
-    Exercise.allStruct().then(result => {
-      this.categoryMap = getMap(result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; }), 'id', 'title');
-      this.setState({ exercise: result });
-    });
+    this.categoryMap = {};
+    this.moneyList = [{
+      key: 'id',
+      type: 'hidden',
+    }, {
+      key: 'money',
+      type: 'number',
+      name: '增加金额',
+    }];
     this.columns = [{
       title: '订单时间',
       dataIndex: 'createTime',
@@ -33,18 +34,9 @@ export default class extends Page {
       },
     }, {
       title: '购买',
-      dataIndex: 'module',
+      dataIndex: 'service',
       render: (text, record) => {
-        const m = PayModuleMap[text];
-        switch (text) {
-          case 'service':
-            return `${m}: ${ServiceKeyMap[record.moduleExtend]} ${record.isUse ? '已使用' : ''}`;
-          case 'class':
-            return `${m}: ${this.categoryMap[record.moduleExtend]} ${record.isUse ? '已使用' : ''}`;
-          case 'data':
-          default:
-            return `${m || text}`;
-        }
+        return `${ServiceKeyMap[text]} ${record.isUse ? '已使用' : ''}`;
       },
     }, {
       title: '消费金额',
@@ -53,6 +45,10 @@ export default class extends Page {
         return formatMoney(text);
       },
     }];
+    Exercise.allStruct().then(result => {
+      this.categoryMap = getMap(result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; }), 'id', 'title');
+      this.setState({ exercise: result });
+    });
   }
 
   initData() {
@@ -66,17 +62,46 @@ export default class extends Page {
     handler
       .then(result => {
         this.setState({ data: result });
-        this.refreshPay(this.state.search.page, this.state.search.size);
+        this.refreshService(this.state.search.page, this.state.search.size);
       });
   }
 
-  refreshPay(p, size) {
+  realAction() {
+    this.open({ id: this.state.data.id }, 'real');
+  }
+
+  changeInfo(field, value) {
+    const { real = {} } = this.state;
+    real[field] = value;
+    this.setState({ real });
+  }
+
+  addMoneyAction() {
+    asyncForm('增加金额', this.moneyList, { id: this.state.data.id }, data => {
+      return User.addMoney(data).then(() => {
+        asyncSMessage('添加成功!');
+        this.refresh();
+      });
+    });
+  }
+
+  frozenAction() {
+    asyncDelConfirm('操作确认', '是否要封禁账户?封禁后账户无法使用网站', () => {
+      return User.frozen({ id: this.state.data.id })
+        .then(() => {
+          asyncSMessage('操作成功!');
+          this.refresh();
+        });
+    });
+  }
+
+  refreshService(p, size) {
     const { id } = this.params;
     const { page } = this.state;
     page.current = p || 1;
     page.pageSize = size || 20;
     this.setState({ page });
-    User.listPay({ user_id: id, page: page.current, size: page.pageSize })
+    User.listService({ user_id: id, page: page.current, size: page.pageSize })
       .then(result => {
         this.setTableData(result.list, result.total);
       });
@@ -85,7 +110,11 @@ export default class extends Page {
   renderBase() {
     const { data = {} } = this.state;
     return <Block>
-      <h1>用户基本信息</h1>
+      <h1>用户基本信息{data.isFrozen === 0 && <Button type='danger' onClick={() => {
+        this.frozenAction();
+      }}>封禁账户</Button>}
+        {data.isFrozen === 1 && '已封禁'}</h1>
+
       <Form>
         <div className="group">
           <h2>个人资料</h2>
@@ -107,12 +136,12 @@ export default class extends Page {
             </Col>
             <Col span={12}>
               <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='用户微信'>
-                {data.wechatOpenidPc !== '' ? '已绑定' : '未绑定'},{data.wechatOpenidWechat !== '' ? '已关注' : '未关注'}
+                {data.wechatUnionid ? '已绑定' : '未绑定'}
               </Form.Item>
             </Col>
             <Col span={12}>
               <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='邮箱'>
-                {data.email !== '' ? '已绑定' : '未绑定'}
+                {data.email ? '已绑定' : '未绑定'}
               </Form.Item>
             </Col>
             <Col span={12}>
@@ -129,8 +158,11 @@ export default class extends Page {
         </div>
 
         <div className="group">
-          <h2>实名认证</h2>
-          <Row>
+          <h2>实名认证{data.realStatus === 0 && <Button onClick={() => {
+            this.realAction();
+          }}>人工认证</Button>}</h2>
+
+          {data.realStatus > 0 && <Row>
             <Col span={12}>
               <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='身份证id'>
                 {data.realIdentity}
@@ -152,11 +184,11 @@ export default class extends Page {
                 <Avatar src={data.realPhotoBack} />
               </Form.Item>
             </Col>
-          </Row>
+          </Row>}
         </div>
         <div className="group">
           <h2>备考信息</h2>
-          <Row>
+          {data.prepareTime && <Row>
             <Col span={12}>
               <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='身份'>
                 {PrepareStatusMap[data.prepareStatus]}
@@ -177,7 +209,7 @@ export default class extends Page {
                 {data.prepareGoal}
               </Form.Item>
             </Col>
-          </Row>
+          </Row>}
         </div>
       </Form>
     </Block>;
@@ -190,6 +222,9 @@ export default class extends Page {
       <div className="group">
         <h2>累计消费金额</h2>
         <span>{data.totalMoney}</span>
+        <Button onClick={() => {
+          this.addMoneyAction();
+        }}>增加线下金额</Button>
       </div>
       <div className="group">
         <h2>已开通服务</h2>
@@ -200,22 +235,23 @@ export default class extends Page {
       <div className="group">
         <h2>消费记录</h2>
         <TableLayout
-          columns={this.columns}
+          columns={this.tableSort(this.columns)}
           list={this.state.list}
           pagination={this.state.page}
           loading={this.props.core.loading}
-          onChange={(pagination) => this.refreshPay(pagination.current, pagination.pageSize)}
+          onChange={(pagination) => this.refreshService(pagination.current, pagination.pageSize)}
         />
       </div>
     </Block>;
   }
 
   renderStudy() {
+    const { data = {} } = this.state;
     return <Block>
       <h1>学习统计</h1>
       <div className="group">
         <h2>累计学习时间</h2>
-
+        <p>{formatSeconds(data.totalTime)}</p>
       </div>
       <div className="group">
         <h2>学习数据</h2>
@@ -231,6 +267,48 @@ export default class extends Page {
       {this.renderBase()}
       {this.renderService()}
       {this.renderStudy()}
+
+      {this.state.real && <Modal visible closable title='实名认证' onCancel={() => {
+        this.close(false, 'real');
+      }} onOk={() => {
+        this.submitReal();
+      }}>
+        <Form>
+          <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='身份id'>
+            <Input value={this.state.real.realIdentity || ''} placeholder='输入身份id' onChange={e => {
+              this.changeInfo('realIdentity', e.target.value);
+            }} />
+          </Form.Item>
+          <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='真实姓名'>
+            <Input value={this.state.real.realName || ''} placeholder='输入真实姓名' onChange={e => {
+              this.changeInfo('realName', e.target.value);
+            }} />
+          </Form.Item>
+          <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='地址'>
+            <Input value={this.state.real.realAddress || ''} placeholder='输入地址' onChange={e => {
+              this.changeInfo('realAddress', e.target.value);
+            }} />
+          </Form.Item>
+          <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='正面照'><Upload
+            showUploadList={false}
+            beforeUpload={(file) => System.uploadImage(file).then((result) => {
+              this.changeInfo('realFront', result.url);
+              return Promise.reject();
+            })}
+          >
+            <Button>上传正面{(this.state.real || {}).realFront ? '(已上传)' : ''}</Button>
+          </Upload></Form.Item>
+          <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='反面照'><Upload
+            showUploadList={false}
+            beforeUpload={(file) => System.uploadImage(file).then((result) => {
+              this.changeInfo('realBack', result.url);
+              return Promise.reject();
+            })}
+          >
+            <Button>上传反面{(this.state.real || {}).realBack ? '(已上传)' : ''}</Button>
+          </Upload></Form.Item>
+        </Form>
+      </Modal>}
     </div>;
   }
 }

+ 1 - 1
front/project/admin/routes/user/feedback/page.js

@@ -217,7 +217,7 @@ export default class extends Page {
       />
       <TableLayout
         select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 3 - 2
front/project/admin/routes/user/index.js

@@ -9,6 +9,7 @@ import student from './student';
 import order from './order';
 import orderDetail from './orderDetail';
 import feedback from './feedback';
-import service from './service';
+import recordAll from './recordAll';
+import recordBuy from './recordBuy';
 
-export default [list, detail, ask, askDetail, preview, exercise, examination, student, order, orderDetail, feedback, service];
+export default [list, detail, ask, askDetail, preview, exercise, examination, student, order, orderDetail, feedback, recordAll, recordBuy];

+ 83 - 71
front/project/admin/routes/user/list/page.js

@@ -6,12 +6,13 @@ import Block from '@src/components/Block';
 import FilterLayout from '@src/layouts/FilterLayout';
 // import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { getMap, formatMoney } from '@src/services/Tools';
-import { SwitchSelect, ServiceKey } from '../../../../Constant';
+import { getMap, formatMoney, formatDate } from '@src/services/Tools';
+import { SwitchSelect, ServiceKey, PassSelect } from '../../../../Constant';
 import { User } from '../../../stores/user';
 
 const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
 const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
+const PassSelectMap = getMap(PassSelect, 'value', 'label');
 export default class extends Page {
   constructor(props) {
     super(props);
@@ -19,83 +20,91 @@ export default class extends Page {
   }
 
   init() {
-    this.filterForm = [
-      {
-        key: 'keyword',
-        type: 'input',
-        name: 'ID/手机号',
-        placeholder: '请输入',
+    this.filterForm = [{
+      key: 'keyword',
+      type: 'input',
+      name: 'ID/手机号',
+      placeholder: '请输入',
+    },
+    {
+      key: 'real',
+      type: 'select',
+      allowClear: true,
+      name: '实名认证',
+      select: PassSelect,
+      placeholder: '请选择',
+      number: true,
+    },
+    {
+      key: 'time',
+      type: 'daterange',
+      name: '注册时间',
+    }];
+    this.columns = [{
+      title: 'ID',
+      dataIndex: 'id',
+    },
+    {
+      title: '手机号',
+      dataIndex: 'mobile',
+    },
+    {
+      title: '注册时间',
+      dataIndex: 'createTime',
+      render: (text) => {
+        return formatDate(text);
       },
-      {
-        key: 'real',
-        type: 'select',
-        allowClear: true,
-        name: '实名认证',
-        select: SwitchSelect,
-        placeholder: '请选择',
-        number: true,
+    },
+    {
+      title: '实名认证',
+      dataIndex: 'realStatus',
+      render: (text) => {
+        return PassSelectMap[text ? 1 : 0];
       },
-    ];
-    this.columns = [
-      {
-        title: 'ID',
-        dataIndex: 'id',
+    },
+    {
+      title: '备考信息',
+      dataIndex: 'prepareStatus',
+      render: (text) => {
+        return SwitchSelectMap[text ? 1 : 0];
       },
-      {
-        title: '手机号',
-        dataIndex: 'mobile',
+    }, {
+      title: '邀请人数',
+      dataIndex: 'inviteNumber',
+    }, {
+      title: '服务中',
+      dataIndex: 'services',
+      render: (text) => {
+        return (text || []).map(row => ServiceKeyMap[row.service]).join(', ');
       },
-      {
-        title: '注册时间',
-        dataIndex: 'createTime',
+    }, {
+      title: '消费金额',
+      sorter: true,
+      dataIndex: 'totalMoney',
+      render: (text) => {
+        return formatMoney(text);
       },
-      {
-        title: '实名认证',
-        dataIndex: 'realStatus',
-        render: (text) => {
-          return SwitchSelectMap[text ? 1 : 0];
-        },
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          {(
+            <Link to={`/user/detail/${record.id}`}>查看</Link>
+          )}
+        </div>;
       },
-      {
-        title: '备考信息',
-        dataIndex: 'prepareStatus',
-        render: (text) => {
-          return SwitchSelectMap[text ? 1 : 0];
-        },
-      }, {
-        title: '邀请人数',
-        dataIndex: 'inviteNumber',
-      }, {
-        title: '学习时长',
-        dataIndex: 'time',
-      }, {
-        title: '服务中',
-        dataIndex: 'services',
-        render: (text) => {
-          return (text || []).map(row => ServiceKeyMap[row.service]).join(', ');
-        },
-      }, {
-        title: '消费金额',
-        dataIndex: 'totalMoney',
-        render: (text) => {
-          return formatMoney(text);
-        },
-      }, {
-        title: '操作',
-        dataIndex: 'handler',
-        render: (text, record) => {
-          return <div className="table-button">
-            {(
-              <Link to={`/user/detail/${record.id}`}>查看</Link>
-            )}
-          </div>;
-        },
-      },
-    ];
+    }];
   }
 
   initData() {
-    User.list(this.state.search).then(result => {
+    const { search } = this.state;
+    const data = Object.assign({}, search);
+    if (data.time) {
+      data.startTime = data.time[0] || '';
+      data.endTime = data.time[1] || '';
+    }
+    User.list(data).then(result => {
       this.setTableData(result.list, result.total);
     });
   }
@@ -107,6 +116,9 @@ export default class extends Page {
         itemList={this.filterForm}
         data={this.state.search}
         onChange={data => {
+          if (data.time.length > 0) {
+            data.time = [data.time[0].format('YYYY-MM-DD HH:mm:ss'), data.time[1].format('YYYY-MM-DD HH:mm:ss')];
+          }
           this.search(data);
         }}
         ref={(ref) => {
@@ -119,7 +131,7 @@ export default class extends Page {
       /> */}
       <TableLayout
         // select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 1 - 1
front/project/admin/routes/user/preview/page.js

@@ -173,7 +173,7 @@ export default class extends Page {
         }} />
       <TableLayout
         select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 15 - 0
front/project/admin/routes/user/recordAll/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/user/record/all',
+  key: 'user-record-all',
+  title: '权限管理',
+  needLogin: true,
+  module,
+  group,
+  index: true,
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/admin/routes/user/recordAll/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#user-record-all {}

+ 1 - 7
front/project/admin/routes/user/service/page.js

@@ -74,12 +74,6 @@ export default class extends Page {
       type: 'select',
       name: '服务参数',
       select: [],
-    }, {
-      key: 'source',
-      type: 'select',
-      name: '开通方式',
-      select: ServiceSource,
-      placeholder: '请选择',
     }];
     this.filterForm = [{
       key: 'userId',
@@ -215,7 +209,7 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 3 - 3
front/project/admin/routes/user/service/index.js

@@ -2,9 +2,9 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/user/service',
-  key: 'user-service',
-  title: '服务管理',
+  path: '/user/record/buy',
+  key: 'user-record-buy',
+  title: '购买记录',
   needLogin: true,
   module,
   group,

+ 1 - 1
front/project/admin/routes/user/service/index.less

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-#user-service {}
+#user-record-buy {}

+ 134 - 0
front/project/admin/routes/user/recordBuy/page.js

@@ -0,0 +1,134 @@
+import React from 'react';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import FilterLayout from '@src/layouts/FilterLayout';
+// import ActionLayout from '@src/layouts/ActionLayout';
+import TableLayout from '@src/layouts/TableLayout';
+import { getMap, formatDate, formatMoney, bindSearch } from '@src/services/Tools';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { ServiceParamMap, ServiceKey, ServiceSource } from '../../../../Constant';
+import { User } from '../../../stores/user';
+
+const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
+const ServiceSourceMap = getMap(ServiceSource, 'value', 'label');
+const ServiceParamList = getMap(Object.keys(ServiceParamMap).map(key => {
+  return { list: ServiceParamMap[key], key };
+}), 'key', 'list');
+const ServiceParamRelation = getMap(Object.keys(ServiceParamMap).map(key => {
+  return { map: getMap(ServiceParamMap[key], 'value', 'label'), key };
+}), 'key', 'map');
+export default class extends Page {
+  init() {
+    this.timeout = null;
+    this.mobile = null;
+
+    this.filterForm = [{
+      key: 'userId',
+      type: 'select',
+      allowClear: true,
+      name: '用户',
+      select: [],
+      number: true,
+      placeholder: '请输入',
+    }, {
+      key: 'service',
+      type: 'select',
+      allowClear: true,
+      name: '服务',
+      select: ServiceKey,
+      onChange: (value) => {
+        this.filterForm[2].select = ServiceParamList[value] || [];
+      },
+    }];
+    this.columns = [{
+      title: '用户ID',
+      dataIndex: 'userId',
+    }, {
+      title: '用户手机',
+      dataIndex: 'user.mobile',
+    }, {
+      title: '用户姓名',
+      dataIndex: 'user.nickname',
+    }, {
+      title: '开通服务',
+      dataIndex: 'service',
+      render: (text, record) => {
+        return `${ServiceKeyMap[text]}${(ServiceParamRelation[record.service] || {})[text] || ''}`;
+      },
+    }, {
+      title: '开通时间',
+      dataIndex: 'time',
+      render: (text, record) => {
+        return `${record.startTime ? formatDate(record.startTime) : ''} - ${record.endTime ? formatDate(record.endTime) : ''}`;
+      },
+    }, {
+      title: '开通方式',
+      dataIndex: 'source',
+      render: (text) => {
+        return ServiceSourceMap[text] || '';
+      },
+    }, {
+      title: '累计消费金额',
+      dataIndex: 'user.totalMoney',
+      render: (text) => {
+        return formatMoney(text);
+      },
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          {record.isUsed > 0 && !record.isStop && (
+            <a onClick={() => {
+              this.stopAction(record.id);
+            }}>停用</a>
+          )}
+        </div>;
+      },
+    },
+    ];
+    bindSearch(this.filterForm, 'userId', this, (search) => {
+      return User.list(search);
+    }, (row) => {
+      return {
+        title: `${row.nickname}(${row.mobile})`,
+        value: row.id,
+      };
+    }, this.state.search.userId ? Number(this.state.search.userId) : [], null);
+  }
+
+  initData() {
+    User.listService(this.state.search).then(result => {
+      this.setTableData(result.list, result.total);
+    });
+  }
+
+  stopAction(id) {
+    return User.editService({ id, isStop: 1 }).then(() => {
+      asyncSMessage('停用成功!');
+      this.refresh();
+    });
+  }
+
+  renderView() {
+    return <Block flex>
+      <FilterLayout
+        show
+        itemList={this.filterForm}
+        data={this.state.search}
+        onChange={data => {
+          this.search(data);
+        }} />
+      <TableLayout
+        columns={this.tableSort(this.columns)}
+        list={this.state.list}
+        pagination={this.state.page}
+        loading={this.props.core.loading}
+        onChange={(pagination, filters, sorter) => this.tableChange(pagination, filters, sorter)}
+        onSelect={(keys, rows) => this.tableSelect(keys, rows)}
+        selectedKeys={this.state.selectedKeys}
+      />
+    </Block>;
+  }
+}

+ 1 - 1
front/project/admin/routes/user/student/page.js

@@ -117,7 +117,7 @@ export default class extends Page {
       /> */}
       <TableLayout
         // select
-        columns={this.columns}
+        columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}
         loading={this.props.core.loading}

+ 21 - 1
front/project/admin/stores/preview.js

@@ -6,7 +6,7 @@ export default class PreviewStore extends BaseStore {
   }
 
   get(params) {
-    return this.apiPost('/preview/detail', params);
+    return this.apiGet('/preview/detail', params);
   }
 
   add(params) {
@@ -20,6 +20,26 @@ export default class PreviewStore extends BaseStore {
   del(params) {
     return this.apiDel('/preview/delete', params);
   }
+
+  listAssign(params) {
+    return this.apiGet('/preview/assign/list', params);
+  }
+
+  getAssign(params) {
+    return this.apiGet('/preview/assign/detail', params);
+  }
+
+  addAssign(params) {
+    return this.apiPost('/preview/assign/add', params);
+  }
+
+  editAssign(params) {
+    return this.apiPut('/preview/assign/edit', params);
+  }
+
+  delAssign(params) {
+    return this.apiDel('/preview/assign/delete', params);
+  }
 }
 
 export const Preview = new PreviewStore({ key: 'preview' });

+ 27 - 7
front/project/admin/stores/user.js

@@ -22,19 +22,19 @@ export default class UserStore extends BaseStore {
   }
 
   get(params) {
-    return this.apiPost('/user/detail', params);
+    return this.apiGet('/user/detail', params);
   }
 
-  add(params) {
-    return this.apiPost('/user/add', params);
+  addMoney(params) {
+    return this.apiPost('/user/money/add', params);
   }
 
-  edit(params) {
-    return this.apiPut('/user/edit', params);
+  frozen(params) {
+    return this.apiPost('/user/frozen', params);
   }
 
-  del(params) {
-    return this.apiDel('/user/delete', params);
+  real(params) {
+    return this.apiPost('/user/real', params);
   }
 
   listFeedbackError(params) {
@@ -108,6 +108,26 @@ export default class UserStore extends BaseStore {
   listOrder(params) {
     return this.apiGet('/user/order/list', params);
   }
+
+  listRecord(params) {
+    return this.apiGet('/user/record/list', params);
+  }
+
+  getOrder(params) {
+    return this.apiGet('/user/order/detail', params);
+  }
+
+  listAbnormal(params) {
+    return this.apiGet('/user/abnormal/list', params);
+  }
+
+  getAbnormal(params) {
+    return this.apiGet('/user/abnormal/detail', params);
+  }
+
+  handleAbnormal(params) {
+    return this.apiPost('/user/abnormal/handler', params);
+  }
 }
 
 export const User = new UserStore({ key: 'users' });

+ 3 - 3
front/project/www/routes/examination/main/page.js

@@ -452,9 +452,9 @@ export default class extends Page {
             onClose={() => {
               this.clearExercise();
             }}
-            onContinue={() => {}}
-            onRestart={() => {}}
-            onNext={() => {}}
+            onContinue={() => { }}
+            onRestart={() => { }}
+            onNext={() => { }}
           />
         )}
         <div className="content">

+ 10 - 0
front/src/containers/Page.js

@@ -121,6 +121,16 @@ export default class extends Component {
     this.setState({ selectedKeys, selectedRows });
   }
 
+  tableSort(columns) {
+    const { order, direction } = this.state.search;
+    return columns.map(row => {
+      if (row.dataIndex === order) {
+        row.sortOrder = direction === 'asc' ? 'ascend' : 'descend';
+      }
+      return row;
+    });
+  }
+
   tableChange(pagination, filters, sorter = {}) {
     this.search({
       page: pagination.current,

+ 6 - 6
front/src/services/Tools.js

@@ -378,16 +378,16 @@ export function bindSearch(targetList, field, Component, listFunc, render, def,
     showArrow: true,
     filterOption: false,
     onSearch: keyword => {
-      searchFunc({ page: 1, number: 5, keyword });
+      searchFunc({ page: 1, size: 5, keyword });
     },
     notFoundContent: notFound,
   };
   targetList[index] = Object.assign(targetList[index], item);
   if (def) {
     if (targetList[index].type === 'multiple' || targetList[index].mode === 'multiple') {
-      searchFunc({ ids: def, page: 1, number: def.length });
+      searchFunc({ ids: def, page: 1, size: def.length });
     } else {
-      searchFunc({ ids: [def], page: 1, number: 1 });
+      searchFunc({ ids: [def], page: 1, size: 1 });
     }
   } else {
     item.onSearch();
@@ -422,13 +422,13 @@ export function generateSearch(field, props, Component, listFunc, render, def, n
     });
   };
   item.onSearch = keyword => {
-    searchFunc({ page: 1, number: 5, keyword });
+    searchFunc({ page: 1, size: 5, keyword });
   };
   if (def) {
     if (item.mode === 'multiple' || item.type === 'multiple') {
-      searchFunc({ ids: def, page: 1, number: def.length });
+      searchFunc({ ids: def, page: 1, size: def.length });
     } else {
-      searchFunc({ ids: [def], page: 1, number: 1 });
+      searchFunc({ ids: [def], page: 1, size: 1 });
     }
   } else {
     item.onSearch();

+ 1 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/trade/RecordSource.java

@@ -8,6 +8,7 @@ public enum RecordSource {
     REAL("real"),
     INVITE("invite"),
     PREPARE("prepare"),
+    BACKEND("backend"),
     GIFT_COURSE("gift_course"),
     GIFT_ACTIVITY("gift_activity"),
 

+ 47 - 3
server/data/src/main/java/com/qxgmat/data/dao/entity/Comment.java

@@ -41,6 +41,15 @@ public class Comment implements Serializable {
     @Column(name = "`position`")
     private String position;
 
+    /**
+     * 是否展示
+     */
+    @Column(name = "`is_show`")
+    private Integer isShow;
+
+    /**
+     * 是否用于展示
+     */
     @Column(name = "`is_special`")
     private Integer isSpecial;
 
@@ -163,14 +172,36 @@ public class Comment implements Serializable {
     }
 
     /**
-     * @return is_special
+     * 获取是否展示
+     *
+     * @return is_show - 是否展示
+     */
+    public Integer getIsShow() {
+        return isShow;
+    }
+
+    /**
+     * 设置是否展示
+     *
+     * @param isShow 是否展示
+     */
+    public void setIsShow(Integer isShow) {
+        this.isShow = isShow;
+    }
+
+    /**
+     * 获取是否用于展示
+     *
+     * @return is_special - 是否用于展示
      */
     public Integer getIsSpecial() {
         return isSpecial;
     }
 
     /**
-     * @param isSpecial
+     * 设置是否用于展示
+     *
+     * @param isSpecial 是否用于展示
      */
     public void setIsSpecial(Integer isSpecial) {
         this.isSpecial = isSpecial;
@@ -234,6 +265,7 @@ public class Comment implements Serializable {
         sb.append(", avatar=").append(avatar);
         sb.append(", channel=").append(channel);
         sb.append(", position=").append(position);
+        sb.append(", isShow=").append(isShow);
         sb.append(", isSpecial=").append(isSpecial);
         sb.append(", isSystem=").append(isSystem);
         sb.append(", createTime=").append(createTime);
@@ -312,7 +344,19 @@ public class Comment implements Serializable {
         }
 
         /**
-         * @param isSpecial
+         * 设置是否展示
+         *
+         * @param isShow 是否展示
+         */
+        public Builder isShow(Integer isShow) {
+            obj.setIsShow(isShow);
+            return this;
+        }
+
+        /**
+         * 设置是否用于展示
+         *
+         * @param isSpecial 是否用于展示
          */
         public Builder isSpecial(Integer isSpecial) {
             obj.setIsSpecial(isSpecial);

+ 42 - 7
server/data/src/main/java/com/qxgmat/data/dao/entity/Faq.java

@@ -60,7 +60,13 @@ public class Faq implements Serializable {
     private Integer managerId;
 
     /**
-     * 是否精选:0不是精选,1是精选
+     * 是否展示
+     */
+    @Column(name = "`is_show`")
+    private Integer isShow;
+
+    /**
+     * 是否用于展示
      */
     @Column(name = "`is_special`")
     private Integer isSpecial;
@@ -259,18 +265,36 @@ public class Faq implements Serializable {
     }
 
     /**
-     * 获取是否精选:0不是精选,1是精选
+     * 获取是否展示
+     *
+     * @return is_show - 是否展示
+     */
+    public Integer getIsShow() {
+        return isShow;
+    }
+
+    /**
+     * 设置是否展示
      *
-     * @return is_special - 是否精选:0不是精选,1是精选
+     * @param isShow 是否展示
+     */
+    public void setIsShow(Integer isShow) {
+        this.isShow = isShow;
+    }
+
+    /**
+     * 获取是否用于展示
+     *
+     * @return is_special - 是否用于展示
      */
     public Integer getIsSpecial() {
         return isSpecial;
     }
 
     /**
-     * 设置是否精选:0不是精选,1是精选
+     * 设置是否用于展示
      *
-     * @param isSpecial 是否精选:0不是精选,1是精选
+     * @param isSpecial 是否用于展示
      */
     public void setIsSpecial(Integer isSpecial) {
         this.isSpecial = isSpecial;
@@ -395,6 +419,7 @@ public class Faq implements Serializable {
         sb.append(", channel=").append(channel);
         sb.append(", position=").append(position);
         sb.append(", managerId=").append(managerId);
+        sb.append(", isShow=").append(isShow);
         sb.append(", isSpecial=").append(isSpecial);
         sb.append(", answerStatus=").append(answerStatus);
         sb.append(", answerTime=").append(answerTime);
@@ -506,9 +531,19 @@ public class Faq implements Serializable {
         }
 
         /**
-         * 设置是否精选:0不是精选,1是精选
+         * 设置是否展示
+         *
+         * @param isShow 是否展示
+         */
+        public Builder isShow(Integer isShow) {
+            obj.setIsShow(isShow);
+            return this;
+        }
+
+        /**
+         * 设置是否用于展示
          *
-         * @param isSpecial 是否精选:0不是精选,1是精选
+         * @param isSpecial 是否用于展示
          */
         public Builder isSpecial(Integer isSpecial) {
             obj.setIsSpecial(isSpecial);

+ 156 - 16
server/data/src/main/java/com/qxgmat/data/dao/entity/PreviewAssign.java

@@ -18,6 +18,12 @@ public class PreviewAssign implements Serializable {
     private Integer paperId;
 
     /**
+     * 独立的作业标题
+     */
+    @Column(name = "`title`")
+    private String title;
+
+    /**
      * 课程id
      */
     @Column(name = "`course_id`")
@@ -30,6 +36,12 @@ public class PreviewAssign implements Serializable {
     private Integer courseNo;
 
     /**
+     * 课程模块
+     */
+    @Column(name = "`course_module`")
+    private String courseModule;
+
+    /**
      * 课程时间段
      */
     @Column(name = "`course_time`")
@@ -42,10 +54,22 @@ public class PreviewAssign implements Serializable {
     private Integer courseAppointment;
 
     /**
-     * 做题完成时间
+     * 题目编号id:json
      */
-    @Column(name = "`target_time`")
-    private Date targetTime;
+    @Column(name = "`question_no_ids`")
+    private Integer[] questionNoIds;
+
+    /**
+     * 做题开始时间
+     */
+    @Column(name = "`start_time`")
+    private Date startTime;
+
+    /**
+     * 做题结束时间
+     */
+    @Column(name = "`end_time`")
+    private Date endTime;
 
     @Column(name = "`create_time`")
     private Date createTime;
@@ -85,6 +109,24 @@ public class PreviewAssign implements Serializable {
     }
 
     /**
+     * 获取独立的作业标题
+     *
+     * @return title - 独立的作业标题
+     */
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * 设置独立的作业标题
+     *
+     * @param title 独立的作业标题
+     */
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    /**
      * 获取课程id
      *
      * @return course_id - 课程id
@@ -121,6 +163,24 @@ public class PreviewAssign implements Serializable {
     }
 
     /**
+     * 获取课程模块
+     *
+     * @return course_module - 课程模块
+     */
+    public String getCourseModule() {
+        return courseModule;
+    }
+
+    /**
+     * 设置课程模块
+     *
+     * @param courseModule 课程模块
+     */
+    public void setCourseModule(String courseModule) {
+        this.courseModule = courseModule;
+    }
+
+    /**
      * 获取课程时间段
      *
      * @return course_time - 课程时间段
@@ -157,21 +217,57 @@ public class PreviewAssign implements Serializable {
     }
 
     /**
-     * 获取做题完成时间
+     * 获取题目编号id:json
+     *
+     * @return question_no_ids - 题目编号id:json
+     */
+    public Integer[] getQuestionNoIds() {
+        return questionNoIds;
+    }
+
+    /**
+     * 设置题目编号id:json
+     *
+     * @param questionNoIds 题目编号id:json
+     */
+    public void setQuestionNoIds(Integer[] questionNoIds) {
+        this.questionNoIds = questionNoIds;
+    }
+
+    /**
+     * 获取做题开始时间
+     *
+     * @return start_time - 做题开始时间
+     */
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    /**
+     * 设置做题开始时间
      *
-     * @return target_time - 做题完成时间
+     * @param startTime 做题开始时间
      */
-    public Date getTargetTime() {
-        return targetTime;
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
     }
 
     /**
-     * 设置做题完成时间
+     * 获取做题结束时间
      *
-     * @param targetTime 做题完成时间
+     * @return end_time - 做题结束时间
      */
-    public void setTargetTime(Date targetTime) {
-        this.targetTime = targetTime;
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    /**
+     * 设置做题结束时间
+     *
+     * @param endTime 做题结束时间
+     */
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
     }
 
     /**
@@ -196,11 +292,15 @@ public class PreviewAssign implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", paperId=").append(paperId);
+        sb.append(", title=").append(title);
         sb.append(", courseId=").append(courseId);
         sb.append(", courseNo=").append(courseNo);
+        sb.append(", courseModule=").append(courseModule);
         sb.append(", courseTime=").append(courseTime);
         sb.append(", courseAppointment=").append(courseAppointment);
-        sb.append(", targetTime=").append(targetTime);
+        sb.append(", questionNoIds=").append(questionNoIds);
+        sb.append(", startTime=").append(startTime);
+        sb.append(", endTime=").append(endTime);
         sb.append(", createTime=").append(createTime);
         sb.append("]");
         return sb.toString();
@@ -236,6 +336,16 @@ public class PreviewAssign implements Serializable {
         }
 
         /**
+         * 设置独立的作业标题
+         *
+         * @param title 独立的作业标题
+         */
+        public Builder title(String title) {
+            obj.setTitle(title);
+            return this;
+        }
+
+        /**
          * 设置课程id
          *
          * @param courseId 课程id
@@ -256,6 +366,16 @@ public class PreviewAssign implements Serializable {
         }
 
         /**
+         * 设置课程模块
+         *
+         * @param courseModule 课程模块
+         */
+        public Builder courseModule(String courseModule) {
+            obj.setCourseModule(courseModule);
+            return this;
+        }
+
+        /**
          * 设置课程时间段
          *
          * @param courseTime 课程时间段
@@ -276,12 +396,32 @@ public class PreviewAssign implements Serializable {
         }
 
         /**
-         * 设置做题完成时间
+         * 设置题目编号id:json
+         *
+         * @param questionNoIds 题目编号id:json
+         */
+        public Builder questionNoIds(Integer[] questionNoIds) {
+            obj.setQuestionNoIds(questionNoIds);
+            return this;
+        }
+
+        /**
+         * 设置做题开始时间
+         *
+         * @param startTime 做题开始时间
+         */
+        public Builder startTime(Date startTime) {
+            obj.setStartTime(startTime);
+            return this;
+        }
+
+        /**
+         * 设置做题结束时间
          *
-         * @param targetTime 做题完成时间
+         * @param endTime 做题结束时间
          */
-        public Builder targetTime(Date targetTime) {
-            obj.setTargetTime(targetTime);
+        public Builder endTime(Date endTime) {
+            obj.setEndTime(endTime);
             return this;
         }
 

+ 35 - 35
server/data/src/main/java/com/qxgmat/data/dao/entity/User.java

@@ -214,12 +214,6 @@ public class User implements Serializable {
     private Date latestLoginTime;
 
     /**
-     * 累计警告次数
-     */
-    @Column(name = "`total_alert`")
-    private Integer totalAlert;
-
-    /**
      * 是否冻结
      */
     @Column(name = "`is_frozen`")
@@ -240,6 +234,12 @@ public class User implements Serializable {
     @Column(name = "`textbook_email_subscribe`")
     private Integer textbookEmailSubscribe;
 
+    /**
+     * 累计告警字数
+     */
+    @Column(name = "`total_alert`")
+    private Integer totalAlert;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -865,24 +865,6 @@ public class User implements Serializable {
     }
 
     /**
-     * 获取累计警告次数
-     *
-     * @return total_alert - 累计警告次数
-     */
-    public Integer getTotalAlert() {
-        return totalAlert;
-    }
-
-    /**
-     * 设置累计警告次数
-     *
-     * @param totalAlert 累计警告次数
-     */
-    public void setTotalAlert(Integer totalAlert) {
-        this.totalAlert = totalAlert;
-    }
-
-    /**
      * 获取是否冻结
      *
      * @return is_frozen - 是否冻结
@@ -950,6 +932,24 @@ public class User implements Serializable {
         this.textbookEmailSubscribe = textbookEmailSubscribe;
     }
 
+    /**
+     * 获取累计告警字数
+     *
+     * @return total_alert - 累计告警字数
+     */
+    public Integer getTotalAlert() {
+        return totalAlert;
+    }
+
+    /**
+     * 设置累计告警字数
+     *
+     * @param totalAlert 累计告警字数
+     */
+    public void setTotalAlert(Integer totalAlert) {
+        this.totalAlert = totalAlert;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -991,11 +991,11 @@ public class User implements Serializable {
         sb.append(", registerCity=").append(registerCity);
         sb.append(", latestLoginIp=").append(latestLoginIp);
         sb.append(", latestLoginTime=").append(latestLoginTime);
-        sb.append(", totalAlert=").append(totalAlert);
         sb.append(", isFrozen=").append(isFrozen);
         sb.append(", createTime=").append(createTime);
         sb.append(", dataEmailSubscribe=").append(dataEmailSubscribe);
         sb.append(", textbookEmailSubscribe=").append(textbookEmailSubscribe);
+        sb.append(", totalAlert=").append(totalAlert);
         sb.append("]");
         return sb.toString();
     }
@@ -1358,16 +1358,6 @@ public class User implements Serializable {
         }
 
         /**
-         * 设置累计警告次数
-         *
-         * @param totalAlert 累计警告次数
-         */
-        public Builder totalAlert(Integer totalAlert) {
-            obj.setTotalAlert(totalAlert);
-            return this;
-        }
-
-        /**
          * 设置是否冻结
          *
          * @param isFrozen 是否冻结
@@ -1405,6 +1395,16 @@ public class User implements Serializable {
             return this;
         }
 
+        /**
+         * 设置累计告警字数
+         *
+         * @param totalAlert 累计告警字数
+         */
+        public Builder totalAlert(Integer totalAlert) {
+            obj.setTotalAlert(totalAlert);
+            return this;
+        }
+
         public User build() {
             return this.obj;
         }

+ 35 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrderRecord.java

@@ -72,6 +72,12 @@ public class UserOrderRecord implements Serializable {
     private Integer number;
 
     /**
+     * vs课程编号
+     */
+    @Column(name = "`vs_no`")
+    private Integer vsNo;
+
+    /**
      * 回复时长
      */
     @Column(name = "`ask_time`")
@@ -355,6 +361,24 @@ public class UserOrderRecord implements Serializable {
     }
 
     /**
+     * 获取vs课程编号
+     *
+     * @return vs_no - vs课程编号
+     */
+    public Integer getVsNo() {
+        return vsNo;
+    }
+
+    /**
+     * 设置vs课程编号
+     *
+     * @param vsNo vs课程编号
+     */
+    public void setVsNo(Integer vsNo) {
+        this.vsNo = vsNo;
+    }
+
+    /**
      * 获取回复时长
      *
      * @return ask_time - 回复时长
@@ -637,6 +661,7 @@ public class UserOrderRecord implements Serializable {
         sb.append(", source=").append(source);
         sb.append(", teacherId=").append(teacherId);
         sb.append(", number=").append(number);
+        sb.append(", vsNo=").append(vsNo);
         sb.append(", askTime=").append(askTime);
         sb.append(", cctalkName=").append(cctalkName);
         sb.append(", isSubscribe=").append(isSubscribe);
@@ -776,6 +801,16 @@ public class UserOrderRecord implements Serializable {
         }
 
         /**
+         * 设置vs课程编号
+         *
+         * @param vsNo vs课程编号
+         */
+        public Builder vsNo(Integer vsNo) {
+            obj.setVsNo(vsNo);
+            return this;
+        }
+
+        /**
          * 设置回复时长
          *
          * @param askTime 回复时长

+ 3 - 2
server/data/src/main/java/com/qxgmat/data/dao/mapping/CommentMapper.xml

@@ -11,6 +11,7 @@
     <result column="avatar" jdbcType="VARCHAR" property="avatar" />
     <result column="channel" jdbcType="VARCHAR" property="channel" />
     <result column="position" jdbcType="VARCHAR" property="position" />
+    <result column="is_show" jdbcType="INTEGER" property="isShow" />
     <result column="is_special" jdbcType="INTEGER" property="isSpecial" />
     <result column="is_system" jdbcType="INTEGER" property="isSystem" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
@@ -25,8 +26,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `nickname`, `avatar`, `channel`, `position`, `is_special`, `is_system`, 
-    `create_time`
+    `id`, `user_id`, `nickname`, `avatar`, `channel`, `position`, `is_show`, `is_special`, 
+    `is_system`, `create_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 3 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/FaqMapper.xml

@@ -14,6 +14,7 @@
     <result column="channel" jdbcType="VARCHAR" property="channel" />
     <result column="position" jdbcType="VARCHAR" property="position" />
     <result column="manager_id" jdbcType="INTEGER" property="managerId" />
+    <result column="is_show" jdbcType="INTEGER" property="isShow" />
     <result column="is_special" jdbcType="INTEGER" property="isSpecial" />
     <result column="answer_status" jdbcType="INTEGER" property="answerStatus" />
     <result column="answer_time" jdbcType="TIMESTAMP" property="answerTime" />
@@ -32,7 +33,8 @@
       WARNING - @mbg.generated
     -->
     `id`, `faq_module`, `user_id`, `email`, `phone`, `message`, `channel`, `position`, 
-    `manager_id`, `is_special`, `answer_status`, `answer_time`, `is_system`, `create_time`
+    `manager_id`, `is_show`, `is_special`, `answer_status`, `answer_time`, `is_system`, 
+    `create_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 7 - 3
server/data/src/main/java/com/qxgmat/data/dao/mapping/PreviewAssignMapper.xml

@@ -7,18 +7,22 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="paper_id" jdbcType="INTEGER" property="paperId" />
+    <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="course_id" jdbcType="INTEGER" property="courseId" />
     <result column="course_no" jdbcType="INTEGER" property="courseNo" />
+    <result column="course_module" jdbcType="VARCHAR" property="courseModule" />
     <result column="course_time" jdbcType="INTEGER" property="courseTime" />
     <result column="course_appointment" jdbcType="INTEGER" property="courseAppointment" />
-    <result column="target_time" jdbcType="TIMESTAMP" property="targetTime" />
+    <result column="question_no_ids" jdbcType="VARCHAR" property="questionNoIds" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler" />
+    <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
+    <result column="end_time" jdbcType="TIMESTAMP" property="endTime" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `paper_id`, `course_id`, `course_no`, `course_time`, `course_appointment`, 
-    `target_time`, `create_time`
+    `id`, `paper_id`, `title`, `course_id`, `course_no`, `course_module`, `course_time`, 
+    `course_appointment`, `question_no_ids`, `start_time`, `end_time`, `create_time`
   </sql>
 </mapper>

+ 3 - 3
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserMapper.xml

@@ -40,11 +40,11 @@
     <result column="register_city" jdbcType="VARCHAR" property="registerCity" />
     <result column="latest_login_ip" jdbcType="VARCHAR" property="latestLoginIp" />
     <result column="latest_login_time" jdbcType="TIMESTAMP" property="latestLoginTime" />
-    <result column="total_alert" jdbcType="INTEGER" property="totalAlert" />
     <result column="is_frozen" jdbcType="INTEGER" property="isFrozen" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="data_email_subscribe" jdbcType="INTEGER" property="dataEmailSubscribe" />
     <result column="textbook_email_subscribe" jdbcType="INTEGER" property="textbookEmailSubscribe" />
+    <result column="total_alert" jdbcType="INTEGER" property="totalAlert" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
@@ -56,7 +56,7 @@
     `real_photo_front`, `real_photo_back`, `real_status`, `prepare_time`, `prepare_status`, 
     `prepare_goal`, `prepare_examination_time`, `prepare_score_time`, `latest_exercise`, 
     `latest_error`, `origin_id`, `invite_code`, `total_money`, `invite_number`, `register_ip`, 
-    `register_city`, `latest_login_ip`, `latest_login_time`, `total_alert`, `is_frozen`, 
-    `create_time`, `data_email_subscribe`, `textbook_email_subscribe`
+    `register_city`, `latest_login_ip`, `latest_login_time`, `is_frozen`, `create_time`, 
+    `data_email_subscribe`, `textbook_email_subscribe`, `total_alert`
   </sql>
 </mapper>

+ 2 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderRecordMapper.xml

@@ -16,6 +16,7 @@
     <result column="source" jdbcType="VARCHAR" property="source" />
     <result column="teacher_id" jdbcType="INTEGER" property="teacherId" />
     <result column="number" jdbcType="INTEGER" property="number" />
+    <result column="vs_no" jdbcType="INTEGER" property="vsNo" />
     <result column="ask_time" jdbcType="INTEGER" property="askTime" />
     <result column="cctalk_name" jdbcType="VARCHAR" property="cctalkName" />
     <result column="is_subscribe" jdbcType="INTEGER" property="isSubscribe" />
@@ -37,7 +38,7 @@
       WARNING - @mbg.generated
     -->
     `id`, `user_id`, `order_id`, `parent_id`, `product_type`, `product_id`, `service`, 
-    `param`, `source`, `teacher_id`, `number`, `ask_time`, `cctalk_name`, `is_subscribe`, 
+    `param`, `source`, `teacher_id`, `number`, `vs_no`, `ask_time`, `cctalk_name`, `is_subscribe`, 
     `start_time`, `end_time`, `use_start_time`, `use_end_time`, `is_used`, `use_time`, 
     `is_stop`, `stop_time`, `is_suspend`, `suspend_time`, `restore_time`, `create_time`
   </sql>

+ 18 - 0
server/data/src/main/java/com/qxgmat/data/relation/PreviewPaperRelationMapper.java

@@ -0,0 +1,18 @@
+package com.qxgmat.data.relation;
+
+import com.qxgmat.data.dao.entity.PreviewPaper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * Created by gaojie on 2017/11/9.
+ */
+public interface PreviewPaperRelationMapper {
+    List<PreviewPaper> listAdmin(
+            @Param("courseModule") String courseModule,
+            @Param("structId") Integer structId,
+            @Param("order") String order,
+            @Param("direction") String direction
+    );
+}

+ 0 - 15
server/data/src/main/java/com/qxgmat/data/relation/UserPaperRelationMapper.java

@@ -13,21 +13,6 @@ import java.util.List;
  */
 public interface UserPaperRelationMapper {
 
-    List<UserPaper> listPreviewAdmin(
-            @Param("category") Number category,
-            @Param("userId") Number userId,
-            @Param("previewId") Number previewId,
-            @Param("startTime") String startTime,
-            @Param("endTime") String endTime
-    );
-
-    List<UserPaper> listPreview(
-            @Param("category") Number category,
-            @Param("userId") Number userId,
-            @Param("endTime") String endTime,
-            @Param("finish") Boolean finish
-    );
-
     List<UserPaper> listPreviewGroupTop(
             @Param("userId") Number userId,
             @Param("top") Number top

+ 1 - 1
server/data/src/main/java/com/qxgmat/data/relation/mapping/CourseExperienceRelationMapper.xml

@@ -12,7 +12,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    cd.`id`
+    ce.`id`
   </sql>
 
   <!--累加记录-->

+ 1 - 1
server/data/src/main/java/com/qxgmat/data/relation/mapping/CoursePackageRelationMapper.xml

@@ -12,7 +12,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    cd.`id`
+    cp.`id`
   </sql>
 
   <!--累加记录-->

+ 3 - 3
server/data/src/main/java/com/qxgmat/data/relation/mapping/CourseRelationMapper.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.qxgmat.data.relation.CoursePackageRelationMapper">
-  <resultMap id="IdMap" type="com.qxgmat.data.dao.entity.CoursePackage">
+<mapper namespace="com.qxgmat.data.relation.CourseRelationMapper">
+  <resultMap id="IdMap" type="com.qxgmat.data.dao.entity.Course">
     <!--
       WARNING - @mbg.generated
     -->
@@ -12,7 +12,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    cd.`id`
+    c.`id`
   </sql>
 
   <!--累加记录-->

+ 31 - 0
server/data/src/main/java/com/qxgmat/data/relation/mapping/PreviewPaperRelationMapper.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qxgmat.data.relation.PreviewPaperRelationMapper">
+  <resultMap id="IdMap" type="com.qxgmat.data.dao.entity.PreviewPaper">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+  </resultMap>
+  <sql id="Id_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    pp.`id`
+  </sql>
+
+  <select id="listAdmin" resultMap="IdMap">
+    select
+    <include refid="Id_Column_List" />
+    from `preview_paper` pp
+      left join `course` c on c.`id` = pp.`course_id`
+    <if test="structId != null">
+      and (c.`struct_id` = #{structId,jdbcType=VARCHAR} or c.`parent_struct_id` = #{structId, jdbcType=VARCHAR} )
+    </if>
+    where c.`id` > 0
+    <if test="courseModule != null">
+      and pp.`course_module` = #{courseModule,jdbcType=VARCHAR}
+    </if>
+    order by ${order} ${direction}
+  </select>
+</mapper>

+ 0 - 58
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserPaperRelationMapper.xml

@@ -27,64 +27,6 @@
     WHERE `id` = #{id, jdbcType=VARCHAR}
   </update>
 
-  <!--
-    用户预习作业列表: 后台
-    https://blog.csdn.net/t_1007/article/details/52369261
-  -->
-  <select id="listPreviewAdmin" resultMap="IdMap">
-    select
-    <include refid="Id_Column_List" />
-    from `user_paper` up
-    left join `preview_paper` hp on hp.`id` = up.`module_id`
-      and up.`module` = "preview"
-      <if test="previewId != null">
-        and hp.`id` = #{previewId,jdbcType=VARCHAR}
-      </if>
-      <if test="category != null">
-        and hp.`category` = #{category,jdbcType=VARCHAR}
-      </if>
-    where
-    hp.`id` &gt; 0
-    <if test="userId != null">
-      and up.`user_id` = #{userId,jdbcType=VARCHAR}
-    </if>
-    <if test="startTime != null">
-      and up.`createTime` &gt; #{startTime,jdbcType=VARCHAR}
-    </if>
-    <if test="endTime != null">
-      and up.`createTime` &lt; #{endTime,jdbcType=VARCHAR}
-    </if>
-  </select>
-
-  <!--
-    用户预习作业列表: 用户端
-  -->
-  <select id="listPreview" resultMap="IdMap">
-    select
-    <include refid="Id_Column_List" />
-    from `user_paper` up
-    left join `preview_paper` hp on hp.`id` = up.`module_id`
-      and up.`module` = "preview"
-      <if test="category != null">
-        and hp.`category` = #{category,jdbcType=VARCHAR}
-      </if>
-      <if test="endTime != null">
-        and hp.`endTime` &lt; #{endTime,jdbcType=VARCHAR}
-      </if>
-    where
-    hp.`id` &gt; null
-    <if test="userId != null">
-      and up.`user_id` = #{userId,jdbcType=VARCHAR}
-    </if>
-    <if test="finish != null">
-      <if test="finish == true">
-      and up.`number` &gt; 1
-      </if>
-      <if test="finish == false">
-        and up.`number` = 0
-      </if>>
-    </if>
-  </select>
 
   <!--
     用户预习作业Top列表: 用户端

+ 2 - 2
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserRelationMapper.xml

@@ -26,9 +26,9 @@
   <update id="accumulation">
     UPDATE `user`
     <trim prefix="set" suffixOverrides=",">
-      `total_money`=`total_money`+#{monney, jdbcType=DECIMAL},
+      `total_money`=`total_money`+#{money, jdbcType=DECIMAL},
     </trim>
-    WHERE `id` = #{userId, jdbcType=VARCHAR}
+    WHERE `id` = #{id, jdbcType=VARCHAR}
   </update>
 
   <!--

+ 4 - 1
server/data/src/main/resources/mybatis-generator.xml

@@ -116,7 +116,10 @@
         <table schema="qianxing" tableName="preview_paper" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
             <columnOverride column="question_no_ids" javaType="Integer[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler"/>
-            <columnOverride column="user_ids" javaType="Integer[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler"/>
+        </table>
+        <table schema="qianxing" tableName="preview_assign" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
+            <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
+            <columnOverride column="question_no_ids" javaType="Integer[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler"/>
         </table>
         <table schema="qianxing" tableName="setting" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>

+ 2 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/AdController.java

@@ -66,6 +66,8 @@ public class AdController {
     public Response<PageMessage<AdListDto>> list(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<Ad> p = adService.select(page, size);
         List<AdListDto> pr = Transform.convert(p, AdListDto.class);

+ 18 - 2
server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java

@@ -133,6 +133,8 @@ public class CourseController {
             @RequestParam(required = false) String courseModule,
             @RequestParam(required = false) Integer structId,
             @RequestParam(required = false) Boolean excludeVs,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<Course> p;
         if (ids != null && ids.length > 0){
@@ -182,6 +184,8 @@ public class CourseController {
     public Response<PageMessage<CoursePackage>> listPackage(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<CoursePackage> p = coursePackageService.select(page, size);
         List<CoursePackage> pr = Transform.convert(p, CoursePackage.class);
@@ -268,6 +272,8 @@ public class CourseController {
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = true) Integer dataId,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<CourseDataHistory> p = courseDataHistoryService.listByData(page, size, dataId);
         List<CourseDataHistory> pr = Transform.convert(p, CourseDataHistory.class);
@@ -371,6 +377,8 @@ public class CourseController {
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) Integer courseId,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<CourseTeacher> p = courseTeacherService.listAdmin(page, size, courseId);
         return ResponseHelp.success(p, page, size, p.getTotal());
@@ -468,6 +476,8 @@ public class CourseController {
             @RequestParam(required = false) Integer[] ids,
             @RequestParam(required = false) Integer courseId,
             @RequestParam(required = false) String keyword,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
 
         Page<CourseTime> p;
@@ -527,6 +537,8 @@ public class CourseController {
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) Integer courseId,
             @RequestParam(required = false) Integer timeId,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<CourseStudentOnline> p = courseStudentOnlineService.listAdmin(page, size, courseId, timeId);
         List<CourseStudentOnlineListDto> pr = Transform.convert(p, CourseStudentOnlineListDto.class);
@@ -587,7 +599,9 @@ public class CourseController {
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) Integer courseId,
-            @RequestParam(required = false) Integer userId
+            @RequestParam(required = false) Integer userId,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction
     ){
         Page<UserOrderRecord> p = userOrderRecordService.listWithStudentAdmin(page, size, courseId, userId);
         List<UserCourseStudentRecordInfoDto> pr = Transform.convert(p, UserCourseStudentRecordInfoDto.class);
@@ -615,7 +629,9 @@ public class CourseController {
             @RequestParam(required = false) Integer structId,
             @RequestParam(required = false) Integer courseId,
             @RequestParam(required = false) Integer userId,
-            @RequestParam(required = false) String teacher
+            @RequestParam(required = false) String teacher,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction
     ){
         Page<UserOrderRecord> p;
         if (ids != null && ids.length > 0){

+ 2 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/ExaminationController.java

@@ -84,6 +84,8 @@ public class ExaminationController {
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false, defaultValue = "") String keyword,
             @RequestParam(required = false) Integer[] ids,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<ExaminationPaper> p;
         if (ids != null && ids.length > 0){

+ 2 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/ExerciseController.java

@@ -101,6 +101,8 @@ public class ExerciseController {
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false, defaultValue = "") String keyword,
             @RequestParam(required = false) Integer[] ids,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<ExercisePaper> p;
         if (ids != null && ids.length > 0){

+ 86 - 51
server/gateway-api/src/main/java/com/qxgmat/controller/admin/PreviewController.java

@@ -5,20 +5,20 @@ import com.nuliji.tools.PageMessage;
 import com.nuliji.tools.Response;
 import com.nuliji.tools.ResponseHelp;
 import com.nuliji.tools.Transform;
-import com.qxgmat.data.constants.enums.QuestionType;
-import com.qxgmat.data.constants.enums.module.PaperModule;
-import com.qxgmat.data.constants.enums.module.QuestionModule;
-import com.qxgmat.data.constants.enums.status.PreviewStatus;
+import com.qxgmat.data.constants.enums.module.CourseModule;
+import com.qxgmat.data.constants.enums.status.DirectionStatus;
+import com.qxgmat.data.dao.entity.Course;
+import com.qxgmat.data.dao.entity.PreviewAssign;
 import com.qxgmat.data.dao.entity.PreviewPaper;
-import com.qxgmat.data.dao.entity.UserPaper;
-import com.qxgmat.dto.admin.request.PreviewDto;
-import com.qxgmat.dto.admin.response.PreviewDetailDto;
-import com.qxgmat.dto.admin.response.PreviewListDto;
+import com.qxgmat.data.dao.entity.User;
+import com.qxgmat.dto.admin.extend.CourseExtendDto;
+import com.qxgmat.dto.admin.extend.UserExtendDto;
+import com.qxgmat.dto.admin.request.PreviewAssignDto;
+import com.qxgmat.dto.admin.request.PreviewPaperDto;
+import com.qxgmat.dto.admin.response.PreviewPaperListDto;
 import com.qxgmat.service.extend.PreviewService;
 import com.qxgmat.service.extend.QuestionFlowService;
-import com.qxgmat.service.inline.ManagerLogService;
-import com.qxgmat.service.inline.PreviewPaperService;
-import com.qxgmat.service.inline.QuestionNoService;
+import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
+import java.util.Collection;
 import java.util.List;
 
 @RestController("AdminPreviewController")
@@ -38,12 +39,18 @@ public class PreviewController {
     private ManagerLogService managerLogService;
 
     @Autowired
+    private CourseService courseService;
+
+    @Autowired
     private PreviewService previewService;
 
     @Autowired
     private PreviewPaperService previewPaperService;
 
     @Autowired
+    private PreviewAssignService previewAssignService;
+
+    @Autowired
     private QuestionNoService questionNoService;
 
     @Autowired
@@ -51,40 +58,18 @@ public class PreviewController {
 
     @RequestMapping(value = "/add", method = RequestMethod.POST)
     @ApiOperation(value = "添加预习作业", httpMethod = "POST")
-    public Response<PreviewPaper> add(@RequestBody @Validated PreviewDto dto, HttpServletRequest request) {
+    public Response<PreviewPaper> add(@RequestBody @Validated PreviewPaperDto dto, HttpServletRequest request) {
         PreviewPaper entity = Transform.dtoToEntity(dto);
-
-        // 获取考题模块
-        QuestionModule module = QuestionModule.WithPaper(PaperModule.ValueOf(entity.getPaperModule()));
-
-        UserPaper tmpPaper = UserPaper.builder()
-                .questionNoIds(entity.getQuestionNoIds())
-                .questionNumber(entity.getQuestionNoIds().length)
-                .build();
-        // 初始化paper:主要是计算做题时间
-        questionFlowService.initPaper(tmpPaper, module);
-
-        entity = previewService.edit(entity);
+        previewPaperService.add(entity);
         managerLogService.log(request);
         return ResponseHelp.success(entity);
     }
 
     @RequestMapping(value = "/edit", method = RequestMethod.PUT)
     @ApiOperation(value = "编辑预习作业", httpMethod = "PUT")
-    public Response<Boolean> edit(@RequestBody @Validated PreviewDto dto, HttpServletRequest request) {
+    public Response<Boolean> edit(@RequestBody @Validated PreviewPaperDto dto, HttpServletRequest request) {
         PreviewPaper entity = Transform.dtoToEntity(dto);
-
-        // 获取考题模块
-        QuestionModule module = QuestionModule.WithPaper(PaperModule.ValueOf(entity.getPaperModule()));
-
-        UserPaper tmpPaper = UserPaper.builder()
-                .questionNoIds(entity.getQuestionNoIds())
-                .questionNumber(entity.getQuestionNoIds().length)
-                .build();
-        // 初始化paper:主要是计算做题时间
-        questionFlowService.initPaper(tmpPaper, module);
-
-        entity = previewService.edit(entity);
+        previewPaperService.edit(entity);
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -92,35 +77,85 @@ public class PreviewController {
     @RequestMapping(value = "/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "删除预习作业", httpMethod = "DELETE")
     public Response<Boolean> delete(@RequestParam int id, HttpServletRequest request) {
+        previewPaperService.delete(id);
         managerLogService.log(request);
-        return ResponseHelp.success(previewPaperService.delete(id));
+        return ResponseHelp.success(true);
     }
 
     @RequestMapping(value = "/detail", method = RequestMethod.GET)
     @ApiOperation(value = "获取预习作业", httpMethod = "GET")
-    public Response<PreviewDetailDto> detail(@RequestParam int id, HttpSession session) {
+    public Response<PreviewPaper> detail(@RequestParam int id, HttpSession session) {
         PreviewPaper entity = previewPaperService.get(id);
-        PreviewDetailDto dto = Transform.convert(entity, PreviewDetailDto.class);
-
-//        List<QuestionNoRelation> questionNos = questionNoService.listWithRelationByIds(entity.getQuestionNoIds());
-//        List<QuestionNoExtendDto> questionNoExtendDtos = Transform.convert(questionNos, QuestionNoExtendDto.class);
-//
-//        dto.setQuestionNos(questionNoExtendDtos);
 
-        return ResponseHelp.success(dto);
+        return ResponseHelp.success(entity);
     }
 
     @RequestMapping(value = "/list", method = RequestMethod.GET)
     @ApiOperation(value = "预习作业列表", httpMethod = "GET")
-    public Response<PageMessage<PreviewListDto>> list(
+    public Response<PageMessage<PreviewPaperListDto>> list(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
-            @RequestParam(required = false) Integer category,
-            @RequestParam(required = false) Integer status,
+            @RequestParam(required = false) String courseModule,
+            @RequestParam(required = false) Integer structId,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<PreviewPaper> p = previewPaperService.listAdmin(page, size, category, PreviewStatus.ValueOf(status));
-        List<PreviewListDto> pr = Transform.convert(p, PreviewListDto.class);
+        Page<PreviewPaper> p = previewPaperService.listAdmin(page, size, CourseModule.ValueOf(courseModule), structId, order, DirectionStatus.ValueOf(direction));
+        List<PreviewPaperListDto> pr = Transform.convert(p, PreviewPaperListDto.class);
+
+        // 绑定用户
+        Collection courseIds = Transform.getIds(p, PreviewPaper.class, "courseId");
+        List<Course> courseList = courseService.select(courseIds);
+        Transform.combine(pr, courseList, PreviewPaperListDto.class, "courseId", "course", Course.class, "id", CourseExtendDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
+
+    @RequestMapping(value = "/assign/add", method = RequestMethod.POST)
+    @ApiOperation(value = "添加分配预习作业", httpMethod = "POST")
+    public Response<PreviewAssign> addAssign(@RequestBody @Validated PreviewAssignDto dto, HttpServletRequest request) {
+        PreviewAssign entity = Transform.dtoToEntity(dto);
+        previewAssignService.add(entity);
+        managerLogService.log(request);
+        return ResponseHelp.success(entity);
+    }
+
+    @RequestMapping(value = "/assign/edit", method = RequestMethod.PUT)
+    @ApiOperation(value = "编辑分配预习作业", httpMethod = "PUT")
+    public Response<Boolean> editAssign(@RequestBody @Validated PreviewAssignDto dto, HttpServletRequest request) {
+        PreviewAssign entity = Transform.dtoToEntity(dto);
+        previewAssignService.edit(entity);
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/assign/delete", method = RequestMethod.DELETE)
+    @ApiOperation(value = "删除分配预习作业", httpMethod = "DELETE")
+    public Response<Boolean> deleteAssign(@RequestParam int id, HttpServletRequest request) {
+        previewAssignService.delete(id);
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/assign/detail", method = RequestMethod.GET)
+    @ApiOperation(value = "获取预习作业", httpMethod = "GET")
+    public Response<PreviewAssign> detailAssign(@RequestParam int id, HttpSession session) {
+        PreviewAssign entity = previewAssignService.get(id);
+
+        return ResponseHelp.success(entity);
+    }
+
+    @RequestMapping(value = "/assign/list", method = RequestMethod.GET)
+    @ApiOperation(value = "获取预习作业列表", httpMethod = "GET")
+    public Response<PageMessage<PreviewAssign>> listAssign(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) Integer paperId,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
+            HttpSession session) {
+        Page<PreviewAssign> p = previewAssignService.listAdmin(page, size, paperId, order, DirectionStatus.ValueOf(direction));
+
+        return ResponseHelp.success(p, page, size, p.getTotal());
+    }
 }

+ 4 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/SentenceController.java

@@ -136,6 +136,8 @@ public class SentenceController {
     public Response<PageMessage<SentenceQuestionListDto>> listQuestion(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<SentenceQuestion> p = sentenceQuestionService.select(page, size);
         List<SentenceQuestionListDto> pr = Transform.convert(p, SentenceQuestionListDto.class);
@@ -150,6 +152,8 @@ public class SentenceController {
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) String keyword,
             @RequestParam(required = false) Integer[] ids,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<SentenceQuestion> p;
         if (ids != null && ids.length > 0){

+ 9 - 3
server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java

@@ -387,7 +387,9 @@ public class SettingController {
             @RequestParam(required = false) String channel,
             @RequestParam(required = false) String position,
             @RequestParam(required = false) Integer userId,
-            @RequestParam(required = false) Boolean isSpecial
+            @RequestParam(required = false) Boolean isSpecial,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction
     ){
         Page<Comment> p = commentService.listAdmin(page, size, ChannelModule.ValueOf(channel), position, userId, isSpecial);
         List<CommentDto> pr = Transform.convert(p, CommentDto.class);
@@ -457,7 +459,9 @@ public class SettingController {
             @RequestParam(required = false) String channel,
             @RequestParam(required = false) String position,
             @RequestParam(required = false) Integer answerStatus,
-            @RequestParam(required = false) Boolean isSpecial
+            @RequestParam(required = false) Boolean isSpecial,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction
     ){
         Page<Faq> p = faqService.listAdmin(page, size, FaqModule.ValueOf(faqModule), ChannelModule.ValueOf(channel), position, answerStatus, isSpecial);
         return ResponseHelp.success(p, page, size, p.getTotal());
@@ -490,7 +494,9 @@ public class SettingController {
     @ApiOperation(value = "获取消息列表", httpMethod = "GET")
     private Response<PageMessage<Message>> listMessage(
             @RequestParam(required = false, defaultValue = "1") int page,
-            @RequestParam(required = false, defaultValue = "100") int size
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction
     ){
         Page<Message> p = messageService.select(page, size);
         return ResponseHelp.success(p, page, size, p.getTotal());

+ 143 - 61
server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java

@@ -8,21 +8,17 @@ import com.qxgmat.data.constants.enums.module.FeedbackModule;
 import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.status.FeedbackStatus;
+import com.qxgmat.data.constants.enums.trade.RecordSource;
 import com.qxgmat.data.constants.enums.user.MoneyRange;
 import com.qxgmat.data.dao.entity.*;
-import com.qxgmat.data.relation.entity.UserPreviewPaperRelation;
 import com.qxgmat.dto.admin.extend.*;
-import com.qxgmat.dto.admin.request.UserCourseAppointmentDto;
-import com.qxgmat.dto.admin.request.UserFeedbackErrorDto;
-import com.qxgmat.dto.admin.request.UserInvoiceDto;
-import com.qxgmat.dto.admin.request.UserServiceRecordDto;
+import com.qxgmat.dto.admin.request.*;
 import com.qxgmat.dto.admin.response.*;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.ManagerService;
 import com.qxgmat.service.UserPaperService;
 import com.qxgmat.service.UserServiceService;
-import com.qxgmat.service.extend.OrderFlowService;
-import com.qxgmat.service.extend.PreviewService;
+import com.qxgmat.service.extend.*;
 import com.qxgmat.service.UsersService;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
@@ -44,6 +40,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 import java.io.*;
+import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
@@ -112,34 +109,23 @@ public class UserController {
     private UserOrderRecordService userOrderRecordService;
 
     @Autowired
+    private QuestionFlowService questionFlowService;
+
+    @Autowired
     private OrderFlowService orderFlowService;
 
     @Autowired
+    private CourseExtendService courseExtendService;
+
+    @Autowired
+    private SentenceService sentenceService;
+
+    @Autowired
     private UserInvoiceService userInvoiceService;
 
-//    @RequestMapping(value = "/add", method = RequestMethod.POST)
-//    @ApiOperation(value = "添加用户信息", httpMethod = "POST")
-//    public Response<User> add(@RequestBody @Validated UserDto dto, HttpServletRequest request) {
-//        User entity = Transform.dtoToEntity(dto);
-//        entity = userService.edit(entity);
-//        managerLogService.log(request);
-//        return ResponseHelp.success(Transform.convert(entity, UserDto.class));
-//    }
-//    @RequestMapping(value = "/edit", method = RequestMethod.PUT)
-//    @ApiOperation(value = "修改用户信息", httpMethod = "PUT")
-//    public Response<Boolean> edit(@RequestBody @Validated UserDto dto, HttpServletRequest request) {
-//        User entity = Transform.dtoToEntity(dto);
-//        entity = userService.edit(entity);
-//        managerLogService.log(request);
-//        return ResponseHelp.success(true);
-//    }
-//
-//    @RequestMapping(value = "/delete", method = RequestMethod.DELETE)
-//    @ApiOperation(value = "删除用户", httpMethod = "DELETE")
-//    public Response<Boolean> delete(@RequestParam int id, HttpServletRequest request) {
-//        managerLogService.log(request);
-//        return ResponseHelp.success(userService.delete(id));
-//    }
+    @Autowired
+    private UserAbnormalService userAbnormalService;
+
     @RequestMapping(value = "/token", method = RequestMethod.GET)
     @ApiOperation(value = "获取用户token", httpMethod = "GET")
     public Response<String> token(@RequestParam int id, HttpSession session) {
@@ -154,11 +140,60 @@ public class UserController {
     public Response<UserDetailDto> detail(@RequestParam int id, HttpSession session) {
         User entity = usersService.get(id);
         UserDetailDto dto = Transform.convert(entity, UserDetailDto.class);
+        Integer time = 0;
 
+        time += courseExtendService.studyTime(id);
+        time += sentenceService.studyTime(id);
+        time += questionFlowService.studyTime(id);
+        dto.setTotalTime(time);
 
         return ResponseHelp.success(dto);
     }
 
+    @RequestMapping(value = "/real", method = RequestMethod.POST)
+    @ApiOperation(value = "实名认证", httpMethod = "POST")
+    public Response<Boolean> real(@RequestBody @Validated UserRealDto dto, HttpSession session) {
+        User in = usersService.get(dto.getId());
+        if (in == null){
+            throw new ParameterException("用户不存在");
+        }
+        if (in.getRealTime() != null){
+            throw new ParameterException("实名认证已通过");
+        }
+        User entity = Transform.dtoToEntity(dto);
+        entity.setRealStatus(1);
+        entity.setRealTime(new Date());
+        usersService.edit(entity);
+
+        orderFlowService.giveReal(in.getId());
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/money/add", method = RequestMethod.POST)
+    @ApiOperation(value = "增加用户累计金额", httpMethod = "POST")
+    public Response<Boolean> addMoney(@RequestBody @Validated UserMoneyDto dto, HttpSession session) {
+        User in = usersService.get(dto.getId());
+        if (in == null){
+            throw new ParameterException("用户不存在");
+        }
+        usersService.accumulation(dto.getId(), BigDecimal.valueOf(dto.getMoney()));
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/frozen", method = RequestMethod.POST)
+    @ApiOperation(value = "冻结用户", httpMethod = "POST")
+    public Response<Boolean> frozen(@RequestParam int id, HttpSession session) {
+        User entity = usersService.get(id);
+        if (entity.getIsFrozen() > 0){
+            throw new ParameterException("用户已冻结");
+        }
+        usersService.edit(User.builder()
+                .id(id)
+                .isFrozen(1)
+                .build());
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/list", method = RequestMethod.GET)
     @ApiOperation(value = "用户列表", httpMethod = "GET")
     public Response<PageMessage<UserListDto>> list(
@@ -168,13 +203,15 @@ public class UserController {
             @RequestParam(required = false) Integer[] ids,
             @RequestParam(required = false) Boolean real,
             @RequestParam(required = false) String order,
+            @RequestParam(required = false) String startTime,
+            @RequestParam(required = false) String endTime,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<User> p;
         if (ids != null && ids.length > 0){
             p = usersService.select(ids);
         }else{
-            p = usersService.select(page, size, keyword, real, order, DirectionStatus.ValueOf(direction));
+            p = usersService.select(page, size, keyword, real, startTime, endTime, order, DirectionStatus.ValueOf(direction));
         }
         List<UserListDto> pr = Transform.convert(p, UserListDto.class);
 
@@ -211,7 +248,7 @@ public class UserController {
 
     @RequestMapping(value = "/order/list", method = RequestMethod.GET)
     @ApiOperation(value = "用户订单列表", httpMethod = "GET")
-    public Response<PageMessage<UserListDto>> listOrder(
+    public Response<PageMessage<UserOrderListDto>> listOrder(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) Integer userId,
@@ -219,42 +256,24 @@ public class UserController {
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         Page<UserOrder> p = userOrderService.select(page, size);
-        List<UserListDto> pr = Transform.convert(p, UserListDto.class);
+        List<UserOrderListDto> dtos = Transform.convert(p, UserOrderListDto.class);
 
-        Collection userIds = Transform.getIds(p, User.class, "id");
-        // 绑定用户服务
-        Map<Object, Collection<UserService>> serviceByUser = userServiceService.mapByUser(userIds);
-        Transform.combine(pr, serviceByUser, UserListDto.class, "id", "services", UserServiceExtendDto.class);
-
-        return ResponseHelp.success(pr, page, size, p.getTotal());
+        return ResponseHelp.success(dtos, page, size, p.getTotal());
     }
 
-    @RequestMapping(value = "/preview/list", method = RequestMethod.GET)
-    @ApiOperation(value = "预习作业列表", httpMethod = "GET")
-    public Response<PageMessage<UserPreviewListDto>> list(
+    @RequestMapping(value = "/record/list", method = RequestMethod.GET)
+    @ApiOperation(value = "用户购买列表", httpMethod = "GET")
+    public Response<PageMessage<UserOrderRecord>> listRecord(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
-            @RequestParam(required = false, defaultValue = "0") int category,
             @RequestParam(required = false) Integer userId,
-            @RequestParam(required = false) Integer previewId,
-            @RequestParam(required = false) String startTime,
-            @RequestParam(required = false) String endTime,
+            @RequestParam(required = false) String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        PageResult<UserPreviewPaperRelation> p = previewService.listAdmin(page, size, category, userId, previewId, startTime, endTime);
-
-        // 绑定用户
-        Collection userIds = Transform.getIds(p, UserPreviewPaperRelation.class, "userId");
-        List<User> userList = usersService.select(userIds);
-        Transform.combine(p, userList, UserPreviewPaperRelation.class, "userId", "user", User.class, "id");
+        Page<UserOrderRecord> p = userOrderRecordService.select(page, size);
 
-        // 绑定预习作业
-        Collection previewIds = Transform.getIds(p, UserPreviewPaperRelation.class, "moduleId");
-        List<PreviewPaper> previewPaperList = previewPaperService.select(previewIds);
-        Transform.combine(p, previewPaperList, UserPreviewPaperRelation.class, "moduleId", "preview", PreviewPaper.class, "id");
 
-        List<UserPreviewListDto> pr = Transform.convert(p, UserPreviewListDto.class);
-
-        return ResponseHelp.success(pr, page, size, p.getTotal());
+        return ResponseHelp.success(p, page, size, p.getTotal());
     }
 
     @RequestMapping(value = "/feedback_error/edit", method = RequestMethod.PUT)
@@ -392,6 +411,8 @@ public class UserController {
             }
             entity.setUserId(user.getId());
         }
+        // 后台服务创建,都默认你为后台创建
+        entity.setSource(RecordSource.BACKEND.key);
         orderFlowService.addRecord(entity);
         return ResponseHelp.success(true);
     }
@@ -419,7 +440,9 @@ public class UserController {
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) String service,
             @RequestParam(required = false) String param,
-            @RequestParam(required = false) Integer userId
+            @RequestParam(required = false) Integer userId,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction
     ){
         Page<UserOrderRecord> p = userOrderRecordService.listWithServiceAdmin(page, size, ServiceKey.ValueOf(service), param, userId);
         List<UserServiceRecordInfoDto> pr = Transform.convert(p, UserServiceRecordInfoDto.class);
@@ -482,13 +505,19 @@ public class UserController {
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) Integer[] ids,
             @RequestParam(required = false) Integer recordId,
+            @RequestParam(required = false) Integer userId,
+            @RequestParam(required = false) Integer courseId,
+            @RequestParam(required = false) String startTime,
+            @RequestParam(required = false) String endTime,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
 
         Page<UserCourseAppointment> p;
         if (ids != null && ids.length > 0){
             p = userCourseAppointmentService.select(ids);
         }else{
-            p = userCourseAppointmentService.listAdmin(page, size, recordId);
+            p = userCourseAppointmentService.listAdmin(page, size, recordId, userId, courseId, startTime, endTime);
         }
         List<UserCourseAppointmentInfoDto> pr = Transform.convert(p, UserCourseAppointmentInfoDto.class);
 
@@ -617,4 +646,57 @@ public class UserController {
         }
         return ResponseHelp.success(true);
     }
+    @RequestMapping(value = "/abnormal/handler", method = RequestMethod.POST)
+    @ApiOperation(value = "异常登录处理", httpMethod = "POST")
+    public Response<Boolean> editAbnormal(@RequestBody @Validated UserAbnormalHandlerDto dto, HttpServletRequest request) {
+        UserAbnormal entity = Transform.dtoToEntity(dto);
+        UserAbnormal in = userAbnormalService.get(entity.getId());
+        if (in.getIsAlert() != null && in.getIsAlert()  > 0){
+            throw new ParameterException("已处理");
+        }
+        if (in.getIsIgnore() != null && in.getIsIgnore() > 0){
+            throw new ParameterException("已处理");
+        }
+
+        if (dto.getIsAlert() != null && dto.getIsAlert() > 1){
+            User user = usersService.get(in.getUserId());
+            usersService.edit(User.builder().id(user.getId()).totalAlert(user.getTotalAlert() + 1).build());
+        }
+
+        entity = userAbnormalService.edit(entity);
+
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/abnormal/detail", method = RequestMethod.GET)
+    @ApiOperation(value = "异常登录详情", httpMethod = "GET")
+    public Response<UserAbnormalInfoDto> detailAbnormal(@RequestParam int id, HttpServletRequest request) {
+        UserAbnormal entity = userAbnormalService.get(id);
+        UserAbnormalInfoDto dto = Transform.convert(entity, UserAbnormalInfoDto.class);
+        User user = usersService.get(entity.getUserId());
+        UserExtendDto userDto = Transform.convert(user, UserExtendDto.class);
+        dto.setUser(userDto);
+
+        return ResponseHelp.success(dto);
+    }
+
+    @RequestMapping(value = "/abnormal/list", method = RequestMethod.GET)
+    @ApiOperation(value = "异常登录列表", httpMethod = "GET")
+    public Response<PageMessage<UserAbnormalInfoDto>> listAbnormal(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
+            HttpSession session) {
+        Page<UserAbnormal> p = userAbnormalService.listAdmin(page, size, order, DirectionStatus.ValueOf(direction));
+        List<UserAbnormalInfoDto> pr = Transform.convert(p, UserAbnormalInfoDto.class);
+
+        // 绑定用户
+        Collection userIds = Transform.getIds(p, UserAbnormal.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Transform.combine(pr, userList, UserAbnormalInfoDto.class, "userId", "user", User.class, "id", UserExtendDto.class);
+
+        return ResponseHelp.success(pr, page, size, p.getTotal());
+    }
 }

+ 55 - 71
server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java

@@ -6,13 +6,10 @@ import com.nuliji.tools.*;
 import com.qxgmat.data.constants.enums.ExperienceDayRange;
 import com.qxgmat.data.constants.enums.ExperienceScoreRange;
 import com.qxgmat.data.constants.enums.module.CourseModule;
-import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.user.DataType;
 import com.qxgmat.data.dao.entity.*;
-import com.qxgmat.data.relation.entity.UserPreviewPaperRelation;
 import com.qxgmat.dto.extend.CourseExtendDto;
-import com.qxgmat.dto.extend.UserPreviewPaperExtendDto;
 import com.qxgmat.dto.response.*;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.extend.PreviewService;
@@ -23,7 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpSession;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -207,71 +203,59 @@ public class CourseController {
         return ResponseHelp.success(p, page, size, p.getTotal());
     }
 
-    @RequestMapping(value = "/progress", method = RequestMethod.GET)
-    @ApiOperation(value = "获取课程进度", notes = "获取所有课程及状态进度", httpMethod = "GET")
-    public Response<List<UserCourseDetailDto>> progress()  {
-        User user = (User) shiroHelp.getLoginUser();
-        List<UserCourse> userCourseList = userOrderRecordService.getByUser(user.getId());
-        List<UserCourseDetailDto> dtos = Transform.convert(userCourseList, UserCourseDetailDto.class);
-
-        // 获取每个科目的最后2次作业
-        Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByCategory(user.getId(), 3);
-        Transform.combine(dtos, previewMap, UserCourseDetailDto.class, "courseId", "previews", UserPreviewPaperExtendDto.class);
-
-        // 获取课程状态:已购买未开通
-        List<UserOrderRecord> records = userOrderRecordService.listUnUse(user.getId(), ProductType.COURSE);
-        Collection ids = Transform.getIds(userCourseList, UserCourse.class, "courseId");
-        for(UserOrderRecord record : records){
-            Integer courseId = record.getProductId();
-            if (!ids.contains(courseId)){
-                UserCourseDetailDto dto = new UserCourseDetailDto();
-                dto.setCourseId(courseId);
-                dto.setPayed(true);
-                dtos.add(dto);
-            }
-        }
-        // todo 区分完成状态
-
-        return ResponseHelp.success(dtos);
-    }
-
-    @RequestMapping(value = "/preview/list", method = RequestMethod.GET)
-    @ApiOperation(value = "获取预习作业列表", notes = "获取预习作业列表", httpMethod = "GET")
-    public Response<PageMessage<UserPreviewPaperExtendDto>> listPreview(
-            @RequestParam(required = false, defaultValue = "1") int page,
-            @RequestParam(required = false, defaultValue = "100") int size,
-            @RequestParam(required = false) Number category,
-            @RequestParam(required = false) String endTime,
-            @RequestParam(required = false) Boolean finish
-    )  {
-        User user = (User) shiroHelp.getLoginUser();
-        PageResult<UserPreviewPaperRelation> p = previewService.list(page, size, category, user.getId(), endTime, finish);
-
-        List<UserPreviewPaperExtendDto> pr = Transform.convert(p, UserPreviewPaperExtendDto.class);
-
-        // 获取试卷统计信息
-        Map map = Transform.getMap(p, UserPreviewPaperRelation.class, "id", "paper");
-        Map<Integer, Integer[]> questionNoIdsMap = new HashMap<>();
-        for(Object value : map.keySet()){
-            Integer key = (Integer) value;
-            PreviewPaper preview = (PreviewPaper) map.get(key);
-            questionNoIdsMap.put(key, preview.getQuestionNoIds());
-        }
-        Map statMap = questionNoService.statPaperMap(questionNoIdsMap);
-        Transform.combine(pr, statMap, UserPreviewPaperExtendDto.class, "id", "stat");
-
-        return ResponseHelp.success(pr, page, size, p.getTotal());
-    }
-
-    @RequestMapping(value = "/preview/paper", method = RequestMethod.GET)
-    @ApiOperation(value = "获取预习作业", notes = "获取预习作业", httpMethod = "GET")
-    public Response<PaperBaseDto> detailPreview(
-            @RequestParam(required = true, name="id") Integer paperId
-    )  {
-        User user = (User) shiroHelp.getLoginUser();
-        PreviewPaper preview = previewPaperService.get(paperId);
-        PaperBaseDto paperDto = Transform.convert(preview, PaperBaseDto.class);
-
-        return ResponseHelp.success(paperDto);
-    }
+//    @RequestMapping(value = "/progress", method = RequestMethod.GET)
+//    @ApiOperation(value = "获取课程进度", notes = "获取所有课程及状态进度", httpMethod = "GET")
+//    public Response<List<UserCourseDetailDto>> progress()  {
+//        User user = (User) shiroHelp.getLoginUser();
+//        List<UserCourse> userCourseList = userOrderRecordService.getByUser(user.getId());
+//        List<UserCourseDetailDto> dtos = Transform.convert(userCourseList, UserCourseDetailDto.class);
+//
+//        // 获取每个科目的最后2次作业
+//        Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByCategory(user.getId(), 3);
+//        Transform.combine(dtos, previewMap, UserCourseDetailDto.class, "courseId", "previews", UserPreviewPaperExtendDto.class);
+//
+//        // 获取课程状态:已购买未开通
+//        List<UserOrderRecord> records = userOrderRecordService.listUnUse(user.getId(), ProductType.COURSE);
+//        Collection ids = Transform.getIds(userCourseList, UserCourse.class, "courseId");
+//        for(UserOrderRecord record : records){
+//            Integer courseId = record.getProductId();
+//            if (!ids.contains(courseId)){
+//                UserCourseDetailDto dto = new UserCourseDetailDto();
+//                dto.setCourseId(courseId);
+//                dto.setPayed(true);
+//                dtos.add(dto);
+//            }
+//        }
+//        // todo 区分完成状态
+//
+//        return ResponseHelp.success(dtos);
+//    }
+
+//    @RequestMapping(value = "/preview/list", method = RequestMethod.GET)
+//    @ApiOperation(value = "获取预习作业列表", notes = "获取预习作业列表", httpMethod = "GET")
+//    public Response<PageMessage<UserPreviewPaperExtendDto>> listPreview(
+//            @RequestParam(required = false, defaultValue = "1") int page,
+//            @RequestParam(required = false, defaultValue = "100") int size,
+//            @RequestParam(required = false) Number category,
+//            @RequestParam(required = false) String endTime,
+//            @RequestParam(required = false) Boolean finish
+//    )  {
+//        User user = (User) shiroHelp.getLoginUser();
+//        PageResult<UserPreviewPaperRelation> p = previewService.list(page, size, category, user.getId(), endTime, finish);
+//
+//        List<UserPreviewPaperExtendDto> pr = Transform.convert(p, UserPreviewPaperExtendDto.class);
+//
+//        // 获取试卷统计信息
+//        Map map = Transform.getMap(p, UserPreviewPaperRelation.class, "id", "paper");
+//        Map<Integer, Integer[]> questionNoIdsMap = new HashMap<>();
+//        for(Object value : map.keySet()){
+//            Integer key = (Integer) value;
+//            PreviewPaper preview = (PreviewPaper) map.get(key);
+//            questionNoIdsMap.put(key, preview.getQuestionNoIds());
+//        }
+//        Map statMap = questionNoService.statPaperMap(questionNoIdsMap);
+//        Transform.combine(pr, statMap, UserPreviewPaperExtendDto.class, "id", "stat");
+//
+//        return ResponseHelp.success(pr, page, size, p.getTotal());
+//    }
 }

+ 20 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/UserExtendDto.java

@@ -15,6 +15,10 @@ public class UserExtendDto {
 
     private Integer totalMoney;
 
+    private String registerIp;
+
+    private String registerCity;
+
     public Integer getId() {
         return id;
     }
@@ -54,4 +58,20 @@ public class UserExtendDto {
     public void setEmail(String email) {
         this.email = email;
     }
+
+    public String getRegisterIp() {
+        return registerIp;
+    }
+
+    public void setRegisterIp(String registerIp) {
+        this.registerIp = registerIp;
+    }
+
+    public String getRegisterCity() {
+        return registerCity;
+    }
+
+    public void setRegisterCity(String registerCity) {
+        this.registerCity = registerCity;
+    }
 }

+ 99 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/PreviewAssignDto.java

@@ -0,0 +1,99 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.PreviewAssign;
+
+import java.util.Date;
+
+@Dto(entity = PreviewAssign.class)
+public class PreviewAssignDto {
+    private Integer id;
+
+    private Integer paperId;
+
+    private String title;
+
+    private Integer courseId;
+
+    private Integer courseNo;
+
+    private String courseModule;
+
+    private Integer courseTime;
+
+    private Integer courseAppointment;
+
+    private Date targetTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(Integer paperId) {
+        this.paperId = paperId;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Integer getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Integer courseId) {
+        this.courseId = courseId;
+    }
+
+    public Integer getCourseNo() {
+        return courseNo;
+    }
+
+    public void setCourseNo(Integer courseNo) {
+        this.courseNo = courseNo;
+    }
+
+    public String getCourseModule() {
+        return courseModule;
+    }
+
+    public void setCourseModule(String courseModule) {
+        this.courseModule = courseModule;
+    }
+
+    public Integer getCourseTime() {
+        return courseTime;
+    }
+
+    public void setCourseTime(Integer courseTime) {
+        this.courseTime = courseTime;
+    }
+
+    public Integer getCourseAppointment() {
+        return courseAppointment;
+    }
+
+    public void setCourseAppointment(Integer courseAppointment) {
+        this.courseAppointment = courseAppointment;
+    }
+
+    public Date getTargetTime() {
+        return targetTime;
+    }
+
+    public void setTargetTime(Date targetTime) {
+        this.targetTime = targetTime;
+    }
+}

+ 30 - 23
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/PreviewDto.java

@@ -7,21 +7,20 @@ import javax.validation.constraints.NotEmpty;
 import java.util.Date;
 
 @Dto(entity = PreviewPaper.class)
-public class PreviewDto {
-
+public class PreviewPaperDto {
     private Integer id;
 
-    @NotEmpty(message = "标题不能为空!")
     private String title;
 
-    private Date startTime;
+    private Integer courseId;
+
+    private String courseModule;
 
-    private Date endTime;
+    private Integer courseNo;
 
-    @NotEmpty(message = "题目不能为空!")
     private Integer[] questionNoIds;
 
-    private Integer[] userIds;
+    private String paperModule;
 
     public Integer getId() {
         return id;
@@ -39,35 +38,43 @@ public class PreviewDto {
         this.title = title;
     }
 
-    public Date getStartTime() {
-        return startTime;
+    public Integer[] getQuestionNoIds() {
+        return questionNoIds;
     }
 
-    public void setStartTime(Date startTime) {
-        this.startTime = startTime;
+    public void setQuestionNoIds(Integer[] questionNoIds) {
+        this.questionNoIds = questionNoIds;
     }
 
-    public Date getEndTime() {
-        return endTime;
+    public Integer getCourseId() {
+        return courseId;
     }
 
-    public void setEndTime(Date endTime) {
-        this.endTime = endTime;
+    public void setCourseId(Integer courseId) {
+        this.courseId = courseId;
     }
 
-    public Integer[] getUserIds() {
-        return userIds;
+    public String getCourseModule() {
+        return courseModule;
     }
 
-    public void setUserIds(Integer[] userIds) {
-        this.userIds = userIds;
+    public void setCourseModule(String courseModule) {
+        this.courseModule = courseModule;
     }
 
-    public Integer[] getQuestionNoIds() {
-        return questionNoIds;
+    public Integer getCourseNo() {
+        return courseNo;
     }
 
-    public void setQuestionNoIds(Integer[] questionNoIds) {
-        this.questionNoIds = questionNoIds;
+    public void setCourseNo(Integer courseNo) {
+        this.courseNo = courseNo;
+    }
+
+    public String getPaperModule() {
+        return paperModule;
+    }
+
+    public void setPaperModule(String paperModule) {
+        this.paperModule = paperModule;
     }
 }

+ 37 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserAbnormalHandlerDto.java

@@ -0,0 +1,37 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserAbnormal;
+
+@Dto(entity = UserAbnormal.class)
+public class UserAbnormalHandlerDto {
+    private Integer id;
+
+    private Integer isAlert;
+
+    private Integer isIgnore;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getIsAlert() {
+        return isAlert;
+    }
+
+    public void setIsAlert(Integer isAlert) {
+        this.isAlert = isAlert;
+    }
+
+    public Integer getIsIgnore() {
+        return isIgnore;
+    }
+
+    public void setIsIgnore(Integer isIgnore) {
+        this.isIgnore = isIgnore;
+    }
+}

+ 27 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserMoneyDto.java

@@ -0,0 +1,27 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.User;
+
+@Dto(entity = User.class)
+public class UserMoneyDto {
+    private Integer id;
+
+    private Integer money;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getMoney() {
+        return money;
+    }
+
+    public void setMoney(Integer money) {
+        this.money = money;
+    }
+}

+ 67 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserRealDto.java

@@ -0,0 +1,67 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.User;
+
+@Dto(entity = User.class)
+public class UserRealDto {
+    private Integer id;
+
+    private String realName;
+
+    private String realAddress;
+
+    private String realIdentity;
+
+    private String realPhotoFront;
+
+    private String realPhotoBack;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getRealName() {
+        return realName;
+    }
+
+    public void setRealName(String realName) {
+        this.realName = realName;
+    }
+
+    public String getRealAddress() {
+        return realAddress;
+    }
+
+    public void setRealAddress(String realAddress) {
+        this.realAddress = realAddress;
+    }
+
+    public String getRealIdentity() {
+        return realIdentity;
+    }
+
+    public void setRealIdentity(String realIdentity) {
+        this.realIdentity = realIdentity;
+    }
+
+    public String getRealPhotoFront() {
+        return realPhotoFront;
+    }
+
+    public void setRealPhotoFront(String realPhotoFront) {
+        this.realPhotoFront = realPhotoFront;
+    }
+
+    public String getRealPhotoBack() {
+        return realPhotoBack;
+    }
+
+    public void setRealPhotoBack(String realPhotoBack) {
+        this.realPhotoBack = realPhotoBack;
+    }
+}

+ 0 - 90
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/PreviewListDto.java

@@ -1,90 +0,0 @@
-package com.qxgmat.dto.admin.response;
-
-import com.nuliji.tools.annotation.Dto;
-import com.qxgmat.data.dao.entity.PreviewPaper;
-
-import java.util.Date;
-
-@Dto(entity = PreviewPaper.class)
-public class PreviewListDto {
-
-    private Integer id;
-
-    private String title;
-
-    private Integer category;
-
-    private Date startTime;
-
-    private Date endTime;
-
-    private Integer[] questionNoIds;
-
-    private Integer[] userIds;
-
-    private Integer finish;
-
-    public Integer getId() {
-        return id;
-    }
-
-    public void setId(Integer id) {
-        this.id = id;
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    public Date getStartTime() {
-        return startTime;
-    }
-
-    public void setStartTime(Date startTime) {
-        this.startTime = startTime;
-    }
-
-    public Date getEndTime() {
-        return endTime;
-    }
-
-    public void setEndTime(Date endTime) {
-        this.endTime = endTime;
-    }
-
-    public Integer getFinish() {
-        return finish;
-    }
-
-    public void setFinish(Integer finish) {
-        this.finish = finish;
-    }
-
-    public Integer[] getUserIds() {
-        return userIds;
-    }
-
-    public void setUserIds(Integer[] userIds) {
-        this.userIds = userIds;
-    }
-
-    public Integer getCategory() {
-        return category;
-    }
-
-    public void setCategory(Integer category) {
-        this.category = category;
-    }
-
-    public Integer[] getQuestionNoIds() {
-        return questionNoIds;
-    }
-
-    public void setQuestionNoIds(Integer[] questionNoIds) {
-        this.questionNoIds = questionNoIds;
-    }
-}

+ 112 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/PreviewPaperListDto.java

@@ -0,0 +1,112 @@
+package com.qxgmat.dto.admin.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.PreviewPaper;
+import com.qxgmat.dto.admin.extend.CourseExtendDto;
+
+import java.util.Date;
+
+
+@Dto(entity = PreviewPaper.class)
+public class PreviewPaperListDto {
+
+    private Integer id;
+
+    private String title;
+
+    private Integer courseId;
+
+    private CourseExtendDto course;
+
+    private String courseModule;
+
+    private Integer courseNo;
+
+    private Integer[] questionNoIds;
+
+    private String paperModule;
+
+    private Date createTime;
+
+    private Date updateTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Integer getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Integer courseId) {
+        this.courseId = courseId;
+    }
+
+    public CourseExtendDto getCourse() {
+        return course;
+    }
+
+    public void setCourse(CourseExtendDto course) {
+        this.course = course;
+    }
+
+    public String getCourseModule() {
+        return courseModule;
+    }
+
+    public void setCourseModule(String courseModule) {
+        this.courseModule = courseModule;
+    }
+
+    public Integer getCourseNo() {
+        return courseNo;
+    }
+
+    public void setCourseNo(Integer courseNo) {
+        this.courseNo = courseNo;
+    }
+
+    public Integer[] getQuestionNoIds() {
+        return questionNoIds;
+    }
+
+    public void setQuestionNoIds(Integer[] questionNoIds) {
+        this.questionNoIds = questionNoIds;
+    }
+
+    public String getPaperModule() {
+        return paperModule;
+    }
+
+    public void setPaperModule(String paperModule) {
+        this.paperModule = paperModule;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+}

+ 91 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserAbnormalInfoDto.java

@@ -0,0 +1,91 @@
+package com.qxgmat.dto.admin.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserAbnormal;
+import com.qxgmat.dto.admin.extend.UserExtendDto;
+
+import java.util.Date;
+
+@Dto(entity = UserAbnormal.class)
+public class UserAbnormalInfoDto {
+
+    private Integer id;
+
+    private Integer userId;
+
+    private UserExtendDto user;
+
+    private Integer isAlert;
+
+    private Integer isIgnore;
+
+    private String loginIp;
+
+    private String loginCity;
+
+    private Date createTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public UserExtendDto getUser() {
+        return user;
+    }
+
+    public void setUser(UserExtendDto user) {
+        this.user = user;
+    }
+
+    public Integer getIsAlert() {
+        return isAlert;
+    }
+
+    public void setIsAlert(Integer isAlert) {
+        this.isAlert = isAlert;
+    }
+
+    public Integer getIsIgnore() {
+        return isIgnore;
+    }
+
+    public void setIsIgnore(Integer isIgnore) {
+        this.isIgnore = isIgnore;
+    }
+
+    public String getLoginIp() {
+        return loginIp;
+    }
+
+    public void setLoginIp(String loginIp) {
+        this.loginIp = loginIp;
+    }
+
+    public String getLoginCity() {
+        return loginCity;
+    }
+
+    public void setLoginCity(String loginCity) {
+        this.loginCity = loginCity;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+}

+ 301 - 1
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserDetailDto.java

@@ -14,14 +14,74 @@ public class UserDetailDto {
 
     private Integer id;
 
+    private String nickname;
+
+    private String avatar;
+
+    private String email;
+
+    private String area;
+
     private String mobile;
 
-    private Date createTime;
+    private String wechatUnionid;
+
+    private Date realTime;
+
+    private String realName;
+
+    private String realAddress;
+
+    private String realIdentity;
+
+    private String realPhotoFront;
+
+    private String realPhotoBack;
 
     private Integer realStatus;
 
+    private Date prepareTime;
+
+    private String prepareStatus;
+
+    private Integer prepareGoal;
+
+    private String prepareExaminationTime;
+
+    private Date prepareScoreTime;
+
+    private Integer latestExercise;
+
+    private Integer latestError;
+
+    private Integer originId;
+
+    private String inviteCode;
+
     private BigDecimal totalMoney;
 
+    private Integer inviteNumber;
+
+    private String registerIp;
+
+    private String registerCity;
+
+    private String latestLoginIp;
+
+    private Date latestLoginTime;
+
+    private Integer totalAlert;
+
+    private Integer isFrozen;
+
+    private Date createTime;
+
+    private Integer dataEmailSubscribe;
+
+    private Integer textbookEmailSubscribe;
+
+    private Integer totalTime;
+
     private Collection<UserServiceExtendDto> services;
 
     public Integer getId() {
@@ -71,4 +131,244 @@ public class UserDetailDto {
     public void setServices(Collection<UserServiceExtendDto> services) {
         this.services = services;
     }
+
+    public String getNickname() {
+        return nickname;
+    }
+
+    public void setNickname(String nickname) {
+        this.nickname = nickname;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public String getArea() {
+        return area;
+    }
+
+    public void setArea(String area) {
+        this.area = area;
+    }
+
+    public String getWechatUnionid() {
+        return wechatUnionid;
+    }
+
+    public void setWechatUnionid(String wechatUnionid) {
+        this.wechatUnionid = wechatUnionid;
+    }
+
+    public Date getRealTime() {
+        return realTime;
+    }
+
+    public void setRealTime(Date realTime) {
+        this.realTime = realTime;
+    }
+
+    public String getRealName() {
+        return realName;
+    }
+
+    public void setRealName(String realName) {
+        this.realName = realName;
+    }
+
+    public String getRealAddress() {
+        return realAddress;
+    }
+
+    public void setRealAddress(String realAddress) {
+        this.realAddress = realAddress;
+    }
+
+    public String getRealIdentity() {
+        return realIdentity;
+    }
+
+    public void setRealIdentity(String realIdentity) {
+        this.realIdentity = realIdentity;
+    }
+
+    public String getRealPhotoFront() {
+        return realPhotoFront;
+    }
+
+    public void setRealPhotoFront(String realPhotoFront) {
+        this.realPhotoFront = realPhotoFront;
+    }
+
+    public String getRealPhotoBack() {
+        return realPhotoBack;
+    }
+
+    public void setRealPhotoBack(String realPhotoBack) {
+        this.realPhotoBack = realPhotoBack;
+    }
+
+    public Date getPrepareTime() {
+        return prepareTime;
+    }
+
+    public void setPrepareTime(Date prepareTime) {
+        this.prepareTime = prepareTime;
+    }
+
+    public String getPrepareStatus() {
+        return prepareStatus;
+    }
+
+    public void setPrepareStatus(String prepareStatus) {
+        this.prepareStatus = prepareStatus;
+    }
+
+    public Integer getPrepareGoal() {
+        return prepareGoal;
+    }
+
+    public void setPrepareGoal(Integer prepareGoal) {
+        this.prepareGoal = prepareGoal;
+    }
+
+    public String getPrepareExaminationTime() {
+        return prepareExaminationTime;
+    }
+
+    public void setPrepareExaminationTime(String prepareExaminationTime) {
+        this.prepareExaminationTime = prepareExaminationTime;
+    }
+
+    public Date getPrepareScoreTime() {
+        return prepareScoreTime;
+    }
+
+    public void setPrepareScoreTime(Date prepareScoreTime) {
+        this.prepareScoreTime = prepareScoreTime;
+    }
+
+    public Integer getLatestExercise() {
+        return latestExercise;
+    }
+
+    public void setLatestExercise(Integer latestExercise) {
+        this.latestExercise = latestExercise;
+    }
+
+    public Integer getLatestError() {
+        return latestError;
+    }
+
+    public void setLatestError(Integer latestError) {
+        this.latestError = latestError;
+    }
+
+    public Integer getOriginId() {
+        return originId;
+    }
+
+    public void setOriginId(Integer originId) {
+        this.originId = originId;
+    }
+
+    public String getInviteCode() {
+        return inviteCode;
+    }
+
+    public void setInviteCode(String inviteCode) {
+        this.inviteCode = inviteCode;
+    }
+
+    public Integer getInviteNumber() {
+        return inviteNumber;
+    }
+
+    public void setInviteNumber(Integer inviteNumber) {
+        this.inviteNumber = inviteNumber;
+    }
+
+    public String getRegisterIp() {
+        return registerIp;
+    }
+
+    public void setRegisterIp(String registerIp) {
+        this.registerIp = registerIp;
+    }
+
+    public String getRegisterCity() {
+        return registerCity;
+    }
+
+    public void setRegisterCity(String registerCity) {
+        this.registerCity = registerCity;
+    }
+
+    public String getLatestLoginIp() {
+        return latestLoginIp;
+    }
+
+    public void setLatestLoginIp(String latestLoginIp) {
+        this.latestLoginIp = latestLoginIp;
+    }
+
+    public Date getLatestLoginTime() {
+        return latestLoginTime;
+    }
+
+    public void setLatestLoginTime(Date latestLoginTime) {
+        this.latestLoginTime = latestLoginTime;
+    }
+
+    public Integer getTotalAlert() {
+        return totalAlert;
+    }
+
+    public void setTotalAlert(Integer totalAlert) {
+        this.totalAlert = totalAlert;
+    }
+
+    public Integer getIsFrozen() {
+        return isFrozen;
+    }
+
+    public void setIsFrozen(Integer isFrozen) {
+        this.isFrozen = isFrozen;
+    }
+
+    public Integer getDataEmailSubscribe() {
+        return dataEmailSubscribe;
+    }
+
+    public void setDataEmailSubscribe(Integer dataEmailSubscribe) {
+        this.dataEmailSubscribe = dataEmailSubscribe;
+    }
+
+    public Integer getTextbookEmailSubscribe() {
+        return textbookEmailSubscribe;
+    }
+
+    public void setTextbookEmailSubscribe(Integer textbookEmailSubscribe) {
+        this.textbookEmailSubscribe = textbookEmailSubscribe;
+    }
+
+    public Integer getTotalTime() {
+        return totalTime;
+    }
+
+    public void setTotalTime(Integer totalTime) {
+        this.totalTime = totalTime;
+    }
 }

+ 40 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserOrderListDto.java

@@ -0,0 +1,40 @@
+package com.qxgmat.dto.admin.response;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserOrder;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Dto(entity = UserOrder.class)
+public class UserOrderListDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private JSONArray productTypes;
+
+    private String payMethod;
+
+    private BigDecimal money;
+
+    private BigDecimal originMoney;
+
+    private BigDecimal invoiceMoney;
+
+    private JSONObject promote;
+
+    private Integer askTime;
+
+    private Long payId;
+
+    private Integer payStatus;
+
+    private Date createTime;
+
+    private Date payTime;
+
+    private String transactionNo;
+}

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserServiceRecordInfoDto.java

@@ -26,6 +26,8 @@ public class UserServiceRecordInfoDto {
 
     private Date endTime;
 
+    private Date createTime;
+
     public Integer getId() {
         return id;
     }
@@ -97,4 +99,12 @@ public class UserServiceRecordInfoDto {
     public void setUser(UserExtendDto user) {
         this.user = user;
     }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
 }

+ 0 - 1
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserTextbookFeedbackInfoDto.java

@@ -10,7 +10,6 @@ import java.util.Date;
 
 @Dto(entity = UserTextbookFeedback.class)
 public class UserTextbookFeedbackInfoDto {
-
     private Integer id;
 
     private Integer userId;

+ 0 - 69
server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserPreviewPaperExtendDto.java

@@ -1,69 +0,0 @@
-package com.qxgmat.dto.extend;
-
-import com.nuliji.tools.annotation.Dto;
-import com.qxgmat.data.dao.entity.UserPaper;
-import com.qxgmat.data.inline.PaperStat;
-
-@Dto(entity = UserPaper.class)
-public class UserPreviewPaperExtendDto {
-
-    private Integer id;
-
-    private UserReportExtendDto report;
-
-    private Integer times;
-
-    private Integer time;
-
-    private String title;
-
-    private PaperStat stat;
-
-    public Integer getId() {
-        return id;
-    }
-
-    public void setId(Integer id) {
-        this.id = id;
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    public UserReportExtendDto getReport() {
-        return report;
-    }
-
-    public void setReport(UserReportExtendDto report) {
-        this.report = report;
-    }
-
-    public PaperStat getStat() {
-        return stat;
-    }
-
-    public void setStat(PaperStat stat) {
-        this.stat = stat;
-    }
-
-    public Integer getTimes() {
-        return times;
-    }
-
-    public void setTimes(Integer times) {
-        this.times = times;
-    }
-
-    public Integer getTime() {
-        return time;
-    }
-
-    public void setTime(Integer time) {
-        this.time = time;
-    }
-}

+ 4 - 4
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseDetailDto.java

@@ -2,7 +2,7 @@ package com.qxgmat.dto.response;
 
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.UserCourse;
-import com.qxgmat.dto.extend.UserPreviewPaperExtendDto;
+import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
 
 import java.util.Date;
 import java.util.List;
@@ -17,7 +17,7 @@ public class UserCourseDetailDto {
 
     private Boolean payed;
 
-    private List<UserPreviewPaperExtendDto> papers;
+    private List<UserPaperBaseExtendDto> papers;
 
     public Date getStartTime() {
         return startTime;
@@ -43,11 +43,11 @@ public class UserCourseDetailDto {
         this.payed = payed;
     }
 
-    public List<UserPreviewPaperExtendDto> getPapers() {
+    public List<UserPaperBaseExtendDto> getPapers() {
         return papers;
     }
 
-    public void setPapers(List<UserPreviewPaperExtendDto> papers) {
+    public void setPapers(List<UserPaperBaseExtendDto> papers) {
         this.papers = papers;
     }
 

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserQuestionDetailDto.java

@@ -13,6 +13,8 @@ public class UserQuestionDetailDto {
 
     private Integer no;
 
+    private Integer stageNo;
+
     private UserPaperBaseExtendDto paper;
 
     private UserReportExtendDto report;
@@ -178,4 +180,12 @@ public class UserQuestionDetailDto {
     public void setQuestionType(String questionType) {
         this.questionType = questionType;
     }
+
+    public Integer getStageNo() {
+        return stageNo;
+    }
+
+    public void setStageNo(Integer stageNo) {
+        this.stageNo = stageNo;
+    }
 }

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserTextbookInfoDto.java

@@ -11,6 +11,8 @@ public class UserTextbookInfoDto {
 
     private Integer unUseRecord;
 
+    private Boolean emailSubscribe;
+
     public Boolean getHasService() {
         return hasService;
     }
@@ -42,4 +44,12 @@ public class UserTextbookInfoDto {
     public void setSecond(TextbookLibrary second) {
         this.second = second;
     }
+
+    public Boolean getEmailSubscribe() {
+        return emailSubscribe;
+    }
+
+    public void setEmailSubscribe(Boolean emailSubscribe) {
+        this.emailSubscribe = emailSubscribe;
+    }
 }

+ 2 - 1
server/gateway-api/src/main/java/com/qxgmat/help/AiHelp.java

@@ -32,7 +32,8 @@ public class AiHelp {
         // City类可用于IPDB格式的IPv4免费库,IPv4与IPv6的每周高级版、每日标准版、每日高级版、每日专业版、每日旗舰版
         if (path.startsWith(".")){
             // 相对路径,从resource中获取
-            cityDb = new City(this.getClass().getClassLoader().getResourceAsStream(path.replaceFirst(".", "")));
+            logger.debug(this.getClass().getClassLoader().getResource(path).toString());
+            cityDb = new City(this.getClass().getClassLoader().getResourceAsStream(path));
         }else{
             cityDb = new City(path);
         }

+ 16 - 4
server/gateway-api/src/main/java/com/qxgmat/service/UsersService.java

@@ -30,6 +30,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
+import java.math.BigDecimal;
 import java.util.*;
 
 /**
@@ -480,10 +481,11 @@ public class UsersService extends AbstractService {
 
     /**
      * 累加支付金额
-     * @param order
+     * @param userId
+     * @param money
      */
-    public void accumulation(UserOrder order){
-        userRelationMapper.accumulation(order.getUserId(), order.getMoney());
+    public void accumulation(Integer userId, BigDecimal money){
+        userRelationMapper.accumulation(userId, money);
     }
 
     public boolean equalsPassword(User user, String password){
@@ -537,7 +539,7 @@ public class UsersService extends AbstractService {
 
     public Page<User> select(int page, int pageSize){return select(userMapper, page, pageSize);
     }
-    public Page<User> select(int page, int pageSize, String keyword, Boolean real, String order, DirectionStatus direction){
+    public Page<User> select(int page, int pageSize, String keyword, Boolean real, String startTime, String endTime, String order, DirectionStatus direction){
         Example example = new Example(User.class);
         if(keyword != null)
             example.and(
@@ -549,6 +551,16 @@ public class UsersService extends AbstractService {
             example.and(
                     example.createCriteria().andEqualTo("realStatus", real?1:0)
             );
+        if (startTime != null){
+            example.and(
+                    example.createCriteria().andGreaterThanOrEqualTo("createTime", startTime)
+            );
+        }
+        if(endTime != null){
+            example.and(
+                    example.createCriteria().andLessThan("createTime", endTime)
+            );
+        }
         if(order==null||order.isEmpty()) order = "id";
         if (direction != null){
             switch(direction){

+ 2 - 1
server/gateway-api/src/main/java/com/qxgmat/service/annotation/InitRecord.java

@@ -1,8 +1,9 @@
 package com.qxgmat.service.annotation;
 
+import com.qxgmat.data.dao.entity.UserOrder;
 import com.qxgmat.data.dao.entity.UserOrderRecord;
 
 
 public interface InitRecord {
-    UserOrderRecord callback(UserOrderRecord record);
+    UserOrderRecord callback(UserOrder order, UserOrderRecord record);
 }

+ 9 - 0
server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java

@@ -38,4 +38,13 @@ public class CourseExtendService {
     public void restoreCourse(Integer userId, Integer courseId){
 
     }
+
+    /**
+     * 累计听课学习时间
+     * @param userId
+     * @return
+     */
+    public Integer studyTime(Integer userId){
+        return 0;
+    }
 }

+ 39 - 14
server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java

@@ -310,7 +310,7 @@ public class OrderFlowService {
                 originMoney = originMoney.add(checkout.getMoney());
             }
 
-            // 套餐费用
+            // 套餐费用: 添加时计算
             List<UserOrderCheckout> packageCheckout = userOrderCheckoutList.stream().filter((checkout)-> checkout.getProductType().equals(ProductType.COURSE_PACKAGE.key)).collect(Collectors.toList());
             for(UserOrderCheckout checkout : packageCheckout){
                 courseMoney = courseMoney.add(checkout.getMoney());
@@ -326,7 +326,7 @@ public class OrderFlowService {
             order.setOriginMoney(originMoney);
         }));
 
-        initRecordCallback.put(ProductType.COURSE, (record -> {
+        initRecordCallback.put(ProductType.COURSE, ((order, record) -> {
             Course course = courseService.get(record.getId());
             CourseModule courseModule = CourseModule.ValueOf(course.getCourseModule());
             // 小班课程不会存在记录,在线视频和1v1授课都有有效期
@@ -334,23 +334,30 @@ public class OrderFlowService {
             Date endTime = Tools.addDate(startTime, courseModule.expireDay);
             record.setStartTime(startTime);
             record.setEndTime(endTime);
+            // 绑定订单的回复时长
+            record.setAskTime(order.getAskTime());
+            // 计算vs课程编号
+            if (courseModule == CourseModule.VS){
+                Integer no = userOrderRecordService.maxVsNo(order.getUserId());
+                record.setVsNo(no+1);
+            }
             return record;
         }));
-        initRecordCallback.put(ProductType.COURSE_PACKAGE, (record->{
+        initRecordCallback.put(ProductType.COURSE_PACKAGE, ((order, record)->{
             // 资料无需开启,也没有有效期
             record.setIsUsed(1);
             Date time = new Date();
             record.setUseTime(time);
             return record;
         }));
-        initRecordCallback.put(ProductType.DATA, (record->{
+        initRecordCallback.put(ProductType.DATA, ((order, record)->{
             // 资料无需开启,也没有有效期
             record.setIsUsed(1);
             Date time = new Date();
             record.setUseTime(time);
             return record;
         }));
-        initRecordCallback.put(ProductType.SERVICE, (record->{
+        initRecordCallback.put(ProductType.SERVICE, ((order, record)->{
             if(record.getService().equals(ServiceKey.VIP.key)){
                 // VIP直接开通
                 record = useRecordCallback.get(ProductType.SERVICE).callback(record);
@@ -579,19 +586,33 @@ public class OrderFlowService {
                 .build());
 
         // 转换购物车记录为购买记录
-        // todo 套餐保留关系
         List<UserOrderCheckout> checkoutList = userOrderCheckoutService.allByUser(userId, orderId);
-        userOrderCheckoutService.deleteByUser(userId, orderId);
-        for(UserOrderCheckout checkout : checkoutList){
+        List<UserOrderCheckout> pList = checkoutList.stream().filter((checkout)-> checkout.getParentId() == 0).collect(Collectors.toList());
+        List<UserOrderCheckout> cList = checkoutList.stream().filter((checkout)-> checkout.getParentId() > 0).collect(Collectors.toList());
+        Map<Integer, Integer> pMap = new HashMap<>();
+        for(UserOrderCheckout checkout : pList){
+            UserOrderRecord record = Transform.convert(checkout, UserOrderRecord.class);
+            record.setSource(RecordSource.FromPayModule(payMethod).key);
+            InitRecord callback = initRecordCallback.get(ProductType.ValueOf(record.getProductType()));
+            callback.callback(userOrder, record);
+            record = userOrderRecordService.add(record);
+            // 建立新旧id关系
+            pMap.put(checkout.getId(), record.getId());
+        }
+
+        for(UserOrderCheckout checkout : cList){
             UserOrderRecord record = Transform.convert(checkout, UserOrderRecord.class);
+            // 记录parentid
+            record.setParentId(pMap.get(checkout.getParentId()));
             record.setSource(RecordSource.FromPayModule(payMethod).key);
             InitRecord callback = initRecordCallback.get(ProductType.ValueOf(record.getProductType()));
-            callback.callback(record);
+            callback.callback(userOrder, record);
             userOrderRecordService.add(record);
         }
+        userOrderCheckoutService.deleteByUser(userId, orderId);
 
         // 累加用户支付信息
-        usersService.accumulation(userOrder);
+        usersService.accumulation(userOrder.getUserId(), userOrder.getMoney());
         return true;
     }
 
@@ -739,7 +760,8 @@ public class OrderFlowService {
     @Transactional
     public UserOrderRecord addRecord(UserOrderRecord record){
         InitRecord callback = initRecordCallback.get(ProductType.ValueOf(record.getProductType()));
-        callback.callback(record);
+        // 虚拟order
+        callback.callback(UserOrder.builder().build(), record);
         return userOrderRecordService.add(record);
     }
 
@@ -788,7 +810,8 @@ public class OrderFlowService {
                 .param(ServiceVipKey.DAY7.key)
                 .source(RecordSource.INVITE.key)
                 .build();
-        record = initRecordCallback.get(ProductType.SERVICE).callback(record);
+        // 虚拟order
+        record = initRecordCallback.get(ProductType.SERVICE).callback(UserOrder.builder().build(), record);
         userOrderRecordService.add(record);
     }
 
@@ -803,7 +826,8 @@ public class OrderFlowService {
                 .param(ServiceVipKey.DAY7.key)
                 .source(RecordSource.PREPARE.key)
                 .build();
-        record = initRecordCallback.get(ProductType.SERVICE).callback(record);
+        // 虚拟order
+        record = initRecordCallback.get(ProductType.SERVICE).callback(UserOrder.builder().build(), record);
         userOrderRecordService.add(record);
     }
 
@@ -818,7 +842,8 @@ public class OrderFlowService {
                 .param(ServiceVipKey.MONTH6.key)
                 .source(RecordSource.REAL.key)
                 .build();
-        record = initRecordCallback.get(ProductType.SERVICE).callback(record);
+        // 虚拟order
+        record = initRecordCallback.get(ProductType.SERVICE).callback(UserOrder.builder().build(), record);
         userOrderRecordService.add(record);
     }
 

+ 0 - 134
server/gateway-api/src/main/java/com/qxgmat/service/extend/PreviewService.java

@@ -51,67 +51,6 @@ public class PreviewService extends AbstractService {
     private UserPaperRelationMapper userPaperRelationMapper;
 
     /**
-     * 查找用户作业,并关联用户完成度最高的最后一次
-     * @param page
-     * @param size
-     * @param category
-     * @param userId
-     * @param previewId
-     * @param startTime
-     * @param endTime
-     * @return
-     */
-    public PageResult<UserPreviewPaperRelation> listAdmin(int page, int size, Number category, Number userId, Number previewId, String startTime, String endTime){
-        Page<UserPaper> p = page(() -> {
-            userPaperRelationMapper.listPreviewAdmin(category, userId, previewId, startTime, endTime);
-        }, page, size);
-
-        Collection ids = Transform.getIds(p, UserPaper.class, "id");
-
-        // 获取详细数据
-        List<UserPaper> list = userPaperService.select(ids);
-        List<UserPreviewPaperRelation> pr = Transform.convert(list, UserPreviewPaperRelation.class);
-
-        // 获取完成度最高的作业结果
-        List<UserReport> reportList = userReportRelationMapper.listFinishLast(ids, startTime, endTime);
-        Collection reportIds = Transform.getIds(reportList, UserReport.class, "id");
-        Transform.replace(reportList, userReportService.select(reportIds), UserReport.class, "id");
-
-        Transform.combine(p, reportList, UserPreviewPaperRelation.class, "id", "report", UserReport.class, "paperId");
-
-        return new PageResult<>(pr, p.getTotal());
-    }
-
-    /**
-     * 查找用户作业, 并关联用户最后一次作业
-     * @param page
-     * @param size
-     * @param category
-     * @param userId
-     * @param endTime
-     * @param finish
-     * @return
-     */
-    public PageResult<UserPreviewPaperRelation> list(int page, int size, Number category, Number userId, String endTime, Boolean finish){
-        Page<UserPaper> p = page(()->{
-            userPaperRelationMapper.listPreview(category, userId, endTime, finish);
-        },page, size);
-
-        Collection ids = Transform.getIds(p, UserPaper.class, "id");
-
-        // 获取详细数据
-        List<UserPaper> list = userPaperService.select(ids);
-        List<UserPreviewPaperRelation> pr = Transform.convert(list, UserPreviewPaperRelation.class);
-
-        // 获取最后一次作业结果
-        List<UserReport> reportList = userReportService.listWithLater(ids);
-
-        Transform.combine(p, reportList, UserPreviewPaperRelation.class, "id", "report", UserReport.class, "paperId");
-
-        return new PageResult<>(pr, p.getTotal());
-    }
-
-    /**
      * 获取用户分组作业列表
      * @param userId
      * @param top
@@ -147,77 +86,4 @@ public class PreviewService extends AbstractService {
         }
         return relationMap;
     }
-
-    @Transactional
-    public PreviewPaper add(PreviewPaper previewPaper){
-        int result = insert(previewPaperMapper, previewPaper);
-        previewPaper = one(previewPaperMapper, previewPaper.getId());
-        if(previewPaper == null){
-            throw new SystemException("预习作业添加失败");
-        }
-        Integer[] userIds = previewPaper.getUserIds();
-
-        for(Integer userId : userIds){
-            userPaperService.add(UserPaper.builder()
-                    .paperOrigin(PaperOrigin.PREVIEW.key)
-                    .paperModule(previewPaper.getPaperModule())
-                    .originId(previewPaper.getId())
-                    .userId(userId)
-                    .title(previewPaper.getTitle())
-                    .questionNoIds(previewPaper.getQuestionNoIds())
-                    .questionNumber(previewPaper.getQuestionNoIds().length)
-                    .build()
-            );
-        }
-        return previewPaper;
-    }
-
-    @Transactional
-    public PreviewPaper edit(PreviewPaper previewPaper){
-        PreviewPaper in = one(previewPaperMapper, previewPaper.getId());
-        if(in == null){
-            throw new ParameterException("预习作业不存在");
-        }
-        int result = update(previewPaperMapper, previewPaper);
-
-        List<Integer> oldList = Arrays.stream(in.getUserIds()).collect(Collectors.toList());
-        List<Integer> newList = Arrays.stream(previewPaper.getUserIds()).collect(Collectors.toList());
-
-        // 移除旧用户试卷
-        oldList.removeAll(newList);
-        userPaperService.removeByUsersAndPaper(oldList, PaperOrigin.PREVIEW, previewPaper.getId());
-
-        // 获取旧用户做为编辑试卷
-        List<Integer> editList = Arrays.stream(in.getUserIds()).collect(Collectors.toList());
-        editList.removeAll(oldList);
-
-        for(Integer userId : newList){
-            if (!editList.contains(userId)){
-                // 添加
-                userPaperService.add(UserPaper.builder()
-                        .paperOrigin(PaperOrigin.PREVIEW.key)
-                        .paperModule(previewPaper.getPaperModule())
-                        .originId(previewPaper.getId())
-                        .userId(userId)
-                        .title(previewPaper.getTitle())
-                        .questionNoIds(previewPaper.getQuestionNoIds())
-                        .questionNumber(previewPaper.getQuestionNoIds().length)
-                        .build()
-                );
-                editList.remove(userId);
-            }
-        }
-        // 修改
-        userPaperService.editByUsersAndPaper(UserPaper.builder()
-                        .paperOrigin(PaperOrigin.PREVIEW.key)
-                        .paperModule(previewPaper.getPaperModule())
-                        .title(previewPaper.getTitle())
-                        .questionNoIds(previewPaper.getQuestionNoIds())
-                        .questionNumber(previewPaper.getQuestionNoIds().length)
-                .build(),
-                oldList,
-                PaperOrigin.PREVIEW, previewPaper.getId());
-
-        return previewPaper;
-    }
 }

+ 65 - 18
server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java

@@ -37,6 +37,9 @@ public class QuestionFlowService {
     private QuestionService questionService;
 
     @Resource
+    private CourseService courseService;
+
+    @Resource
     private SentenceQuestionService sentenceQuestionService;
 
     @Resource
@@ -49,6 +52,12 @@ public class QuestionFlowService {
     private TextbookPaperService textbookPaperService;
 
     @Resource
+    private PreviewPaperService previewPaperService;
+
+    @Resource
+    private PreviewAssignService previewAssignService;
+
+    @Resource
     private UsersService usersService;
 
     @Resource
@@ -64,6 +73,9 @@ public class QuestionFlowService {
     private UserQuestionService userQuestionService;
 
     @Resource
+    private UserCourseAppointmentService userCourseAppointmentService;
+
+    @Resource
     private ExaminationService examinationService;
 
     @Resource
@@ -75,8 +87,11 @@ public class QuestionFlowService {
     // 初始化试卷的callback,根据试卷模块进行后续处理
     private Map<PaperModule, InitPaper> initPaperCallback = new HashMap<>();
 
+    // 初始化试卷的callback,根据试卷来源进行后续处理
+    private Map<PaperOrigin, InitPaper> initOriginPaperCallback = new HashMap<>();
+
     // 重置试卷的callback,根据试卷模块进行后续处理
-    private Map<PaperModule, InitPaper> resetPaperCallback = new HashMap<>();
+    private Map<PaperOrigin, InitPaper> resetPaperCallback = new HashMap<>();
 
     // 初始化报告的callback,根据试卷模块进行后续处理
     private Map<PaperModule, InitReport> initReportCallback = new HashMap<>();
@@ -113,6 +128,27 @@ public class QuestionFlowService {
             userPaper.setTime(time);
         });
 
+        initOriginPaperCallback.put(PaperOrigin.PREVIEW, (userPaper, id)->{
+            // id为分配表,找寻对应的paper
+            PreviewAssign assign = previewAssignService.get(id);
+            PreviewPaper paper = previewPaperService.get(assign.getPaperId());
+            // 根据不同课程处理标题信息
+            CourseModule courseModule = CourseModule.ValueOf(assign.getCourseModule());
+            switch(courseModule){
+                case VIDEO:
+                case ONLINE:
+                    userPaper.setTitle(paper.getTitle());
+                case VS:
+                    userPaper.setTitle(assign.getTitle());
+            }
+
+            userPaper.setQuestionNoIds(paper.getQuestionNoIds());
+            // 根据papermodule得到考试时长
+            QuestionModule module = QuestionModule.WithPaper(PaperModule.ValueOf(paper.getPaperModule()));
+            InitPaper callback = makePaperCallback.get(module);
+            callback.callback(userPaper, id);
+        });
+
         initPaperCallback.put(PaperModule.EXERCISE, (userPaper, id)->{
             ExercisePaper paper = exercisePaperService.get(id);
             userPaper.setTitle(paper.getTitle());
@@ -150,7 +186,7 @@ public class QuestionFlowService {
         initReportCallback.put(PaperModule.EXERCISE, (report, paper)->{
             JSONObject setting = report.getSetting();
             if (setting.getBoolean("disorder")){
-                // 随机试题
+                // 随机试题: 选项随机,前端处理
 //                report.setQuestionNoIds(this.randomQuestionNoIds(paper.getQuestionNoIds()));
             }
         });
@@ -163,7 +199,7 @@ public class QuestionFlowService {
         initReportCallback.put(PaperModule.EXAMINATION, (report, paper)->{
             JSONObject setting = report.getSetting();
             if (setting.getBoolean("disorder")){
-                // 随机试题选项
+                // 随机试题: 选项随机,前端处理
 //                report.setQuestionNoIds(this.randomQuestionNoIds(paper.getQuestionNoIds()));
             }
             JSONArray order = setting.getJSONArray("order");
@@ -181,6 +217,12 @@ public class QuestionFlowService {
             toolsService.examinationReportInit(report, order);
         });
 
+        resetPaperCallback.put(PaperOrigin.PREVIEW, (userPaper, id)->{
+            // 重新初始化
+            InitPaper callback = initOriginPaperCallback.get(PaperOrigin.PREVIEW);
+            callback.callback(userPaper, id);
+        });
+
         nextCallback.put(PaperModule.EXERCISE, (question, report, lastQuestion)->{
             Integer questionNoId = this.nextId(report.getQuestionNoIds(), lastQuestion!=null ? lastQuestion.getQuestionNoId():null);
             if (questionNoId == 0) return false;
@@ -352,19 +394,6 @@ public class QuestionFlowService {
     }
 
     /**
-     * 初始化paper数据
-     * @param userPaper
-     * @param module
-     * @return
-     */
-    @Transactional
-    public UserPaper initPaper(UserPaper userPaper, QuestionModule module){
-        InitPaper callback = makePaperCallback.get(module);
-        callback.callback(userPaper, userPaper.getOriginId());
-        return userPaper;
-    }
-
-    /**
      * 获取做题paper
      * @param userId
      * @param origin
@@ -377,7 +406,11 @@ public class QuestionFlowService {
         UserPaper paper = userPaperService.getByPaper(userId, origin, paperId);
         if (paper == null){
             paper = userPaperService.newByPaper(userId, origin, module, paperId);
-            InitPaper callback  = initPaperCallback.get(module);
+            // 先判断来源初始化,如果没有,则根据模块初始化
+            InitPaper callback = initOriginPaperCallback.get(origin);
+            if (callback == null) {
+                callback  = initPaperCallback.get(module);
+            }
             callback.callback(paper, paperId);
             paper = userPaperService.add(paper);
         }
@@ -399,7 +432,12 @@ public class QuestionFlowService {
         UserReport userReport = null;
         if (paper.getIsReset() > 0) {
             paper.setIsReset(0);
-            // todo 预习作业重新获取
+
+            // 是否对重置有特殊处理
+            InitPaper callback = resetPaperCallback.get(origin);
+            if (callback != null){
+                callback.callback(paper, paperId);
+            }
             userPaperService.edit(paper);
         }else if(setting == null){
             // 对于长难句这种没有设置开始页的,读取最后一次
@@ -632,6 +670,15 @@ public class QuestionFlowService {
     }
 
     /**
+     * 累计考试学习时间
+     * @param userId
+     * @return
+     */
+    public Integer studyTime(Integer userId){
+        return 0;
+    }
+
+    /**
      * 模考完整出题逻辑
      * @param paper
      * @param report

+ 9 - 0
server/gateway-api/src/main/java/com/qxgmat/service/extend/SentenceService.java

@@ -171,4 +171,13 @@ public class SentenceService {
         // 将新组卷启用
         sentencePaperService.updatePaper(ids, 1);
     }
+
+    /**
+     * 累计长难句学习时间
+     * @param userId
+     * @return
+     */
+    public Integer studyTime(Integer userId){
+        return 0;
+    }
 }

+ 99 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewAssignService.java

@@ -0,0 +1,99 @@
+package com.qxgmat.service.inline;
+
+import com.github.pagehelper.Page;
+import com.nuliji.tools.AbstractService;
+import com.nuliji.tools.exception.ParameterException;
+import com.nuliji.tools.exception.SystemException;
+import com.nuliji.tools.mybatis.Example;
+import com.qxgmat.data.constants.enums.status.DirectionStatus;
+import com.qxgmat.data.constants.enums.status.PreviewStatus;
+import com.qxgmat.data.dao.PreviewAssignMapper;
+import com.qxgmat.data.dao.PreviewPaperMapper;
+import com.qxgmat.data.dao.entity.PreviewAssign;
+import com.qxgmat.data.dao.entity.PreviewPaper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class PreviewAssignService extends AbstractService {
+    private static final Logger logger = LoggerFactory.getLogger(PreviewAssignService.class);
+
+    @Resource
+    private PreviewAssignMapper previewAssignMapper;
+
+    public Page<PreviewAssign> listAdmin(int page, int size, Integer paperId, String order, DirectionStatus direction){
+        Example example = new Example(PreviewAssign.class);
+        if(paperId != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("paperId", paperId)
+            );
+        }
+
+        if(order == null || order.isEmpty()) order = "id";
+        switch(direction){
+            case ASC:
+                example.orderBy(order).asc();
+                break;
+            case DESC:
+            default:
+                example.orderBy(order).desc();
+        }
+        return select(previewAssignMapper, example, page, size);
+    }
+
+    public PreviewAssign add(PreviewAssign assign){
+        int result = insert(previewAssignMapper, assign);
+        assign = one(previewAssignMapper, assign.getId());
+        if(assign == null){
+            throw new SystemException("预习作业添加失败");
+        }
+        return assign;
+    }
+
+    public PreviewAssign edit(PreviewAssign assign){
+        PreviewAssign in = one(previewAssignMapper, assign.getId());
+        if(in == null){
+            throw new ParameterException("预习作业不存在");
+        }
+        int result = update(previewAssignMapper, assign);
+        return assign;
+    }
+
+    public boolean delete(Number id){
+        PreviewAssign in = one(previewAssignMapper, id);
+        if(in == null){
+            throw new ParameterException("预习作业不存在");
+        }
+        int result = delete(previewAssignMapper, id);
+        return result > 0;
+    }
+
+    public PreviewAssign get(Number id){
+        PreviewAssign in = one(previewAssignMapper, id);
+
+        if(in == null){
+            throw new ParameterException("预习作业不存在");
+        }
+        return in;
+    }
+
+    public Page<PreviewAssign> select(int page, int pageSize){
+        return select(previewAssignMapper, page, pageSize);
+    }
+
+    public Page<PreviewAssign> select(Integer[] ids){
+        return page(()->select(previewAssignMapper, ids), 1, ids.length);
+    }
+
+    public List<PreviewAssign> select(Collection ids){
+        return select(previewAssignMapper, ids);
+    }
+
+}

+ 34 - 22
server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewPaperService.java

@@ -8,14 +8,17 @@ import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
 import com.qxgmat.data.constants.enums.QuestionType;
+import com.qxgmat.data.constants.enums.module.CourseModule;
 import com.qxgmat.data.constants.enums.module.PaperModule;
 import com.qxgmat.data.constants.enums.module.PaperOrigin;
 import com.qxgmat.data.constants.enums.module.QuestionModule;
+import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.status.PreviewStatus;
 import com.qxgmat.data.dao.PreviewPaperMapper;
 import com.qxgmat.data.dao.entity.PreviewPaper;
 import com.qxgmat.data.dao.entity.UserPaper;
 import com.qxgmat.data.dao.entity.UserReport;
+import com.qxgmat.data.relation.PreviewPaperRelationMapper;
 import com.qxgmat.data.relation.UserPaperRelationMapper;
 import com.qxgmat.data.relation.UserReportRelationMapper;
 import com.qxgmat.data.relation.entity.UserPreviewPaperRelation;
@@ -37,29 +40,38 @@ public class PreviewPaperService extends AbstractService {
     @Resource
     private PreviewPaperMapper previewPaperMapper;
 
-    public Page<PreviewPaper> listAdmin(int page, int pageSize, Integer category, PreviewStatus status){
-        Example example = new Example(PreviewPaper.class);
-        if(category != null && category>0)
-            example.and(
-                    example.createCriteria().andEqualTo("category", category)
-            );
-        if (status!= PreviewStatus.ALL){
-            tk.mybatis.mapper.entity.Example.Criteria criteria = example.createCriteria();
-            switch(status){
-                case NO_START:
-                    criteria.andLessThan("startTime", new Date());
-                    break;
-                case PROCESS:
-                    criteria.andGreaterThanOrEqualTo("startTime", new Date()).andLessThan("endTime", new Date());
-                    break;
-                case FINISH:
-                    criteria.andGreaterThanOrEqualTo("endTime", new Date());
-                    break;
-            }
-            example.and(criteria);
+    @Resource
+    private PreviewPaperRelationMapper previewPaperRelationMapper;
+
+    public Page<PreviewPaper> listAdmin(int page, int size, CourseModule courseModule, Integer structId, String order, DirectionStatus direction){
+        Page<PreviewPaper> p = page(() -> {
+            previewPaperRelationMapper.listAdmin(courseModule != null ? courseModule.key : null, structId, order, direction != null ? direction.key : null);
+        }, page, size);
+
+        Collection ids = Transform.getIds(p, PreviewPaper.class, "id");
+
+        // 获取详细数据
+        List<PreviewPaper> list = select(ids);
+        Transform.replace(p, list, PreviewPaper.class, "id");
+        return p;
+    }
+
+    public PreviewPaper add(PreviewPaper paper){
+        int result = insert(previewPaperMapper, paper);
+        paper = one(previewPaperMapper, paper.getId());
+        if(paper == null){
+            throw new SystemException("预习作业添加失败");
+        }
+        return paper;
+    }
+
+    public PreviewPaper edit(PreviewPaper paper){
+        PreviewPaper in = one(previewPaperMapper, paper.getId());
+        if(in == null){
+            throw new ParameterException("预习作业不存在");
         }
-        example.orderBy("id").desc();
-        return select(previewPaperMapper, example, page, pageSize);
+        int result = update(previewPaperMapper, paper);
+        return paper;
     }
 
     public boolean delete(Number id){

+ 0 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserAbnormalService.java


Неке датотеке нису приказане због велике количине промена