Sfoglia il codice sorgente

feat(server): 评论、faq、广告等

Go 6 anni fa
parent
commit
9ec84c8113
86 ha cambiato i file con 2726 aggiunte e 277 eliminazioni
  1. 15 1
      front/project/Constant.js
  2. 1 0
      front/project/admin/routes/course/dataDetail/page.js
  3. 13 3
      front/project/admin/routes/interaction/comment/page.js
  4. 16 3
      front/project/admin/routes/interaction/faq/page.js
  5. 1 0
      front/project/admin/routes/interaction/feedback/page.js
  6. 4 3
      front/project/admin/routes/ready/article/page.js
  7. 6 6
      front/project/admin/routes/ready/room/page.js
  8. 130 24
      front/project/admin/routes/show/ad/page.js
  9. 25 4
      front/project/admin/routes/show/comment/page.js
  10. 21 4
      front/project/admin/routes/show/faq/page.js
  11. 7 0
      front/project/admin/routes/user/abnormal/page.js
  12. 13 1
      front/project/admin/routes/user/detail/page.js
  13. 1 0
      front/project/admin/routes/user/recordAll/page.js
  14. 1 0
      front/project/admin/routes/user/recordBuy/page.js
  15. 8 0
      front/project/admin/stores/system.js
  16. 4 0
      front/project/admin/stores/user.js
  17. 50 18
      front/project/h5/components/Block/index.js
  18. 97 6
      front/project/h5/routes/product/bought/page.js
  19. 3 0
      front/project/www/routes/exercise/main/page.js
  20. 9 7
      front/project/www/routes/sentence/read/page.js
  21. 42 10
      front/project/www/stores/my.js
  22. 18 2
      front/src/services/Tools.js
  23. 70 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/Ad.java
  24. 39 4
      server/data/src/main/java/com/qxgmat/data/dao/entity/Comment.java
  25. 61 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/CourseData.java
  26. 39 4
      server/data/src/main/java/com/qxgmat/data/dao/entity/Faq.java
  27. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserInvoice.java
  28. 3 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/AdMapper.xml
  29. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CommentMapper.xml
  30. 6 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseDataMapper.xml
  31. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/FaqMapper.xml
  32. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserInvoiceMapper.xml
  33. 7 0
      server/data/src/main/java/com/qxgmat/data/relation/CommentRelationMapper.java
  34. 7 0
      server/data/src/main/java/com/qxgmat/data/relation/FaqRelationMapper.java
  35. 9 0
      server/data/src/main/java/com/qxgmat/data/relation/UserOrderRecordRelationMapper.java
  36. 21 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/CommentRelationMapper.xml
  37. 20 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/FaqRelationMapper.xml
  38. 0 11
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/AdController.java
  39. 29 5
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java
  40. 24 5
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  41. 14 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  42. 4 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java
  43. 49 7
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  44. 125 12
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  45. 50 16
      server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java
  46. 12 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/SentenceController.java
  47. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/UserExtendDto.java
  48. 21 10
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/AdDto.java
  49. 17 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/CommentOrderDto.java
  50. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/CourseDataDto.java
  51. 13 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/FaqOrderDto.java
  52. 0 2
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserOrderRecordListDto.java
  53. 69 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/CommentExtendDto.java
  54. 22 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/CourseDataExtendDto.java
  55. 80 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/CourseNoExtendDto.java
  56. 69 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/FaqExtendDto.java
  57. 74 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserOrderRecordExtendDto.java
  58. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserPaperBaseExtendDto.java
  59. 14 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/CourseRestoreDto.java
  60. 14 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/CourseSuspendDto.java
  61. 33 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDataDetailDto.java
  62. 12 10
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDataListDto.java
  63. 192 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDetailDto.java
  64. 12 10
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseListDto.java
  65. 22 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CoursePackageDetailDto.java
  66. 11 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CoursePackageListDto.java
  67. 101 11
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseDetailDto.java
  68. 4 3
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseProgressDto.java
  69. 26 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseTimeDto.java
  70. 4 3
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderPreDto.java
  71. 242 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderRecordListDto.java
  72. 22 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentenceInfoDto.java
  73. 90 10
      server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java
  74. 23 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java
  75. 50 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  76. 9 20
      server/gateway-api/src/main/java/com/qxgmat/service/extend/PreviewService.java
  77. 36 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/AdService.java
  78. 41 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CommentService.java
  79. 19 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseDataService.java
  80. 28 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseNoService.java
  81. 20 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CoursePackageService.java
  82. 41 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/FaqService.java
  83. 29 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseAppointmentService.java
  84. 27 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseProgressService.java
  85. 83 15
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java
  86. 9 0
      server/tools/src/main/java/com/nuliji/tools/Tools.java

+ 15 - 1
front/project/Constant.js

@@ -113,7 +113,7 @@ export const FaqChannel = [
   { label: '模考', value: 'examination' },
   { label: 'CAT难度适应性', value: 'cat', parent: 'examination' },
   { label: '非CAT', value: 'nocat', parent: 'examination' },
-  { label: '数学机经', value: 'textbook', parent: 'textbook' },
+  { label: '数学机经', value: 'textbook' },
   { label: '换库', value: 'library' },
   { label: '课堂-课程', value: 'course' },
   { label: '首页', value: 'index', parent: 'course' },
@@ -137,6 +137,20 @@ export const CommentChannel = [
   { label: '课堂-资料', value: 'course_data' },
 ];
 
+export const AdChannel = [
+  { label: '个人中心', value: 'my' },
+  { label: '我的', value: 'self', parent: 'my' },
+  { label: '课程', value: 'course' },
+  { label: '首页', value: 'index', parent: 'course' },
+];
+
+export const AdPlace = [
+  { label: '右1', value: '1', parent: 'my-self' },
+  { label: '右2', value: '2', parent: 'my-self' },
+  { label: '右3', value: '3', parent: 'my-self' },
+  { label: '顶部', value: 'top', parent: 'course-index' },
+];
+
 export const MobileArea = ['+86', '+1'].map(row => {
   return { label: row, value: row };
 });

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

@@ -133,6 +133,7 @@ export default class extends Page {
       if (!err) {
         const data = form.getFieldsValue();
         data.parentStructId = this.exerciseMap[data.structId] ? this.exerciseMap[data.structId].parentId : 0;
+        data.isSentence = this.exerciseMap[data.structId] ? (this.exerciseMap[data.structId].extend === 'sentence' ? 1 : 0) : 0;
         Course.editData(data).then(() => {
           asyncSMessage('保存成功');
         }).catch((e) => {

+ 13 - 3
front/project/admin/routes/interaction/comment/page.js

@@ -5,7 +5,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, bindSearch, formatDate, formatTreeData } from '@src/services/Tools';
+import { getMap, bindSearch, formatDate, formatTreeData, flattenTree } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
 import { CommentChannel, SwitchSelect, MoneyRange } from '../../../../Constant';
 import { System } from '../../../stores/system';
@@ -13,7 +13,14 @@ import { User } from '../../../stores/user';
 import { Course } from '../../../stores/course';
 
 const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
-const CommentChannelMap = getMap(CommentChannel, 'value', 'label');
+const CommentChannelTree = formatTreeData(CommentChannel, 'value', 'label', 'parent');
+const CommentChannelFlatten = flattenTree(CommentChannelTree, (row, item) => {
+  row = Object.assign({}, row);
+  row.value = `${item.value}-${row.value}`;
+  row.label = `${item.label}-${row.label}`;
+  return row;
+}, 'children');
+const CommentChannelMap = getMap(CommentChannelFlatten, 'value', 'label');
 export default class extends Page {
   init() {
     this.itemList = [{
@@ -30,7 +37,7 @@ export default class extends Page {
       type: 'cascader',
       allowClear: true,
       name: '频道',
-      select: formatTreeData(CommentChannel.filter(row => row.type !== 'manual'), 'value', 'label', 'parent'),
+      select: CommentChannelTree,
       placeholder: '请选择',
       onChange: (value) => {
         this.changeSearch(this.filterForm, this, value.join('-'), null);
@@ -137,6 +144,8 @@ export default class extends Page {
           return Course.list(Object.assign({ courseModule: 'video' }, search));
         } if (key === 'course-vs') {
           return Course.list(Object.assign({ courseModule: 'vs' }, search));
+        } if (key === 'course-package') {
+          return Course.listPackage(search);
         }
         return Course.listData(search);
       }, (row) => {
@@ -149,6 +158,7 @@ export default class extends Page {
     } else {
       list[1].disabled = true;
     }
+    component.setState({ load: false });
   }
 
   initData() {

+ 16 - 3
front/project/admin/routes/interaction/faq/page.js

@@ -5,7 +5,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, formatDate, formatTreeData, bindSearch } from '@src/services/Tools';
+import { getMap, formatDate, formatTreeData, bindSearch, flattenTree } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
 import { FaqChannel, SwitchSelect, AskStatus, MoneyRange } from '../../../../Constant';
 import { System } from '../../../stores/system';
@@ -13,8 +13,16 @@ import { User } from '../../../stores/user';
 import { Course } from '../../../stores/course';
 
 const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
-const FaqChannelMap = getMap(FaqChannel, 'value', 'label');
 const AskStatusMap = getMap(AskStatus, 'value', 'label');
+
+const FaqChannelTree = formatTreeData(FaqChannel, 'value', 'label', 'parent');
+const FaqChannelFlatten = flattenTree(FaqChannelTree, (row, item) => {
+  row = Object.assign({}, row);
+  row.value = `${item.value}-${row.value}`;
+  row.label = `${item.label}-${row.label}`;
+  return row;
+}, 'children');
+const FaqChannelMap = getMap(FaqChannelFlatten, 'value', 'label');
 export default class extends Page {
   init() {
     this.formF = null;
@@ -49,7 +57,7 @@ export default class extends Page {
       type: 'cascader',
       allowClear: true,
       name: '频道',
-      select: formatTreeData(FaqChannel, 'value', 'label', 'parent'),
+      select: FaqChannelTree,
       placeholder: '请选择',
       onChange: (value) => {
         this.changeSearch(this.filterForm, this, value.join('-'), null);
@@ -178,6 +186,10 @@ export default class extends Page {
       bindSearch(list, 'position', component, (search) => {
         if (key === 'course-video') {
           return Course.list(Object.assign({ courseModule: 'video' }, search));
+        } if (key === 'course-vs') {
+          return Course.list(Object.assign({ courseModule: 'vs' }, search));
+        } if (key === 'course-package') {
+          return Course.listPackage(search);
         }
         return Course.listData(search);
       }, (row) => {
@@ -190,6 +202,7 @@ export default class extends Page {
     } else {
       list[1].disabled = true;
     }
+    component.setState({ load: false });
   }
 
   initData() {

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

@@ -198,6 +198,7 @@ export default class extends Page {
       list[3].disabled = true;
       list[4].disabled = true;
     }
+    component.setState({ load: false });
   }
 
   initData() {

+ 4 - 3
front/project/admin/routes/ready/article/page.js

@@ -27,7 +27,7 @@ export default class extends Page {
       select: [],
       number: true,
       onChange: (value) => {
-        this.onChangeSearch(this.filterForm, value);
+        this.changeSearch(this.filterForm, this, value);
       },
     }, {
       key: 'categoryId',
@@ -86,12 +86,12 @@ export default class extends Page {
       });
       this.categoryMap = getMap(result, 'id', 'title');
       this.filterForm[0].select = this.categoryList.filter(row => row.parentId === 0);
-      this.onChangeSearch(this.filterForm, this.state.search.parentCategoryId);
+      this.onChangeSearch(this.filterForm, this, this.state.search.parentCategoryId);
       this.initData();
     });
   }
 
-  onChangeSearch(list, value) {
+  changeSearch(list, component, value) {
     if (value) {
       list[1].disabled = false;
       list[1].select = this.categoryList.filter(row => row.parentId === value);
@@ -99,6 +99,7 @@ export default class extends Page {
       list[1].disabled = true;
       list[1].select = [];
     }
+    component.setState({ load: false });
   }
 
   initData() {

+ 6 - 6
front/project/admin/routes/ready/room/page.js

@@ -24,7 +24,7 @@ export default class extends Page {
       placeholder: '请选择',
       select: RoomPosition,
       onChange: (text) => {
-        this.onChangeSearch(this.filterForm, this, text);
+        this.changeSearch(this.filterForm, this, text);
       },
     }, {
       key: 'areaId',
@@ -45,7 +45,7 @@ export default class extends Page {
       name: '区域',
       select: RoomPosition,
       onChange: (text) => {
-        this.onChangeSearch(this.itemList, this.formF, text, 2);
+        this.changeSearch(this.itemList, this.formF, text, 2);
       },
       required: true,
     }, {
@@ -112,11 +112,11 @@ export default class extends Page {
         return row;
       });
       this.areaMap = getMap(result, 'id', 'title');
-      this.onChangeSearch(this.filterForm, this, this.state.search.position);
+      this.changeSearch(this.filterForm, this, this.state.search.position);
     });
   }
 
-  onChangeSearch(list, component, value, index = 1) {
+  changeSearch(list, component, value, index = 1) {
     if (value) {
       list[index].select = this.areaList.filter(row => row.position === value);
       list[index].disabled = list[index].select.length === 0;
@@ -144,7 +144,7 @@ export default class extends Page {
       });
     }).then(component => {
       this.formF = component;
-      this.onChangeSearch(this.itemList, this.formF, null, 2);
+      this.changeSearch(this.itemList, this.formF, null, 2);
     });
   }
 
@@ -157,7 +157,7 @@ export default class extends Page {
       });
     }).then(component => {
       this.formF = component;
-      this.onChangeSearch(this.itemList, this.formF, row.position, 2);
+      this.changeSearch(this.itemList, this.formF, row.position, 2);
     });
   }
 

+ 130 - 24
front/project/admin/routes/show/ad/page.js

@@ -1,94 +1,200 @@
 import React from 'react';
-import { Link } from 'react-router-dom';
-import { Button } from 'antd';
+// import { Link } from 'react-router-dom';
+// import { Button } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
-import FilterLayout from '@src/layouts/FilterLayout';
+// import FilterLayout from '@src/layouts/FilterLayout';
 import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { formatDate, getMap } from '@src/services/Tools';
-import { ArticleChannel } from '../../../../Constant';
+import { asyncSMessage, asyncForm, asyncDelConfirm } from '@src/services/AsyncTools';
+import { formatDate, getMap, formatTreeData, flattenTree } from '@src/services/Tools';
+import { AdChannel, AdPlace } from '../../../../Constant';
 import { System } from '../../../stores/system';
 
-const ArticleChannelMap = getMap(ArticleChannel, 'value', 'label');
 
+const AdChannelTree = formatTreeData(AdChannel, 'value', 'label', 'parent');
+const AdChannelFlatten = flattenTree(AdChannelTree, (row, item) => {
+  row = Object.assign({}, row);
+  row.value = `${item.value}-${row.value}`;
+  row.label = `${item.label}-${row.label}`;
+  return row;
+}, 'children');
+const AdChannelMap = getMap(AdChannelFlatten, 'value', 'label');
+
+const AdPlaceTree = formatTreeData([].concat(AdChannelFlatten.map(row => Object.assign({}, row)), AdPlace), 'value', 'label', 'parent');
+const AdPlaceChannelMap = getMap(AdPlaceTree, 'value', 'children');
 export default class extends Page {
   init() {
-    this.filterForm = [{
+    this.formF = null;
+    this.itemList = [{
+      key: 'id',
+      type: 'hidden',
+    }, {
       key: 'channel',
-      type: 'select',
+      type: 'cascader',
       allowClear: true,
       name: '频道',
-      select: ArticleChannel,
+      select: AdChannelTree,
       placeholder: '请选择',
+      onChange: (value) => {
+        this.changeSearch(this.itemList, this.formF, value.join('-'), null, 2);
+      },
     }, {
-      key: 'position',
+      key: 'place',
       type: 'select',
       allowClear: true,
       name: '位置',
       select: [],
       placeholder: '请选择',
+    }, {
+      key: 'title',
+      type: 'input',
+      name: '广告名称',
+    }, {
+      key: 'time',
+      type: 'daterange',
+      name: '广告时间',
+    }, {
+      key: 'image',
+      type: 'image',
+      name: '广告图片',
+      onUpload: ({ file }) => {
+        return System.uploadImage(file).then(result => { return result; });
+      },
+    }, {
+      key: 'link',
+      type: 'input',
+      name: '链接地址',
+    }];
+    this.filterForm = [{
+      key: 'channel',
+      type: 'cascader',
+      allowClear: true,
+      name: '频道',
+      select: AdChannelTree,
+      placeholder: '请选择',
     }];
     this.actionList = [{
       key: 'add',
       type: 'primary',
       name: '创建',
-      render: (item) => {
-        return <Link to='/show/article/detail'><Button>{item.name}</Button></Link>;
-      },
     }];
     this.columns = [{
       title: '频道',
       dataIndex: 'channel',
       render: (text) => {
-        return ArticleChannelMap[text] || '';
+        return AdChannelMap[text] || '';
       },
     }, {
       title: '位置',
-      dataIndex: 'position',
+      dataIndex: 'place',
     }, {
-      title: '文章标题',
+      title: '广告名称',
       dataIndex: 'title',
     }, {
-      title: '更新时间',
-      dataIndex: 'updateTime',
-      render: (text) => {
-        return formatDate(text);
+      title: '展示时间',
+      dataIndex: 'time',
+      render: (text, record) => {
+        return record.startTime && record.endTime ? `${formatDate(record.startTime, 'YYYY-MM-DD')}-${formatDate(record.endTime, 'YYYY-MM-DD')}` : '长期';
       },
     }, {
       title: '操作',
       dataIndex: 'handler',
       render: (text, record) => {
         return <div className="table-button">
-          {<Link to={`/show/article/detail/${record.id}`}>编辑</Link>}
+          {(
+            <a onClick={() => {
+              this.editAction(record);
+            }}>编辑</a>
+          )}
+          {(
+            <a onClick={() => {
+              this.deleteAction(record);
+            }}>删除</a>
+          )}
         </div>;
       },
     }];
   }
 
+  changeSearch(list, component, key, value, index = 1) {
+    if (key) {
+      list[index].select = AdPlaceChannelMap[key];
+      list[index].disabled = false;
+    } else {
+      list[index].disabled = true;
+    }
+    if (!value) component.setFieldsValue({ [list[index].key]: null });
+    component.setState({ load: false });
+  }
+
   initData() {
     System.listAd(this.state.search).then(result => {
       this.setTableData(result.list, result.total);
     });
   }
 
+  addAction() {
+    asyncForm('创建广告', this.itemList, {}, data => {
+      if (data.time.length > 0) {
+        data.startTime = data.time[0].format('YYYY-MM-DD HH:mm:ss');
+        data.endTime = data.time[1].format('YYYY-MM-DD HH:mm:ss');
+      }
+      data.channel = data.channel.join('-');
+      return System.addComment(data).then(() => {
+        asyncSMessage('添加成功!');
+        this.refresh();
+      });
+    }).then(component => {
+      this.formF = component;
+      this.changeSearch(this.itemList, this.formF, null, null, 2);
+    });
+  }
+
+  editAction(row) {
+    row.channel = row.split('-');
+    asyncForm('编辑广告', this.itemList, row, data => {
+      if (data.time.length > 0) {
+        data.startTime = data.time[0].format('YYYY-MM-DD HH:mm:ss');
+        data.endTime = data.time[1].format('YYYY-MM-DD HH:mm:ss');
+      }
+      data.channel = data.channel.join('-');
+      return System.editComment(data).then(() => {
+        asyncSMessage('编辑成功!');
+        this.refresh();
+      });
+    }).then(component => {
+      this.formF = component;
+      this.changeSearch(this.itemList, this.formF, row.channel, row.place, 2);
+    });
+  }
+
+  deleteAction(row) {
+    asyncDelConfirm('删除确认', '是否删除选中?', () => {
+      const handler = System.delAd({ id: row.id });
+      return handler.then(() => {
+        asyncSMessage('删除成功!');
+        this.refresh();
+      });
+    });
+  }
+
   renderView() {
     return <Block flex>
-      <FilterLayout
+      {/* <FilterLayout
         show
         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.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}

+ 25 - 4
front/project/admin/routes/show/comment/page.js

@@ -5,13 +5,21 @@ 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, bindSearch, formatTreeData } from '@src/services/Tools';
+import { getMap, formatDate, bindSearch, formatTreeData, flattenTree } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
+import { Switch } from 'antd';
 import { CommentChannel, SystemSelect } from '../../../../Constant';
 import { System } from '../../../stores/system';
 import { Course } from '../../../stores/course';
 
-const CommentChannelMap = getMap(CommentChannel, 'value', 'label');
+const CommentChannelTree = formatTreeData(CommentChannel, 'value', 'label', 'parent');
+const CommentChannelFlatten = flattenTree(CommentChannelTree, (row, item) => {
+  row = Object.assign({}, row);
+  row.value = `${item.value}-${row.value}`;
+  row.label = `${item.label}-${row.label}`;
+  return row;
+}, 'children');
+const CommentChannelMap = getMap(CommentChannelFlatten, 'value', 'label');
 const SystemSelectMap = getMap(SystemSelect, 'value', 'label');
 export default class extends Page {
   init() {
@@ -19,6 +27,12 @@ export default class extends Page {
       key: 'add',
       type: 'primary',
       name: '创建',
+    }, {
+      key: 'switch',
+      name: '切换模式',
+      render: () => {
+        return <Switch on />;
+      },
     }];
     this.formF = null;
     this.itemList = [{
@@ -29,7 +43,7 @@ export default class extends Page {
       type: 'cascader',
       allowClear: true,
       name: '频道',
-      select: formatTreeData(CommentChannel, 'value', 'label', 'parent'),
+      select: CommentChannelTree,
       placeholder: '请选择',
       onChange: (value) => {
         this.changeSearch(this.itemList, this.formF, value.join('-'), null, 2);
@@ -137,6 +151,8 @@ export default class extends Page {
           return Course.list(Object.assign({ courseModule: 'video' }, search));
         } if (key === 'course-vs') {
           return Course.list(Object.assign({ courseModule: 'vs' }, search));
+        } if (key === 'course-package') {
+          return Course.listPackage(search);
         }
         return Course.listData(search);
       }, (row) => {
@@ -149,6 +165,7 @@ export default class extends Page {
     } else {
       list[index].disabled = true;
     }
+    component.setState({ load: false });
   }
 
   initData() {
@@ -162,12 +179,14 @@ export default class extends Page {
       data.isShow = 1;
       data.isSystem = 1;
       data.isSpecial = 1;
+      data.channel = data.channel.join('-');
       return System.addComment(data).then(() => {
         asyncSMessage('添加成功!');
         this.refresh();
       });
     }).then(component => {
       this.formF = component;
+      this.changeSearch(this.itemList, this.formF, null, null, 2);
     });
   }
 
@@ -176,14 +195,16 @@ export default class extends Page {
     if (row.userId) {
       item = this.userItemList;
     }
+    row.channel = row.split('-');
     asyncForm('编辑', item, row, data => {
+      data.channel = data.channel.join('-');
       return System.editComment(data).then(() => {
         asyncSMessage('编辑成功!');
         this.refresh();
       });
     }).then(component => {
       this.formF = component;
-      this.changeSearch(this.filterForm, this, row.channel, row.position, 2);
+      this.changeSearch(this.itemList, this.formF, row.channel, row.position, 2);
     });
   }
 

+ 21 - 4
front/project/admin/routes/show/faq/page.js

@@ -5,14 +5,22 @@ 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, bindSearch, formatTreeData } from '@src/services/Tools';
+import { getMap, formatDate, bindSearch, formatTreeData, flattenTree } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
 import { FaqChannel, SystemSelect } from '../../../../Constant';
 import { System } from '../../../stores/system';
 import { Course } from '../../../stores/course';
 
-const FaqChannelMap = getMap(FaqChannel, 'value', 'label');
 const SystemSelectMap = getMap(SystemSelect, 'value', 'label');
+
+const FaqChannelTree = formatTreeData(FaqChannel, 'value', 'label', 'parent');
+const FaqChannelFlatten = flattenTree(FaqChannelTree, (row, item) => {
+  row = Object.assign({}, row);
+  row.value = `${item.value}-${row.value}`;
+  row.label = `${item.label}-${row.label}`;
+  return row;
+}, 'children');
+const FaqChannelMap = getMap(FaqChannelFlatten, 'value', 'label');
 export default class extends Page {
   init() {
     this.actionList = [{
@@ -28,7 +36,7 @@ export default class extends Page {
       key: 'channel',
       type: 'cascader',
       name: '频道',
-      select: formatTreeData(FaqChannel, 'value', 'label', 'parent'),
+      select: FaqChannelTree,
       placeholder: '请选择',
       onChange: (value) => {
         this.changeSearch(this.itemList, this.formF, value.join('-'), null, 2);
@@ -120,6 +128,10 @@ export default class extends Page {
       bindSearch(list, 'position', component, (search) => {
         if (key === 'course-video') {
           return Course.list(Object.assign({ courseModule: 'video' }, search));
+        } if (key === 'course-vs') {
+          return Course.list(Object.assign({ courseModule: 'vs' }, search));
+        } if (key === 'course-package') {
+          return Course.listPackage(search);
         }
         return Course.listData(search);
       }, (row) => {
@@ -132,6 +144,7 @@ export default class extends Page {
     } else {
       list[index].disabled = true;
     }
+    component.setState({ load: false });
   }
 
   initData() {
@@ -145,24 +158,28 @@ export default class extends Page {
       data.isShow = 1;
       data.isSystem = 1;
       data.isSpecial = 1;
+      data.channel = data.channel.join('-');
       return System.addFAQ(data).then(() => {
         asyncSMessage('添加成功!');
         this.refresh();
       });
     }).then(component => {
       this.formF = component;
+      this.changeSearch(this.itemList, this.formF, null, null, 2);
     });
   }
 
   editAction(row) {
+    row.channel = row.split('-');
     asyncForm('编辑', this.itemList, row, data => {
+      data.channel = data.channel.join('-');
       return System.editFAQ(data).then(() => {
         asyncSMessage('编辑成功!');
         this.refresh();
       });
     }).then(component => {
       this.formF = component;
-      this.changeSearch(this.filterForm, this, row.channel, row.position, 2);
+      this.changeSearch(this.itemList, this.formF, row.channel, row.position, 2);
     });
   }
 

+ 7 - 0
front/project/admin/routes/user/abnormal/page.js

@@ -72,6 +72,13 @@ export default class extends Page {
       dataIndex: 'handler',
       render: (text, record) => {
         return <div className="table-button">
+          {record.isIgnore && '已忽略'}
+          {record.isAlert && '已警告'}
+          {record.isAlert && record.user.isFrozen && (
+            <a onClick={() => {
+              this.noFrozenAction(record);
+            }}>取消封禁</a>
+          )}
           {!record.isIgnore && !record.isAlert && (
             <a onClick={() => {
               this.alertAction(record);

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

@@ -95,6 +95,16 @@ export default class extends Page {
     });
   }
 
+  noFrozenAction() {
+    asyncDelConfirm('操作确认', '是否要取消封禁账户?取消后账户可以使用网站', () => {
+      return User.frozen({ id: this.state.data.id })
+        .then(() => {
+          asyncSMessage('操作成功!');
+          this.refresh();
+        });
+    });
+  }
+
   refreshService(p, size) {
     const { id } = this.params;
     const { page } = this.state;
@@ -113,7 +123,9 @@ export default class extends Page {
       <h1>用户基本信息{data.isFrozen === 0 && <Button type='danger' onClick={() => {
         this.frozenAction();
       }}>封禁账户</Button>}
-        {data.isFrozen === 1 && '已封禁'}</h1>
+        {data.isFrozen === 1 && <Button type='danger' onClick={() => {
+          this.noFrozenAction();
+        }}>取消封禁</Button>}</h1>
 
       <Form>
         <div className="group">

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

@@ -258,6 +258,7 @@ export default class extends Page {
       list[2].disabled = true;
       list[3].disabled = true;
     }
+    component.setState({ load: false });
   }
 
   initData() {

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

@@ -155,6 +155,7 @@ export default class extends Page {
       list[2].disabled = true;
       list[3].disabled = true;
     }
+    component.setState({ load: false });
   }
 
   initData() {

+ 8 - 0
front/project/admin/stores/system.js

@@ -217,6 +217,10 @@ export default class SystemStore extends BaseStore {
     return this.apiPut('/setting/comment/edit', params);
   }
 
+  orderComment(params) {
+    return this.apiPut('/setting/comment/order', params);
+  }
+
   delComment(params) {
     return this.apiDel('/setting/comment/delete', params);
   }
@@ -233,6 +237,10 @@ export default class SystemStore extends BaseStore {
     return this.apiPut('/setting/faq/edit', params);
   }
 
+  orderFAQ(params) {
+    return this.apiPut('/setting/faq/order', params);
+  }
+
   delFAQ(params) {
     return this.apiDel('/setting/faq/delete', params);
   }

+ 4 - 0
front/project/admin/stores/user.js

@@ -37,6 +37,10 @@ export default class UserStore extends BaseStore {
     return this.apiPost('/user/frozen', params);
   }
 
+  noFrozen(params) {
+    return this.apiPost('/user/nofrozen', params);
+  }
+
   real(params) {
     return this.apiPost('/user/real', params);
   }

+ 50 - 18
front/project/h5/components/Block/index.js

@@ -2,13 +2,20 @@ import React, { Component } from 'react';
 import './index.less';
 import { Icon } from 'antd-mobile';
 import Assets from '@src/components/Assets';
-import { getMap } from '@src/services/Tools';
-import { CrowdList } from '../../../Constant';
+import { getMap, formatDate } from '@src/services/Tools';
+import { CrowdList, ServiceParamMap } from '../../../Constant';
 import Tag from '../Tag';
 import Money from '../Money';
 import Button from '../Button';
 
 const CrowdMap = getMap(CrowdList, 'value', 'label');
+const ServiceParamRelation = getMap(Object.keys(ServiceParamMap).map(key => {
+  const list = (ServiceParamMap[key] || []).map((row, index) => {
+    row.index = index;
+    return row;
+  });
+  return { map: getMap(list, 'value'), key };
+}), 'key', 'map');
 
 export class Block extends Component {
   render() {
@@ -53,6 +60,7 @@ export class LinkBlock extends Component {
 export class CourseBlock extends Component {
   render() {
     const { data } = this.props;
+    const comment = data.comments ? data.comments[0] : {};
     return (
       <Block className="course-block" onClick={() => {
         linkTo(`/product/course/detail/${data.id}`);
@@ -64,9 +72,7 @@ export class CourseBlock extends Component {
             <div className="teacher">
               授课老师<span>{data.teacher}</span>
             </div>
-            <div className="desc">
-              {data.comment}
-            </div>
+            <div className="desc">{comment.content}</div>
             <div className="division" />
             <div className="data">
               {CrowdMap[data.crowd] && <Tag size="small">适合{CrowdMap[data.crowd]}</Tag>}
@@ -84,21 +90,21 @@ export class CourseBlock extends Component {
 export class CoursePackageBlock extends Component {
   render() {
     const { theme, data } = this.props;
+    const comment = data.comments ? data.comments[0] : {};
     return (
       <TagBlock className="course-co-block" theme={theme} tag={data.tag} onClick={() => {
         linkTo(`/product/course/package/${data.id}`);
       }}>
         <div className="title">{data.title}</div>
         <div className="info">
-          <div className="teacher">
-            授课老师{(data.courses || []).map(row => {
+          <div className="teacher">授课老师{(data.courses || []).map(row => {
             return <span>{row.teacher}</span>;
           })}
           </div>
           {CrowdMap[data.crowd] && <Tag size="small">适合{CrowdMap[data.crowd]}</Tag>}
         </div>
         <div className="desc">
-          {data.comment}
+          {comment.content}
         </div>
       </TagBlock>
     );
@@ -108,6 +114,7 @@ export class CoursePackageBlock extends Component {
 export class DataBlock extends Component {
   render() {
     const { data } = this.props;
+    const comment = data.comments ? data.comments[0] : {};
     return (
       <Block className="data-block" onClick={() => {
         linkTo(`/product/data/detail/${data.id}`);
@@ -115,7 +122,7 @@ export class DataBlock extends Component {
         <Assets name="d_b" src={data.cover} />
         <div className="info">
           <div className="title">{data.title}</div>
-          <div className="desc">{data.comment}</div>
+          <div className="desc">{comment.content}</div>
           <div className="division" />
           <div className="data">
             <div className="people">{data.saleNumber}人已购</div>
@@ -129,26 +136,51 @@ export class DataBlock extends Component {
 
 export class BuyBlock extends Component {
   render() {
-    const { theme } = this.props;
+    const { theme, data, onBuy, onOpen, onRead } = this.props;
+    const now = new Date().getTime();
+    let { title, price } = data;
+    if (data.productType === 'course') {
+      ({ title, price } = (data.course || {}));
+    }
+    if (data.productType === 'data') {
+      ({ title, price } = (data.data || {}));
+    }
+    if (data.productType === 'service') {
+      const p = ServiceParamRelation[data.service][data.param];
+      const index = p ? p.index : 0;
+      ({ title, price } = (data.serviceInfo || {}).package[index]);
+    }
+    const expire = data.useEndTime && new Date(data.useEndTime).getTime() < now.getTime();
     return (
       <TopBlock className="buy-block" theme={theme}>
         <div className="block-left">
           <div className="title">
-            <Tag theme="border" radius size="small">
+            {expire && < Tag theme="border" radius size="small">
               已到期
-            </Tag>
-            VIP会员
+            </Tag>}
+            {!expire && data.productType !== 'data' && data.isUsed && < Tag theme="border" radius size="small">
+              已开通
+            </Tag>}
+            {!expire && data.productType !== 'data' && !data.isUsed && < Tag theme="border" radius size="small">
+              未开通
+            </Tag>}
+            {title}
           </div>
-          <div className="date">有效期:2019-11-20</div>
-          <div className="desc">请访问千行 GMAT 官网开通使用</div>
+          {((data.useEndTime || data.endTime) && data.productType === 'service') && <div className="date">有效期:{formatDate(data.useEndTime || data.endTime, 'YYYY-MM-DD')}</div>}
+          {data.productType === 'data' && <div className="date">最新更新:{formatDate(data.data.latestTime, 'YYYY-MM-DD HH:mm:ss')}</div>}
+          {data.productType === 'course' && !data.isUsed && <div className="date">有效期:{formatDate(data.endTime, 'YYYY-MM-DD')}</div>}
+          {data.productType === 'course' && data.isUsed && <div className="date">课程学习时间:{formatDate(data.useStartTime, 'YYYY-MM-DD')}-{formatDate(data.useEndTime, 'YYYY-MM-DD')}</div>}
+          {data.service !== 'textbook' && !data.isUsed && <div className="desc">请访问千行 GMAT 官网开通使用</div>}
         </div>
         <div className="block-right">
           <div className="btn">
-            <Button radius>开通</Button>
+            {!data.isUsed && <Button radius onClick={() => onOpen && onOpen(data)}>开通</Button>}
+            {expire && data.service === 'vip' && <Button radius onClick={() => onBuy && onBuy(data)}>立即购买</Button>}
+            {data.productType === 'data' && data.data.resource && <Button radius onClick={() => onRead && onRead(data)}>阅读</Button>}
           </div>
-          <div className="tip">¥888/ 3个月</div>
+          {expire && data.service === 'vip' && <div className="tip">¥{price}/{title}</div>}
         </div>
-      </TopBlock>
+      </TopBlock >
     );
   }
 }

+ 97 - 6
front/project/h5/routes/product/bought/page.js

@@ -1,18 +1,109 @@
 import React from 'react';
 import './index.less';
+import { ListView } from 'antd-mobile';
 import Page from '@src/containers/Page';
+import ListData from '@src/services/ListData';
 import { BuyBlock } from '../../../components/Block';
+import { Order } from '../../../stores/order';
 
 export default class extends Page {
-  init() {}
+  initState() {
+    return {
+      listMap: {},
+    };
+  }
+
+  init() {
+  }
+
+  initData() {
+    return this.initListKeys(['data']).then(() => {
+      return this.getList('data', 1);
+    });
+  }
+
+  initListKeys(keys) {
+    const { listMap = {} } = this.state;
+    keys.forEach(key => {
+      listMap[key] = new ListData();
+    });
+    this.setState({ listMap });
+    return Promise.resolve();
+  }
+
+  getList(key, page) {
+    Order.listRecord(Object.assign({ page }, this.state.search)).then(result => {
+      const { listMap = {} } = this.state;
+      if (page === 1) {
+        // todo 是否重新读取第一页为刷新所有数据
+        listMap[key] = new ListData();
+      }
+      listMap[key].get(page, result, this.state.search.size);
+      this.setState({ listMap });
+    });
+  }
 
   renderView() {
     return (
-      <div>
-        <BuyBlock theme="not" />
-        <BuyBlock />
-        <BuyBlock theme="end" />
-      </div>
+      this.renderList()
     );
   }
+
+  renderRow(rowData) {
+    let theme = 'default';
+    if (!rowData.isUsed) {
+      theme = 'not';
+    } else if (rowData.useEndTime && new Date(rowData.useEndTime).getTime() < new Date().getTime()) {
+      theme = 'end';
+    }
+    return <BuyBlock data={rowData} theme={theme} onOpen={() => {
+      linkTo(`/open/${rowData.id}`);
+    }} onBuy={() => {
+      linkTo(`/product/service/${rowData.service}`);
+    }} onRead={() => {
+      openLink(rowData.data.resource);
+    }} />;
+  }
+
+  renderList() {
+    const { listMap } = this.state;
+    const { data = {} } = listMap;
+    const { dataSource = {}, bottom, loading, finish, maxSectionId = 1, total } = data;
+    if (total === 0) return this.renderEmpty();
+    return (
+      <ListView
+        className="list"
+        ref={el => {
+          this.lv = el;
+        }}
+        dataSource={dataSource}
+        renderFooter={() => (
+          <div style={{ padding: 30, textAlign: 'center' }}>{loading ? '加载中...' : bottom ? '没有更多了' : ''}</div>
+        )}
+        renderRow={(rowData, sectionID, rowID) => this.renderRow(rowData, sectionID, rowID)}
+        style={{
+          height: this.state.height,
+          overflow: 'auto',
+        }}
+        pageSize={this.state.search.size}
+        scrollRenderAheadDistance={500}
+        onEndReached={() => {
+          if (loading) return;
+          if (bottom) {
+            if (!finish) {
+              data.finish = true;
+              // this.setState({ time: new Date() })
+            }
+            return;
+          }
+          this.getList('data', maxSectionId + 1);
+        }}
+        onEndReachedThreshold={10}
+      />
+    );
+  }
+
+  renderEmpty() {
+    return <div />;
+  }
 }

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

@@ -737,6 +737,9 @@ export default class extends Page {
               onPreview={() => {
                 this.onPreviewList(row.id);
               }}
+              previewAction={(type, item) => {
+                this.previewAction(type, item);
+              }}
             />;
           })}
         </Division>

+ 9 - 7
front/project/www/routes/sentence/read/page.js

@@ -7,7 +7,7 @@ import Progress from '../../../components/Progress';
 import Assets from '../../../../../src/components/Assets';
 import { Sentence } from '../../../stores/sentence';
 import { Main } from '../../../stores/main';
-import { formatMoney } from '../../../../../src/services/Tools';
+import { formatMoney, formatDate } from '../../../../../src/services/Tools';
 
 export default class extends Page {
   constructor(props) {
@@ -209,7 +209,7 @@ export default class extends Page {
   }
 
   renderBody() {
-    const { showMenu, article, index, chapterMap = {}, info = {} } = this.state;
+    const { showMenu, article, index, chapterMap = {}, info = {}, sentence = {} } = this.state;
     return article ? (
       <div className="layout-body">
         <div className="crumb">千行长难句解析 >> {(chapterMap[article.chapter] || {}).title}</div>
@@ -223,13 +223,15 @@ export default class extends Page {
       <div className="free-over">
         <div className="free-over-title">试读已结束,购买后可继续阅读。</div>
         <div className="free-over-btn" onClick={() => {
-          window.location.href = info.link;
+          openLink(info.link);
         }}>{formatMoney(info.price)} | 立即购买</div>
         <div className="free-over-desc">
-          <div className="free-over-desc-title">{info.title}</div>
-          <div className="free-over-desc-content">
-            {info.description}
-          </div>
+          {(sentence.comments || []).map(row => {
+            return [
+              <div className="free-over-desc-title">{row.nickname} {formatDate(row.createTime, 'YYYY-MM-DD')}</div>,
+              <div className="free-over-desc-content">{row.content}</div>,
+            ];
+          })}
         </div>
       </div>
     </div>

+ 42 - 10
front/project/www/stores/my.js

@@ -166,8 +166,8 @@ export default class MyStore extends BaseStore {
    * @param {*} order
    * @param {*} direction
    */
-  listQuestionCollect({ module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
-    return this.apiGet('/my/collect/question/list', { module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
+  listQuestionCollect({ keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/collect/question/list', { keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
   }
 
   /**
@@ -176,8 +176,8 @@ export default class MyStore extends BaseStore {
    * @param {*} page
    * @param {*} size
    */
-  listError({ module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
-    return this.apiGet('/my/error/list', { module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
+  listError({ keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/error/list', { keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
   }
 
   /**
@@ -251,8 +251,8 @@ export default class MyStore extends BaseStore {
    * @param {*} order
    * @param {*} direction
    */
-  questionNoteList({ module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
-    return this.apiGet('/my/note/question/list', { module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
+  questionNoteList({ keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/note/question/list', { keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
   }
 
   /**
@@ -266,8 +266,8 @@ export default class MyStore extends BaseStore {
    * @param {*} order
    * @param {*} direction
    */
-  reportList({ module, origin, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
-    return this.apiGet('/my/report/list', { module, origin, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
+  reportList({ keyword, module, origin, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/report/list', { keyword, module, origin, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
   }
 
   /**
@@ -290,8 +290,8 @@ export default class MyStore extends BaseStore {
     return this.apiDel('/my/ask/question/delete', { id });
   }
 
-  listQuestionAsk({ module, questionTypes, structIds, latest, year, askStatus, page, size, startTime, endTime, order, direction }) {
-    return this.apiGet('/my/ask/question/list', { module, questionTypes, structIds, latest, year, askStatus, page, size, startTime, endTime, order, direction });
+  listQuestionAsk({ keyword, module, questionTypes, structIds, latest, year, askStatus, page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/ask/question/list', { keyword, module, questionTypes, structIds, latest, year, askStatus, page, size, startTime, endTime, order, direction });
   }
 
   /**
@@ -359,6 +359,38 @@ export default class MyStore extends BaseStore {
   addComment(channel, position, content) {
     return this.apiPost('/my/comment', { channel, position, content });
   }
+
+  /**
+   * 购买的课程列表
+   * @param {*} param0
+   */
+  listCourse({ page, size, courseModule, isUsed, isEnd, order, direction }) {
+    return this.apiGet('/my/course/list', { page, size, courseModule, isUsed, isEnd, order, direction });
+  }
+
+  /**
+   * 申请停课
+   * @param {*} recordId
+   */
+  suspendCourse(recordId) {
+    return this.apiPost('/my/course/suspend', { recordId });
+  }
+
+  /**
+   * 申请恢复课程
+   * @param {*} recordId
+   */
+  restoreCourse(recordId) {
+    return this.apiPost('/my/course/restore', { recordId });
+  }
+
+  /**
+   * 课程时间表
+   * @param {*} recordId
+   */
+  timeCourse(recordId) {
+    return this.apiGet('/my/course/time', { recordId });
+  }
 }
 
 export const My = new MyStore({ key: 'my' });

+ 18 - 2
front/src/services/Tools.js

@@ -160,10 +160,26 @@ export function SortByProps(item1, item2, props) {
   return false;
 }
 
-export function getMap(list, key = 'value', value = null) {
+export function flattenTree(tree, fn, children = 'children') {
+  const l = tree.map(item => {
+    if (item[children] && item[children].length > 0) {
+      const list = flattenTree(item[children], fn, children);
+      return list.map((row) => fn(row, item));
+    }
+    return [item];
+  });
+  return [].concat(...l);
+}
+
+export function getMap(list, key = 'value', value = null, children = null) {
   const map = {};
   for (let i = 0; i < list.length; i += 1) {
-    map[list[i][key]] = value ? list[i][value] : list[i];
+    const item = list[i];
+    const v = value ? item[value] : item;
+    if (children && v && v[children] && v[children].length > 0) {
+      v[children] = getMap(v[children], key, value, children);
+    }
+    map[item[key]] = v;
   }
   return map;
 }

+ 70 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/Ad.java

@@ -18,6 +18,18 @@ public class Ad implements Serializable {
     private String title;
 
     /**
+     * 频道页面
+     */
+    @Column(name = "`channel`")
+    private String channel;
+
+    /**
+     * 位置
+     */
+    @Column(name = "`place`")
+    private String place;
+
+    /**
      * 广告图片
      */
     @Column(name = "`image`")
@@ -82,6 +94,42 @@ public class Ad implements Serializable {
     }
 
     /**
+     * 获取频道页面
+     *
+     * @return channel - 频道页面
+     */
+    public String getChannel() {
+        return channel;
+    }
+
+    /**
+     * 设置频道页面
+     *
+     * @param channel 频道页面
+     */
+    public void setChannel(String channel) {
+        this.channel = channel;
+    }
+
+    /**
+     * 获取位置
+     *
+     * @return place - 位置
+     */
+    public String getPlace() {
+        return place;
+    }
+
+    /**
+     * 设置位置
+     *
+     * @param place 位置
+     */
+    public void setPlace(String place) {
+        this.place = place;
+    }
+
+    /**
      * 获取广告图片
      *
      * @return image - 广告图片
@@ -179,6 +227,8 @@ public class Ad implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", title=").append(title);
+        sb.append(", channel=").append(channel);
+        sb.append(", place=").append(place);
         sb.append(", image=").append(image);
         sb.append(", position=").append(position);
         sb.append(", link=").append(link);
@@ -218,6 +268,26 @@ public class Ad implements Serializable {
         }
 
         /**
+         * 设置频道页面
+         *
+         * @param channel 频道页面
+         */
+        public Builder channel(String channel) {
+            obj.setChannel(channel);
+            return this;
+        }
+
+        /**
+         * 设置位置
+         *
+         * @param place 位置
+         */
+        public Builder place(String place) {
+            obj.setPlace(place);
+            return this;
+        }
+
+        /**
          * 设置广告图片
          *
          * @param image 广告图片

+ 39 - 4
server/data/src/main/java/com/qxgmat/data/dao/entity/Comment.java

@@ -39,7 +39,7 @@ public class Comment implements Serializable {
      * 位置
      */
     @Column(name = "`position`")
-    private String position;
+    private Integer position;
 
     /**
      * 是否展示
@@ -59,6 +59,12 @@ public class Comment implements Serializable {
     @Column(name = "`is_system`")
     private Integer isSystem;
 
+    /**
+     * 排序
+     */
+    @Column(name = "`order`")
+    private Integer order;
+
     @Column(name = "`create_time`")
     private Date createTime;
 
@@ -158,7 +164,7 @@ public class Comment implements Serializable {
      *
      * @return position - 位置
      */
-    public String getPosition() {
+    public Integer getPosition() {
         return position;
     }
 
@@ -167,7 +173,7 @@ public class Comment implements Serializable {
      *
      * @param position 位置
      */
-    public void setPosition(String position) {
+    public void setPosition(Integer position) {
         this.position = position;
     }
 
@@ -226,6 +232,24 @@ public class Comment implements Serializable {
     }
 
     /**
+     * 获取排序
+     *
+     * @return order - 排序
+     */
+    public Integer getOrder() {
+        return order;
+    }
+
+    /**
+     * 设置排序
+     *
+     * @param order 排序
+     */
+    public void setOrder(Integer order) {
+        this.order = order;
+    }
+
+    /**
      * @return create_time
      */
     public Date getCreateTime() {
@@ -268,6 +292,7 @@ public class Comment implements Serializable {
         sb.append(", isShow=").append(isShow);
         sb.append(", isSpecial=").append(isSpecial);
         sb.append(", isSystem=").append(isSystem);
+        sb.append(", order=").append(order);
         sb.append(", createTime=").append(createTime);
         sb.append(", content=").append(content);
         sb.append("]");
@@ -338,7 +363,7 @@ public class Comment implements Serializable {
          *
          * @param position 位置
          */
-        public Builder position(String position) {
+        public Builder position(Integer position) {
             obj.setPosition(position);
             return this;
         }
@@ -374,6 +399,16 @@ public class Comment implements Serializable {
         }
 
         /**
+         * 设置排序
+         *
+         * @param order 排序
+         */
+        public Builder order(Integer order) {
+            obj.setOrder(order);
+            return this;
+        }
+
+        /**
          * @param createTime
          */
         public Builder createTime(Date createTime) {

+ 61 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/CourseData.java

@@ -37,6 +37,12 @@ public class CourseData implements Serializable {
     private Integer parentStructId;
 
     /**
+     * 是否是长难句
+     */
+    @Column(name = "`is_sentence`")
+    private Integer isSentence;
+
+    /**
      * 资料形式
      */
     @Column(name = "`data_type`")
@@ -120,6 +126,9 @@ public class CourseData implements Serializable {
     @Column(name = "`update_time`")
     private Date updateTime;
 
+    @Column(name = "`latest_time`")
+    private Date latestTime;
+
     /**
      * 摘要介绍
      */
@@ -233,6 +242,24 @@ public class CourseData implements Serializable {
     }
 
     /**
+     * 获取是否是长难句
+     *
+     * @return is_sentence - 是否是长难句
+     */
+    public Integer getIsSentence() {
+        return isSentence;
+    }
+
+    /**
+     * 设置是否是长难句
+     *
+     * @param isSentence 是否是长难句
+     */
+    public void setIsSentence(Integer isSentence) {
+        this.isSentence = isSentence;
+    }
+
+    /**
      * 获取资料形式
      *
      * @return data_type - 资料形式
@@ -495,6 +522,20 @@ public class CourseData implements Serializable {
     }
 
     /**
+     * @return latest_time
+     */
+    public Date getLatestTime() {
+        return latestTime;
+    }
+
+    /**
+     * @param latestTime
+     */
+    public void setLatestTime(Date latestTime) {
+        this.latestTime = latestTime;
+    }
+
+    /**
      * 获取摘要介绍
      *
      * @return description - 摘要介绍
@@ -577,6 +618,7 @@ public class CourseData implements Serializable {
         sb.append(", comment=").append(comment);
         sb.append(", structId=").append(structId);
         sb.append(", parentStructId=").append(parentStructId);
+        sb.append(", isSentence=").append(isSentence);
         sb.append(", dataType=").append(dataType);
         sb.append(", isNovice=").append(isNovice);
         sb.append(", isOriginal=").append(isOriginal);
@@ -592,6 +634,7 @@ public class CourseData implements Serializable {
         sb.append(", saleNumber=").append(saleNumber);
         sb.append(", createTime=").append(createTime);
         sb.append(", updateTime=").append(updateTime);
+        sb.append(", latestTime=").append(latestTime);
         sb.append(", description=").append(description);
         sb.append(", content=").append(content);
         sb.append(", authorContent=").append(authorContent);
@@ -660,6 +703,16 @@ public class CourseData implements Serializable {
         }
 
         /**
+         * 设置是否是长难句
+         *
+         * @param isSentence 是否是长难句
+         */
+        public Builder isSentence(Integer isSentence) {
+            obj.setIsSentence(isSentence);
+            return this;
+        }
+
+        /**
          * 设置资料形式
          *
          * @param dataType 资料形式
@@ -806,6 +859,14 @@ public class CourseData implements Serializable {
         }
 
         /**
+         * @param latestTime
+         */
+        public Builder latestTime(Date latestTime) {
+            obj.setLatestTime(latestTime);
+            return this;
+        }
+
+        /**
          * 设置摘要介绍
          *
          * @param description 摘要介绍

+ 39 - 4
server/data/src/main/java/com/qxgmat/data/dao/entity/Faq.java

@@ -39,7 +39,7 @@ public class Faq implements Serializable {
      * 位置
      */
     @Column(name = "`position`")
-    private String position;
+    private Integer position;
 
     /**
      * 处理人
@@ -77,6 +77,12 @@ public class Faq implements Serializable {
     @Column(name = "`is_system`")
     private Integer isSystem;
 
+    /**
+     * 排序
+     */
+    @Column(name = "`order`")
+    private Integer order;
+
     @Column(name = "`create_time`")
     private Date createTime;
 
@@ -185,7 +191,7 @@ public class Faq implements Serializable {
      *
      * @return position - 位置
      */
-    public String getPosition() {
+    public Integer getPosition() {
         return position;
     }
 
@@ -194,7 +200,7 @@ public class Faq implements Serializable {
      *
      * @param position 位置
      */
-    public void setPosition(String position) {
+    public void setPosition(Integer position) {
         this.position = position;
     }
 
@@ -307,6 +313,24 @@ public class Faq implements Serializable {
     }
 
     /**
+     * 获取排序
+     *
+     * @return order - 排序
+     */
+    public Integer getOrder() {
+        return order;
+    }
+
+    /**
+     * 设置排序
+     *
+     * @param order 排序
+     */
+    public void setOrder(Integer order) {
+        this.order = order;
+    }
+
+    /**
      * @return create_time
      */
     public Date getCreateTime() {
@@ -374,6 +398,7 @@ public class Faq implements Serializable {
         sb.append(", answerStatus=").append(answerStatus);
         sb.append(", answerTime=").append(answerTime);
         sb.append(", isSystem=").append(isSystem);
+        sb.append(", order=").append(order);
         sb.append(", createTime=").append(createTime);
         sb.append(", content=").append(content);
         sb.append(", answer=").append(answer);
@@ -445,7 +470,7 @@ public class Faq implements Serializable {
          *
          * @param position 位置
          */
-        public Builder position(String position) {
+        public Builder position(Integer position) {
             obj.setPosition(position);
             return this;
         }
@@ -521,6 +546,16 @@ public class Faq implements Serializable {
         }
 
         /**
+         * 设置排序
+         *
+         * @param order 排序
+         */
+        public Builder order(Integer order) {
+            obj.setOrder(order);
+            return this;
+        }
+
+        /**
          * @param createTime
          */
         public Builder createTime(Date createTime) {

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

@@ -24,6 +24,12 @@ public class UserInvoice implements Serializable {
     private Integer orderId;
 
     /**
+     * 记录id:独立课程id或套餐的记录id
+     */
+    @Column(name = "`record_id`")
+    private Integer recordId;
+
+    /**
      * 发票类型
      */
     @Column(name = "`invoice_type`")
@@ -112,6 +118,24 @@ public class UserInvoice implements Serializable {
     }
 
     /**
+     * 获取记录id:独立课程id或套餐的记录id
+     *
+     * @return record_id - 记录id:独立课程id或套餐的记录id
+     */
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    /**
+     * 设置记录id:独立课程id或套餐的记录id
+     *
+     * @param recordId 记录id:独立课程id或套餐的记录id
+     */
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+
+    /**
      * 获取发票类型
      *
      * @return invoice_type - 发票类型
@@ -238,6 +262,7 @@ public class UserInvoice implements Serializable {
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
         sb.append(", orderId=").append(orderId);
+        sb.append(", recordId=").append(recordId);
         sb.append(", invoiceType=").append(invoiceType);
         sb.append(", title=").append(title);
         sb.append(", identity=").append(identity);
@@ -299,6 +324,16 @@ public class UserInvoice implements Serializable {
         }
 
         /**
+         * 设置记录id:独立课程id或套餐的记录id
+         *
+         * @param recordId 记录id:独立课程id或套餐的记录id
+         */
+        public Builder recordId(Integer recordId) {
+            obj.setRecordId(recordId);
+            return this;
+        }
+
+        /**
          * 设置发票类型
          *
          * @param invoiceType 发票类型

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

@@ -7,6 +7,8 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="title" jdbcType="VARCHAR" property="title" />
+    <result column="channel" jdbcType="VARCHAR" property="channel" />
+    <result column="place" jdbcType="VARCHAR" property="place" />
     <result column="image" jdbcType="VARCHAR" property="image" />
     <result column="position" jdbcType="INTEGER" property="position" />
     <result column="link" jdbcType="VARCHAR" property="link" />
@@ -17,6 +19,6 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title`, `image`, `position`, `link`, `start_time`, `end_time`
+    `id`, `title`, `channel`, `place`, `image`, `position`, `link`, `start_time`, `end_time`
   </sql>
 </mapper>

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

@@ -10,10 +10,11 @@
     <result column="nickname" jdbcType="VARCHAR" property="nickname" />
     <result column="avatar" jdbcType="VARCHAR" property="avatar" />
     <result column="channel" jdbcType="VARCHAR" property="channel" />
-    <result column="position" jdbcType="VARCHAR" property="position" />
+    <result column="position" jdbcType="INTEGER" 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="order" jdbcType="INTEGER" property="order" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.Comment">
@@ -27,7 +28,7 @@
       WARNING - @mbg.generated
     -->
     `id`, `user_id`, `nickname`, `avatar`, `channel`, `position`, `is_show`, `is_special`, 
-    `is_system`, `create_time`
+    `is_system`, `order`, `create_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -10,6 +10,7 @@
     <result column="comment" jdbcType="VARCHAR" property="comment" />
     <result column="struct_id" jdbcType="INTEGER" property="structId" />
     <result column="parent_struct_id" jdbcType="INTEGER" property="parentStructId" />
+    <result column="is_sentence" jdbcType="INTEGER" property="isSentence" />
     <result column="data_type" jdbcType="VARCHAR" property="dataType" />
     <result column="is_novice" jdbcType="INTEGER" property="isNovice" />
     <result column="is_original" jdbcType="INTEGER" property="isOriginal" />
@@ -25,6 +26,7 @@
     <result column="sale_number" jdbcType="INTEGER" property="saleNumber" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+    <result column="latest_time" jdbcType="TIMESTAMP" property="latestTime" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.CourseData">
     <!--
@@ -39,9 +41,10 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title`, `comment`, `struct_id`, `parent_struct_id`, `data_type`, `is_novice`, 
-    `is_original`, `price`, `pages`, `link`, `cover`, `resource`, `trail_resource`, `trail_start`, 
-    `trail_end`, `view_number`, `sale_number`, `create_time`, `update_time`
+    `id`, `title`, `comment`, `struct_id`, `parent_struct_id`, `is_sentence`, `data_type`, 
+    `is_novice`, `is_original`, `price`, `pages`, `link`, `cover`, `resource`, `trail_resource`, 
+    `trail_start`, `trail_end`, `view_number`, `sale_number`, `create_time`, `update_time`, 
+    `latest_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -10,13 +10,14 @@
     <result column="email" jdbcType="VARCHAR" property="email" />
     <result column="message" jdbcType="INTEGER" property="message" />
     <result column="channel" jdbcType="VARCHAR" property="channel" />
-    <result column="position" jdbcType="VARCHAR" property="position" />
+    <result column="position" jdbcType="INTEGER" 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" />
     <result column="is_system" jdbcType="INTEGER" property="isSystem" />
+    <result column="order" jdbcType="INTEGER" property="order" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.Faq">
@@ -31,7 +32,7 @@
       WARNING - @mbg.generated
     -->
     `id`, `user_id`, `email`, `message`, `channel`, `position`, `manager_id`, `is_show`, 
-    `is_special`, `answer_status`, `answer_time`, `is_system`, `create_time`
+    `is_special`, `answer_status`, `answer_time`, `is_system`, `order`, `create_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -8,6 +8,7 @@
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="order_id" jdbcType="INTEGER" property="orderId" />
+    <result column="record_id" jdbcType="INTEGER" property="recordId" />
     <result column="invoice_type" jdbcType="VARCHAR" property="invoiceType" />
     <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="identity" jdbcType="VARCHAR" property="identity" />
@@ -20,7 +21,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `order_id`, `invoice_type`, `title`, `identity`, `is_download`, 
+    `id`, `user_id`, `order_id`, `record_id`, `invoice_type`, `title`, `identity`, `is_download`, 
     `is_finish`, `create_time`, `update_time`
   </sql>
 </mapper>

+ 7 - 0
server/data/src/main/java/com/qxgmat/data/relation/CommentRelationMapper.java

@@ -3,6 +3,7 @@ package com.qxgmat.data.relation;
 import com.qxgmat.data.dao.entity.Comment;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -21,4 +22,10 @@ public interface CommentRelationMapper {
             String order,
             String direction
     );
+
+    List<Comment> groupByPosition(
+            @Param("channel") String channel,
+            @Param("position") Collection positions,
+            @Param("top") Integer top
+    );
 }

+ 7 - 0
server/data/src/main/java/com/qxgmat/data/relation/FaqRelationMapper.java

@@ -3,6 +3,7 @@ package com.qxgmat.data.relation;
 import com.qxgmat.data.dao.entity.Faq;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -22,4 +23,10 @@ public interface FaqRelationMapper {
             String order,
             String direction
     );
+
+    List<Faq> groupByPosition(
+            @Param("channel") String channel,
+            @Param("position") Collection positions,
+            @Param("top") Integer top
+    );
 }

+ 9 - 0
server/data/src/main/java/com/qxgmat/data/relation/UserOrderRecordRelationMapper.java

@@ -30,6 +30,15 @@ public interface UserOrderRecordRelationMapper {
             @Param("direction") String direction
     );
 
+    List<UserOrderRecord> listWithCourse(
+            @Param("userId") Integer userId,
+            @Param("vsType") String courseModule,
+            @Param("isUsed") Boolean isUsed,
+            @Param("isEnd") Boolean isEnd,
+            @Param("order") String order,
+            @Param("direction") String direction
+    );
+
     List<CourseStudentNumberRelation> groupByTime(
             @Param("ids") Collection ids
     );

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

@@ -11,7 +11,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    f.`id`
+    c.`id`
   </sql>
 
   <!--用户提问列表-->
@@ -56,4 +56,24 @@
     </if>
     order by ${order} ${direction}
   </select>
+
+  <select id="groupByPosition" resultMap="IdMap">
+    select id, `position`
+    from
+    (
+    select c.id, c.`position`, c.`order`
+    (@num:=if(@group = `position`, @num +1, if(@group := `position`, 1, 1))) row_number
+    from comment c
+    CROSS JOIN (select @num:=0, @group:=null) c
+    where
+    c.`channel` = #{channel,jdbcType=VARCHAR}
+    and c.`position` IN
+    <foreach collection="positions" item="item" index="index" open="(" close=")" separator=",">
+      #{item}
+    </foreach>
+    order by c.`position`, c.`order` desc
+    ) as c
+    where x.row_number &lt; #{top,jdbcType=INTEGER}
+    order by x.`order` desc
+  </select>
 </mapper>

+ 20 - 0
server/data/src/main/java/com/qxgmat/data/relation/mapping/FaqRelationMapper.xml

@@ -59,4 +59,24 @@
     </if>
     order by ${order} ${direction}
   </select>
+
+  <select id="groupByPosition" resultMap="IdMap">
+    select id, `position`
+    from
+    (
+    select c.id, c.`position`, c.`order`
+    (@num:=if(@group = `position`, @num +1, if(@group := `position`, 1, 1))) row_number
+    from comment c
+    CROSS JOIN (select @num:=0, @group:=null) c
+    where
+    c.`channel` = #{channel,jdbcType=VARCHAR}
+    and c.`position` IN
+    <foreach collection="positions" item="item" index="index" open="(" close=")" separator=",">
+      #{item}
+    </foreach>
+    order by c.`position`, c.`order` desc
+    ) as x
+    where x.row_number &lt; #{top,jdbcType=INTEGER}
+    order by x.`order` desc
+  </select>
 </mapper>

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

@@ -1,11 +0,0 @@
-package com.qxgmat.controller.admin;
-
-import io.swagger.annotations.Api;
-import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.*;
-
-@RestController("AdminAdController")
-@RequestMapping("/admin/ad")
-@Api(tags = "广告接口", description = "广告相关", produces = MediaType.APPLICATION_JSON_VALUE)
-public class AdController {
-}

+ 29 - 5
server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java

@@ -198,10 +198,11 @@ 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) String keyword,
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<CoursePackage> p = coursePackageService.select(page, size);
+        Page<CoursePackage> p = coursePackageService.listAdmin(page, size, keyword, order, DirectionStatus.ValueOf(direction));
         List<CoursePackage> pr = Transform.convert(p, CoursePackage.class);
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
@@ -244,12 +245,13 @@ public class CourseController {
     public Response<PageMessage<CourseData>> listData(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String keyword,
             @RequestParam(required = false) Integer structId,
             @RequestParam(required = false) String dataType,
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<CourseData> p = courseDataService.listAdmin(page, size, structId, DataType.ValueOf(dataType), order, DirectionStatus.ValueOf(direction));
+        Page<CourseData> p = courseDataService.listAdmin(page, size, keyword, structId, DataType.ValueOf(dataType), order, DirectionStatus.ValueOf(direction));
         List<CourseData> pr = Transform.convert(p, CourseData.class);
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
@@ -259,6 +261,17 @@ public class CourseController {
     public Response<CourseDataHistory> addDataHistory(@RequestBody @Validated CourseDataHistoryDto dto, HttpServletRequest request) {
         CourseDataHistory entity = Transform.dtoToEntity(dto);
         entity = courseDataHistoryService.add(entity);
+        CourseData in = courseDataService.get(entity.getDataId());
+        CourseData tmp = CourseData.builder()
+                .id(entity.getDataId())
+                .build();
+        if(in.getLatestTime() == null){
+            tmp.setLatestTime(entity.getTime());
+            courseDataService.edit(tmp);
+        }else if(entity.getTime().after(in.getLatestTime())){
+            tmp.setLatestTime(entity.getTime());
+            courseDataService.edit(tmp);
+        }
         managerLogService.log(request);
         return ResponseHelp.success(Transform.convert(entity, CourseDataHistory.class));
     }
@@ -268,6 +281,17 @@ public class CourseController {
     public Response<Boolean> editDataHistory(@RequestBody @Validated CourseDataHistoryDto dto, HttpServletRequest request) {
         CourseDataHistory entity = Transform.dtoToEntity(dto);
         entity = courseDataHistoryService.edit(entity);
+        CourseData in = courseDataService.get(entity.getDataId());
+        CourseData tmp = CourseData.builder()
+                .id(entity.getDataId())
+                .build();
+        if(in.getLatestTime() == null){
+            tmp.setLatestTime(entity.getTime());
+            courseDataService.edit(tmp);
+        }else if(entity.getTime().after(in.getLatestTime())){
+            tmp.setLatestTime(entity.getTime());
+            courseDataService.edit(tmp);
+        }
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -549,7 +573,7 @@ public class CourseController {
 
     @RequestMapping(value = "/student/online/edit", method = RequestMethod.PUT)
     @ApiOperation(value = "编辑学员", httpMethod = "PUT")
-    public Response<Boolean> editStudentVideo(@RequestBody @Validated CourseStudentOnlineDto dto, HttpServletRequest request) {
+    public Response<Boolean> editStudentOnline(@RequestBody @Validated CourseStudentOnlineDto dto, HttpServletRequest request) {
         UserOrderRecord entity = Transform.dtoToEntity(dto);
         entity = userOrderRecordService.edit(entity);
         managerLogService.log(request);
@@ -566,14 +590,14 @@ public class CourseController {
 
     @RequestMapping(value = "/student/online/detail", method = RequestMethod.GET)
     @ApiOperation(value = "获取学员", httpMethod = "GET")
-    public Response<UserOrderRecord> detailStudentVideo(@RequestParam int id,HttpSession session) {
+    public Response<UserOrderRecord> detailStudentOnline(@RequestParam int id,HttpSession session) {
         UserOrderRecord entity = userOrderRecordService.get(id);
         return ResponseHelp.success(Transform.convert(entity, UserOrderRecord.class));
     }
 
     @RequestMapping(value = "/student/online/list", method = RequestMethod.GET)
     @ApiOperation(value = "课时学员", httpMethod = "GET")
-    public Response<PageMessage<CourseStudentOnlineListDto>> listStudentVideo(
+    public Response<PageMessage<CourseStudentOnlineListDto>> listStudentOnline(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) Integer courseId,

+ 24 - 5
server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java

@@ -16,6 +16,7 @@ import com.qxgmat.dto.admin.response.CommentInfoDto;
 import com.qxgmat.dto.admin.response.FaqInfoDto;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.UsersService;
+import com.qxgmat.service.extend.MessageExtendService;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -77,6 +78,9 @@ public class SettingController {
     private MessageService messageService;
 
     @Autowired
+    private MessageExtendService messageExtendService;
+
+    @Autowired
     private UsersService usersService;
 
     @Autowired
@@ -449,6 +453,13 @@ public class SettingController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/comment/order", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改评价排序", httpMethod = "PUT")
+    private Response<Boolean> orderComment(@RequestBody @Validated CommentOrderDto dto){
+        commentService.updateOrder(dto.getIds());
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/comment/list", method = RequestMethod.GET)
     @ApiOperation(value = "获取评价列表", httpMethod = "GET")
     private Response<PageMessage<CommentInfoDto>> listComment(
@@ -517,7 +528,7 @@ public class SettingController {
         if(in.getIsSystem() == 0 && dto.getSendUser() != null && dto.getSendUser()){
             // 回复邮箱
             if (in.getEmail() != null && !in.getEmail().isEmpty()){
-                //
+                // 发送邮件,取消
             }
             // 回复站内信
             if(in.getMessage() != null && in.getMessage() > 0){
@@ -528,6 +539,13 @@ public class SettingController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/faq/order", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改faq排序", httpMethod = "PUT")
+    private Response<Boolean> orderFaq(@RequestBody @Validated FaqOrderDto dto){
+        faqService.updateOrder(dto.getIds());
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/faq/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "删除评价", httpMethod = "DELETE")
     private Response<Boolean> deleteFaq(@RequestParam int id){
@@ -614,7 +632,7 @@ public class SettingController {
     @ApiOperation(value = "添加广告", httpMethod = "POST")
     public Response<Ad> add(@RequestBody @Validated AdDto dto, HttpServletRequest request) {
         Ad entity = Transform.dtoToEntity(dto);
-        entity = adService.edit(entity);
+        entity = adService.addAd(entity);
         managerLogService.log(request);
         return ResponseHelp.success(entity);
     }
@@ -623,7 +641,7 @@ public class SettingController {
     @ApiOperation(value = "修改广告", httpMethod = "PUT")
     public Response<Boolean> edit(@RequestBody @Validated AdDto dto, HttpServletRequest request) {
         Ad entity = Transform.dtoToEntity(dto);
-        entity = adService.edit(entity);
+        entity = adService.editAd(entity);
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -631,8 +649,9 @@ public class SettingController {
     @RequestMapping(value = "/ad/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "删除广告", httpMethod = "DELETE")
     public Response<Boolean> delete(@RequestParam int id, HttpServletRequest request) {
+        adService.delete(id);
         managerLogService.log(request);
-        return ResponseHelp.success(adService.delete(id));
+        return ResponseHelp.success(true);
     }
 
     @RequestMapping(value = "/ad/detail", method = RequestMethod.GET)
@@ -652,7 +671,7 @@ public class SettingController {
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<Ad> p = adService.list(page, size, channel, position, order, DirectionStatus.ValueOf(direction));
+        Page<Ad> p = adService.listAdmin(page, size, channel, position, order, DirectionStatus.ValueOf(direction));
         List<AdInfoDto> pr = Transform.convert(p, AdInfoDto.class);
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }

+ 14 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java

@@ -232,6 +232,20 @@ public class UserController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/nofrozen", method = RequestMethod.POST)
+    @ApiOperation(value = "取消冻结用户", httpMethod = "POST")
+    public Response<Boolean> noFrozen(@RequestParam int id, HttpSession session) {
+        User entity = usersService.get(id);
+        if (entity.getIsFrozen() == 0){
+            throw new ParameterException("用户未冻结");
+        }
+        usersService.edit(User.builder()
+                .id(id)
+                .isFrozen(0)
+                .build());
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/list", method = RequestMethod.GET)
     @ApiOperation(value = "用户列表", httpMethod = "GET")
     public Response<PageMessage<UserListDto>> list(

+ 4 - 2
server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java

@@ -76,8 +76,10 @@ public class BaseController {
 
     @RequestMapping(value = "/ad", method = RequestMethod.GET)
     @ApiOperation(value = "获取广告", notes = "获取广告列表", httpMethod = "GET")
-    public Response<List<Ad>> ad()  {
-        List<Ad> adList = adService.all();
+    public Response<List<Ad>> ad(
+            @RequestParam(required = true) String channel
+    )  {
+        List<Ad> adList = adService.all(channel);
         return ResponseHelp.success(adList);
     }
 

+ 49 - 7
server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java

@@ -12,9 +12,7 @@ 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.CourseTeacherExtendDto;
-import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
+import com.qxgmat.dto.extend.*;
 import com.qxgmat.dto.response.*;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.extend.PreviewService;
@@ -37,6 +35,12 @@ public class CourseController {
     private ShiroHelp shiroHelp;
 
     @Autowired
+    private CommentService commentService;
+
+    @Autowired
+    private FaqService faqService;
+
+    @Autowired
     private CourseService courseService;
 
     @Autowired
@@ -94,19 +98,31 @@ public class CourseController {
         Page<Course> p = courseService.list(page, size, CourseModule.VIDEO, structId, order, DirectionStatus.ValueOf(direction));
 
         List<CourseListDto> pr = Transform.convert(p, CourseListDto.class);
+        Collection ids = Transform.getIds(pr, CourseListDto.class, "id");
+        // 评论
+        Map<Object, Collection<Comment>> commentMap = commentService.groupByPosition("course-video", ids, 1);
+        Transform.combine(pr, commentMap, CourseListDto.class, "id", "comments", CommentExtendDto.class);
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
     @RequestMapping(value = "/detail", method = RequestMethod.GET)
     @ApiOperation(value = "课程详情", httpMethod = "GET")
-    public Response<Course> detail(
+    public Response<CourseDetailDto> detail(
             @RequestParam(required = true) Integer courseId
     ) {
         User user = (User) shiroHelp.getLoginUser();
 
         Course course = courseService.get(courseId);
+        CourseDetailDto dto = Transform.convert(course, CourseDetailDto.class);
 
-        return ResponseHelp.success(course);
+        // 评论
+        List<Comment> commentList = commentService.list(1, 10, "course-"+course.getCourseModule(), course.getId().toString());
+        dto.setComments(Transform.convert(commentList, CommentExtendDto.class));
+        // faq
+        List<Faq> faqList = faqService.list(1, 10, "course-"+course.getCourseModule(), course.getId().toString());
+        dto.setFaqs(Transform.convert(faqList, FaqExtendDto.class));
+
+        return ResponseHelp.success(dto);
     }
 
     @RequestMapping(value = "/simple", method = RequestMethod.GET)
@@ -135,6 +151,7 @@ public class CourseController {
         Page<CoursePackage> p = coursePackageService.list(page, size, structId, isSpecial, order, DirectionStatus.ValueOf(direction));
 
         List<CoursePackageListDto> pr = Transform.convert(p, CoursePackageListDto.class);
+        Collection ids = Transform.getIds(pr, CoursePackageListDto.class, "id");
 
         Map<Integer, Integer[]> courseIdsMap = new HashMap<>();
         for(CoursePackage cp : p){
@@ -143,6 +160,10 @@ public class CourseController {
         Map courseMap = courseService.groupByMap(courseIdsMap);
         Transform.combine(pr, courseMap, CoursePackageListDto.class, "id", "courses", CourseExtendDto.class);
 
+        // 评论
+        Map<Object, Collection<Comment>> commentMap = commentService.groupByPosition("course-package", ids, 1);
+        Transform.combine(pr, commentMap, CourseListDto.class, "id", "comments", CommentExtendDto.class);
+
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
@@ -160,6 +181,13 @@ public class CourseController {
 
         dto.setCourses(Transform.convert(courseList, CourseExtendDto.class));
 
+        // 评论
+        List<Comment> commentList = commentService.list(1, 10, "course-package", coursePackage.getId().toString());
+        dto.setComments(Transform.convert(commentList, CommentExtendDto.class));
+        // faq
+        List<Faq> faqList = faqService.list(1, 10, "course-package", coursePackage.getId().toString());
+        dto.setFaqs(Transform.convert(faqList, FaqExtendDto.class));
+
         return ResponseHelp.success(dto);
     }
 
@@ -178,20 +206,34 @@ public class CourseController {
 
         Page<CourseData> p = courseDataService.list(page, size, structId, DataType.ValueOf(dataType), isNovice, isOriginal, order, DirectionStatus.ValueOf(direction));
         List<CourseDataListDto> pr = Transform.convert(p, CourseDataListDto.class);
+        Collection ids = Transform.getIds(pr, CourseDataListDto.class, "id");
+
+        // 评论
+        Map<Object, Collection<Comment>> commentMap = commentService.groupByPosition("course_data", ids, 1);
+        Transform.combine(pr, commentMap, CourseListDto.class, "id", "comments", CommentExtendDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
     @RequestMapping(value = "/data/detail", method = RequestMethod.GET)
     @ApiOperation(value = "资料详情", httpMethod = "GET")
-    public Response<CourseData> detailData(
+    public Response<CourseDataDetailDto> detailData(
             @RequestParam(required = true) Integer dataId
     ) {
         User user = (User) shiroHelp.getLoginUser();
 
         CourseData courseData = courseDataService.get(dataId);
 
-        return ResponseHelp.success(courseData);
+        CourseDataDetailDto dto = Transform.convert(courseData, CourseDataDetailDto.class);
+
+        // 评论
+        List<Comment> commentList = commentService.list(1, 10, "course_package", courseData.getId().toString());
+        dto.setComments(Transform.convert(commentList, CommentExtendDto.class));
+        // faq
+        List<Faq> faqList = faqService.list(1, 10, "course_package", courseData.getId().toString());
+        dto.setFaqs(Transform.convert(faqList, FaqExtendDto.class));
+
+        return ResponseHelp.success(dto);
     }
 
     @RequestMapping(value = "/data/history", method = RequestMethod.GET)

+ 125 - 12
server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java

@@ -28,10 +28,7 @@ import com.qxgmat.help.AiHelp;
 import com.qxgmat.help.MailHelp;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.*;
-import com.qxgmat.service.extend.CourseExtendService;
-import com.qxgmat.service.extend.OrderFlowService;
-import com.qxgmat.service.extend.QuestionFlowService;
-import com.qxgmat.service.extend.SentenceService;
+import com.qxgmat.service.extend.*;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -80,6 +77,9 @@ public class MyController {
     private MailHelp mailHelp;
 
     @Autowired
+    private PreviewService previewService;
+
+    @Autowired
     private SettingService settingService;
 
     @Autowired
@@ -119,6 +119,9 @@ public class MyController {
     private CourseDataHistoryService courseDataHistoryService;
 
     @Autowired
+    private CourseTeacherService courseTeacherService;
+
+    @Autowired
     private FaqService faqService;
 
     @Autowired
@@ -134,6 +137,12 @@ public class MyController {
     private UserCourseRecordService userCourseRecordService;
 
     @Autowired
+    private UserCourseAppointmentService userCourseAppointmentService;
+
+    @Autowired
+    private UserCourseProgressService userCourseProgressService;
+
+    @Autowired
     private UserSentenceRecordService userSentenceRecordService;
 
     @Autowired
@@ -193,6 +202,9 @@ public class MyController {
     @Autowired
     private OrderFlowService orderFlowService;
 
+    @Autowired
+    private MessageExtendService messageExtendService;
+
     @RequestMapping(value = "/email", method = RequestMethod.POST)
     @ApiOperation(value = "绑定邮箱", httpMethod = "POST")
     public Response<Boolean> email(@RequestBody @Validated UserEmailDto dto, HttpSession session, HttpServletRequest request) {
@@ -316,12 +328,7 @@ public class MyController {
     @ApiOperation(value = "发送邮件邀请", httpMethod = "POST")
     public Response<Boolean> inviteEmail(@RequestBody @Validated InviteEmailDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
-        Map<String, String> map = new HashMap<>();
-        mailHelp.sendBaseMail(
-                Strings.join(Arrays.stream(dto.getEmails()).collect(Collectors.toList()), ';'),
-                "邮件邀请",
-                "",
-                map);
+        messageExtendService.sendInviteEmail(dto.getEmails(), user.getInviteCode());
         return ResponseHelp.success(true);
     }
 
@@ -1393,7 +1400,8 @@ public class MyController {
         User user = (User) shiroHelp.getLoginUser();
         entity.setUserId(user.getId());
         entity.setMessage(1);
-        entity.setEmail(user.getEmail());
+        // 取消邮箱发送
+//        entity.setEmail(user.getEmail());
         faqService.add(entity);
 
         return ResponseHelp.success(true);
@@ -1436,7 +1444,7 @@ public class MyController {
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) Integer structId,
             @RequestParam(required = false) String dataType,
-            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "id") String order, // latest_time, sale_number
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
         User user = (User) shiroHelp.getLoginUser();
@@ -1445,4 +1453,109 @@ public class MyController {
 
         return ResponseHelp.success(p, page, size, p.getTotal());
     }
+
+    @RequestMapping(value = "/course/list", method = RequestMethod.GET)
+    @ApiOperation(value = "购买的课程记录", httpMethod = "GET")
+    public Response<PageMessage<UserCourseDetailDto>> listCourse(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String courseModule,
+            @RequestParam(required = false) Boolean isUsed,
+            @RequestParam(required = false) Boolean isEnd,
+            @RequestParam(required = false, defaultValue = "id") String order, // useEndTime desc
+            @RequestParam(required = false, defaultValue = "desc") String direction,
+            HttpSession session) {
+        User user = (User) shiroHelp.getLoginUser();
+
+        Page<UserOrderRecord> p = userOrderRecordService.listWithCourse(page, size, user.getId(), CourseModule.ValueOf(courseModule), isUsed, isEnd, order, DirectionStatus.ValueOf(direction));
+        List<UserCourseDetailDto> pr = Transform.convert(p, UserCourseDetailDto.class);
+
+        Collection recordIds = Transform.getIds(p, UserOrderRecord.class,"id");
+
+        // 绑定课程
+        Collection courseIds = Transform.getIds(p, UserOrderRecord.class, "productId");
+        List<Course> courseList = courseService.select(courseIds);
+        Transform.combine(pr, courseList, UserCourseDetailDto.class, "productId", "course", Course.class, "id", CourseExtendDto.class);
+
+        // 绑定课时、预约、进度
+        Map<Object, Collection<CourseNo>> courseNoMap = courseNoService.groupByCourseId(courseIds);
+        Transform.combine(pr, courseNoMap, UserCourseDetailDto.class, "productId", "courseNos", CourseNoExtendDto.class);
+
+        Map<Object, Collection<UserCourseAppointment>> appointmentMap = userCourseAppointmentService.groupByRecordId(recordIds);
+        Transform.combine(pr, appointmentMap, UserCourseDetailDto.class, "productId", "appointments", UserCourseAppointmentExtendDto.class);
+
+        Map<Object, Collection<UserCourseProgress>> progressMap = userCourseProgressService.groupByRecordId(recordIds);
+        for(UserCourseDetailDto dto : pr){
+            Collection<UserCourseProgress> list = progressMap.get(dto.getId());
+            if (list == null || list.size() == 0) continue;
+            Collection<CourseNo> courseNos = courseNoMap.get(dto.getProductId());
+            dto.setCurrentNo(courseExtendService.computeCourseNoCurrent(courseNos, list));
+        }
+
+        // 获取每个科目的所有作业
+        Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByCourseId(user.getId(), recordIds, 1000);
+        Transform.combine(pr, previewMap, UserCourseDetailDto.class, "productId", "papers", UserPaperBaseExtendDto.class);
+
+        // 绑定老师
+        Collection teacherIds = Transform.getIds(p, UserOrderRecord.class, "teacherId");
+        List<CourseTeacher> teacherList = courseTeacherService.select(teacherIds);
+        Transform.combine(pr, teacherList, UserCourseDetailDto.class, "teacherId", "teacher", CourseTeacher.class, "id", CourseTeacherExtendDto.class);
+
+
+        return ResponseHelp.success(pr, page, size, p.getTotal());
+    }
+
+    @RequestMapping(value = "/course/suspend", method = RequestMethod.POST)
+    @ApiOperation(value = "申请停课", notes = "申请停课", httpMethod = "POST")
+    public Response<Boolean> suspendCourse(@RequestBody @Validated CourseSuspendDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+
+        courseExtendService.suspendCourse(user.getId(), dto.getRecordId());
+
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/course/restore", method = RequestMethod.POST)
+    @ApiOperation(value = "恢复停课", notes = "恢复停课", httpMethod = "POST")
+    public Response<Boolean> restoreCourse(@RequestBody @Validated CourseRestoreDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+
+        courseExtendService.restoreCourse(user.getId(), dto.getRecordId());
+
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/course/time", method = RequestMethod.GET)
+    @ApiOperation(value = "时间表", notes = "时间表", httpMethod = "GET")
+    public Response<List<UserCourseTimeDto>> timeCourse(int id)  {
+        User user = (User) shiroHelp.getLoginUser();
+        List<UserCourseTimeDto> dtos = new ArrayList<>();
+
+        UserOrderRecord record = userOrderRecordService.get(id);
+        if (record == null){
+            throw new ParameterException("记录不存在");
+        }
+        if (!record.getUserId().equals(user.getId())){
+            throw new ParameterException("记录不存在");
+        }
+
+        // 获取停课记录
+        Date suspend = record.getSuspendTime();
+        if (suspend != null){
+            Date restore = record.getRestoreTime();
+            if (restore == null) restore = new Date();
+
+            while(suspend.before(restore)){
+                UserCourseTimeDto dto = new UserCourseTimeDto();
+                dto.setType("stop");
+                dto.setDay(Tools.day(suspend));
+                dtos.add(dto);
+                suspend = Tools.addDate(suspend, 1);
+            }
+        }
+
+        // todo 获取听课记录
+
+        return ResponseHelp.success(dtos);
+    }
 }

+ 50 - 16
server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java

@@ -1,5 +1,6 @@
 package com.qxgmat.controller.api;
 
+import com.alibaba.fastjson.JSONObject;
 import com.github.pagehelper.Page;
 import com.nuliji.tools.PageMessage;
 import com.nuliji.tools.Response;
@@ -7,6 +8,8 @@ import com.nuliji.tools.ResponseHelp;
 import com.nuliji.tools.Transform;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.pay.data.PayResponseData;
+import com.qxgmat.data.constants.enums.ServiceKey;
+import com.qxgmat.data.constants.enums.SettingKey;
 import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.trade.PayChannel;
@@ -15,11 +18,13 @@ import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.dto.extend.CourseDataExtendDto;
 import com.qxgmat.dto.extend.CourseExtendDto;
 import com.qxgmat.dto.extend.CoursePackageExtendDto;
+import com.qxgmat.dto.extend.UserOrderRecordExtendDto;
 import com.qxgmat.dto.request.OrderConfirmDto;
 import com.qxgmat.dto.request.PayOrderDto;
 import com.qxgmat.dto.request.RecordAddDto;
 import com.qxgmat.dto.request.RecordUseDto;
 import com.qxgmat.dto.response.UserOrderPreDto;
+import com.qxgmat.dto.response.UserOrderRecordListDto;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.extend.OrderFlowService;
 import com.qxgmat.service.extend.TradeService;
@@ -34,8 +39,7 @@ import org.springframework.web.bind.annotation.*;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 import java.math.BigDecimal;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @RestController
@@ -83,8 +87,8 @@ public class OrderController {
         //
         List<UserOrderCheckout> list = userOrderCheckoutService.allByUser(user.getId(), 0);
         UserOrder order = orderFlowService.preOrderWithCheckout(user.getId(), list);
-
-        return ResponseHelp.success(detail(user.getId(), order, list));
+        List<UserOrderRecordExtendDto> dtos = Transform.convert(list, UserOrderRecordExtendDto.class);
+        return ResponseHelp.success(detail(user.getId(), order, dtos));
     }
 
     @RequestMapping(value = "/checkout/add", method = RequestMethod.POST)
@@ -184,20 +188,50 @@ public class OrderController {
         return ResponseHelp.success(order.getPayStatus() > 0);
     }
 
-
     @RequestMapping(value = "/record/list", method = RequestMethod.GET)
     @ApiOperation(value = "获取订单记录列表", notes = "获取订单记录列表", httpMethod = "GET")
-    public Response<PageMessage<UserOrderRecord>> listRecord(
+    public Response<PageMessage<UserOrderRecordListDto>> listRecord(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String productType,
+            @RequestParam(required = false) Integer productId,
+            @RequestParam(required = false) String service,
+            @RequestParam(required = false) Boolean isUsed,
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session)  {
         User user = (User) shiroHelp.getLoginUser();
 
-        Page<UserOrderRecord> p = userOrderRecordService.list(page, size, user.getId(), order, DirectionStatus.ValueOf(direction));
+        Page<UserOrderRecord> p = userOrderRecordService.list(page, size, user.getId(), ProductType.ValueOf(productType), productId, ServiceKey.ValueOf(service), isUsed, order, DirectionStatus.ValueOf(direction));
+
+        List<UserOrderRecordListDto> pr = Transform.convert(p, UserOrderRecordListDto.class);
+
+        // 绑定服务
+        Map<Object, JSONObject> serviceList = new HashMap<Object, JSONObject>(){{
+            Setting vipSetting = settingService.getByKey(SettingKey.SERVICE_VIP);
+            put(ServiceKey.VIP.key, vipSetting.getValue());
+            Setting textbookSetting = settingService.getByKey(SettingKey.SERVICE_TEXTBOOK);
+            put(ServiceKey.TEXTBOOK.key, textbookSetting.getValue());
+            Setting qxCatSetting = settingService.getByKey(SettingKey.SERVICE_QX_CAT);
+            put(ServiceKey.QX_CAT.key, qxCatSetting.getValue());
+        }};
+        List<UserOrderRecordListDto> prService = pr.stream().filter((row)-> row.getProductType().equals(ProductType.SERVICE.key)).collect(Collectors.toList());
+        Transform.combine(prService, serviceList, UserOrderRecordListDto.class, "service", "serviceInfo");
+
+        // 绑定课程
+        List<UserOrderRecordListDto> prCourse = pr.stream().filter((row)-> row.getProductType().equals(ProductType.COURSE.key)).collect(Collectors.toList());
+        Collection courseIds = Transform.getIds(prCourse, UserOrderRecordListDto.class, "productId");
+        List<Course> courseList = courseService.select(courseIds);
+        Transform.combine(prCourse, courseList, UserOrderRecordListDto.class, "productId", "course", Course.class, "id", CourseExtendDto.class);
+
+        // 绑定资料
+        List<UserOrderRecordListDto> prData = pr.stream().filter((row)-> row.getProductType().equals(ProductType.DATA.key)).collect(Collectors.toList());
+        Collection dataIds = Transform.getIds(prData, UserOrderRecordListDto.class, "productId");
+        List<CourseData> dataList = courseDataService.select(dataIds);
+        Transform.combine(prData, dataList, UserOrderRecordListDto.class, "productId", "data", CourseData.class, "id", CourseDataExtendDto.class);
+
 
-        return ResponseHelp.success(p, page, size, p.getTotal());
+        return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
     @RequestMapping(value = "/record/detail", method = RequestMethod.GET)
@@ -231,28 +265,28 @@ public class OrderController {
      * @param list
      * @return
      */
-    public UserOrderPreDto detail(Integer userId, UserOrder order, List<UserOrderCheckout> list)  {
+    public UserOrderPreDto detail(Integer userId, UserOrder order, List<UserOrderRecordExtendDto> list)  {
         UserOrderPreDto dto = Transform.convert(order, UserOrderPreDto.class);
         if (list == null){
-            list = userOrderCheckoutService.allByUser(userId, order.getId());
+            list = Transform.convert(userOrderCheckoutService.allByUser(userId, order.getId()), UserOrderRecordExtendDto.class);
         }
         dto.setCheckouts(list);
 
         // 获取所有课程信息
-        List<UserOrderCheckout> courseCheckout = list.stream().filter((checkout)-> checkout.getProductType().equals(ProductType.COURSE.key)).collect(Collectors.toList());
-        Collection courseIds = Transform.getIds(courseCheckout, UserOrderCheckout.class, "productId");
+        List<UserOrderRecordExtendDto> courseCheckout = list.stream().filter((checkout)-> checkout.getProductType().equals(ProductType.COURSE.key)).collect(Collectors.toList());
+        Collection courseIds = Transform.getIds(courseCheckout, UserOrderRecordExtendDto.class, "productId");
         List<Course> courseList = courseService.select(courseIds);
         dto.setCourses(Transform.convert(courseList, CourseExtendDto.class));
 
         // 获取所有资料信息
-        List<UserOrderCheckout> dataCheckout = list.stream().filter((checkout)-> checkout.getProductType().equals(ProductType.DATA.key)).collect(Collectors.toList());
-        Collection dataIds = Transform.getIds(dataCheckout, UserOrderCheckout.class, "productId");
+        List<UserOrderRecordExtendDto> dataCheckout = list.stream().filter((checkout)-> checkout.getProductType().equals(ProductType.DATA.key)).collect(Collectors.toList());
+        Collection dataIds = Transform.getIds(dataCheckout, UserOrderRecordExtendDto.class, "productId");
         List<CourseData> dataList = courseDataService.select(dataIds);
         dto.setDatas(Transform.convert(dataList, CourseDataExtendDto.class));
 
         // 获取所有套餐信息
-        List<UserOrderCheckout> packageCheckout = list.stream().filter((checkout)-> checkout.getProductType().equals(ProductType.COURSE_PACKAGE.key)).collect(Collectors.toList());
-        Collection packageIds = Transform.getIds(packageCheckout, UserOrderCheckout.class, "productId");
+        List<UserOrderRecordExtendDto> packageCheckout = list.stream().filter((checkout)-> checkout.getProductType().equals(ProductType.COURSE_PACKAGE.key)).collect(Collectors.toList());
+        Collection packageIds = Transform.getIds(packageCheckout, UserOrderRecordExtendDto.class, "productId");
         List<CoursePackage> packageList = coursePackageService.select(packageIds);
         dto.setPackages(Transform.convert(packageList, CoursePackageExtendDto.class));
 

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

@@ -9,6 +9,7 @@ import com.qxgmat.data.constants.enums.SettingKey;
 import com.qxgmat.data.constants.enums.logic.SentenceLogic;
 import com.qxgmat.data.constants.enums.module.PaperOrigin;
 import com.qxgmat.data.dao.entity.*;
+import com.qxgmat.dto.extend.CommentExtendDto;
 import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
 import com.qxgmat.dto.extend.UserReportExtendDto;
 import com.qxgmat.dto.request.*;
@@ -63,6 +64,12 @@ public class SentenceController
     private SettingService settingService;
 
     @Autowired
+    private CourseDataService courseDataService;
+
+    @Autowired
+    private CommentService commentService;
+
+    @Autowired
     private UsersService usersService;
 
     @Autowired
@@ -103,6 +110,11 @@ public class SentenceController
         dto.setChapters(value.getJSONArray("chapters"));
         dto.setTrailPages(value.getInteger("trailPages"));
 
+        // 评论: 通过资料的长难句分类获取该资料的评论
+        CourseData courseData = courseDataService.getSentence();
+        List<Comment> commentList = commentService.list(1, 1, "course_data", courseData.getId().toString());
+        dto.setComments(Transform.convert(commentList, CommentExtendDto.class));
+
         return ResponseHelp.success(dto);
     }
 

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

@@ -19,6 +19,8 @@ public class UserExtendDto {
 
     private String registerCity;
 
+    private Integer isFrozen;
+
     public Integer getId() {
         return id;
     }
@@ -74,4 +76,12 @@ public class UserExtendDto {
     public void setRegisterCity(String registerCity) {
         this.registerCity = registerCity;
     }
+
+    public Integer getIsFrozen() {
+        return isFrozen;
+    }
+
+    public void setIsFrozen(Integer isFrozen) {
+        this.isFrozen = isFrozen;
+    }
 }

+ 21 - 10
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/AdDto.java

@@ -16,8 +16,11 @@ public class AdDto {
     @NotEmpty(message = "广告名称不能为空!")
     private String title;
 
-    @NotNull(message = "位置不能为空!")
-    private Integer position;
+    @NotEmpty(message = "频道不能为空!")
+    private String channel;
+
+    @NotEmpty(message = "位置不能为空!")
+    private String place;
 
     private Date startTime;
 
@@ -45,14 +48,6 @@ public class AdDto {
         this.title = title;
     }
 
-    public Integer getPosition() {
-        return position;
-    }
-
-    public void setPosition(Integer position) {
-        this.position = position;
-    }
-
     public Date getStartTime() {
         return startTime;
     }
@@ -84,4 +79,20 @@ public class AdDto {
     public void setLink(String link) {
         this.link = link;
     }
+
+    public String getChannel() {
+        return channel;
+    }
+
+    public void setChannel(String channel) {
+        this.channel = channel;
+    }
+
+    public String getPlace() {
+        return place;
+    }
+
+    public void setPlace(String place) {
+        this.place = place;
+    }
 }

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

@@ -0,0 +1,17 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+
+import java.util.Date;
+
+public class CommentOrderDto {
+    private Integer[] ids;
+
+    public Integer[] getIds() {
+        return ids;
+    }
+
+    public void setIds(Integer[] ids) {
+        this.ids = ids;
+    }
+}

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

@@ -17,6 +17,8 @@ public class CourseDataDto {
 
     private String dataType;
 
+    private Integer isSentence;
+
     private Integer isNovice;
 
     private Integer isOriginal;
@@ -156,4 +158,12 @@ public class CourseDataDto {
     public void setMethodContent(String methodContent) {
         this.methodContent = methodContent;
     }
+
+    public Integer getIsSentence() {
+        return isSentence;
+    }
+
+    public void setIsSentence(Integer isSentence) {
+        this.isSentence = isSentence;
+    }
 }

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

@@ -0,0 +1,13 @@
+package com.qxgmat.dto.admin.request;
+
+public class FaqOrderDto {
+    private Integer[] ids;
+
+    public Integer[] getIds() {
+        return ids;
+    }
+
+    public void setIds(Integer[] ids) {
+        this.ids = ids;
+    }
+}

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

@@ -1,7 +1,5 @@
 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.UserOrderRecord;
 import com.qxgmat.dto.admin.extend.CourseDataExtendDto;

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

@@ -0,0 +1,69 @@
+package com.qxgmat.dto.extend;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.Comment;
+
+import java.util.Date;
+
+@Dto(entity = Comment.class)
+public class CommentExtendDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private String nickname;
+
+    private String avatar;
+
+    private String content;
+
+    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 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 getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+}

+ 22 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/CourseDataExtendDto.java

@@ -3,12 +3,18 @@ package com.qxgmat.dto.extend;
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.CourseData;
 
+import java.util.Date;
+
 @Dto(entity = CourseData.class)
 public class CourseDataExtendDto {
     private Integer id;
 
     private String title;
 
+    private String resource;
+
+    private Date latestTime;
+
     public Integer getId() {
         return id;
     }
@@ -24,4 +30,20 @@ public class CourseDataExtendDto {
     public void setTitle(String title) {
         this.title = title;
     }
+
+    public String getResource() {
+        return resource;
+    }
+
+    public void setResource(String resource) {
+        this.resource = resource;
+    }
+
+    public Date getLatestTime() {
+        return latestTime;
+    }
+
+    public void setLatestTime(Date latestTime) {
+        this.latestTime = latestTime;
+    }
 }

+ 80 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/CourseNoExtendDto.java

@@ -0,0 +1,80 @@
+package com.qxgmat.dto.extend;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.Course;
+import com.qxgmat.data.dao.entity.CourseNo;
+
+import java.math.BigDecimal;
+
+@Dto(entity = CourseNo.class)
+public class CourseNoExtendDto {
+    private Integer id;
+
+    private Integer courseId;
+
+    private Integer no;
+
+    private String title;
+
+    private Integer askNumber;
+
+    private Integer answerNumber;
+
+    private Boolean note;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Integer courseId) {
+        this.courseId = courseId;
+    }
+
+    public Integer getNo() {
+        return no;
+    }
+
+    public void setNo(Integer no) {
+        this.no = no;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Integer getAskNumber() {
+        return askNumber;
+    }
+
+    public void setAskNumber(Integer askNumber) {
+        this.askNumber = askNumber;
+    }
+
+    public Integer getAnswerNumber() {
+        return answerNumber;
+    }
+
+    public void setAnswerNumber(Integer answerNumber) {
+        this.answerNumber = answerNumber;
+    }
+
+    public Boolean getNote() {
+        return note;
+    }
+
+    public void setNote(Boolean note) {
+        this.note = note;
+    }
+}

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

@@ -0,0 +1,69 @@
+package com.qxgmat.dto.extend;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.Faq;
+
+import java.util.Date;
+
+@Dto(entity = Faq.class)
+public class FaqExtendDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private Date answerTime;
+
+    private Date createTime;
+
+    private String content;
+
+    private String answer;
+
+    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 Date getAnswerTime() {
+        return answerTime;
+    }
+
+    public void setAnswerTime(Date answerTime) {
+        this.answerTime = answerTime;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+}

+ 74 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserOrderRecordExtendDto.java

@@ -0,0 +1,74 @@
+package com.qxgmat.dto.extend;
+
+
+public class UserOrderRecordExtendDto {
+    private String productType;
+
+    private String productId;
+
+    private String service;
+
+    private String param;
+
+    private String number;
+
+    private String originMoney;
+
+    private String money;
+
+    public String getProductType() {
+        return productType;
+    }
+
+    public void setProductType(String productType) {
+        this.productType = productType;
+    }
+
+    public String getProductId() {
+        return productId;
+    }
+
+    public void setProductId(String productId) {
+        this.productId = productId;
+    }
+
+    public String getService() {
+        return service;
+    }
+
+    public void setService(String service) {
+        this.service = service;
+    }
+
+    public String getParam() {
+        return param;
+    }
+
+    public void setParam(String param) {
+        this.param = param;
+    }
+
+    public String getNumber() {
+        return number;
+    }
+
+    public void setNumber(String number) {
+        this.number = number;
+    }
+
+    public String getOriginMoney() {
+        return originMoney;
+    }
+
+    public void setOriginMoney(String originMoney) {
+        this.originMoney = originMoney;
+    }
+
+    public String getMoney() {
+        return money;
+    }
+
+    public void setMoney(String money) {
+        this.money = money;
+    }
+}

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserPaperBaseExtendDto.java

@@ -21,6 +21,8 @@ public class UserPaperBaseExtendDto {
 
     private Date latestTime;
 
+    private UserReportExtendDto report;
+
     public Integer getId() {
         return id;
     }
@@ -68,4 +70,12 @@ public class UserPaperBaseExtendDto {
     public void setTitle(String title) {
         this.title = title;
     }
+
+    public UserReportExtendDto getReport() {
+        return report;
+    }
+
+    public void setReport(UserReportExtendDto report) {
+        this.report = report;
+    }
 }

+ 14 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/CourseRestoreDto.java

@@ -0,0 +1,14 @@
+package com.qxgmat.dto.request;
+
+
+public class CourseRestoreDto {
+    private Integer recordId;
+
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+}

+ 14 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/CourseSuspendDto.java

@@ -0,0 +1,14 @@
+package com.qxgmat.dto.request;
+
+
+public class CourseSuspendDto {
+    private Integer recordId;
+
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+}

+ 33 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDataDetailDto.java

@@ -0,0 +1,33 @@
+package com.qxgmat.dto.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.CourseData;
+import com.qxgmat.dto.extend.CommentExtendDto;
+import com.qxgmat.dto.extend.FaqExtendDto;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+@Dto(entity = CourseData.class)
+public class CourseDataDetailDto extends CourseData {
+
+    private Collection<CommentExtendDto> comments;
+
+    private Collection<FaqExtendDto> faqs;
+
+    public Collection<CommentExtendDto> getComments() {
+        return comments;
+    }
+
+    public void setComments(Collection<CommentExtendDto> comments) {
+        this.comments = comments;
+    }
+
+    public Collection<FaqExtendDto> getFaqs() {
+        return faqs;
+    }
+
+    public void setFaqs(Collection<FaqExtendDto> faqs) {
+        this.faqs = faqs;
+    }
+}

+ 12 - 10
server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDataListDto.java

@@ -2,8 +2,10 @@ package com.qxgmat.dto.response;
 
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.CourseData;
+import com.qxgmat.dto.extend.CommentExtendDto;
 
 import java.math.BigDecimal;
+import java.util.Collection;
 
 @Dto(entity = CourseData.class)
 public class CourseDataListDto {
@@ -11,8 +13,6 @@ public class CourseDataListDto {
 
     private String title;
 
-    private String comment;
-
     private Integer structId;
 
     private Integer parentStructId;
@@ -35,6 +35,8 @@ public class CourseDataListDto {
 
     private Integer saleNumber;
 
+    private Collection<CommentExtendDto> comments;
+
     public Integer getId() {
         return id;
     }
@@ -51,14 +53,6 @@ public class CourseDataListDto {
         this.title = title;
     }
 
-    public String getComment() {
-        return comment;
-    }
-
-    public void setComment(String comment) {
-        this.comment = comment;
-    }
-
     public Integer getStructId() {
         return structId;
     }
@@ -146,4 +140,12 @@ public class CourseDataListDto {
     public void setSaleNumber(Integer saleNumber) {
         this.saleNumber = saleNumber;
     }
+
+    public Collection<CommentExtendDto> getComments() {
+        return comments;
+    }
+
+    public void setComments(Collection<CommentExtendDto> comments) {
+        this.comments = comments;
+    }
 }

+ 192 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDetailDto.java

@@ -0,0 +1,192 @@
+package com.qxgmat.dto.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.Course;
+import com.qxgmat.dto.extend.CommentExtendDto;
+import com.qxgmat.dto.extend.FaqExtendDto;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+@Dto(entity = Course.class)
+public class CourseDetailDto extends Course {
+    private Integer id;
+
+    private Integer structId;
+
+    private Integer parentStructId;
+
+    private String courseModule;
+
+    private String vsType;
+
+    private String videoType;
+
+    private String extend;
+
+    private String title;
+
+    private String comment;
+
+    private String crowd;
+
+    private BigDecimal price;
+
+    private String teacher;
+
+    private String cover;
+
+    private Integer expireDays;
+
+    private Integer expireTime;
+
+    private Integer useExpireTime;
+
+    private Collection<CommentExtendDto> comments;
+
+    private Collection<FaqExtendDto> faqs;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getStructId() {
+        return structId;
+    }
+
+    public void setStructId(Integer structId) {
+        this.structId = structId;
+    }
+
+    public Integer getParentStructId() {
+        return parentStructId;
+    }
+
+    public void setParentStructId(Integer parentStructId) {
+        this.parentStructId = parentStructId;
+    }
+
+    public String getCourseModule() {
+        return courseModule;
+    }
+
+    public void setCourseModule(String courseModule) {
+        this.courseModule = courseModule;
+    }
+
+    public String getVsType() {
+        return vsType;
+    }
+
+    public void setVsType(String vsType) {
+        this.vsType = vsType;
+    }
+
+    public String getVideoType() {
+        return videoType;
+    }
+
+    public void setVideoType(String videoType) {
+        this.videoType = videoType;
+    }
+
+    public String getExtend() {
+        return extend;
+    }
+
+    public void setExtend(String extend) {
+        this.extend = extend;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getCrowd() {
+        return crowd;
+    }
+
+    public void setCrowd(String crowd) {
+        this.crowd = crowd;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public String getTeacher() {
+        return teacher;
+    }
+
+    public void setTeacher(String teacher) {
+        this.teacher = teacher;
+    }
+
+    public Integer getExpireDays() {
+        return expireDays;
+    }
+
+    public void setExpireDays(Integer expireDays) {
+        this.expireDays = expireDays;
+    }
+
+    public Integer getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(Integer expireTime) {
+        this.expireTime = expireTime;
+    }
+
+    public Integer getUseExpireTime() {
+        return useExpireTime;
+    }
+
+    public void setUseExpireTime(Integer useExpireTime) {
+        this.useExpireTime = useExpireTime;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public String getCover() {
+        return cover;
+    }
+
+    public void setCover(String cover) {
+        this.cover = cover;
+    }
+
+    public Collection<CommentExtendDto> getComments() {
+        return comments;
+    }
+
+    public void setComments(Collection<CommentExtendDto> comments) {
+        this.comments = comments;
+    }
+
+    public Collection<FaqExtendDto> getFaqs() {
+        return faqs;
+    }
+
+    public void setFaqs(Collection<FaqExtendDto> faqs) {
+        this.faqs = faqs;
+    }
+}

+ 12 - 10
server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseListDto.java

@@ -2,8 +2,10 @@ package com.qxgmat.dto.response;
 
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.Course;
+import com.qxgmat.dto.extend.CommentExtendDto;
 
 import java.math.BigDecimal;
+import java.util.Collection;
 
 @Dto(entity = Course.class)
 public class CourseListDto {
@@ -23,8 +25,6 @@ public class CourseListDto {
 
     private String title;
 
-    private String comment;
-
     private String crowd;
 
     private BigDecimal price;
@@ -39,6 +39,8 @@ public class CourseListDto {
 
     private Integer useExpireTime;
 
+    private Collection<CommentExtendDto> comments;
+
     public Integer getId() {
         return id;
     }
@@ -151,14 +153,6 @@ public class CourseListDto {
         this.useExpireTime = useExpireTime;
     }
 
-    public String getComment() {
-        return comment;
-    }
-
-    public void setComment(String comment) {
-        this.comment = comment;
-    }
-
     public String getCover() {
         return cover;
     }
@@ -166,4 +160,12 @@ public class CourseListDto {
     public void setCover(String cover) {
         this.cover = cover;
     }
+
+    public Collection<CommentExtendDto> getComments() {
+        return comments;
+    }
+
+    public void setComments(Collection<CommentExtendDto> comments) {
+        this.comments = comments;
+    }
 }

+ 22 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/CoursePackageDetailDto.java

@@ -3,7 +3,9 @@ package com.qxgmat.dto.response;
 import com.alibaba.fastjson.JSONObject;
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.CoursePackage;
+import com.qxgmat.dto.extend.CommentExtendDto;
 import com.qxgmat.dto.extend.CourseExtendDto;
+import com.qxgmat.dto.extend.FaqExtendDto;
 
 import java.math.BigDecimal;
 import java.util.Collection;
@@ -23,6 +25,10 @@ public class CoursePackageDetailDto {
 
     private Collection<CourseExtendDto> courses;
 
+    private Collection<CommentExtendDto> comments;
+
+    private Collection<FaqExtendDto> faqs;
+
     private JSONObject gift;
 
     private Integer saleNumber;
@@ -110,4 +116,20 @@ public class CoursePackageDetailDto {
     public void setCourses(Collection<CourseExtendDto> courses) {
         this.courses = courses;
     }
+
+    public Collection<CommentExtendDto> getComments() {
+        return comments;
+    }
+
+    public void setComments(Collection<CommentExtendDto> comments) {
+        this.comments = comments;
+    }
+
+    public Collection<FaqExtendDto> getFaqs() {
+        return faqs;
+    }
+
+    public void setFaqs(Collection<FaqExtendDto> faqs) {
+        this.faqs = faqs;
+    }
 }

+ 11 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/CoursePackageListDto.java

@@ -3,6 +3,7 @@ package com.qxgmat.dto.response;
 import com.alibaba.fastjson.JSONObject;
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.CoursePackage;
+import com.qxgmat.dto.extend.CommentExtendDto;
 import com.qxgmat.dto.extend.CourseExtendDto;
 
 import java.math.BigDecimal;
@@ -23,6 +24,8 @@ public class CoursePackageListDto {
 
     private Collection<CourseExtendDto> courses;
 
+    private Collection<CommentExtendDto> comments;
+
     private JSONObject gift;
 
     private Integer saleNumber;
@@ -110,4 +113,12 @@ public class CoursePackageListDto {
     public void setCourses(Collection<CourseExtendDto> courses) {
         this.courses = courses;
     }
+
+    public Collection<CommentExtendDto> getComments() {
+        return comments;
+    }
+
+    public void setComments(Collection<CommentExtendDto> comments) {
+        this.comments = comments;
+    }
 }

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

@@ -2,9 +2,9 @@ package com.qxgmat.dto.response;
 
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.UserOrderRecord;
-import com.qxgmat.dto.extend.CourseExtendDto;
-import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
+import com.qxgmat.dto.extend.*;
 
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 
@@ -16,6 +16,12 @@ public class UserCourseDetailDto {
 
     private CourseExtendDto course;
 
+    private Integer vsNo;
+
+    private Integer teacherId;
+
+    private CourseTeacherExtendDto teacher;
+
     private Date startTime;
 
     private Date endTime;
@@ -34,7 +40,19 @@ public class UserCourseDetailDto {
 
     private Date restoreTime;
 
-    private List<UserPaperBaseExtendDto> papers;
+    private Collection<CourseNoExtendDto> courseNos;
+
+    private Integer currentNo;
+
+    private Collection<UserCourseAppointmentExtendDto> appointments;
+
+    private Integer askNumber;
+
+    private Integer answerNumber;
+
+    private Integer noteNumber;
+
+    private Collection<UserPaperBaseExtendDto> papers;
 
     public Date getStartTime() {
         return startTime;
@@ -44,14 +62,6 @@ public class UserCourseDetailDto {
         this.startTime = startTime;
     }
 
-    public List<UserPaperBaseExtendDto> getPapers() {
-        return papers;
-    }
-
-    public void setPapers(List<UserPaperBaseExtendDto> papers) {
-        this.papers = papers;
-    }
-
     public Integer getId() {
         return id;
     }
@@ -139,4 +149,84 @@ public class UserCourseDetailDto {
     public void setIsUsed(Integer isUsed) {
         this.isUsed = isUsed;
     }
+
+    public Collection<CourseNoExtendDto> getCourseNos() {
+        return courseNos;
+    }
+
+    public void setCourseNos(Collection<CourseNoExtendDto> courseNos) {
+        this.courseNos = courseNos;
+    }
+
+    public Integer getAskNumber() {
+        return askNumber;
+    }
+
+    public void setAskNumber(Integer askNumber) {
+        this.askNumber = askNumber;
+    }
+
+    public Integer getAnswerNumber() {
+        return answerNumber;
+    }
+
+    public void setAnswerNumber(Integer answerNumber) {
+        this.answerNumber = answerNumber;
+    }
+
+    public Integer getNoteNumber() {
+        return noteNumber;
+    }
+
+    public void setNoteNumber(Integer noteNumber) {
+        this.noteNumber = noteNumber;
+    }
+
+    public Collection<UserCourseAppointmentExtendDto> getAppointments() {
+        return appointments;
+    }
+
+    public void setAppointments(Collection<UserCourseAppointmentExtendDto> appointments) {
+        this.appointments = appointments;
+    }
+
+    public Collection<UserPaperBaseExtendDto> getPapers() {
+        return papers;
+    }
+
+    public void setPapers(Collection<UserPaperBaseExtendDto> papers) {
+        this.papers = papers;
+    }
+
+    public CourseTeacherExtendDto getTeacher() {
+        return teacher;
+    }
+
+    public void setTeacher(CourseTeacherExtendDto teacher) {
+        this.teacher = teacher;
+    }
+
+    public Integer getVsNo() {
+        return vsNo;
+    }
+
+    public void setVsNo(Integer vsNo) {
+        this.vsNo = vsNo;
+    }
+
+    public Integer getTeacherId() {
+        return teacherId;
+    }
+
+    public void setTeacherId(Integer teacherId) {
+        this.teacherId = teacherId;
+    }
+
+    public Integer getCurrentNo() {
+        return currentNo;
+    }
+
+    public void setCurrentNo(Integer currentNo) {
+        this.currentNo = currentNo;
+    }
 }

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

@@ -6,6 +6,7 @@ import com.qxgmat.dto.extend.CourseExtendDto;
 import com.qxgmat.dto.extend.CourseTeacherExtendDto;
 import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
 
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 
@@ -43,7 +44,7 @@ public class UserCourseProgressDto {
 
     private Date restoreTime;
 
-    private List<UserPaperBaseExtendDto> papers;
+    private Collection<UserPaperBaseExtendDto> papers;
 
     public Date getStartTime() {
         return startTime;
@@ -53,11 +54,11 @@ public class UserCourseProgressDto {
         this.startTime = startTime;
     }
 
-    public List<UserPaperBaseExtendDto> getPapers() {
+    public Collection<UserPaperBaseExtendDto> getPapers() {
         return papers;
     }
 
-    public void setPapers(List<UserPaperBaseExtendDto> papers) {
+    public void setPapers(Collection<UserPaperBaseExtendDto> papers) {
         this.papers = papers;
     }
 

+ 26 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseTimeDto.java

@@ -0,0 +1,26 @@
+package com.qxgmat.dto.response;
+
+
+import java.util.Date;
+
+public class UserCourseTimeDto {
+    private Date day;
+
+    private String type; // preview, course, stop
+
+    public Date getDay() {
+        return day;
+    }
+
+    public void setDay(Date day) {
+        this.day = day;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+}

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

@@ -6,6 +6,7 @@ import com.qxgmat.data.dao.entity.UserOrderCheckout;
 import com.qxgmat.dto.extend.CourseDataExtendDto;
 import com.qxgmat.dto.extend.CourseExtendDto;
 import com.qxgmat.dto.extend.CoursePackageExtendDto;
+import com.qxgmat.dto.extend.UserOrderRecordExtendDto;
 
 import java.math.BigDecimal;
 import java.util.List;
@@ -19,7 +20,7 @@ public class UserOrderPreDto {
 
     private JSONArray gift;
 
-    private List<UserOrderCheckout> checkouts;
+    private List<UserOrderRecordExtendDto> checkouts;
 
     private List<CourseExtendDto> courses;
 
@@ -51,11 +52,11 @@ public class UserOrderPreDto {
         this.promote = promote;
     }
 
-    public List<UserOrderCheckout> getCheckouts() {
+    public List<UserOrderRecordExtendDto> getCheckouts() {
         return checkouts;
     }
 
-    public void setCheckouts(List<UserOrderCheckout> checkouts) {
+    public void setCheckouts(List<UserOrderRecordExtendDto> checkouts) {
         this.checkouts = checkouts;
     }
 

+ 242 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderRecordListDto.java

@@ -0,0 +1,242 @@
+package com.qxgmat.dto.response;
+
+import com.alibaba.fastjson.JSONObject;
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserOrderRecord;
+import com.qxgmat.dto.extend.CourseDataExtendDto;
+import com.qxgmat.dto.extend.CourseExtendDto;
+
+import java.util.Date;
+
+@Dto(entity = UserOrderRecord.class)
+public class UserOrderRecordListDto {
+    private Integer id;
+
+    private Integer orderId;
+
+    private Integer parentId;
+
+    private String productType;
+
+    private Integer productId;
+
+    private CourseExtendDto course;
+
+    private CourseDataExtendDto data;
+
+    private String service;
+
+    private String param;
+
+    private JSONObject serviceInfo;
+
+    private String source;
+
+    private Integer number;
+
+    private Integer vsNo;
+
+    private Integer askTime;
+
+    private Date startTime;
+
+    private Date endTime;
+
+    private Date useStartTime;
+
+    private Date useEndTime;
+
+    private Integer isUsed;
+
+    private Date useTime;
+
+    private Integer isStop;
+
+    private Date stopTime;
+
+    private Date createTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getOrderId() {
+        return orderId;
+    }
+
+    public void setOrderId(Integer orderId) {
+        this.orderId = orderId;
+    }
+
+    public Integer getParentId() {
+        return parentId;
+    }
+
+    public void setParentId(Integer parentId) {
+        this.parentId = parentId;
+    }
+
+    public String getProductType() {
+        return productType;
+    }
+
+    public void setProductType(String productType) {
+        this.productType = productType;
+    }
+
+    public Integer getProductId() {
+        return productId;
+    }
+
+    public void setProductId(Integer productId) {
+        this.productId = productId;
+    }
+
+    public CourseExtendDto getCourse() {
+        return course;
+    }
+
+    public void setCourse(CourseExtendDto course) {
+        this.course = course;
+    }
+
+    public CourseDataExtendDto getData() {
+        return data;
+    }
+
+    public void setData(CourseDataExtendDto data) {
+        this.data = data;
+    }
+
+    public String getService() {
+        return service;
+    }
+
+    public void setService(String service) {
+        this.service = service;
+    }
+
+    public String getParam() {
+        return param;
+    }
+
+    public void setParam(String param) {
+        this.param = param;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public Integer getVsNo() {
+        return vsNo;
+    }
+
+    public void setVsNo(Integer vsNo) {
+        this.vsNo = vsNo;
+    }
+
+    public Integer getAskTime() {
+        return askTime;
+    }
+
+    public void setAskTime(Integer askTime) {
+        this.askTime = askTime;
+    }
+
+    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 Date getUseStartTime() {
+        return useStartTime;
+    }
+
+    public void setUseStartTime(Date useStartTime) {
+        this.useStartTime = useStartTime;
+    }
+
+    public Date getUseEndTime() {
+        return useEndTime;
+    }
+
+    public void setUseEndTime(Date useEndTime) {
+        this.useEndTime = useEndTime;
+    }
+
+    public Integer getIsUsed() {
+        return isUsed;
+    }
+
+    public void setIsUsed(Integer isUsed) {
+        this.isUsed = isUsed;
+    }
+
+    public Date getUseTime() {
+        return useTime;
+    }
+
+    public void setUseTime(Date useTime) {
+        this.useTime = useTime;
+    }
+
+    public Integer getIsStop() {
+        return isStop;
+    }
+
+    public void setIsStop(Integer isStop) {
+        this.isStop = isStop;
+    }
+
+    public Date getStopTime() {
+        return stopTime;
+    }
+
+    public void setStopTime(Date stopTime) {
+        this.stopTime = stopTime;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public JSONObject getServiceInfo() {
+        return serviceInfo;
+    }
+
+    public void setServiceInfo(JSONObject serviceInfo) {
+        this.serviceInfo = serviceInfo;
+    }
+}

+ 22 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentenceInfoDto.java

@@ -1,7 +1,9 @@
 package com.qxgmat.dto.response;
 
 import com.alibaba.fastjson.JSONArray;
+import com.qxgmat.dto.extend.CommentExtendDto;
 
+import java.util.Collection;
 import java.util.Map;
 
 public class UserSentenceInfoDto {
@@ -11,6 +13,10 @@ public class UserSentenceInfoDto {
 
     private String code;
 
+    private Integer dataId;
+
+    private Collection<CommentExtendDto> comments;
+
     public String getCode() {
         return code;
     }
@@ -34,4 +40,20 @@ public class UserSentenceInfoDto {
     public void setTrailPages(Number trailPages) {
         this.trailPages = trailPages;
     }
+
+    public Integer getDataId() {
+        return dataId;
+    }
+
+    public void setDataId(Integer dataId) {
+        this.dataId = dataId;
+    }
+
+    public Collection<CommentExtendDto> getComments() {
+        return comments;
+    }
+
+    public void setComments(Collection<CommentExtendDto> comments) {
+        this.comments = comments;
+    }
 }

+ 90 - 10
server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java

@@ -2,21 +2,18 @@ package com.qxgmat.service.extend;
 
 import com.nuliji.tools.Tools;
 import com.nuliji.tools.Transform;
+import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.QuestionSubject;
 import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.module.VideoCourseType;
-import com.qxgmat.data.dao.entity.Course;
-import com.qxgmat.data.dao.entity.UserCourse;
-import com.qxgmat.data.dao.entity.UserOrderRecord;
+import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.UserRecordStatRelation;
-import com.qxgmat.service.inline.CourseService;
-import com.qxgmat.service.inline.UserCourseRecordService;
-import com.qxgmat.service.inline.UserCourseService;
-import com.qxgmat.service.inline.UserOrderRecordService;
+import com.qxgmat.service.inline.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.Resource;
 import java.util.*;
 
 @Service
@@ -34,6 +31,9 @@ public class CourseExtendService {
     @Autowired
     private UserCourseRecordService userCourseRecordService;
 
+    @Resource
+    private UserCourseProgressService userCourseProgressService;
+
     /**
      * 计算vs课程有效期
      * @param vsNumber
@@ -54,12 +54,50 @@ public class CourseExtendService {
         return course.getExpireDays();
     }
 
-    public void suspendCourse(Integer userId, Integer courseId){
-
+    public void suspendCourse(Integer userId, Integer recordId){
+        UserOrderRecord record = userOrderRecordService.get(recordId);
+        if (record == null){
+            throw new ParameterException("记录不存在");
+        }
+        if (!record.getUserId().equals(userId)){
+            throw new ParameterException("记录不存在");
+        }
+        if (record.getIsUsed() == 0){
+            throw new ParameterException("课程还未开通");
+        }
+        if (record.getIsSuspend() > 0){
+            throw new ParameterException("已停课1次");
+        }
+        userOrderRecordService.edit(UserOrderRecord.builder()
+                .id(record.getId())
+                .isSuspend(1)
+                .suspendTime(new Date())
+                .build());
     }
 
-    public void restoreCourse(Integer userId, Integer courseId){
+    public void restoreCourse(Integer userId, Integer recordId){
+        UserOrderRecord record = userOrderRecordService.get(recordId);
+        if (record == null){
+            throw new ParameterException("记录不存在");
+        }
+        if (!record.getUserId().equals(userId)){
+            throw new ParameterException("记录不存在");
+        }
+        if (record.getIsUsed() == 0){
+            throw new ParameterException("课程还未开通");
+        }
+        if (record.getIsSuspend() == 0){
+            throw new ParameterException("无需恢复");
+        }
+        if (record.getRestoreTime() != null){
+            throw new ParameterException("已恢复");
+        }
 
+        userOrderRecordService.edit(UserOrderRecord.builder()
+                .id(record.getId())
+                .restoreTime(new Date())
+                .useEndTime(Tools.addDate(record.getUseEndTime(), (int)(new Date().getTime() - record.getSuspendTime().getTime() / 86400000)))
+                .build());
     }
 
     /**
@@ -112,4 +150,46 @@ public class CourseExtendService {
         }
         return minId;
     }
+
+    /**
+     * 获取已完成课时序号
+     * @param courseNoList
+     * @param progressList
+     * @return
+     */
+    public int computeCourseNoFinish(Collection<CourseNo> courseNoList, Collection<UserCourseProgress> progressList){
+        Map progressMap = Transform.getMap(progressList, UserCourseProgress.class, "courseNoId");
+
+        int min = 0;
+        for(CourseNo no : courseNoList){
+            UserCourseProgress progress = (UserCourseProgress)progressMap.get(no.getId());
+            if(progress == null) continue;
+            if (min != 0 && min > no.getNo()) continue;
+            if (progress.getProgress() > 50) {
+                min = no.getNo();
+            }
+        }
+        return min;
+    }
+
+    /**
+     * 获取当前课时
+     * @param courseNoList
+     * @param progressList
+     * @return
+     */
+    public int computeCourseNoCurrent(Collection<CourseNo> courseNoList, Collection<UserCourseProgress> progressList){
+        Map progressMap = Transform.getMap(progressList, UserCourseProgress.class, "courseNoId");
+
+        int min = 0;
+        for(CourseNo no : courseNoList){
+            // 如果进度不过半,按当前课时+下一课时
+            // 如果进度过半,下2次课时
+            UserCourseProgress progress = (UserCourseProgress)progressMap.get(no.getId());
+            if(progress == null) continue;
+            if (min != 0 && min > no.getNo()) continue;
+            min = no.getNo();
+        }
+        return min;
+    }
 }

+ 23 - 1
server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java

@@ -5,19 +5,24 @@ import com.nuliji.tools.MessageHelp;
 import com.nuliji.tools.exception.SystemException;
 import com.qxgmat.data.constants.enums.user.MessageType;
 import com.qxgmat.data.dao.entity.UserMessage;
+import com.qxgmat.help.MailHelp;
 import com.qxgmat.service.inline.MessageService;
 import com.qxgmat.service.inline.UserMessageService;
+import org.apache.logging.log4j.util.Strings;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.util.Arrays;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 @Service
 public class MessageExtendService {
 
     @Resource
-    private MessageHelp messageHelp;
+    private MailHelp mailHelp;
 
     @Resource
     private UserMessageService userMessageService;
@@ -49,6 +54,23 @@ public class MessageExtendService {
         }
     }
 
+    /**
+     * 发送邀请邮件
+     * @param emails
+     */
+    public void sendInviteEmail(String[] emails, String code){
+        Map<String, String> map = new HashMap<>();
+        mailHelp.sendBaseMail(
+                Strings.join(Arrays.stream(emails).collect(Collectors.toList()), ';'),
+                "邮件邀请",
+                "",
+                map);
+    }
+
+    public void sendTextbookUpdate(){
+
+    }
+
     private String replaceBody(String body, Map<String, String> params){
         for(String key :params.keySet()){
             body = body.replaceAll("{"+key+"}", params.get(key));

+ 50 - 1
server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java

@@ -411,6 +411,52 @@ public class OrderFlowService {
             record.setIsUsed(1);
             Date time = new Date();
             record.setUseTime(time);
+            // 处理套餐赠送
+            CoursePackage coursePackage = coursePackageService.get(record.getProductId());
+
+            InitRecord callback = initRecordCallback.get(ProductType.SERVICE);
+            JSONObject gift = coursePackage.getGift();
+            Integer qxCat = gift.getInteger(ServiceKey.QX_CAT.key);
+            if (qxCat != null && qxCat > 0){
+                // 添加cat
+                UserOrderRecord giftRecord = UserOrderRecord.builder()
+                        .orderId(order.getId())
+                        .userId(order.getUserId())
+                        .productType(ProductType.SERVICE.key)
+                        .service(ServiceKey.QX_CAT.key)
+                        .source(RecordSource.GIFT_COURSE.key)
+                        .build();
+                callback.callback(order, giftRecord);
+                userOrderRecordService.add(giftRecord);
+            }
+            Integer textbook = gift.getInteger(ServiceKey.TEXTBOOK.key);
+            if (textbook != null && textbook > 0){
+                // 添加text
+                UserOrderRecord giftRecord = UserOrderRecord.builder()
+                        .orderId(order.getId())
+                        .userId(order.getUserId())
+                        .productType(ProductType.SERVICE.key)
+                        .service(ServiceKey.TEXTBOOK.key)
+                        .source(RecordSource.GIFT_COURSE.key)
+                        .build();
+                callback.callback(order, giftRecord);
+                userOrderRecordService.add(giftRecord);
+            }
+            String vip = gift.getString(ServiceKey.VIP.key);
+            ServiceVipKey vipKey = ServiceVipKey.ValueOf(vip);
+            if (vipKey != null){
+                // 添加vip,并开通
+                UserOrderRecord giftRecord = UserOrderRecord.builder()
+                        .orderId(order.getId())
+                        .userId(order.getUserId())
+                        .productType(ProductType.SERVICE.key)
+                        .service(ServiceKey.VIP.key)
+                        .param(vipKey.key)
+                        .source(RecordSource.GIFT_COURSE.key)
+                        .build();
+                callback.callback(order, giftRecord);
+                userOrderRecordService.add(giftRecord);
+            }
             return record;
         }));
         initRecordCallback.put(ProductType.DATA, ((order, record)->{
@@ -449,7 +495,10 @@ public class OrderFlowService {
 
             Course course = courseService.get(record.getProductId());
             Integer expireDay = 0;
-            if (course.getCourseModule().equals(CourseModule.VS.key)){
+            if (course.getCourseModule().equals(CourseModule.ONLINE.key)){
+                // 小班课程,无需更新有效时间
+                return record;
+            }else if (course.getCourseModule().equals(CourseModule.VS.key)){
                 // 根据课时数进行计算
                 expireDay = courseExtendService.computeExpire(record.getNumber(), course);
             }else{

+ 9 - 20
server/gateway-api/src/main/java/com/qxgmat/service/extend/PreviewService.java

@@ -51,6 +51,9 @@ public class PreviewService extends AbstractService {
     @Resource
     private UserCourseService userCourseService;
 
+    @Resource
+    private CourseExtendService courseExtendService;
+
     /**
      * 获取用户分组作业列表
      * @param userId
@@ -108,27 +111,13 @@ public class PreviewService extends AbstractService {
                     size = 2;
                     page = 1;
                     List<CourseNo> courseNoList = courseNoService.allCourse(course.getId());
+                    List<UserCourseProgress> progressList = userCourseProgressService.listCourse(record.getId(), course.getId());
                     // 查询课时进度
-                    List<UserCourseProgress> progressList =userCourseProgressService.listCourse(record.getId(), course.getId());
-                    Map progressMap = Transform.getMap(progressList, UserCourseProgress.class, "courseNoId");
-
-                    Collections.reverse(courseNoList);
-                    int min = 1;
-                    for(CourseNo no : courseNoList){
-                        // 如果进度不过半,按当前课时+下一课时
-                        // 如果进度过半,下2次课时
-                        UserCourseProgress progress = (UserCourseProgress)progressMap.get(no.getId());
-                        if(progress == null) continue;
-                        if (progress.getProgress() > 50) {
-                            min = no.getNo() + 1;
-                        }else{
-                            min = no.getNo();
-                        }
-                        break;
-                    }
-                    int max = min+size -1;
-                    int finalMin = min;
-                    List<CourseNo> select = courseNoList.stream().filter(row->row.getNo() >= finalMin && row.getNo()<=max).collect(Collectors.toList());
+                    // 如果进度不过半,按当前课时+下一课时
+                    // 如果进度过半,下2次课时
+                    int min = courseExtendService.computeCourseNoFinish(courseNoList, progressList);
+                    int max = min+size;
+                    List<CourseNo> select = courseNoList.stream().filter(row->row.getNo() > min && row.getNo()<= max).collect(Collectors.toList());
                     previewAssignList = previewAssignService.listByCourseNos(course.getId(), getIds(select, CourseNo.class, "id"));
                 }else{
                     previewAssignList = previewAssignService.listByCourse(page, size, course.getId(), userId, times);

+ 36 - 2
server/gateway-api/src/main/java/com/qxgmat/service/inline/AdService.java

@@ -11,6 +11,7 @@ import com.qxgmat.data.dao.entity.Ad;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.util.Collection;
@@ -24,7 +25,7 @@ public class AdService extends AbstractService {
     @Resource
     private AdMapper adMapper;
 
-    public Page<Ad> list(int page, int size, String channel, String position, String order, DirectionStatus direction){
+    public Page<Ad> listAdmin(int page, int size, String channel, String position, String order, DirectionStatus direction){
         Example example = new Example(Ad.class);
         if (channel != null)
             example.and(
@@ -49,7 +50,7 @@ public class AdService extends AbstractService {
         return select(adMapper, example, page, size);
     }
 
-    public List<Ad> all(){
+    public List<Ad> all(String channel){
         Example example = new Example(Ad.class);
         Date day = new Date();
         example.and(
@@ -62,10 +63,43 @@ public class AdService extends AbstractService {
                         .orLessThan("endTime", day)
                         .orIsNull("endTime")
         );
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("channel", channel)
+        );
         example.orderBy("position").asc();
         return select(adMapper, example);
     }
 
+    public Ad getAd(String channel, String place){
+        Example example = new Example(Ad.class);
+        Date day = new Date();
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("channel", channel)
+                        .andEqualTo("place", place)
+        );
+        return one(adMapper, example);
+    }
+
+    @Transactional
+    public Ad addAd(Ad entity){
+        Ad in = getAd(entity.getChannel(), entity.getPlace());
+        if (in != null){
+            throw new ParameterException("广告位已设置");
+        }
+        return add(entity);
+    }
+
+    @Transactional
+    public Ad editAd(Ad entity){
+        Ad in = getAd(entity.getChannel(), entity.getPlace());
+        if (in != null && !in.getId().equals(entity.getId())){
+            throw new ParameterException("广告位已设置");
+        }
+        return edit(entity);
+    }
+
     public Ad add(Ad ad){
         int result = insert(adMapper, ad);
         ad = one(adMapper, ad.getId());

+ 41 - 2
server/gateway-api/src/main/java/com/qxgmat/service/inline/CommentService.java

@@ -15,10 +15,10 @@ import com.qxgmat.data.relation.CommentRelationMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
 
 @Service
 public class CommentService extends AbstractService {
@@ -65,9 +65,48 @@ public class CommentService extends AbstractService {
                 example.createCriteria()
                     .orEqualTo("isShow", 1)
         );
+        example.orderBy("order").desc();
         return select(commentMapper, example, page, size);
     }
 
+    public Map<Object, Collection<Comment>> groupByPosition(String channel, Collection positions, Integer top){
+        Map<Object, Collection<Comment>> result = new HashMap<>();
+        List<Comment> commentList = commentRelationMapper.groupByPosition(channel, positions, top);
+        Collection<Comment> tmp;
+        for(Comment comment : commentList){
+            if (!result.containsKey(comment.getPosition())){
+                tmp = new ArrayList<>();
+                result.put(comment.getPosition(), tmp);
+            }else{
+                tmp = result.get(comment.getPosition());
+            }
+            tmp.add(comment);
+        }
+        return result;
+    }
+
+    /**
+     * 根据列表顺序全部更新排序:从大到小
+     * @param ids
+     */
+    @Transactional
+    public void updateOrder(Integer[] ids){
+        int order = ids.length;
+        List<Comment> commentList = select(commentMapper, ids);
+        Map commentMap = Transform.getMap(commentList, Comment.class, "id");
+        for (Integer id : ids){
+            Comment comment = (Comment)commentMap.get(id);
+            if (comment == null) continue;
+            if (comment.getOrder() == order) continue;
+            update(commentMapper, Comment.builder()
+                    .id(id)
+                    .order(order)
+                    .build()
+            );
+            order -= 1;
+        }
+    }
+
     public Comment add(Comment comment){
         int result = insert(commentMapper, comment);
         comment = one(commentMapper, comment.getId());

+ 19 - 1
server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseDataService.java

@@ -31,8 +31,14 @@ public class CourseDataService extends AbstractService {
     @Resource
     private CourseDataRelationMapper courseDataRelationMapper;
 
-    public Page<CourseData> listAdmin(int page, int size, Integer structId, DataType dataType, String order, DirectionStatus direction){
+    public Page<CourseData> listAdmin(int page, int size,String keyword, Integer structId, DataType dataType, String order, DirectionStatus direction){
         Example example = new Example(CourseData.class);
+        if(keyword != null){
+            example.and(
+                    example.createCriteria()
+                            .andLike("title", "%"+keyword+"%")
+            );
+        }
         if(structId != null){
             example.and(
                     example.createCriteria()
@@ -124,6 +130,18 @@ public class CourseDataService extends AbstractService {
         courseDataRelationMapper.accumulation(dataId, sale, view);
     }
 
+    /**
+     * 获取长难句资料:添加时根据struct=sentence判断设置
+     * @return
+     */
+    public CourseData getSentence(){
+        Example example = new Example(CourseData.class);
+        example.and(
+                example.createCriteria().andEqualTo("isSentence", 1)
+        );
+        return one(courseDataMapper, example);
+    }
+
     public CourseData add(CourseData courseData){
         int result = insert(courseDataMapper, courseData);
         courseData = one(courseDataMapper, courseData.getId());

+ 28 - 2
server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseNoService.java

@@ -12,8 +12,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
 
 @Service
 public class CourseNoService extends AbstractService {
@@ -34,6 +33,33 @@ public class CourseNoService extends AbstractService {
         return select(courseNoMapper, example);
     }
 
+    /**
+     * 获取课程课时分组列表
+     * @param courseIds
+     * @return
+     */
+    public Map<Object, Collection<CourseNo>> groupByCourseId(Collection courseIds){
+        Map<Object, Collection<CourseNo>> relationMap = new HashMap<>();
+        Example example = new Example(CourseNo.class);
+        example.and(
+                example.createCriteria()
+                        .andIn("courseId", courseIds)
+        );
+        example.setOrderByClause("courseId asc, no asc");
+        List<CourseNo> nos =  select(courseNoMapper, example);
+        Collection<CourseNo> list;
+        for(CourseNo no : nos){
+            if (!relationMap.containsKey(no.getCourseId())){
+                list = new ArrayList<>();
+                relationMap.put(no.getCourseId(), list);
+            }else{
+                list = relationMap.get(no.getCourseId());
+            }
+            list.add(no);
+        }
+        return relationMap;
+    }
+
     public CourseNo addNo(CourseNo course){
         List<CourseNo> nos = allCourse(course.getCourseId());
         Integer max = 0;

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

@@ -33,6 +33,26 @@ public class CoursePackageService extends AbstractService {
     @Resource
     private CoursePackageRelationMapper coursePackageRelationMapper;
 
+    public Page<CoursePackage> listAdmin(int page, int size,String keyword, String order, DirectionStatus direction){
+        Example example = new Example(CoursePackage.class);
+        if(keyword != null){
+            example.and(
+                    example.createCriteria()
+                            .andLike("title", "%"+keyword+"%")
+            );
+        }
+        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(coursePackageMapper, example, page, size);
+    }
+
     public Page<CoursePackage> list(int page, int size, Integer structId, Boolean isSpecial, String order, DirectionStatus direction){
         Example example = new Example(Course.class);
         if(structId != null){

+ 41 - 2
server/gateway-api/src/main/java/com/qxgmat/service/inline/FaqService.java

@@ -15,10 +15,10 @@ import com.qxgmat.data.relation.FaqRelationMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
 
 @Service
 public class FaqService extends AbstractService {
@@ -65,9 +65,48 @@ public class FaqService extends AbstractService {
                 example.createCriteria()
                         .orEqualTo("isShow", 1)
         );
+        example.orderBy("order").desc();
         return select(faqMapper, example, page, size);
     }
 
+    public Map<Object, Collection<Faq>> groupByPosition(String channel, Collection positions, Integer top){
+        Map<Object, Collection<Faq>> result = new HashMap<>();
+        List<Faq> faqList = faqRelationMapper.groupByPosition(channel, positions, top);
+        Collection<Faq> tmp;
+        for(Faq faq : faqList){
+            if (!result.containsKey(faq.getPosition())){
+                tmp = new ArrayList<>();
+                result.put(faq.getPosition(), tmp);
+            }else{
+                tmp = result.get(faq.getPosition());
+            }
+            tmp.add(faq);
+        }
+        return result;
+    }
+
+    /**
+     * 根据列表顺序全部更新排序:从大到小
+     * @param ids
+     */
+    @Transactional
+    public void updateOrder(Integer[] ids){
+        int order = ids.length;
+        List<Faq> faqList = select(faqMapper, ids);
+        Map faqMap = Transform.getMap(faqList, Faq.class, "id");
+        for (Integer id : ids){
+            Faq faq = (Faq)faqMap.get(id);
+            if (faq == null) continue;
+            if (faq.getOrder() == order) continue;
+            update(faqMapper, Faq.builder()
+                    .id(id)
+                    .order(order)
+                    .build()
+            );
+            order -= 1;
+        }
+    }
+
     public Faq add(Faq faq){
         int result = insert(faqMapper, faq);
         faq = one(faqMapper, faq.getId());

+ 29 - 2
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseAppointmentService.java

@@ -6,14 +6,14 @@ import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
 import com.qxgmat.data.dao.UserCourseAppointmentMapper;
+import com.qxgmat.data.dao.entity.CourseNo;
 import com.qxgmat.data.dao.entity.UserCourseAppointment;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
 
 @Service
 public class UserCourseAppointmentService extends AbstractService {
@@ -59,6 +59,33 @@ public class UserCourseAppointmentService extends AbstractService {
         return select(userCourseAppointmentMapper, example, page, size);
     }
 
+    /**
+     * 获取课程课时分组列表
+     * @param recordIds
+     * @return
+     */
+    public Map<Object, Collection<UserCourseAppointment>> groupByRecordId(Collection recordIds){
+        Map<Object, Collection<UserCourseAppointment>> relationMap = new HashMap<>();
+        Example example = new Example(UserCourseAppointment.class);
+        example.and(
+                example.createCriteria()
+                        .andIn("recordId", recordIds)
+        );
+        example.setOrderByClause("recordId asc, no asc");
+        List<UserCourseAppointment> nos =  select(userCourseAppointmentMapper, example);
+        Collection<UserCourseAppointment> list;
+        for(UserCourseAppointment no : nos){
+            if (!relationMap.containsKey(no.getRecordId())){
+                list = new ArrayList<>();
+                relationMap.put(no.getRecordId(), list);
+            }else{
+                list = relationMap.get(no.getRecordId());
+            }
+            list.add(no);
+        }
+        return relationMap;
+    }
+
     public List<UserCourseAppointment> listByRecord(Integer recordId){
         Example example = new Example(UserCourseAppointment.class);
         example.and(

+ 27 - 2
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseProgressService.java

@@ -13,8 +13,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
 
 @Service
 public class UserCourseProgressService extends AbstractService {
@@ -33,6 +32,32 @@ public class UserCourseProgressService extends AbstractService {
         return select(userCourseProgressMapper, example);
     }
 
+    /**
+     * 获取课程课时分组列表
+     * @param recordIds
+     * @return
+     */
+    public Map<Object, Collection<UserCourseProgress>> groupByRecordId(Collection recordIds){
+        Map<Object, Collection<UserCourseProgress>> relationMap = new HashMap<>();
+        Example example = new Example(UserCourseProgress.class);
+        example.and(
+                example.createCriteria()
+                        .andIn("recordId", recordIds)
+        );
+        List<UserCourseProgress> nos =  select(userCourseProgressMapper, example);
+        Collection<UserCourseProgress> list;
+        for(UserCourseProgress no : nos){
+            if (!relationMap.containsKey(no.getRecordId())){
+                list = new ArrayList<>();
+                relationMap.put(no.getRecordId(), list);
+            }else{
+                list = relationMap.get(no.getRecordId());
+            }
+            list.add(no);
+        }
+        return relationMap;
+    }
+
     public UserCourseProgress add(UserCourseProgress progress){
         int result = insert(userCourseProgressMapper, progress);
         progress = one(userCourseProgressMapper, progress.getId());

+ 83 - 15
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java

@@ -33,6 +33,12 @@ public class UserOrderRecordService extends AbstractService {
     @Resource
     private UserOrderRecordRelationMapper userOrderRecordRelationMapper;
 
+    /**
+     * 获取订单的所有记录
+     * @param userId
+     * @param orderId
+     * @return
+     */
     public List<UserOrderRecord> allByUser(Integer userId, Integer orderId){
         Example example = new Example(UserOrderRecord.class);
         example.and(
@@ -43,6 +49,14 @@ public class UserOrderRecordService extends AbstractService {
         return select(userOrderRecordMapper, example);
     }
 
+    /**
+     * 获取小班课程学员列表
+     * @param page
+     * @param size
+     * @param courseId
+     * @param timeId
+     * @return
+     */
     public Page<UserOrderRecord> listAdminByOnline(int page, int size, Integer courseId, Integer timeId){
         Example example = new Example(UserOrderRecord.class);
         if(courseId != null){
@@ -61,6 +75,13 @@ public class UserOrderRecordService extends AbstractService {
         return select(userOrderRecordMapper, example, page, size);
     }
 
+    /**
+     * 更改小班课程课时时间段
+     * @param courseId
+     * @param timeId
+     * @param startTime
+     * @param endTime
+     */
     public void changeStudentTime(Integer courseId, Integer timeId, Date startTime, Date endTime){
         Example example = new Example(UserOrderRecord.class);
         example.and(
@@ -69,7 +90,7 @@ public class UserOrderRecordService extends AbstractService {
                         .andEqualTo("productId", courseId)
                         .andEqualTo("timeId", timeId)
         );
-        update(userOrderRecordMapper, example, UserOrderRecord.builder().startTime(startTime).endTime(endTime).build());
+        update(userOrderRecordMapper, example, UserOrderRecord.builder().useStartTime(startTime).useEndTime(endTime).build());
     }
 
     public List<CourseStudentNumberRelation> groupByCourse(Collection ids){
@@ -77,6 +98,13 @@ public class UserOrderRecordService extends AbstractService {
         return userOrderRecordRelationMapper.groupByTime(ids);
     }
 
+    /**
+     * 获取小班课程记录
+     * @param userId
+     * @param courseId
+     * @param timeId
+     * @return
+     */
     public UserOrderRecord getByUserAndTime(Integer userId, Integer courseId, Integer timeId){
         Example example = new Example(UserOrderRecord.class);
         example.and(
@@ -152,23 +180,17 @@ public class UserOrderRecordService extends AbstractService {
                                 .andEqualTo("productId", productId)
                 );
 
+        }else if(needPackage != null && !needPackage){
+            example.and(
+                    example.createCriteria()
+                                    .andNotEqualTo("productType", ProductType.COURSE_PACKAGE.key)
+            );
         }
         if (service != null)
             example.and(
                     example.createCriteria()
                             .andEqualTo("service", service.key)
             );
-
-        if(needPackage != null){
-            example.and(
-                    needPackage ?
-                            example.createCriteria()
-                                    .andGreaterThan("parentId", 0) :
-                            example.createCriteria()
-                                    .andEqualTo("parentId", 0)
-            );
-        }
-
         if(order == null || order.isEmpty()) order = "id";
         switch(direction){
             case ASC:
@@ -180,21 +202,44 @@ public class UserOrderRecordService extends AbstractService {
         }
         return select(userOrderRecordMapper, example, page, size);
     }
+
     /**
      * 获取已购买记录
      * @param order
      * @param direction
      * @return
      */
-    public Page<UserOrderRecord> list(int page, int size, Integer userId, String order, DirectionStatus direction){
+    public Page<UserOrderRecord> list(int page, int size, Integer userId, ProductType productType, Integer productId, ServiceKey service, Boolean isUsed, String order, DirectionStatus direction){
         Example example = new Example(UserOrderRecord.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("isStop", 0)
+                        .andNotEqualTo("productType", ProductType.COURSE_PACKAGE.key)
         );
-
-        if(order == null || order.isEmpty()) order = "id";
+        if(isUsed != null){
+            example.and(
+                    example.createCriteria()
+                                    .andEqualTo("isUsed", isUsed ? 1 : 0)
+            );
+        }
+        if(productType != null) {
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("productType", productType.key)
+            );
+            if (productId != null)
+                example.and(
+                        example.createCriteria()
+                                .andEqualTo("productId", productId)
+                );
+        }
+        if (service != null)
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("service", service.key)
+            );
+            if(order == null || order.isEmpty()) order = "id";
         switch(direction){
             case ASC:
                 example.orderBy(order).asc();
@@ -355,6 +400,29 @@ public class UserOrderRecordService extends AbstractService {
         return p;
     }
 
+    /**
+     * 列出用户购课记录
+     * @param page
+     * @param size
+     * @param userId
+     * @return
+     */
+    public Page<UserOrderRecord> listWithCourse(int page, int size, Integer userId, CourseModule courseModule, Boolean isUsed, Boolean isEnd, String order, DirectionStatus direction){
+        if(order == null || order.isEmpty()) order = "id";
+        if (direction == null){
+            direction = DirectionStatus.DESC;
+        }
+        String finalOrder = order;
+        DirectionStatus finalDirection = direction;
+        Page<UserOrderRecord> p = page(
+                ()-> userOrderRecordRelationMapper.listWithCourse(userId, courseModule.key, isUsed, isEnd, finalOrder, finalDirection.key)
+                , page, size);
+
+        Collection ids = Transform.getIds(p, UserOrderRecord.class, "id");
+        Transform.replace(p, select(ids), UserOrderRecord.class, "id");
+
+        return p;
+    }
 
     /**
      * 获取最大vs课程编号

+ 9 - 0
server/tools/src/main/java/com/nuliji/tools/Tools.java

@@ -254,6 +254,15 @@ public class Tools {
         return calendar.getTime();
     }
 
+    public static Date day(Date date){
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        return calendar.getTime();
+    }
+
     public static int year(Date date){
         Calendar calendar = Calendar.getInstance();
         calendar.setTime(date);