瀏覽代碼

feat(front): 个人中心

Go 5 年之前
父節點
當前提交
95910dfac4
共有 99 個文件被更改,包括 3286 次插入1323 次删除
  1. 2 2
      front/project/Constant.js
  2. 61 1
      front/project/h5/routes/product/courseVs/index.less
  3. 22 4
      front/project/h5/routes/product/courseVs/page.js
  4. 0 42
      front/project/h5/routes/product/dataDetail/index.less
  5. 0 10
      front/project/h5/routes/product/dataDetail/page.js
  6. 23 34
      front/project/h5/stores/my.js
  7. 5 4
      front/project/www/components/UserPagination/index.js
  8. 2 1
      front/project/www/components/UserTable/index.js
  9. 6 0
      front/project/www/components/UserTable/index.less
  10. 1 1
      front/project/www/routes/examination/list/page.js
  11. 21 31
      front/project/www/routes/my/answer/page.js
  12. 4 0
      front/project/www/routes/my/collect/index.less
  13. 279 121
      front/project/www/routes/my/collect/page.js
  14. 10 5
      front/project/www/routes/my/course/page.js
  15. 4 0
      front/project/www/routes/my/error/index.less
  16. 237 43
      front/project/www/routes/my/error/page.js
  17. 46 52
      front/project/www/routes/my/main/page.js
  18. 4 0
      front/project/www/routes/my/message/index.less
  19. 24 2
      front/project/www/routes/my/message/page.js
  20. 2 1
      front/project/www/routes/my/note/index.less
  21. 160 82
      front/project/www/routes/my/note/page.js
  22. 4 0
      front/project/www/routes/my/order/index.less
  23. 119 82
      front/project/www/routes/my/order/page.js
  24. 1 0
      front/project/www/routes/my/report/index.less
  25. 317 50
      front/project/www/routes/my/report/page.js
  26. 107 36
      front/project/www/routes/my/tools/page.js
  27. 2 2
      front/project/www/routes/paper/process/page.js
  28. 6 7
      front/project/www/routes/paper/question/detail/index.js
  29. 3 2
      front/project/www/routes/paper/report/page.js
  30. 65 35
      front/project/www/stores/my.js
  31. 4 0
      front/src/services/Tools.js
  32. 20 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/PaperModule.java
  33. 18 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/QuestionModule.java
  34. 21 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/QuestionNoModule.java
  35. 17 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/user/ExportType.java
  36. 7 0
      server/data/src/main/java/com/qxgmat/data/dao/UserExportMapper.java
  37. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/QuestionNo.java
  38. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/SentencePaper.java
  39. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/SentenceQuestion.java
  40. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookPaper.java
  41. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookQuestion.java
  42. 12 12
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserCollectExperience.java
  43. 187 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserExport.java
  44. 77 7
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserPaper.java
  45. 66 31
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserTextbookFeedback.java
  46. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/QuestionNoMapper.xml
  47. 3 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/SentencePaperMapper.xml
  48. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/SentenceQuestionMapper.xml
  49. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookPaperMapper.xml
  50. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookQuestionMapper.xml
  51. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserCollectExperienceMapper.xml
  52. 20 0
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserExportMapper.xml
  53. 5 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserPaperMapper.xml
  54. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserTextbookFeedbackMapper.xml
  55. 11 0
      server/data/src/main/java/com/qxgmat/data/relation/entity/SentenceQuestionRelation.java
  56. 11 0
      server/data/src/main/java/com/qxgmat/data/relation/entity/TextbookQuestionRelation.java
  57. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/CourseDataHistoryRelationMapper.xml
  58. 6 2
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserAskQuestionRelationMapper.xml
  59. 14 12
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserCollectQuestionRelationMapper.xml
  60. 4 2
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserNoteQuestionRelationMapper.xml
  61. 19 5
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserPaperRelationMapper.xml
  62. 18 18
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserQuestionRelationMapper.xml
  63. 20 3
      server/data/src/main/resources/db/migration/V1__init_table.sql
  64. 6 0
      server/data/src/main/resources/mybatis-generator.xml
  65. 186 211
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  66. 3 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java
  67. 17 72
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  68. 4 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/SentenceController.java
  69. 18 12
      server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java
  70. 107 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/PaperExtendDto.java
  71. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/QuestionNoExtendDto.java
  72. 0 10
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserAskQuestionDto.java
  73. 0 11
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCollectQuestionDto.java
  74. 5 15
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCustomBindDto.java
  75. 27 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserExportDto.java
  76. 0 9
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserFeedbackErrorQuestionDto.java
  77. 0 10
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserNoteQuestionDto.java
  78. 19 9
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserTextbookFeedbackDto.java
  79. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCollectQuestionInfoDto.java
  80. 11 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserNoteQuestionInfoDto.java
  81. 19 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderDetailDto.java
  82. 61 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserPaperDto.java
  83. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserQuestionErrorInfoDto.java
  84. 3 3
      server/gateway-api/src/main/java/com/qxgmat/service/UserCollectExperienceService.java
  85. 2 22
      server/gateway-api/src/main/java/com/qxgmat/service/UserCollectQuestionService.java
  86. 32 11
      server/gateway-api/src/main/java/com/qxgmat/service/UserNoteQuestionService.java
  87. 2 1
      server/gateway-api/src/main/java/com/qxgmat/service/UserPaperService.java
  88. 7 0
      server/gateway-api/src/main/java/com/qxgmat/service/annotation/FinishAfterReport.java
  89. 5 3
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExaminationService.java
  90. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  91. 81 13
      server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java
  92. 32 9
      server/gateway-api/src/main/java/com/qxgmat/service/extend/SentenceService.java
  93. 31 7
      server/gateway-api/src/main/java/com/qxgmat/service/extend/TextbookService.java
  94. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/QuestionNoService.java
  95. 86 62
      server/gateway-api/src/main/java/com/qxgmat/service/inline/SentenceQuestionService.java
  96. 85 62
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookQuestionService.java
  97. 12 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookTopicService.java
  98. 73 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserExportService.java
  99. 11 11
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserPaperQuestionService.java

+ 2 - 2
front/project/Constant.js

@@ -102,7 +102,7 @@ export const CrowdList = [{ label: '新手', value: 'novice' }, { label: '非新
 
 export const CourseStatus = [{ label: '未开通', value: 0 }, { label: '学习中', value: 1 }, { label: '已到期', value: 2 }];
 
-export const InvoiceType = [{ label: '个人', value: 'personal' }, { label: '企业', value: 'enterprise' }];
+export const InvoiceType = [{ label: '个人', value: 'personal' }, { label: '公司', value: 'enterprise' }];
 
 export const TextbookQuality = [{ label: '残缺', value: 'incomplete' }, { label: '较完整', value: 'morecomplete' }, { label: '完整', value: 'complete' }];
 
@@ -110,7 +110,7 @@ export const TextbookSubject = [{ label: '数学', value: 'quant' }, { label: '
 
 export const TextbookFeedbackStatus = [{ value: 0, label: '新增' }, { value: 1, label: '采纳' }, { value: 2, label: '忽略' }, { value: 3, label: '无需修改' }];
 
-export const TextbookFeedbackTarget = [{ label: '纠正', value: 'correct' }, { label: '完善', value: 'perfect' }, { label: '反馈全新', value: 'new' }];
+export const TextbookFeedbackTarget = [{ label: '纠正解析', value: 'correct', tip: '正确的解析和答案是' }, { label: '完善已有机经', value: 'perfect', tip: '补充内容是' }, { label: '提供全新机经', value: 'new', tip: '新机经是' }];
 
 export const ContractKey = [{ label: '注册', value: 'register' }, { label: '课程购买', value: 'course' }];
 

+ 61 - 1
front/project/h5/routes/product/courseVs/index.less

@@ -1,3 +1,63 @@
 @charset "utf-8";
 
-#product-course-vs {}
+#product-course-vs {
+
+  .fixed {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 64px;
+    line-height: 64px;
+    padding: 0 15px;
+    border-top: 1px solid #eee;
+    background: #fff;
+
+    .action {
+      display: inline-block;
+      margin-right: 10px;
+
+      .minus {
+        display: inline-block;
+        width: 20px;
+        height: 20px;
+        border: 1px solid #dfdfdf;
+        border-radius: 50%;
+        text-align: center;
+        line-height: 20px;
+        font-size: 10px;
+
+        i {
+          vertical-align: none;
+        }
+      }
+
+      .num {
+        display: inline-block;
+        color: #686872;
+        margin: 0 15px;
+      }
+
+      .add {
+        display: inline-block;
+        width: 20px;
+        height: 20px;
+        background: #41a6f3;
+        border-radius: 50%;
+        color: #fff;
+        text-align: center;
+        line-height: 20px;
+        font-size: 10px;
+
+        i {
+          vertical-align: none;
+        }
+      }
+    }
+
+    .fee {
+      display: inline-block;
+    }
+  }
+
+}

+ 22 - 4
front/project/h5/routes/product/courseVs/page.js

@@ -7,21 +7,30 @@ import Button from '../../../components/Button';
 import { FAQItem, CommentItem } from '../../../components/Item';
 import { Course } from '../../../stores/course';
 import { Order } from '../../../stores/order';
+import Icon from '../../../components/Icon';
 
 export default class extends Page {
   initState() {
-    return { tab: 'serviceContent' };
+    return { tab: 'serviceContent', number: 1 };
   }
 
   initData() {
     const { id } = this.params;
     Course.get(id).then(result => {
       this.setState({ data: result });
+      this.changeNumber(1);
     });
   }
 
+  changeNumber(number) {
+    const { data } = this.state;
+    const price = data.price * number;
+    this.setState({ number, price });
+  }
+
   buy() {
-    Order.speedPay({ productType: 'course', productId: this.params.id, number: 1 });
+    const { number } = this.state;
+    Order.speedPay({ productType: 'course', productId: this.params.id, number });
   }
 
   renderText() {
@@ -46,7 +55,7 @@ export default class extends Page {
   }
 
   renderView() {
-    const { data = {}, tab } = this.state;
+    const { data = {}, tab, number, price } = this.state;
     return (
       <div>
         <div className="b-g" style={{ backgroundImage: `url(${data.cover})` }}>
@@ -70,8 +79,17 @@ export default class extends Page {
           {this.renderText()}
         </div>
         <div className="fixed">
+          <div className="action">
+            <div className="minus" onClick={() => this.changeNumber(number === 1 ? number : number - 1)}>
+              <Icon type="minus" />
+            </div>
+            <div className="num">{number}</div>
+            <div className="add" onClick={() => this.changeNumber(number + 1)}>
+              <Icon type="plus" />
+            </div>
+          </div>
           <div className="fee">
-            总额: <Money value={data.price} size="lager" />
+            总额: <Money value={price} size="lager" />
           </div>
           <Button width={110} className="f-r" radius onClick={() => {
             this.buy();

+ 0 - 42
front/project/h5/routes/product/dataDetail/index.less

@@ -57,48 +57,6 @@
     border-top: 1px solid #eee;
     background: #fff;
 
-    .action {
-      display: inline-block;
-      margin-right: 10px;
-
-      .minus {
-        display: inline-block;
-        width: 20px;
-        height: 20px;
-        border: 1px solid #dfdfdf;
-        border-radius: 50%;
-        text-align: center;
-        line-height: 20px;
-        font-size: 10px;
-
-        i {
-          vertical-align: none;
-        }
-      }
-
-      .num {
-        display: inline-block;
-        color: #686872;
-        margin: 0 15px;
-      }
-
-      .add {
-        display: inline-block;
-        width: 20px;
-        height: 20px;
-        background: #41a6f3;
-        border-radius: 50%;
-        color: #fff;
-        text-align: center;
-        line-height: 20px;
-        font-size: 10px;
-
-        i {
-          vertical-align: none;
-        }
-      }
-    }
-
     .fee {
       display: inline-block;
     }

+ 0 - 10
front/project/h5/routes/product/dataDetail/page.js

@@ -11,7 +11,6 @@ import { Course } from '../../../stores/course';
 import { Order } from '../../../stores/order';
 import { DataType } from '../../../../Constant';
 import { FAQItem, CommentItem } from '../../../components/Item';
-import Icon from '../../../components/Icon';
 
 const DataTypeMap = getMap(DataType, 'value', 'label');
 
@@ -98,15 +97,6 @@ export default class extends Page {
         />
         {this.renderText()}
         <div className="fixed">
-          <div className="action">
-            <div className="minus">
-              <Icon type="minus" />
-            </div>
-            <div className="num">10</div>
-            <div className="add">
-              <Icon type="plus" />
-            </div>
-          </div>
           <div className="fee">
             总额: <Money value={data.price} size="lager" />
           </div>

+ 23 - 34
front/project/h5/stores/my.js

@@ -133,30 +133,27 @@ export default class MyStore extends BaseStore {
 
   /**
    * 添加收藏
-   * @param {*} questionModule
    * @param {*} questionNoId
    */
-  addQuestionCollect(questionModule, questionNoId) {
-    return this.apiPut('/my/collect/question/add', { questionModule, questionNoId });
+  addQuestionCollect(questionNoId) {
+    return this.apiPut('/my/collect/question/add', { questionNoId });
   }
 
   /**
    * 删除收藏
-   * @param {*} questionModule
    * @param {*} questionNoId
    */
-  delQuestionCollect(questionModule, questionNoId) {
-    return this.apiDel('/my/collect/question/delete', { questionModule, questionNoId });
+  delQuestionCollect(questionNoId) {
+    return this.apiDel('/my/collect/question/delete', { questionNoId });
   }
 
   /**
    * 收藏卷组
-   * @param {*} questionModule
-   * @param {*} questionNoIds
+   * @param {*} questionNoIds: 'questionNoId'
    * @param {*} filterTimes
    */
-  bindQuestionCollect(questionModule, questionNoIds, filterTimes) {
-    return this.apiPost('/my/collect/question/bind', { questionModule, questionNoIds, filterTimes });
+  groupQuestionCollect({ questionNoIds, filterTimes }) {
+    return this.apiPost('/my/collect/question/group', { questionNoIds, filterTimes });
   }
 
   /**
@@ -176,30 +173,28 @@ export default class MyStore extends BaseStore {
 
   /**
    * 获取错题列表
-   * @param {*} questionModule
    * @param {*} page
    * @param {*} size
    */
-  listError(questionModule, page, size) {
-    return this.apiGet('/my/error/list', { questionModule, page, size });
+  listError({ keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order }) {
+    return this.apiGet('/my/error/list', { keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order });
   }
 
   /**
    * 错题组卷
-   * @param {*} questionModule
-   * @param {*} questionNoIds
+   * @param {*} questionNoIds: 'questionNoId'
    * @param {*} filterTimes
    */
-  bindError(questionModule, questionNoIds, filterTimes) {
-    return this.apiPost('/my/error/bind', { questionModule, questionNoIds, filterTimes });
+  groupError({ questionNoIds, filterTimes }) {
+    return this.apiPost('/my/error/group', { questionNoIds, filterTimes });
   }
 
   /**
    * 错题移除
-   * @param {*} ids
+   * @param {*} questionNoIds
    */
-  clearError(ids) {
-    return this.apiPost('/my/error/clear', { questionNoIds: ids });
+  clearError(questionNoIds) {
+    return this.apiPost('/my/error/clear', { questionNoIds });
   }
 
   /**
@@ -212,7 +207,6 @@ export default class MyStore extends BaseStore {
 
   /**
    * 更新笔记
-   * @param {*} questionModule
    * @param {*} questionNoId
    * @param {*} content
    * @param {*} qxContent
@@ -220,23 +214,20 @@ export default class MyStore extends BaseStore {
    * @param {*} associationContent
    * @param {*} qaContent
    */
-  updateQuestionNote(questionModule, questionNoId, { content, qxContent, officialContent, associationContent, qaContent }) {
-    return this.apiPut('/my/note/question', { questionModule, questionNoId, content, qxContent, officialContent, associationContent, qaContent });
+  updateQuestionNote(questionNoId, { content, qxContent, officialContent, associationContent, qaContent }) {
+    return this.apiPut('/my/note/question', { questionNoId, content, qxContent, officialContent, associationContent, qaContent });
   }
 
   /**
    * 获取笔记列表
-   * @param {*} questionModule
-   * @param {*} questionType
    * @param {*} page
    * @param {*} size
    * @param {*} startTime
    * @param {*} endTime
    * @param {*} order
-   * @param {*} direction
    */
-  questionNoteList(questionModule, questionType, page, size, startTime, endTime, order, direction) {
-    return this.apiGet('/my/note/question/list', { questionModule, questionType, page, size, startTime, endTime, order, direction });
+  listQuestionNote({ keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order }) {
+    return this.apiGet('/my/note/question/list', { keyword, module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order });
   }
 
   /**
@@ -258,25 +249,23 @@ export default class MyStore extends BaseStore {
    * 添加题目提问
    * @param {*} userQuestionId : 用于获取预习作业,判断权限
    * @param {*} target
-   * @param {*} questionModule
    * @param {*} questionNoId
    * @param {*} content
    */
-  addQuestionAsk(userQuestionId, target, questionModule, questionNoId, originContent, content) {
-    return this.apiPost('/my/ask/question', { userQuestionId, target, questionModule, questionNoId, originContent, content });
+  addQuestionAsk(userQuestionId, target, questionNoId, originContent, content) {
+    return this.apiPost('/my/ask/question', { userQuestionId, target, questionNoId, originContent, content });
   }
 
   /**
    * 添加题目勘误
-   * @param {*} questionModule
    * @param {*} questionNoId
    * @param {*} title
    * @param {*} position
    * @param {*} originContent
    * @param {*} content
    */
-  addFeedbackErrorQuestion(questionModule, questionNoId, title, position, originContent, content) {
-    return this.apiPost('/my/feedback/error/question', { questionModule, questionNoId, title, position, originContent, content });
+  addFeedbackErrorQuestion(questionNoId, title, position, originContent, content) {
+    return this.apiPost('/my/feedback/error/question', { questionNoId, title, position, originContent, content });
   }
 
   /**

+ 5 - 4
front/project/www/components/UserPagination/index.js

@@ -4,18 +4,19 @@ import Icon from '../Icon';
 
 export default class extends Component {
   onChangePage(page) {
-    const { current, total, onChange } = this.props;
-    if (page < current || page > total) return;
+    const { total, pageSize, onChange } = this.props;
+    const all = Math.ceil(total / pageSize);
+    if (page <= 0 || page > all) return;
     if (onChange) onChange(page);
   }
 
   render() {
-    const { current, total } = this.props;
+    const { current, total, pageSize } = this.props;
     return (
       <div className="user-pagination">
         <Icon name="arrow-left-small" onClick={() => this.onChangePage(current - 1)} />
         <span>
-          <b>{current}</b>/{total}
+          <b>{current}</b>/{Math.ceil(total / pageSize)}
         </span>
         <Icon name="arrow-right-small" onClick={() => this.onChangePage(current + 1)} />
       </div>

+ 2 - 1
front/project/www/components/UserTable/index.js

@@ -31,6 +31,7 @@ export default class UserTable extends Component {
       selectList = [],
       current,
       total,
+      pageSize,
       size = 'basic',
       even = 'even',
       theme = 'defalut',
@@ -105,7 +106,7 @@ export default class UserTable extends Component {
           </tbody>
         </table>
         {data.length === 0 && <div className="empty">暂无数据</div>}
-        {total && data.length > 0 && <UserPagination total={total} current={current} onChange={onChange} />}
+        {total > 0 && data.length > 0 && <UserPagination total={total} pageSize={pageSize} current={current} onChange={onChange} />}
       </div>
     );
   }

+ 6 - 0
front/project/www/components/UserTable/index.less

@@ -104,4 +104,10 @@
   tr {
     background: #ECEDEE;
   }
+}
+
+.user-table.top {
+  td {
+    vertical-align: top;
+  }
 }

+ 1 - 1
front/project/www/routes/examination/list/page.js

@@ -414,7 +414,7 @@ export default class extends Page {
         render: () => {
           return (
             <div className="table-row">
-              <div className="f-s-12">仅CAT模考</div>
+              <div className="f-s-12">仅CAT模考<br />提供分数</div>
             </div>
           );
         },

+ 21 - 31
front/project/www/routes/my/answer/page.js

@@ -2,8 +2,9 @@ import React from 'react';
 import { Link } from 'react-router-dom';
 import './index.less';
 import Page from '@src/containers/Page';
-import { timeRange, getMap } from '@src/services/Tools';
+import { timeRange, getMap, formatDate } from '@src/services/Tools';
 import UserLayout from '../../../layouts/User';
+import Button from '../../../components/Button';
 import UserTable from '../../../components/UserTable';
 import UserAction from '../../../components/UserAction';
 import UserPagination from '../../../components/UserPagination';
@@ -20,46 +21,27 @@ const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
 const columns = [
   {
     key: 'questionType',
-    title: '笔记对象',
     width: 140,
     render(text, row) {
-      return row.group ? (
-        <div className="group">
-          <Link to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}>{QuestionTypeMap[text]}</Link>
-        </div>
-      ) : (
-        <div className="sub">{QuestionTypeMap[text]}</div>
-      );
+      return <div className="group">
+        <Link to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}>{QuestionTypeMap[text]}</Link>
+      </div>;
     },
   },
   {
     key: 'title',
-    title: '更新时间',
     width: 100,
     render(text, row) {
-      return row.group ? (
-        <div className="group">
-          <Link to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}>{text}</Link>
-        </div>
-      ) : (<div className="sub">
-        <div className="date">{text.split(' ')[0]}</div>
-        <div className="date">{text.split(' ')[1]}</div>
-      </div>
-      );
+      return <div className="group">
+        <Link to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}>{text}</Link>
+      </div>;
     },
   },
   {
     key: 'content',
-    title: '内容',
     width: 540,
     render(text, row) {
-      return row.group ? (
-        <div className="group text-hidden">
-          <Link to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}>{text}</Link>
-        </div>
-      ) : (
-        <div className="sub">{text}</div>
-      );
+      return <div className="group text-hidden"><Link to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}>{text}</Link></div>;
     },
   },
 ];
@@ -154,6 +136,13 @@ export default class extends Page {
     this.setState({ selectList });
   }
 
+  delAsk(id) {
+    My.delQuestionAsk(id)
+      .then(() => {
+        this.refresh();
+      });
+  }
+
   renderView() {
     const { config } = this.props;
     return <UserLayout active={config.key} menu={menu} center={this.renderTable()} />;
@@ -239,7 +228,7 @@ export default class extends Page {
               <Switch
                 checked={Number(filterMap.askStatus)}
                 onChange={() => {
-                  filterMap.askStatus = Number(filterMap.askStatus) ? 0 : 1;
+                  filterMap.askStatus = Number(filterMap.askStatus) ? null : 1;
                   this.onFilter(filterMap);
                 }}
               />
@@ -254,12 +243,13 @@ export default class extends Page {
               <div className="answer-layout">
                 <div className="title">
                   提问区域: <b>{AskTargetMap[item.target]}</b>
+                  {item.answerStatus === 0 && <div className='f-r'><Button radius size='small' onClick={() => this.delAsk(item.id)}>删除</Button></div>}
                 </div>
-                <div className="small-tag">提问</div>
+                <div><div className="small-tag">提问</div><div className='f-r t-2 t-s-12'>{formatDate(item.createTime, 'YYYY-MM-DD HH:mm:ss')}</div></div>
                 <div className="desc">
                   <OpenText>{item.content}</OpenText>
                 </div>
-                {item.answerStatus > 0 && <div className="small-tag">回答</div>}
+                {item.answerStatus > 0 && <div><div className="small-tag">回答</div><div className='f-r t-2 t-s-12'>{formatDate(item.answerTime, 'YYYY-MM-DD HH:mm:ss')}</div></div>}
                 {item.answerStatus > 0 && <div className="desc">
                   <OpenText>{item.answer}</OpenText>
                 </div>}
@@ -268,7 +258,7 @@ export default class extends Page {
           );
         })}
         {total > 0 && list.length > 0 && (
-          <UserPagination total={total} current={page} onChange={p => this.onChangePage(p)} />
+          <UserPagination total={total} pageSize={this.state.search.size} current={page} onChange={p => this.onChangePage(p)} />
         )}
       </div>
     );

+ 4 - 0
front/project/www/routes/my/collect/index.less

@@ -24,6 +24,10 @@
 
     .user-table {
       font-size: 12px;
+
+      td {
+        vertical-align: top;
+      }
     }
 
     .article-item {

文件差異過大導致無法顯示
+ 279 - 121
front/project/www/routes/my/collect/page.js


+ 10 - 5
front/project/www/routes/my/course/page.js

@@ -612,6 +612,7 @@ class CourseOnline extends Component {
         title: '预习作业',
         key: 'paper',
         render: (text) => {
+          text = text || {};
           const progress = text.report ? formatPercent(text.report.userNumber, text.report.questionNumber) : 0;
           const times = text.paper ? text.paper.times : 0;
           return <div>
@@ -665,8 +666,8 @@ class CourseOnline extends Component {
       {
         title: '笔记',
         key: 'note',
-        render: () => {
-          return <GIcon name="note" active />;
+        render: (text, record) => {
+          return <GIcon name="note" active={record.note} />;
         },
       },
       {
@@ -1023,6 +1024,7 @@ class CourseVs extends Component {
           title: '预习作业',
           key: 'paper',
           render: (text) => {
+            text = text || {};
             const progress = text.report ? formatPercent(text.report.userNumber, text.report.questionNumber) : 0;
             const times = text.paper ? text.paper.times : 0;
             return <div>
@@ -1060,21 +1062,24 @@ class CourseVs extends Component {
           title: '授课时间',
           key: 'time',
           render: (text, record) => {
-            return `${formatDate(record.startTime, 'YYYY-MM-DD HH:mm:ss')} ~ ${formatDate(record.endTime, 'HH:mm:ss')}`;
+            return <div className="sub">
+              <div className="t-2 t-s-12">{formatDate(record.startTime, 'YYYY-MM-DD')}</div>
+              <div className="t-6 t-s-12">{formatDate(record.startTime, 'HH:mm:ss')} ~ {formatDate(record.endTime, 'HH:mm:ss')}</div>
+            </div>;
           },
         },
         {
           title: '课后笔记',
           key: 'note',
           render: (text, record) => {
-            return record.noteList ? <a onClick={() => this.props.onNote(record)}>查看</a> : <span>查看</span>;
+            return record.noteList && record.noteList.length > 0 ? <a onClick={() => this.props.onNote(record)}>查看</a> : <span>查看</span>;
           },
         },
         {
           title: '课后补充',
           key: 'supply',
           render: (text, record) => {
-            return record.supplyList ? <a onClick={() => this.props.onSupply(record)}>查看</a> : <span>查看</span>;
+            return record.supplyList && record.supplyList.length > 0 ? <a onClick={() => this.props.onSupply(record)}>查看</a> : <span>查看</span>;
           },
         },
       ],

+ 4 - 0
front/project/www/routes/my/error/index.less

@@ -24,6 +24,10 @@
 
     .user-table {
       font-size: 12px;
+
+      td {
+        vertical-align: top;
+      }
     }
   }
 }

+ 237 - 43
front/project/www/routes/my/error/page.js

@@ -2,41 +2,91 @@ import React from 'react';
 import './index.less';
 import { Icon, Checkbox } from 'antd';
 import Page from '@src/containers/Page';
-import { timeRange } from '@src/services/Tools';
+import { timeRange, formatDate, getMap, formatSeconds, formatPercent } from '@src/services/Tools';
 import UserLayout from '../../../layouts/User';
 import UserTable from '../../../components/UserTable';
 import UserAction from '../../../components/UserAction';
+import { RealAuth } from '../../../components/OtherModal';
+import Examination from '../../../components/Examination';
+import VipRenew from '../../../components/VipRenew';
 import menu, { refreshQuestionType, refreshStruct } from '../index';
 import Tabs from '../../../components/Tabs';
 import Modal from '../../../components/Modal';
 import Select from '../../../components/Select';
 import GIcon from '../../../components/Icon';
-import { TimeRange } from '../../../../Constant';
+import { TimeRange, QuestionType } from '../../../../Constant';
 import { My } from '../../../stores/my';
+import { Question } from '../../../stores/question';
+
+const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
 
 const columns = [
-  { key: 'question_type', title: '题型', fixSort: true },
-  { key: 'title', title: '题目ID', fixSort: true },
-  { key: 'description', title: '内容' },
-  { key: 'time', title: '耗时', sort: true },
-  { key: 'correct', title: '错误率', sort: true },
-  { key: 'latest_time', title: '最近做题' },
+  {
+    key: 'question_type',
+    title: '题型',
+    render: (text, record) => {
+      return QuestionTypeMap[record.questionType];
+    },
+    fixSort: true,
+  },
+  {
+    key: 'title',
+    title: '题目ID',
+    fixSort: true,
+  },
+  {
+    key: 'description',
+    title: '内容',
+  },
+  {
+    key: 'time',
+    title: '耗时',
+    sort: true,
+    render: (text, record) => {
+      return <div className="sub">
+        <div className="t-2 t-s-12">{formatSeconds(record.stat.userTime / record.stat.userNumber)}</div>
+        <div className="t-6 t-s-12">全站{formatSeconds(record.questionNo.totalTime / record.questionNo.totalNumber)}</div>
+      </div>;
+    },
+  },
+  {
+    key: 'correct',
+    title: '错误率',
+    sort: true,
+    render: (text, record) => {
+      return <div className="sub">
+        <div className="t-2 t-s-12">{formatPercent(record.stat.userCorrect, record.stat.userNumber, false)}</div>
+        <div className="t-6 t-s-12">{record.stat.userCorrect}/{record.stat.userNumber}</div>
+      </div>;
+    },
+  },
+  {
+    key: 'latest_time',
+    title: '最近做题',
+    render: (text) => {
+      return <div className="sub">
+        <div className="t-2 t-s-12">{text.split(' ')[0]}</div>
+        <div className="t-6 t-s-12">{text.split(' ')[1]}</div>
+      </div>;
+    },
+  },
   {
     key: '',
     title: '',
-    render() {
-      return [<GIcon name="star" className="m-r-5" />, <GIcon name="note" />];
+    render: (text, record) => {
+      return <div><GIcon name="star" active={record.collect} className="m-r-5" /><GIcon name="note" active={record.note} /></div>;
     },
   },
 ];
 
+
 const exportType = [
-  { key: '1', title: '题目' },
-  { key: '2', title: '官方解析' },
-  { key: '3', title: '我的笔记' },
-  { key: '4', title: '千行解析', auth: true },
-  { key: '5', title: '题源联想', auth: true },
-  { key: '6', title: '相关问答', auth: true },
+  { key: 'question', title: '题目' },
+  { key: 'official', title: '官方解析' },
+  { key: 'note', title: '我的笔记' },
+  { key: 'qx', title: '千行解析', auth: true },
+  { key: 'association', title: '题源联想', auth: true },
+  { key: 'qa', title: '相关问答', auth: true },
 ];
 
 export default class extends Page {
@@ -66,6 +116,13 @@ export default class extends Page {
     if (data.timerange) {
       data.filterMap.timerange = data.timerange;
     }
+    const { info = {} } = this.props.user;
+    if (info.latestExercise) {
+      // 获取最后一次错题记录
+      Question.baseReport(info.latestError).then(result => {
+        this.setState({ latest: result });
+      });
+    }
     const [startTime, endTime] = timeRange(data.timerange);
     refreshQuestionType(this, data.subject, data.questionType, {
       all: true,
@@ -77,7 +134,7 @@ export default class extends Page {
         needPreview: false,
         needTextbook: true,
       }).then(({ structIds, latest, year }) => {
-        My.listQuestionAsk(
+        My.listError(
           Object.assign(
             { module: data.tab, questionTypes, structIds, latest, year, startTime, endTime },
             this.state.search,
@@ -135,12 +192,116 @@ export default class extends Page {
     }
   }
 
-  onAction() {}
-
   onSelect(selectList) {
     this.setState({ selectList });
   }
 
+  onAction(key) {
+    const { info } = this.props.user;
+    const { selectList } = this.state;
+    switch (key) {
+      case 'help':
+        this.setState({ showTips: true });
+        return;
+      case 'clear':
+        if (selectList.length === 0) {
+          this.setState({ showWarn: true, warn: { title: '移除', content: '不可少于1题,请重新选择' } });
+          return;
+        }
+        this.setState({ showClearCollectConfirm: true, clearInfo: { questionNoIds: selectList } });
+        break;
+      case 'group':
+        if (!info.vip) {
+          this.setState({ showVip: true });
+          return;
+        }
+        if (selectList.length < 0) {
+          this.setState({ showWarn: true, warn: { title: '组卷练习', content: '不可小于10题,请重新选择' } });
+          return;
+        }
+        if (selectList.length > 50) {
+          this.setState({ showWarn: true, warn: { title: '组卷练习', content: '不可多余50题,请重新选择' } });
+          return;
+        }
+        if (selectList.length === 0) {
+          this.setState({ showWarn: true, warn: { title: '组卷练习', content: '不可同时选中语文题和数学题,请重新选择。' } });
+          return;
+        }
+        this.setState({ showGroupConfirm: true, groupInfo: { questionNoIds: selectList } });
+        break;
+      case 'export':
+        if (!info.vip) {
+          this.setState({ showVip: true });
+          return;
+        }
+        if (selectList.length < 0) {
+          this.setState({ showWarn: true, warn: { title: '导出', content: '不可小于10题,请重新选择' } });
+          return;
+        }
+        if (selectList.length > 100) {
+          this.setState({ showWarn: true, warn: { title: '导出', content: '不可多余100题,请重新选择' } });
+          return;
+        }
+        if (info.bindReal) {
+          this.setState({ showExportConfirm: true, exportInfo: { info: exportType.map(row => row.key), questionNoIds: selectList } });
+        } else {
+          this.setState({ showExportAuthConfirm: true, exportInfo: { info: exportType.filter(row => !row.auth).map(row => row.key), questionNoIds: selectList } });
+        }
+        break;
+      default:
+    }
+  }
+
+  clearErrorQuestion() {
+    const { clearInfo } = this.state;
+    My.clearError(clearInfo.questionNoIds)
+      .then(() => {
+        this.refresh();
+      })
+      .catch(e => {
+        this.setState({ warn: { title: '移除', content: e.message }, showClearCollectConfirm: false });
+      });
+  }
+
+  group() {
+    const { groupInfo } = this.state;
+    My.groupQuestionCollect(groupInfo)
+      .then((result) => {
+        Question.startLink('error', result);
+        this.setState({ showGroupConfirm: false });
+      })
+      .catch(e => {
+        this.setState({ warn: { title: '组卷练习', content: e.message }, showGroupConfirm: false });
+      });
+  }
+
+  export() {
+    const { exportInfo } = this.state;
+    this.setState({ showExportWait: true, showExportConfirm: false, showExportAuthConfirm: false });
+    My.exportQuestion(exportInfo)
+      .then((result) => {
+        openLink(`/export/${result.id}`);
+        this.setState({ showExportWait: false });
+      })
+      .catch(e => {
+        this.setState({ warn: { title: '导出', content: e.message }, showExportWait: false });
+      });
+  }
+
+  remove(report) {
+    My.removeError(report.id)
+      .then(() => {
+        this.refresh();
+      });
+  }
+
+  clearErrorReport() {
+    My.clearLatestError()
+      .then(() => {
+        this.setState({ latest: null });
+      });
+  }
+
   renderView() {
     const { config } = this.props;
     return <UserLayout active={config.key} menu={menu} center={this.renderTable()} />;
@@ -156,8 +317,10 @@ export default class extends Page {
       filterMap = {},
       sortMap = {},
       list = [],
+      latest,
     } = this.state;
     const { selectList = [], allChecked, page, total } = this.state;
+    const { info } = this.props.user;
     return (
       <div className="table-layout">
         <Tabs
@@ -220,14 +383,18 @@ export default class extends Page {
           allChecked={allChecked}
           help
           btnList={[
-            { title: '移除', key: 'remove' },
-            { title: '组卷', key: 'group', tag: 'vip' },
-            { title: '导出', key: 'export', tag: 'vip', disabled: true },
+            { title: '移除', key: 'clear' },
+            { title: '组卷', key: 'group', tag: 'vip', disabled: !info.vip },
+            { title: '导出', key: 'export', tag: 'vip', disabled: !info.vip },
           ]}
           right={
-            <div className="tip">
-              2019-06-03 1530 组卷50题,做对30题。<span>移除正确题目</span>
-              <Icon type="close-circle" theme="filled" />
+            latest && <div className="tip">
+              {formatDate(latest.updateTime, 'YYYY-MM-DD HH:mm')} 组卷{latest.questionNumber}题,做对{latest.userCorrect}题。<span onClick={() => {
+                this.remove(latest);
+              }}>移除正确题目</span>
+              <Icon type="close-circle" theme="filled" onClick={() => {
+                this.clearErrorReport();
+              }} />
             </div>
           }
           onAll={checked => this.onAll(checked)}
@@ -240,6 +407,7 @@ export default class extends Page {
           data={list}
           current={page}
           total={total}
+          pageSize={this.state.search.size}
           selectList={selectList}
           onSelect={l => this.onSelect(l)}
           onSort={v => this.onSort(v)}
@@ -251,8 +419,10 @@ export default class extends Page {
   }
 
   renderModal() {
+    const { showTips, showWarn, warn = {}, showClearCollectConfirm, clearInfo = {}, showGroupConfirm, groupInfo = {}, showExportConfirm, showExportAuthConfirm, exportInfo = {}, showExportWait, showExamination, showVip, showReal } = this.state;
+    const { info } = this.props.user;
     return [
-      <Modal title="操作提示" confirmText="好的,知道了" btnAlign="center" onConfirm={() => {}}>
+      <Modal show={showTips} title="操作提示" confirmText="好的,知道了" btnAlign="center" onConfirm={() => this.setState({ showTips: false })}>
         <div className="flex-layout m-b-2">
           <div className="flex-block">
             <div className="t-1 t-s-18">组卷功能</div>
@@ -272,49 +442,55 @@ export default class extends Page {
           查阅以上信息。
         </div>
       </Modal>,
-      <Modal title="组卷练习" confirmText="好的,知道了" btnAlign="center" onConfirm={() => {}}>
-        <div className="t-2 t-s-18">不可同时选中语文题和数学题,请重新选择。</div>
+      <Modal show={showWarn} title={warn.title} confirmText="好的,知道了" btnAlign="center" onConfirm={() => this.setState({ showWarn: false })}>
+        <div className="t-2 t-s-18">{warn.content}</div>
       </Modal>,
-      <Modal title="组卷练习" confirmText="开始练习" onConfirm={() => {}} onCancel={() => {}}>
+      <Modal show={showGroupConfirm} title="组卷练习" confirmText="开始练习" onConfirm={() => this.group()} onCancel={() => this.setState({ showGroupConfirm: false })}>
         <div className="t-2 t-s-18">
-          您共选择了 <span className="t-4">50</span> 道题目,是否开始练习?
-        </div>
+          您共选择了 <span className="t-4">{groupInfo.questionNoIds ? groupInfo.questionNoIds.length : 0}</span> 道题目,是否开始练习?</div>
         <div className="t-2 t-s-16">
           <Checkbox checked className="m-r-5" />
           剔除已组卷练习 <Select
             theme="white"
-            value="1"
-            list={[{ key: '1', title: '2' }, { key: '2', title: '3' }]}
+            value={groupInfo.filterTimes}
+            list={[{ key: 2, title: '2' }, { key: 3, title: '3' }]}
+            onChange={(key) => {
+              groupInfo.filterTimes = key;
+              this.setState({ groupInfo });
+            }}
           />{' '}
           次以上的错题
         </div>
       </Modal>,
-      <Modal title="移出" onConfirm={() => {}} onCancel={() => {}}>
+      <Modal show={showClearCollectConfirm} title="移出" onConfirm={() => this.clearErrorQuestion()} onCancel={() => this.setState({ showClearCollectConfirm: false })}>
         <div className="t-2 t-s-18">
-          当前选中的 <span className="t-4">50</span> 道题即将被移出错题本,移出后无法恢复,是否继续?
+          当前选中的 <span className="t-4">{clearInfo.questionNoIds ? clearInfo.questionNoIds.length : 0}</span> 道题即将被移出错题本,移出后无法恢复,是否继续?
         </div>
       </Modal>,
-      <Modal title="导出" confirmText="好的,知道了" btnAlign="center" onConfirm={() => {}}>
+      <Modal show={showExportWait} title="导出" confirmText="好的,知道了" btnAlign="center" onConfirm={() => this.setState({ showExportWait: false })}>
         <div className="t-2 t-s-18">正在下载中,请不要关闭当前页面!</div>
       </Modal>,
-      <Modal title="导出" confirmText="导出" onConfirm={() => {}} onCancel={() => {}}>
+      <Modal show={showExportConfirm} title="导出" confirmText="导出" onConfirm={() => this.export()} onCancel={() => this.setState({ showExportConfirm: false })}>
         <div className="t-2 t-s-18 m-b-5">
-          当前共选中 <span className="t-4">50</span> 道题,请确认需要导出的内容:
+          当前共选中 <span className="t-4">{exportInfo.questionNoIds ? exportInfo.questionNoIds.length : 0}</span> 道题,请确认需要导出的内容:
         </div>
         <div className="t-2 t-s-16">
           {exportType.map(item => {
             return (
               <div className="d-i-b m-b-5" style={{ width: 135 }}>
-                <Checkbox checked className="m-r-5" />
+                <Checkbox checked={exportInfo.info ? exportInfo.info.indexOf(item.key) >= 0 : false} className="m-r-5" onChange={() => {
+                  exportInfo.info.push(item.key);
+                  this.setState({ exportInfo });
+                }} />
                 {item.title}
               </div>
             );
           })}
         </div>
       </Modal>,
-      <Modal title="导出" confirmText="导出" onConfirm={() => {}} onCancel={() => {}}>
+      <Modal show={showExportAuthConfirm} title="导出" confirmText="导出" onConfirm={() => this.export()} onCancel={() => this.setState({ showExportAuthConfirm: false })}>
         <div className="t-2 t-s-18 m-b-5">
-          当前共选中 <span className="t-4">50</span> 道题,请确认需要导出的内容:
+          当前共选中 <span className="t-4">{exportInfo.questionNoIds ? exportInfo.questionNoIds.length : 0}</span> 道题,请确认需要导出的内容:
         </div>
         <div className="t-2 t-s-16 m-b-2">
           {exportType
@@ -322,7 +498,10 @@ export default class extends Page {
             .map(item => {
               return (
                 <div className="d-i-b m-b-2" style={{ width: 135 }}>
-                  <Checkbox checked className="m-r-5" />
+                  <Checkbox checked={exportInfo.info ? exportInfo.info.indexOf(item.key) >= 0 : false} className="m-r-5" onChange={() => {
+                    exportInfo.info.push(item.key);
+                    this.setState({ exportInfo });
+                  }} />
                   {item.title}
                 </div>
               );
@@ -330,7 +509,7 @@ export default class extends Page {
         </div>
         <div className="b-b m-b-2 m-t-2" />
         <div className="t-3 m-b-1">
-          以下内容需实名认证后才可导出: <a className="f-r">去认证 ></a>
+          以下内容需实名认证后才可导出: <a className="f-r" onClick={() => this.setState({ showExportAuthConfirm: false, showReal: true })}>去认证 ></a>
         </div>
         <div className="t-2 t-s-16 m-b-2">
           {exportType
@@ -345,6 +524,21 @@ export default class extends Page {
             })}
         </div>
       </Modal>,
+      <Examination
+        show={showExamination}
+        data={info}
+        onConfirm={() => this.setState({ showExamination: false })}
+        onCancel={() => this.setState({ showExamination: false })}
+        onClose={() => this.setState({ showExamination: false })}
+      />,
+      <RealAuth show={showReal} data={info} onConfirm={() => this.setState({ showReal: false })} />,
+      <VipRenew
+        show={showVip}
+        data={info}
+        onReal={() => this.setState({ showVip: false, showReal: true })}
+        onPrepare={() => this.setState({ showVip: false, showExamination: true })}
+        onClose={() => this.setState({ showVip: false })}
+      />,
     ];
   }
 }

+ 46 - 52
front/project/www/routes/my/main/page.js

@@ -59,10 +59,12 @@ class LogItem extends Component {
   }
 
   renderExercise() {
+    const { data } = this.props;
+    const { detail = [] } = data;
     const { open } = this.state;
     return (
       <div hidden={!open} className="table">
-        <UserTable size="small" columns={this.columns} data={[{ title: '123' }]} />
+        <UserTable size="small" columns={this.columns} data={detail} />
         <div className="t-r">
           <Link to="/exercise">继续练习></Link>
         </div>
@@ -71,45 +73,38 @@ class LogItem extends Component {
   }
 
   renderExamination() {
+    const { data } = this.props;
+    const { detail = [] } = data;
     const { open } = this.state;
     return (
       <div hidden={!open} className="table">
-        <div className="title p-l-5 m-b-1 t-1 t-s-18 f-w-b">
-          千行-CAT01<div className="f-r t-3 t-s-12 f-w-d">2019-05-16 16:21:06</div>
-        </div>
-        <UserTable
-          size="small"
-          columns={[
-            { key: '1', title: '' },
-            { key: '2', title: 'Tobal' },
-            { key: '3', title: 'IR' },
-            { key: '4', title: 'Verbal' },
-            { key: '5', title: 'Quant' },
-          ]}
-          data={[{ title: '123' }]}
-        />
-        <div className="title p-l-5 m-b-1 t-1 t-s-18 f-w-b m-t-2">
-          净化版GWD-CAT01 <div className="f-r t-3 t-s-12 f-w-d">2019-05-16 16:21:06</div>
-        </div>
-        <UserTable
-          size="small"
-          columns={[
-            { key: '1', title: '' },
-            { key: '2', title: 'Tobal' },
-            { key: '3', title: 'IR' },
-            { key: '4', title: 'Verbal' },
-            { key: '5', title: 'Quant' },
-          ]}
-          data={[{ title: '123' }]}
-        />
+        {detail.map(row => {
+          return [
+            <div className="title p-l-5 m-b-1 t-1 t-s-18 f-w-b">
+              {row.title}<div className="f-r t-3 t-s-12 f-w-d">{row.time}</div>
+            </div>,
+            <UserTable
+              size="small"
+              columns={[
+                { key: '', title: '', render: () => '得分/排名' },
+                { key: 'total', title: 'Total', render: (text, record) => `${record.totalScore}/${record.totalRank}th` },
+                { key: 'ir', title: 'IR', render: (text, record) => `${record.irScore}/${record.irRank}th` },
+                { key: '4', title: 'Verbal', render: (text, record) => `${record.verbalScore}/${record.verbalRank}th` },
+                { key: '5', title: 'Quant', render: (text, record) => `${record.quantScore}/${record.quantRank}th` },
+              ]}
+              data={[row.score]}
+            />];
+        })}
         <div className="t-r">
-          <Link to="/exercise">继续练习></Link>
+          <Link to="/examination">继续练习></Link>
         </div>
       </div>
     );
   }
 
   renderCourse() {
+    const { data } = this.props;
+    const { detail = [] } = data;
     const { open } = this.state;
     return (
       <div hidden={!open} className="table">
@@ -117,16 +112,23 @@ class LogItem extends Component {
           header={false}
           size="small"
           even="odd"
-          columns={[{ key: '1', width: 100 }, { key: '2', width: 310 }, { key: '3', width: 120 }]}
-          data={[
-            { 1: '语法SC', 2: '课时3:代词的指代', 3: '2019-04-27\n 15:14:29' },
-            { 1: '逻辑 RC', 2: '课时3:代词的指代', 3: '2019-04-27\n 15:14:29' },
-            { 1: '阅读CR', 2: '课时3:代词的指代', 3: '2019-04-27\n 15:14:29' },
-            { 1: '逻辑 RC', 2: '课时3:代词的指代', 3: '2019-04-27\n 15:14:29' },
-          ]}
+          columns={[
+            { key: 'type', width: 100 },
+            { key: 'title', width: 310 },
+            {
+              key: 'time',
+              width: 120,
+              render: (text) => {
+                return <div className="sub">
+                  <div className="t-2 t-s-12">{text.split(' ')[0]}</div>
+                  <div className="t-6 t-s-12">{text.split(' ')[1]}</div>
+                </div>;
+              },
+            }]}
+          data={detail}
         />
         <div className="t-r">
-          <Link to="/exercise">继续练习></Link>
+          <Link to="/course">继续学习></Link>
         </div>
       </div>
     );
@@ -280,9 +282,7 @@ export default class extends Page {
           },
           { title: `<b>${result.exerciseQuestion || '-'}</b>题`, width: 80 },
           {
-            title: `超过了<b>${
-              result.exerciseExceed ? formatPercent(result.exerciseExceed.rank, result.exerciseExceed.total) : '-%'
-            }</b>的用户`,
+            title: `超过了<b>${result.exerciseExceed ? formatPercent(result.exerciseExceed.rank, result.exerciseExceed.total) : '-%'}</b>的用户`,
           },
         ],
         detail: [
@@ -319,11 +319,7 @@ export default class extends Page {
           },
           { title: `<b>${result.examinationPaper || '-'}</b>套卷`, width: 80 },
           {
-            title: `超过了<b>${
-              result.examinationExceed
-                ? formatPercent(result.examinationExceed.rank, result.examinationExceed.total)
-                : '-%'
-            }</b>的用户`,
+            title: `超过了<b>${result.examinationExceed ? formatPercent(result.examinationExceed.rank, result.examinationExceed.total) : '-%'}</b>的用户`,
           },
         ],
         detail: result.examinationList.map(row => {
@@ -347,16 +343,14 @@ export default class extends Page {
           },
           { title: `<b>${result.courseNumber || '-'}</b>课时`, width: 80 },
           {
-            title: `超过了<b>${
-              result.courseExceed ? formatPercent(result.courseExceed.rank, result.courseExceed.total) : '-%'
-            }</b>的用户`,
+            title: `超过了<b>${result.courseExceed ? formatPercent(result.courseExceed.rank, result.courseExceed.total) : '-%'}</b>的用户`,
           },
         ],
         detail: result.courseList.map(row => {
           return {
             type: QuestionTypeMap[row.extend],
             title: `课时${row.no}: ${row.title}`,
-            time: formatDate(row.createTime, 'YYYY-MM-DD\n HH:mm:ss'),
+            time: formatDate(row.createTime, 'YYYY-MM-DD HH:mm:ss'),
           };
         }),
       });
@@ -457,8 +451,8 @@ export default class extends Page {
             )}
           </div>
         </div>
-        {study.map((log, index) => {
-          return <LogItem key={index} data={log} />;
+        {study.map((data, index) => {
+          return <LogItem key={index} data={data} />;
         })}
       </div>
     );

+ 4 - 0
front/project/www/routes/my/message/index.less

@@ -11,6 +11,10 @@
 
     .user-table {
       font-size: 12px;
+
+      td {
+        vertical-align: top;
+      }
     }
   }
 }

+ 24 - 2
front/project/www/routes/my/message/page.js

@@ -13,7 +13,28 @@ import { getMap, formatDate } from '../../../../../src/services/Tools';
 
 const MessageTypeMap = getMap(MessageType, 'value', 'label');
 
-const columns = [{ title: '消息', key: 'content' }, { title: '类型', key: 'type' }, { title: '发送时间', key: 'date' }];
+const columns = [
+  {
+    title: '消息',
+    key: 'title',
+    render: (text, row) => {
+      return <div>
+        {row.isRead ? <span className='dot'>{text}</span> : text}
+        {row.content && <div className=''>{row.content}{row.link && <a className='m-l-5' href={row.link} target="_blank">查看详情</a>}</div>}
+      </div>;
+    },
+  },
+  { title: '类型', key: 'type' },
+  {
+    title: '发送时间',
+    key: 'date',
+    render: (text) => {
+      return <div className="sub">
+        <div className="t-2 t-s-12">{text.split(' ')[0]}</div>
+        <div className="t-6 t-s-12">{text.split(' ')[1]}</div>
+      </div>;
+    },
+  }];
 
 export default class extends Page {
   constructor(props) {
@@ -47,7 +68,7 @@ export default class extends Page {
     My.message(Object.assign({ read: data.tab === 'unread' ? 0 : null }, this.state.search)).then(result => {
       result.list = result.list.map(row => {
         row.type = MessageTypeMap[row.type];
-        row.date = formatDate(row.createTime, 'YYYY-MM-DD\n HH:mm:ss');
+        row.date = formatDate(row.createTime, 'YYYY-MM-DDHH:mm:ss');
 
         return row;
       });
@@ -115,6 +136,7 @@ export default class extends Page {
           data={list}
           current={page}
           total={total}
+          pageSize={this.state.search.size}
           onChange={(value) => {
             this.onChangePage(value);
           }}

+ 2 - 1
front/project/www/routes/my/note/index.less

@@ -27,7 +27,7 @@
         margin: 0;
         font-size: 12px;
 
-        th.select {
+        th.check {
           padding-left: 45px;
         }
 
@@ -43,6 +43,7 @@
         td {
           padding: 20px 15px;
           border-bottom: 1px solid #eee;
+          vertical-align: top;
         }
 
         .group {

文件差異過大導致無法顯示
+ 160 - 82
front/project/www/routes/my/note/page.js


+ 4 - 0
front/project/www/routes/my/order/index.less

@@ -6,6 +6,10 @@
 
     .user-table {
       font-size: 12px;
+
+      td {
+        vertical-align: top;
+      }
     }
   }
 }

+ 119 - 82
front/project/www/routes/my/order/page.js

@@ -11,10 +11,11 @@ import Modal from '../../../components/Modal';
 import More from '../../../components/More';
 import IconButton from '../../../components/IconButton';
 import { Order } from '../../../stores/order';
-import { RecordSource, ServiceKey } from '../../../../Constant';
+import { RecordSource, ServiceKey, InvoiceType } from '../../../../Constant';
 
 const RecordSourceMap = getMap(RecordSource, 'value', 'label');
 const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
+
 function formatTitle(record) {
   if (record.productType === 'course-package') {
     return (record.coursePackage || {}).title;
@@ -30,66 +31,7 @@ function formatTitle(record) {
   }
   return '';
 }
-const columns = [
-  { title: '订单编号', key: 'id' },
-  {
-    title: '服务',
-    key: 'title',
-    render: (text, record) => {
-      const actionList = [];
-      if (record.canInvoice && !record.hasInvoice) {
-        actionList.push({ key: 'invoice', label: '开发票' });
-      }
-      actionList.push({ key: 'detail', label: '订单详情' });
-      const onAction = (key) => {
-        switch (key) {
-          case 'invoice':
-            this.setState({ showInvoice: true, invoice: { orderId: record.id, money: record.invoiceMoney } });
-            break;
-          case 'detail':
-            openLink(`/order/detail/${record.id}`);
-            break;
-          default:
-        }
-      };
-      let content = '';
-      if (record.checkouts.length > 3) {
-        content = [<div className="flex-block">{formatTitle(record.checkouts[0])}<br />等{record.checkouts.length}个商品</div>];
-      } else {
-        content = record.checkouts.map(row => {
-          return formatTitle(row);
-        }).join(<br />);
-      }
-      return <div className="t-2">
-        <div className="flex-layout m-b-5">
-          {content}
-          <More actionList={actionList} onAction={onAction} ><IconButton type="more" /></More>
-        </div>
-      </div>;
-    },
-  },
-  {
-    title: '购买时间',
-    key: 'createTime',
-    render: text => {
-      return formatDate(text, 'YYYY-MM-DD\nHH:mm:ss');
-    },
-  },
-  {
-    title: '付款方式',
-    key: 'payMethod',
-    render: (text) => {
-      return RecordSourceMap[text];
-    },
-  },
-  {
-    title: '付款金额',
-    key: 'money',
-    render: text => {
-      return formatMoney(text);
-    },
-  },
-];
+
 
 export default class extends Page {
   constructor(props) {
@@ -97,14 +39,98 @@ export default class extends Page {
     super(props);
   }
 
+  init() {
+    this.columns = [
+      {
+        title: '订单编号',
+        key: 'id',
+        render: (text) => {
+          return text;
+        },
+      },
+      {
+        title: '服务',
+        key: 'title',
+        render: (text, record) => {
+          const actionList = [];
+          // if (record.canInvoice && !record.hasInvoice) {
+          actionList.push({ key: 'invoice', label: '开发票' });
+          // }
+          actionList.push({ key: 'detail', label: '订单详情' });
+          const onAction = (value) => {
+            const { key } = value;
+            switch (key) {
+              case 'invoice':
+                this.setState({ showInvoice: true, invoice: { orderId: record.id, money: record.invoiceMoney, invoiceType: InvoiceType[0].value } });
+                break;
+              case 'detail':
+                openLink(`/order/detail/${record.id}`);
+                break;
+              default:
+            }
+          };
+          let content = [];
+          if (record.checkouts.length > 3) {
+            content.push(<div className="flex-layout m-b-5">
+              <div className="flex-block">{formatTitle(record.checkouts[0])}<br />等{record.checkouts.length}个商品</div>
+              <More menu={actionList} onClick={onAction} ><IconButton type="more" /></More>
+            </div>);
+          } else {
+            content = record.checkouts.map((row, index) => {
+              return <div className="flex-layout m-b-5">
+                <div className="flex-block">{formatTitle(row)}</div>
+                {index === 0 && <More menu={actionList} onClick={onAction} ><IconButton type="more" /></More>}
+                {row.productType === 'data' && <IconButton type="download" onClick={() => {
+                  openLink(row.data.resource);
+                }} />}
+              </div>;
+            });
+          }
+          return <div className="t-2">
+            {content}
+          </div >;
+        },
+      },
+      {
+        title: '购买时间',
+        key: 'createTime',
+        render: text => {
+          return <div className="sub">
+            <div className="t-2 t-s-12">{text.split(' ')[0]}</div>
+            <div className="t-6 t-s-12">{text.split(' ')[1]}</div>
+          </div>;
+        },
+      },
+      {
+        title: '付款方式',
+        key: 'payMethod',
+        render: (text) => {
+          return RecordSourceMap[text];
+        },
+      },
+      {
+        title: '付款金额',
+        key: 'money',
+        render: text => {
+          return <span className='t-7'>¥{formatMoney(text)}</span>;
+        },
+      },
+    ];
+  }
+
   initState() {
     return {
-      list: [{}],
+      list: [],
     };
   }
 
   initData() {
     Order.list(this.state.search).then(result => {
+      result.list = result.list.map(row => {
+        row.checkouts = row.checkouts || [];
+        row.createTime = formatDate(row.createTime, 'YYYY-MM-DD HH:mm:ss');
+        return row;
+      });
       this.setState({ list: result.list, total: result.total, page: this.state.search.page });
     });
   }
@@ -114,6 +140,8 @@ export default class extends Page {
   }
 
   submitInvoice(invoice) {
+    if (!invoice.title) return;
+    if (invoice.invoiceType === 'enterprise' && !invoice.identity) return;
     Order.openInvoice(invoice)
       .then(() => {
         this.refresh();
@@ -130,68 +158,77 @@ export default class extends Page {
   }
 
   renderTable() {
-    const { list, total, page } = this.state;
+    const { list, total, page, showInvoice, showInvoiceFinish, invoice = {} } = this.state;
+    const { info } = this.props.user;
     return (
       <div className="table-layout">
         <UserTable
           size="small"
-          columns={columns}
+          columns={this.columns}
           data={list}
           onChange={p => this.onChangePage(p)}
           total={total}
           current={page}
+          pageSize={this.state.search.size}
         />
-        <Modal title="开发票" width={630} btnType="link" confirmText="申请" onConfirm={() => { }} onCancel={() => { }}>
+        <Modal show={showInvoice} title="开发票" width={630} btnType="link" confirmText="申请" onConfirm={() => this.submitInvoice(invoice)} onCancel={() => this.setState({ showInvoice: false })}>
           <div className="input-layout m-b-2 t-2 t-s-16">
             <div className="label m-r-5">发票类型:</div>
             <div className="input-block">
-              <Radio />
+              <Radio checked />
               普通增值税电子发票
             </div>
           </div>
           <div className="input-layout m-b-2 t-2 t-s-16">
             <div className="label m-r-5">抬头类型:</div>
             <div className="input-block">
-              <span className="m-r-2">
-                <Radio />
-                个人
-              </span>
-              <span className="m-l-2">
-                <Radio />
-                公司
-              </span>
+              {InvoiceType.map(row => {
+                return <span className="m-r-2">
+                  <Radio checked={invoice.invoiceType === row.value} onChange={() => {
+                    invoice.invoiceType = row.value;
+                    this.setState({ invoice });
+                  }} />
+                  {row.label}
+                </span>;
+              })}
             </div>
           </div>
           <div className="input-layout m-b-2 t-2 t-s-16">
             <div className="label m-r-5">发票抬头:</div>
             <div className="input-block">
-              <input style={{ width: 330, paddingTop: 3, paddingBottom: 3 }} className="b-c-1 p-l-1 p-r-1 p-l-1" />
+              <input value={invoice.title} style={{ width: 330, paddingTop: 3, paddingBottom: 3 }} className="b-c-1 p-l-1 p-r-1 p-l-1" onChange={e => {
+                invoice.title = e.target.value;
+                this.setState({ invoice });
+              }} />
             </div>
           </div>
-          <div className="input-layout m-b-2 t-2 t-s-16">
+          {invoice.invoiceType === 'enterprise' && <div className="input-layout m-b-2 t-2 t-s-16">
             <div className="label m-r-5">纳税人识别号:</div>
             <div className="input-block">
-              <input style={{ width: 300, paddingTop: 3, paddingBottom: 3 }} className="b-c-1 p-l-1 p-r-1 p-l-1" />
+              <input value={invoice.identity} style={{ width: 300, paddingTop: 3, paddingBottom: 3 }} className="b-c-1 p-l-1 p-r-1 p-l-1" onChange={e => {
+                invoice.identity = e.target.value;
+                this.setState({ invoice });
+              }} />
             </div>
-          </div>
+          </div>}
           <div className="input-layout m-b-2 t-2 t-s-16">
             <div className="label m-r-5">发票内容:</div>
             <div className="input-block">
-              <Radio />
+              <Radio checked />
               商品明细
             </div>
           </div>
           <div className="input-layout t-2 t-s-16">
             <div className="label m-r-5">发票金额:</div>
-            <div className="input-block">¥ 210010.0</div>
+            <div className="input-block">¥ {formatMoney(invoice.money)}</div>
           </div>
         </Modal>
-        <Modal title="申请成功" width={630} confirmText="好的,知道了" btnAlign="center" onConfirm={() => { }}>
+        <Modal show={showInvoiceFinish} title="申请成功" width={630} confirmText="好的,知道了" btnAlign="center" onConfirm={() => this.setState({ showInvoiceFinish: false })}>
           <div className="t-2 t-s-18">
             <Icon className="t-5 m-r-5" type="check" />
             我们会在三个工作日内将电子发票发送至您的绑定邮箱:
           </div>
-          <div className="t-2 t-s-18">201-30-2103@12321.com</div>
+          <div className="t-2 t-s-18">{info.email}</div>
           <div className="m-b-2 t-2 t-s-18">请注意查收。</div>
           <div className="m-t-2 t-3 t-s-14">我们也会通过站内信的方式通知您</div>
         </Modal>

+ 1 - 0
front/project/www/routes/my/report/index.less

@@ -44,6 +44,7 @@
           padding: 20px 15px;
           background: #FBFBFB;
           border-bottom: 1px solid #eee;
+          vertical-align: top;
         }
 
         .group {

+ 317 - 50
front/project/www/routes/my/report/page.js

@@ -1,35 +1,29 @@
 import React from 'react';
 import './index.less';
 import Page from '@src/containers/Page';
-import { timeRange } from '@src/services/Tools';
+import { timeRange, formatPercent, formatSeconds, formatDate, getMap } from '@src/services/Tools';
 import UserLayout from '../../../layouts/User';
 import UserAction from '../../../components/UserAction';
 import UserTable from '../../../components/UserTable';
 import IconButton from '../../../components/IconButton';
 import menu, { refreshQuestionType, refreshStruct } from '../index';
 import Tabs from '../../../components/Tabs';
-import { TimeRange } from '../../../../Constant';
+import { TimeRange, QuestionType } from '../../../../Constant';
 import { My } from '../../../stores/my';
+import { Main } from '../../../stores/main';
+import { Question } from '../../../stores/question';
+
+const QuestionnTypeMap = getMap(QuestionType, 'value', 'label');
 
-const columns = [
-  { key: 'title', title: '练习册名称', fixSort: true },
-  { key: 'create_time', title: '做题时间', fixSort: true },
-  { key: 'correct', title: '正确率', sort: true },
-  { key: 'time', title: '平均耗时', sort: true },
-  { key: 'progress', title: '完成度' },
-  {
-    key: 'report',
-    title: '报告',
-    render() {
-      return <IconButton type="report" tip="report" />;
-    },
-  },
-];
 
 export default class extends Page {
   constructor(props) {
     props.size = 10;
     super(props);
+    Main.getExaminationNumber().then(nums => {
+      this.nums = nums;
+      this.setState({ load: false });
+    });
   }
 
   initState() {
@@ -38,12 +32,264 @@ export default class extends Page {
       timerange: 'today',
       filterMap: {},
       sortMap: {},
-      data: [{}, {}],
       selectList: [],
       allChecked: false,
     };
   }
 
+  init() {
+    this.exerciseColumns = [
+      {
+        key: 'title',
+        title: '练习册名称',
+        fixSort: true,
+        render: (text, record) => {
+          const { reports } = record;
+          return reports.map((report, index) => {
+            return <div className="sub">
+              {index === 0 && text}
+            </div>;
+          });
+        },
+      },
+      {
+        key: 'latest_time',
+        title: '做题时间',
+        fixSort: true,
+        render: (text, record) => {
+          const { reports } = record;
+          return reports.map(report => {
+            const time = formatDate(report.updateTime, 'YYYY-MM-DD HH:mm:ss');
+            return <div className="sub">
+              <div className="t-2 t-s-12">{time.split(' ')[0]}</div>
+              <div className="t-6 t-s-12">{time.split(' ')[1]}</div>
+            </div>;
+          });
+        },
+      },
+      {
+        key: 'correct',
+        title: '正确率',
+        sort: true,
+        render: (text, record) => {
+          const { reports } = record;
+          return reports.map(report => {
+            return <div className="sub">
+              <div className="t-2 t-s-12">{formatPercent(report.userCorrect, report.userNumber, false)}</div>
+              <div className="t-6 t-s-12">全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}</div>
+            </div>;
+          });
+        },
+      },
+      {
+        key: 'time',
+        title: '平均耗时',
+        sort: true,
+        render: (text, record) => {
+          const { reports } = record;
+          return reports.map(report => {
+            return <div className="sub">
+              <div className="t-2 t-s-12">{formatSeconds(report.userTime / report.userNumber)}</div>
+              <div className="t-6 t-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
+            </div>;
+          });
+        },
+      },
+      {
+        key: 'progress',
+        title: '完成度',
+        render: (text, record) => {
+          const { reports } = record;
+          return reports.map(report => {
+            return <div className="sub">
+              <div className="t-2 t-s-12">{formatPercent(report.userNumber, report.questionNumber)}</div>
+            </div>;
+          });
+        },
+      },
+      {
+        key: 'report',
+        title: '报告',
+        render(text, record) {
+          const { reports } = record;
+          return reports.map(report => {
+            return <div className="sub">
+              <IconButton type="report" tip="report" onClick={() => {
+                Question.reportLink({ report });
+              }} />
+            </div>;
+          });
+        },
+      },
+    ];
+
+    this.examinationColumns = [
+      { key: 'title', title: '模考名称', fixSort: true },
+      {
+        key: 'latest_time',
+        title: '做题时间',
+        fixSort: true,
+        render: (text, record) => {
+          const time = formatDate(record.latestTime, 'YYYY-MM-DD HH:mm:ss');
+          return <div className="sub">
+            <div className="t-2 t-s-12">{time.split(' ')[0]}</div>
+            <div className="t-6 t-s-12">{time.split(' ')[1]}</div>
+          </div>;
+        },
+      },
+      {
+        key: 'overall',
+        title: 'Overall',
+        render: (text, record) => {
+          const [report] = record.reports;
+          if (!report) return null;
+          if (!report.qxCat) return <div className="f-s-12">仅CAT模考<br />提供分数</div>;
+          return <div className="sub">
+            <div className="t-2 t-s-12">{report.score.totalScore}</div>
+            <div className="t-6 t-s-12">{record.qxCat === 1 ? Math.round(record.origin.totalScore / record.origin.totalTimes) : Math.round(record.origin.secondTotalScore / record.origin.secondTotalTimes)}</div>
+          </div>;
+        },
+      },
+      {
+        key: 'verbal',
+        title: 'Verbal',
+        render: (text, record) => {
+          const [report] = record.reports;
+          if (!report) return null;
+          if (!report.qxCat) return <div className="f-s-12">{formatPercent(report.setting.number.verbal, this.nums.verbal.number, false)}</div>;
+          return <div className="sub">
+            <div className="t-2 t-s-12">{report.score.verbalScore}</div>
+            <div className="t-6 t-s-12">{record.qxCat === 1 ? Math.round(record.origin.verbalScore / record.origin.totalTimes) : Math.round(record.origin.secondVerbalScore / record.origin.secondTotalTimes)}</div>
+          </div>;
+        },
+      },
+      {
+        key: 'quant',
+        title: 'Quant',
+        render: (text, record) => {
+          const [report] = record.reports;
+          if (!report) return null;
+          if (!report.qxCat) return <div className="f-s-12">{formatPercent(report.setting.number.quant, this.nums.quant.number, false)}</div>;
+          return <div className="sub">
+            <div className="t-2 t-s-12">{report.score.quantScore}</div>
+            <div className="t-6 t-s-12">{record.qxCat === 1 ? Math.round(record.origin.quantScore / record.origin.totalTimes) : Math.round(record.origin.secondQuantScore / record.origin.secondTotalTimes)}</div>
+          </div>;
+        },
+      },
+      {
+        key: 'ir',
+        title: 'IR',
+        render: (text, record) => {
+          const [report] = record.reports;
+          if (!report) return null;
+          if (!report.qxCat) return <div className="f-s-12">{formatPercent(report.setting.number.ir, this.nums.ir.number, false)}</div>;
+          return <div className="sub">
+            <div className="t-2 t-s-12">{report.score.irScore}</div>
+            <div className="t-6 t-s-12">{record.qxCat === 1 ? Math.round(record.origin.irScore / record.origin.totalTimes) : Math.round(record.origin.secondIrScore / record.origin.secondTotalTimes)}</div>
+          </div>;
+        },
+      },
+      {
+        key: 'report',
+        title: '报告',
+        render: (text, record) => {
+          const [report] = record.reports;
+          return <IconButton type="report" tip="report" onClick={() => {
+            Question.reportLink({ report });
+          }} />;
+        },
+      },
+    ];
+
+    this.originColumns = [
+      {
+        key: 'title',
+        title: '名称',
+        fixSort: true,
+        render: (text, record) => {
+          const { reports } = record;
+          return reports.map((report, index) => {
+            return <div className="sub">
+              {index === 0 && text}
+            </div>;
+          });
+        },
+      },
+      {
+        key: 'latest_time',
+        title: '做题时间',
+        fixSort: true,
+        render: (text, record) => {
+          const time = formatDate(record.latestTime, 'YYYY-MM-DD HH:mm:ss');
+          return <div className="sub">
+            <div className="t-2 t-s-12">{time.split(' ')[0]}</div>
+            <div className="t-6 t-s-12">{time.split(' ')[1]}</div>
+          </div>;
+        },
+      },
+      {
+        key: 'questionType',
+        title: '题型',
+        render: (text, record) => {
+          return record.questionTypes.map(row => <p>{QuestionnTypeMap[row]}</p>);
+        },
+      },
+      {
+        key: 'progress',
+        title: '完成度',
+        render: (text, record) => {
+          const { reports } = record;
+          return reports.map(report => {
+            return <div className="sub">
+              <div className="t-2 t-s-12">{formatPercent(report.userNumber, report.questionNumber, false)}</div>
+              <div className="t-6 t-s-12">{report.userNumber}/{report.questionNumber}</div>
+            </div>;
+          });
+        },
+      },
+      {
+        key: 'correct',
+        title: '正确率',
+        sort: true,
+        render: (text, record) => {
+          const { reports } = record;
+          return reports.map(report => {
+            return <div className="sub">
+              <div className="t-2 t-s-12">{formatPercent(report.userCorrect, report.userNumber, false)}</div>
+            </div>;
+          });
+        },
+      },
+      {
+        key: 'time',
+        title: '平均耗时',
+        sort: true,
+        render: (text, record) => {
+          const { reports } = record;
+          return reports.map(report => {
+            return <div className="sub">
+              <div className="t-2 t-s-12">{formatSeconds(report.userTime / report.userNumber)}</div>
+            </div>;
+          });
+        },
+      },
+      {
+        key: 'report',
+        title: '报告',
+        render: (text, record) => {
+          const { reports } = record;
+          return reports.map(report => {
+            return <div className="sub">
+              <IconButton type="report" tip="report" onClick={() => {
+                Question.reportLink({ report });
+              }} />
+            </div>;
+          });
+        },
+      },
+    ];
+  }
+
   initData() {
     const data = Object.assign(this.state, this.state.search);
     data.filterMap = this.state.search;
@@ -62,7 +308,13 @@ export default class extends Page {
             return `${key} ${data.sortMap[key]}`;
           }).join(','),
         })).then(result => {
-          this.setState({ list: result.list, total: result.total, page: data.page });
+          result.list = result.list.map(row => {
+            row.questionTypes = row.questionTypes || [];
+            row.reports = row.reports || [];
+            row.stat = row.stat || {};
+            return row;
+          });
+          this.setState({ list: result.list, total: result.total, page: data.page, columns: this.originColumns });
         });
         break;
       case 'exercise':
@@ -71,7 +323,7 @@ export default class extends Page {
         refreshQuestionType(this, data.subject, data.questionType, { all: true, needSentence: true, allSubject: true })
           .then(({ questionTypes, courseModules }) => {
             return refreshStruct(this, data.tab, data.one, data.two, {
-              all: true, needPreview: true, needTextbook: true,
+              all: true, needPreview: true, needTextbook: false,
             })
               .then(({ structIds, latest, year }) => {
                 My.listReport(Object.assign({ module: data.tab, questionTypes, structIds, latest, year, courseModules, startTime, endTime }, this.state.search, {
@@ -79,7 +331,13 @@ export default class extends Page {
                     return `${key} ${data.sortMap[key]}`;
                   }).join(','),
                 })).then(result => {
-                  this.setState({ list: result.list, total: result.total, page: data.page });
+                  result.list = result.list.map(row => {
+                    row.questionTypes = row.questionTypes || [];
+                    row.reports = row.reports || [];
+                    row.stat = row.stat || {};
+                    return row;
+                  });
+                  this.setState({ list: result.list, total: result.total, page: data.page, columns: data.tab === 'exercise' ? this.exerciseColumns : this.examinationColumns });
                 });
               });
           });
@@ -113,8 +371,43 @@ export default class extends Page {
   }
 
   renderTable() {
-    const { tab, questionSubjectSelect, questionSubjectMap = {}, oneSelect, twoSelectMap = {}, filterMap = {}, sortMap = {}, list = [] } = this.state;
-    const { selectList = [], page, total } = this.state;
+    const { tab, questionSubjectSelect, questionSubjectMap = {}, oneSelect, twoSelectMap = {}, filterMap = {}, sortMap = {}, list = [], columns = [] } = this.state;
+    const { page, total } = this.state;
+    const selectList = [];
+    if (tab === 'exercise') {
+      selectList.push({
+        children: [{
+          key: 'subject',
+          placeholder: '学科',
+          select: questionSubjectSelect,
+        }, {
+          placeholder: '题型',
+          key: 'questionType',
+          be: 'subject',
+          selectMap: questionSubjectMap,
+        }],
+      });
+    }
+    if (tab === 'exercise' || tab === 'examination') {
+      selectList.push({
+        label: '范围',
+        children: [{
+          key: 'one',
+          placeholder: '全部',
+          select: oneSelect,
+        }, {
+          key: 'two',
+          be: 'one',
+          placeholder: '全部',
+          selectMap: twoSelectMap,
+        }],
+      });
+    }
+    selectList.push({
+      right: true,
+      key: 'timerange',
+      select: TimeRange,
+    });
     return (
       <div className="table-layout">
         <Tabs
@@ -130,34 +423,7 @@ export default class extends Page {
         />
         <UserAction
           search
-          selectList={[{
-            children: [{
-              key: 'subject',
-              placeholder: '学科',
-              select: questionSubjectSelect,
-            }, {
-              placeholder: '题型',
-              key: 'questionType',
-              be: 'subject',
-              selectMap: questionSubjectMap,
-            }],
-          }, {
-            label: '范围',
-            children: [{
-              key: 'one',
-              placeholder: '全部',
-              select: oneSelect,
-            }, {
-              key: 'two',
-              be: 'one',
-              placeholder: '全部',
-              selectMap: twoSelectMap,
-            }],
-          }, {
-            right: true,
-            key: 'timerange',
-            select: TimeRange,
-          }]}
+          selectList={selectList}
           filterMap={filterMap}
           onFilter={value => this.onFilter(value)}
           onChange={key => this.onChangeTab(key)}
@@ -168,6 +434,7 @@ export default class extends Page {
           data={list}
           current={page}
           total={total}
+          pageSize={this.state.search.size}
           selectList={selectList}
           onSelect={l => this.onSelect(l)}
           onSort={v => this.onSort(v)}

+ 107 - 36
front/project/www/routes/my/tools/page.js

@@ -20,13 +20,14 @@ import { My } from '../../../stores/my';
 import { User } from '../../../stores/user';
 import { Order } from '../../../stores/order';
 import { Textbook } from '../../../stores/textbook';
-import { DataType, ServiceKey, RecordSource } from '../../../../Constant';
+import { DataType, ServiceKey, RecordSource, TextbookFeedbackTarget } from '../../../../Constant';
 import { Main } from '../../../stores/main';
 import { Question } from '../../../stores/question';
 import Select from '../../../components/Select';
 
 const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
 const RecordSourceMap = getMap(RecordSource, 'value', 'label');
+const TextbookFeedbackTargetMap = getMap(TextbookFeedbackTarget, 'value', 'tips');
 
 const dataHistoryColumns = [
   { title: '更新时间', key: 'time', width: 120 },
@@ -50,11 +51,17 @@ const openColumns = [
 ];
 
 export default class extends Page {
+  constructor(props) {
+    props.size = 10;
+    super(props);
+  }
+
   initState() {
     return {
       tab: 'data',
       sortMap: {},
       filterMap: {},
+      feedbackError: { position: ['', '', ''] },
     };
   }
 
@@ -194,17 +201,17 @@ export default class extends Page {
       });
     });
     My.listData(Object.assign({}, this.state.search)).then(result => {
-      result = {
-        list: [
-          {
-            title: '123123',
-            latestTime: '',
-            id: 1,
-            number: 10,
-            version: '1231',
-          },
-        ],
-      };
+      // result = {
+      //   list: [
+      //     {
+      //       title: '123123',
+      //       latestTime: '',
+      //       id: 1,
+      //       number: 10,
+      //       version: '1231',
+      //     },
+      //   ],
+      // };
       this.setState({
         list: result.list.map(row => {
           row.time = formatDate(row.time, 'YYYY-MM-DD HH:mm:ss');
@@ -251,6 +258,7 @@ export default class extends Page {
 
   submitComment() {
     const { comment } = this.state;
+    if (!comment.content) return;
     My.addComment(comment.channel, comment.position, comment.content).then(() => {
       this.setState({ showComment: false, showFinish: true, comment: {} });
     });
@@ -258,21 +266,24 @@ export default class extends Page {
 
   submitFeedbackError() {
     const { feedbackError } = this.state;
+    if (!feedbackError.content || !feedbackError.originContent) return;
     My.addFeedbackErrorData(
       feedbackError.dataId,
       feedbackError.title,
-      feedbackError.position,
+      feedbackError.position.join(','),
       feedbackError.originContent,
       feedbackError.content,
     ).then(() => {
-      this.setState({ showFinish: true, showFeedbackError: false, feedbackError: {} });
+      this.setState({ showFinish: true, showFeedbackError: false, feedbackError: { position: ['', '', ''] } });
     });
   }
 
   submitFeedback() {
     const { feedback } = this.state;
-    My.addTextbookFeedback('', feedback.target, feedback.content).then(() => {
-      this.setState({ showFinish: true, showFeedback: false, feedbackError: {} });
+    if (!feedback.content) return;
+    if (feedback.target !== 'new' && !feedback.no) return;
+    My.addTextbookFeedback(feedback.questionSubject, feedback.target, feedback.no, feedback.content).then(() => {
+      this.setState({ showFinish: true, showFeedback: false, feedback: {} });
     });
   }
 
@@ -305,10 +316,12 @@ export default class extends Page {
       tab,
       comment = {},
       feedback = {},
+      feedbackError = {},
       showComment,
       showFinish,
       showUpdate,
       showFeedback,
+      showFeedbackError,
       updateList,
       updateTotal,
       maxHeight,
@@ -344,9 +357,11 @@ export default class extends Page {
         >
           <UserTable
             size="small"
+            theme="top"
             columns={updateData.columns}
             data={updateList}
             current={updateData.page}
+            pageSize={updateData.size}
             onChange={page => {
               updateData.page = page;
               if (updateData.type === 'data') {
@@ -392,43 +407,99 @@ export default class extends Page {
           </div>
         </Modal>
         <Modal
+          show={showFeedbackError}
+          title="纠错"
+          btnType="link"
+          width={630}
+          onConfirm={() => this.submitFeedbackError()}
+          onCancel={() => this.setState({ showFeedbackError: false })}
+        >
+          <div className="t-2 m-b-1 t-s-16">
+            定位:
+            <input value={feedbackError.position[0]} className="t-c b-c-1 m-r-5" style={{ width: 56 }} onChange={e => {
+              feedbackError.position[0] = e.target.value;
+              this.setState({ feedbackError });
+            }} />
+            <span className="require"></span>
+            <input value={feedbackError.position[1]} className="t-c b-c-1 m-r-5" style={{ width: 56 }} onChange={e => {
+              feedbackError.position[1] = e.target.value;
+              this.setState({ feedbackError });
+            }} />
+            <span className="require"></span> , 题号
+            <input value={feedbackError.position[2]} className="t-c b-c-1" style={{ width: 56 }} onChange={e => {
+              feedbackError.position[2] = e.target.value;
+              this.setState({ feedbackError });
+            }} />
+          </div>
+          <div className="t-2 t-s-16">错误内容是:</div>
+          <textarea
+            value={feedbackError.originContent}
+            className="b-c-1 w-10 p-10"
+            rows={10}
+            placeholder={'可简单描述您发现的问题'}
+            onChange={e => {
+              feedbackError.originContent = e.target.value;
+              this.setState({ feedbackError });
+            }}
+          />
+          <div className="t-2 t-s-16">应该更改为:</div>
+          <textarea
+            value={feedbackError.content}
+            className="b-c-1 w-10 p-10"
+            rows={10}
+            placeholder={'提供您认为正确的内容即可'}
+            onChange={e => {
+              feedbackError.content = e.target.value;
+              this.setState({ feedbackError });
+            }}
+          />
+          <div className="b-b m-t-2" />
+        </Modal>
+        <Modal
           show={showFeedback}
           title="反馈"
           width={630}
-          onConfirm={() => this.setState({ showFeedback: false })}
+          onConfirm={() => this.submitFeedback()}
           onCancel={() => this.setState({ showFeedback: false })}
         >
           <div className="t-2 t-s-16 m-b-1">
             机经类别:
             <Select
-              value={feedback.type}
+              value={feedback.questionSubject}
               theme="white"
-              list={[{ title: '数学机经', key: '1' }, { title: '逻辑机经', key: '2' }, { title: '阅读机经', key: '3' }]}
+              list={[{ title: '数学机经', key: 'quant' }, { title: '逻辑机经', key: 'rc' }, { title: '阅读机经', key: 'ir' }]}
+              onChange={(value) => {
+                feedback.questionSubject = value;
+                this.setState({ feedback });
+              }}
             />
             反馈类型:
             <Select
-              value={feedback.cate}
+              value={feedback.target}
               theme="white"
-              list={[
-                { title: '纠正解析', key: '1' },
-                { title: '完善已有机经', key: '2' },
-                { title: '提供全新机经', key: '3' },
-              ]}
+              list={TextbookFeedbackTarget}
+              onChange={(value) => {
+                feedback.target = value;
+                this.setState({ feedback });
+              }}
             />
-            <span hidden={feedback.cate === 3}>
+            <span hidden={feedback.target === 'new'}>
               题号是
-              <input style={{ width: 80 }} className="m-l-1 b-c-1 t-c" />
+              <input value={feedback.no} style={{ width: 80 }} className="m-l-1 b-c-1 t-c" onChange={e => {
+                feedback.no = e.target.value;
+                this.setState({ feedback });
+              }} />
             </span>
           </div>
-          <div className="t-2 t-s-16">正确的解析和答案应该是:</div>
+          <div className="t-2 t-s-16">{TextbookFeedbackTargetMap[feedback.target]}:</div>
           <textarea
-            value={comment.content}
+            value={feedback.content}
             className="b-c-1 w-10 p-10"
             rows={10}
-            placeholder={{ 1: '正确的解析和答案是:', 2: '补充内容是:', 3: '新机经是:' }[feedback.cate]}
+            placeholder={TextbookFeedbackTargetMap[feedback.target]}
             onChange={e => {
-              comment.content = e.target.value;
-              this.setState({ comment });
+              feedback.content = e.target.value;
+              this.setState({ feedback });
             }}
           />
           <div className="b-b m-t-2" />
@@ -520,7 +591,7 @@ export default class extends Page {
                       this.setState({ showUpdate: true });
                       this.dataHistory({ dataId: item.id, page: 1, size: 10 });
                     } else if (key === 'feedback') {
-                      this.setState({ showFeedbackError: true, feedbackError: { dataId: item.id, title: item.title } });
+                      this.setState({ showFeedbackError: true, feedbackError: { dataId: item.id, title: item.title, position: ['', '', ''] } });
                     }
                   }}
                 />
@@ -528,8 +599,8 @@ export default class extends Page {
             );
           })}
         </div>
-        {total && list.length > 0 && (
-          <UserPagination total={total} current={page} onChange={p => this.onChangePage(p)} />
+        {total > 0 && list.length > 0 && (
+          <UserPagination total={total} current={page} pageSize={this.state.search.size} onChange={p => this.onChangePage(p)} />
         )}
       </div>
     );
@@ -588,7 +659,7 @@ export default class extends Page {
                         this.setState({ showUpdate: true });
                         this.textbookHistory({ page: 1, size: 100, subject: item.subject });
                       } else if (key === 'feedback') {
-                        this.setState({ showFeedback: true });
+                        this.setState({ showFeedback: true, feedback: { questionSubject: item.subject, target: TextbookFeedbackTarget[0].value } });
                       }
                     }}
                   />

+ 2 - 2
front/project/www/routes/paper/process/page.js

@@ -304,12 +304,12 @@ export default class extends Page {
   toggleCollect() {
     const { userQuestion = {} } = this.state;
     if (!userQuestion.collect) {
-      My.addQuestionCollect(userQuestion.questionModule, userQuestion.questionNoId).then(() => {
+      My.addQuestionCollect(userQuestion.questionNoId).then(() => {
         userQuestion.collect = true;
         this.setState({ userQuestion });
       });
     } else {
-      My.delQuestionCollect(userQuestion.questionModule, userQuestion.questionNoId).then(() => {
+      My.delQuestionCollect(userQuestion.questionNoId).then(() => {
         userQuestion.collect = false;
         this.setState({ userQuestion });
       });

+ 6 - 7
front/project/www/routes/paper/question/detail/index.js

@@ -55,7 +55,7 @@ export default class extends Component {
     const { userQuestion, questionNo = {} } = this.props;
     const { ask = {} } = this.state;
     if (ask.originContent === '' || ask.content === '' || ask.target === '') return;
-    My.addQuestionAsk(userQuestion.id, ask.target, userQuestion.questionModule, questionNo.id, ask.originContent, ask.content).then(() => {
+    My.addQuestionAsk(userQuestion.id, ask.target, questionNo.id, ask.originContent, ask.content).then(() => {
       this.setState({ askModal: false, askOkModal: true });
     }).catch(err => {
       this.setState({ askError: err.message });
@@ -63,11 +63,10 @@ export default class extends Component {
   }
 
   submitFeedbackError() {
-    const { userQuestion = {}, questionNo = {} } = this.props;
+    const { questionNo = {} } = this.props;
     const { feedback = {} } = this.state;
     if (feedback.originContent === '' || feedback.content === '' || feedback.target === '') return;
     My.addFeedbackErrorQuestion(
-      userQuestion.questionModule,
       questionNo.id,
       questionNo.title,
       feedback.target,
@@ -83,9 +82,9 @@ export default class extends Component {
   }
 
   submitNote(close) {
-    const { userQuestion = {}, questionNo = {} } = this.props;
+    const { questionNo = {} } = this.props;
     const { note = {} } = this.state;
-    My.updateQuestionNote(userQuestion.questionModule, questionNo.id, note)
+    My.updateQuestionNote(questionNo.id, note)
       .then(() => {
         if (close) this.setState({ noteModal: false });
       })
@@ -102,12 +101,12 @@ export default class extends Component {
   toggleCollect() {
     const { userQuestion = {}, questionNo = {}, flow } = this.props;
     if (!userQuestion.collect) {
-      My.addQuestionCollect(userQuestion.questionModule, questionNo.id).then(() => {
+      My.addQuestionCollect(questionNo.id).then(() => {
         userQuestion.collect = true;
         flow.setState({ userQuestion });
       });
     } else {
-      My.delQuestionCollect(userQuestion.questionModule, questionNo.id).then(() => {
+      My.delQuestionCollect(questionNo.id).then(() => {
         userQuestion.collect = false;
         flow.setState({ userQuestion });
       });

+ 3 - 2
front/project/www/routes/paper/report/page.js

@@ -1003,7 +1003,7 @@ export default class extends Page {
           />
         </div>
       </div>
-      <div className="body gray">
+      {report.paperModule !== 'textbook' && <div className="body gray">
         <div className="content">
           <div className="title">难度分析</div>
           <div className="detail-1">
@@ -1029,7 +1029,8 @@ export default class extends Page {
             )}
           />
         </div>
-      </div>
+      </div>}
+
       <div className="body">
         <div className="content">
           <div className="title">知识体系分析</div>

+ 65 - 35
front/project/www/stores/my.js

@@ -133,8 +133,6 @@ export default class MyStore extends BaseStore {
 
   /**
    * 获取收藏心经列表
-   * @param {*} questionModule
-   * @param {*} questionType
    * @param {*} page
    * @param {*} size
    * @param {*} startTime
@@ -143,35 +141,40 @@ export default class MyStore extends BaseStore {
    * @param {*} direction
    */
   listExperienceCollect({ page, size, startTime, endTime, order, direction }) {
-    return this.apiGet('/my/collect/question/list', { page, size, startTime, endTime, order, direction });
+    return this.apiGet('/my/collect/experience/list', { page, size, startTime, endTime, order, direction });
   }
 
   /**
    * 添加题目收藏
-   * @param {*} questionModule
    * @param {*} questionNoId
    */
-  addQuestionCollect(questionModule, questionNoId) {
-    return this.apiPut('/my/collect/question/add', { questionModule, questionNoId });
+  addQuestionCollect(questionNoId) {
+    return this.apiPut('/my/collect/question/add', { questionNoId });
   }
 
   /**
    * 删除题目收藏
-   * @param {*} questionModule
    * @param {*} questionNoId
    */
-  delQuestionCollect(questionModule, questionNoId) {
-    return this.apiDel('/my/collect/question/delete', { questionModule, questionNoId });
+  delQuestionCollect(questionNoId) {
+    return this.apiDel('/my/collect/question/delete', { questionNoId });
+  }
+
+  /**
+   * 收藏题目移除
+   * @param {*} ids: questionId
+   */
+  clearQuestionCollect(questionNoIds) {
+    return this.apiPost('/my/collect/question/clear', { questionNoIds });
   }
 
   /**
    * 收藏卷组
-   * @param {*} questionModule
-   * @param {*} questionNoIds
+   * @param {*} questionNoIds: 'questionNoId'
    * @param {*} filterTimes
    */
-  bindQuestionCollect(questionModule, questionNoIds, filterTimes) {
-    return this.apiPost('/my/collect/question/bind', { questionModule, questionNoIds, filterTimes });
+  groupQuestionCollect({ questionNoIds, filterTimes }) {
+    return this.apiPost('/my/collect/question/group', { questionNoIds, filterTimes });
   }
 
   /**
@@ -188,7 +191,6 @@ export default class MyStore extends BaseStore {
 
   /**
    * 获取错题列表
-   * @param {*} questionModule
    * @param {*} page
    * @param {*} size
    */
@@ -198,20 +200,19 @@ export default class MyStore extends BaseStore {
 
   /**
    * 错题组卷
-   * @param {*} questionModule
-   * @param {*} questionNoIds
+   * @param {*} questionNoIds: 'questionNoId'
    * @param {*} filterTimes
    */
-  bindError(questionModule, questionNoIds, filterTimes) {
-    return this.apiPost('/my/error/bind', { questionModule, questionNoIds, filterTimes });
+  groupError({ questionNoIds, filterTimes }) {
+    return this.apiPost('/my/error/group', { questionNoIds, filterTimes });
   }
 
   /**
    * 错题移除
-   * @param {*} ids
+   * @param {*} ids: userQuestionId
    */
-  clearError(ids) {
-    return this.apiPost('/my/error/clear', { questionNoIds: ids });
+  clearError(questionNoIds) {
+    return this.apiPost('/my/error/clear', { questionNoIds });
   }
 
   /**
@@ -236,7 +237,6 @@ export default class MyStore extends BaseStore {
 
   /**
    * 更新题目笔记
-   * @param {*} questionModule
    * @param {*} questionNoId
    * @param {*} content
    * @param {*} qxContent
@@ -244,8 +244,12 @@ export default class MyStore extends BaseStore {
    * @param {*} associationContent
    * @param {*} qaContent
    */
-  updateQuestionNote(questionModule, questionNoId, { content, qxContent, officialContent, associationContent, qaContent }) {
-    return this.apiPut('/my/note/question', { questionModule, questionNoId, content, qxContent, officialContent, associationContent, qaContent });
+  updateQuestionNote(questionNoId, { content, qxContent, officialContent, associationContent, qaContent }) {
+    return this.apiPut('/my/note/question', { questionNoId, content, qxContent, officialContent, associationContent, qaContent });
+  }
+
+  clearQuestionNote(questionNoIds) {
+    return this.apiPost('/my/note/question/clear', { questionNoIds });
   }
 
   /**
@@ -288,12 +292,11 @@ export default class MyStore extends BaseStore {
    * 添加题目提问
    * @param {*} userQuestionId : 用于获取预习作业,判断权限
    * @param {*} target
-   * @param {*} questionModule
    * @param {*} questionNoId
    * @param {*} content
    */
-  addQuestionAsk(userQuestionId, target, questionModule, questionNoId, originContent, content) {
-    return this.apiPost('/my/ask/question', { userQuestionId, target, questionModule, questionNoId, originContent, content });
+  addQuestionAsk(userQuestionId, target, questionNoId, originContent, content) {
+    return this.apiPost('/my/ask/question', { userQuestionId, target, questionNoId, originContent, content });
   }
 
   /**
@@ -321,15 +324,14 @@ export default class MyStore extends BaseStore {
 
   /**
    * 添加题目勘误
-   * @param {*} questionModule
    * @param {*} questionNoId
    * @param {*} title
    * @param {*} position
    * @param {*} originContent
    * @param {*} content
    */
-  addFeedbackErrorQuestion(questionModule, questionNoId, title, position, originContent, content) {
-    return this.apiPost('/my/feedback/error/question', { questionModule, questionNoId, title, position, originContent, content });
+  addFeedbackErrorQuestion(questionNoId, title, position, originContent, content) {
+    return this.apiPost('/my/feedback/error/question', { questionNoId, title, position, originContent, content });
   }
 
   /**
@@ -346,12 +348,13 @@ export default class MyStore extends BaseStore {
 
   /**
    * 添加机经反馈
-   * @param {*} topicId
+   * @param {*} questionSubject
    * @param {*} target
+   * @param {*} no
    * @param {*} content
    */
-  addTextbookFeedback(topicId, target, content) {
-    return this.apiPost('/my/feedback/textbook', { topicId, target, content });
+  addTextbookFeedback(questionSubject, target, no, content) {
+    return this.apiPost('/my/feedback/textbook', { questionSubject, target, no, content });
   }
 
   /**
@@ -454,14 +457,41 @@ export default class MyStore extends BaseStore {
     return this.apiGet('/my/course/time', { recordId });
   }
 
-  exportQuestionError() {
-    return this.apiPost('/my/export/question/error', {});
+  /**
+   * 导出题目
+   * @param {*} setting
+   */
+  exportQuestion(setting) {
+    return this.apiPost('/my/export/question', { setting });
+  }
+
+  /**
+   * 导出题目笔记
+   * @param {*}} setting
+   */
+  exportNoteQuestion(setting) {
+    return this.apiPost('/my/export/note/question', { setting });
+  }
+
+  /**
+   * 导出课程笔记
+   * @param {*} setting
+   */
+  exportNoteCourse(setting) {
+    return this.apiPost('/my/export/note/course', { setting });
   }
 
+  /**
+   * 关闭导出提示
+   */
   exportTips() {
     return this.apiPost('/my/export/tips', {});
   }
 
+  /**
+   * 关闭评论提示
+   * @param {*}} recordId
+   */
   courseCommentTips(recordId) {
     return this.apiPost('/my/course/comment/tips', { recordId });
   }

+ 4 - 0
front/src/services/Tools.js

@@ -540,6 +540,10 @@ export function toLine(name) {
   return name.replace(/([A-Z])/g, '_$1').toLowerCase();
 }
 
+export function formatMonth(days, number = true) {
+  return number ? parseInt(days / 30, 10) : `${parseInt(days / 30, 10)}个月`;
+}
+
 export function timeRange(timerange) {
   let startTime = null;
   let endTime = null;

+ 20 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/module/PaperModule.java

@@ -55,4 +55,24 @@ public enum PaperModule {
                 return null;
         }
     }
+
+    /**
+     * 根据question模块判断paper模块
+     * @param module
+     * @return
+     */
+    public static PaperModule WithQuestionNo(QuestionNoModule module){
+        switch(module){
+            case EXERCISE:
+                return EXERCISE;
+            case EXAMINATION:
+                return EXAMINATION;
+            case TEXTBOOK:
+                return TEXTBOOK;
+            case SENTENCE:
+                return SENTENCE;
+            default:
+                return null;
+        }
+    }
 }

+ 18 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/module/QuestionModule.java

@@ -37,6 +37,24 @@ public enum QuestionModule {
     }
 
     /**
+     * 根据paper类型判断组卷题目类型
+     * @param module
+     * @return
+     */
+    public static QuestionModule WithQuestionNo(QuestionNoModule module){
+        switch(module){
+            case SENTENCE:
+                return SENTENCE;
+            case TEXTBOOK:
+                return TEXTBOOK;
+            case EXERCISE:
+            case EXAMINATION:
+                return BASE;
+            default:
+                return null;
+        }
+    }
+    /**
      * 根据question题型判断组卷题目类型
      * @param type
      * @return

+ 21 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/module/QuestionNoModule.java

@@ -3,6 +3,8 @@ package com.qxgmat.data.constants.enums.module;
 public enum QuestionNoModule {
     EXERCISE("exercise"),
     EXAMINATION("examination"),
+    TEXTBOOK("textbook"),
+    SENTENCE("sentence"),
     ;
     public String key;
     private QuestionNoModule(String key){
@@ -14,4 +16,23 @@ public enum QuestionNoModule {
         return QuestionNoModule.valueOf(name.toUpperCase());
     }
 
+    /**
+     * 根据paper类型判断组卷题目类型
+     * @param module
+     * @return
+     */
+    public static QuestionNoModule WithPaper(PaperModule module){
+        switch(module){
+            case SENTENCE:
+                return SENTENCE;
+            case TEXTBOOK:
+                return TEXTBOOK;
+            case EXERCISE:
+                return EXERCISE;
+            case EXAMINATION:
+                return EXAMINATION;
+            default:
+                return null;
+        }
+    }
 }

+ 17 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/user/ExportType.java

@@ -0,0 +1,17 @@
+package com.qxgmat.data.constants.enums.user;
+
+public enum ExportType {
+    QUESTION("question"),
+    NOTE_QUESTION("note_question"),
+    NOTE_COURSE("note_course")
+    ;
+    public String key;
+    private ExportType(String key){
+        this.key = key;
+    }
+
+    public static ExportType ValueOf(String name){
+        if (name == null || name.isEmpty()) return null;
+        return ExportType.valueOf(name.toUpperCase());
+    }
+}

+ 7 - 0
server/data/src/main/java/com/qxgmat/data/dao/UserExportMapper.java

@@ -0,0 +1,7 @@
+package com.qxgmat.data.dao;
+
+import com.nuliji.tools.mybatis.Mapper;
+import com.qxgmat.data.dao.entity.UserExport;
+
+public interface UserExportMapper extends Mapper<UserExport> {
+}

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

@@ -59,6 +59,12 @@ public class QuestionNo implements Serializable {
     @Column(name = "`total_correct`")
     private Integer totalCorrect;
 
+    /**
+     * 总收藏数
+     */
+    @Column(name = "`collect_number`")
+    private Integer collectNumber;
+
     @Column(name = "`delete_time`")
     private Date deleteTime;
 
@@ -235,6 +241,24 @@ public class QuestionNo implements Serializable {
     }
 
     /**
+     * 获取总收藏数
+     *
+     * @return collect_number - 总收藏数
+     */
+    public Integer getCollectNumber() {
+        return collectNumber;
+    }
+
+    /**
+     * 设置总收藏数
+     *
+     * @param collectNumber 总收藏数
+     */
+    public void setCollectNumber(Integer collectNumber) {
+        this.collectNumber = collectNumber;
+    }
+
+    /**
      * @return delete_time
      */
     public Date getDeleteTime() {
@@ -299,6 +323,7 @@ public class QuestionNo implements Serializable {
         sb.append(", totalTime=").append(totalTime);
         sb.append(", totalNumber=").append(totalNumber);
         sb.append(", totalCorrect=").append(totalCorrect);
+        sb.append(", collectNumber=").append(collectNumber);
         sb.append(", deleteTime=").append(deleteTime);
         sb.append(", relationQuestion=").append(relationQuestion);
         sb.append(", relationNumber=").append(relationNumber);
@@ -406,6 +431,16 @@ public class QuestionNo implements Serializable {
         }
 
         /**
+         * 设置总收藏数
+         *
+         * @param collectNumber 总收藏数
+         */
+        public Builder collectNumber(Integer collectNumber) {
+            obj.setCollectNumber(collectNumber);
+            return this;
+        }
+
+        /**
          * @param deleteTime
          */
         public Builder deleteTime(Date deleteTime) {

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

@@ -42,6 +42,12 @@ public class SentencePaper implements Serializable {
     private Integer[] questionNoIds;
 
     /**
+     * 长难句ids:json
+     */
+    @Column(name = "`question_s_ids`")
+    private Integer[] questionSIds;
+
+    /**
      * 开放状态:0关闭,1开启
      */
     @Column(name = "`status`")
@@ -157,6 +163,24 @@ public class SentencePaper implements Serializable {
     }
 
     /**
+     * 获取长难句ids:json
+     *
+     * @return question_s_ids - 长难句ids:json
+     */
+    public Integer[] getQuestionSIds() {
+        return questionSIds;
+    }
+
+    /**
+     * 设置长难句ids:json
+     *
+     * @param questionSIds 长难句ids:json
+     */
+    public void setQuestionSIds(Integer[] questionSIds) {
+        this.questionSIds = questionSIds;
+    }
+
+    /**
      * 获取开放状态:0关闭,1开启
      *
      * @return status - 开放状态:0关闭,1开启
@@ -200,6 +224,7 @@ public class SentencePaper implements Serializable {
         sb.append(", logic=").append(logic);
         sb.append(", questionNumber=").append(questionNumber);
         sb.append(", questionNoIds=").append(questionNoIds);
+        sb.append(", questionSIds=").append(questionSIds);
         sb.append(", status=").append(status);
         sb.append(", createTime=").append(createTime);
         sb.append("]");
@@ -276,6 +301,16 @@ public class SentencePaper implements Serializable {
         }
 
         /**
+         * 设置长难句ids:json
+         *
+         * @param questionSIds 长难句ids:json
+         */
+        public Builder questionSIds(Integer[] questionSIds) {
+            obj.setQuestionSIds(questionSIds);
+            return this;
+        }
+
+        /**
          * 设置开放状态:0关闭,1开启
          *
          * @param status 开放状态:0关闭,1开启

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

@@ -47,6 +47,12 @@ public class SentenceQuestion implements Serializable {
     private Integer questionId;
 
     /**
+     * 题目编号id
+     */
+    @Column(name = "`question_no_id`")
+    private Integer questionNoId;
+
+    /**
      * 总作答时间
      */
     @Column(name = "`total_time`")
@@ -195,6 +201,24 @@ public class SentenceQuestion implements Serializable {
     }
 
     /**
+     * 获取题目编号id
+     *
+     * @return question_no_id - 题目编号id
+     */
+    public Integer getQuestionNoId() {
+        return questionNoId;
+    }
+
+    /**
+     * 设置题目编号id
+     *
+     * @param questionNoId 题目编号id
+     */
+    public void setQuestionNoId(Integer questionNoId) {
+        this.questionNoId = questionNoId;
+    }
+
+    /**
      * 获取总作答时间
      *
      * @return total_time - 总作答时间
@@ -289,6 +313,7 @@ public class SentenceQuestion implements Serializable {
         sb.append(", no=").append(no);
         sb.append(", subject=").append(subject);
         sb.append(", questionId=").append(questionId);
+        sb.append(", questionNoId=").append(questionNoId);
         sb.append(", totalTime=").append(totalTime);
         sb.append(", totalNumber=").append(totalNumber);
         sb.append(", totalCorrect=").append(totalCorrect);
@@ -378,6 +403,16 @@ public class SentenceQuestion implements Serializable {
         }
 
         /**
+         * 设置题目编号id
+         *
+         * @param questionNoId 题目编号id
+         */
+        public Builder questionNoId(Integer questionNoId) {
+            obj.setQuestionNoId(questionNoId);
+            return this;
+        }
+
+        /**
          * 设置总作答时间
          *
          * @param totalTime 总作答时间

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

@@ -41,6 +41,12 @@ public class TextbookPaper implements Serializable {
     @Column(name = "`question_no_ids`")
     private Integer[] questionNoIds;
 
+    /**
+     * 机经ids:json
+     */
+    @Column(name = "`question_t_ids`")
+    private Integer[] questionTIds;
+
     @Column(name = "`question_number`")
     private Integer questionNumber;
 
@@ -166,6 +172,24 @@ public class TextbookPaper implements Serializable {
     }
 
     /**
+     * 获取机经ids:json
+     *
+     * @return question_t_ids - 机经ids:json
+     */
+    public Integer[] getQuestionTIds() {
+        return questionTIds;
+    }
+
+    /**
+     * 设置机经ids:json
+     *
+     * @param questionTIds 机经ids:json
+     */
+    public void setQuestionTIds(Integer[] questionTIds) {
+        this.questionTIds = questionTIds;
+    }
+
+    /**
      * @return question_number
      */
     public Integer getQuestionNumber() {
@@ -241,6 +265,7 @@ public class TextbookPaper implements Serializable {
         sb.append(", no=").append(no);
         sb.append(", libraryId=").append(libraryId);
         sb.append(", questionNoIds=").append(questionNoIds);
+        sb.append(", questionTIds=").append(questionTIds);
         sb.append(", questionNumber=").append(questionNumber);
         sb.append(", createTime=").append(createTime);
         sb.append(", status=").append(status);
@@ -319,6 +344,16 @@ public class TextbookPaper implements Serializable {
         }
 
         /**
+         * 设置机经ids:json
+         *
+         * @param questionTIds 机经ids:json
+         */
+        public Builder questionTIds(Integer[] questionTIds) {
+            obj.setQuestionTIds(questionTIds);
+            return this;
+        }
+
+        /**
          * @param questionNumber
          */
         public Builder questionNumber(Integer questionNumber) {

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

@@ -35,6 +35,12 @@ public class TextbookQuestion implements Serializable {
     private Integer questionId;
 
     /**
+     * 题目编号id
+     */
+    @Column(name = "`question_no_id`")
+    private Integer questionNoId;
+
+    /**
      * 总做题时间
      */
     @Column(name = "`total_time`")
@@ -150,6 +156,24 @@ public class TextbookQuestion implements Serializable {
     }
 
     /**
+     * 获取题目编号id
+     *
+     * @return question_no_id - 题目编号id
+     */
+    public Integer getQuestionNoId() {
+        return questionNoId;
+    }
+
+    /**
+     * 设置题目编号id
+     *
+     * @param questionNoId 题目编号id
+     */
+    public void setQuestionNoId(Integer questionNoId) {
+        this.questionNoId = questionNoId;
+    }
+
+    /**
      * 获取总做题时间
      *
      * @return total_time - 总做题时间
@@ -246,6 +270,7 @@ public class TextbookQuestion implements Serializable {
         sb.append(", no=").append(no);
         sb.append(", libraryId=").append(libraryId);
         sb.append(", questionId=").append(questionId);
+        sb.append(", questionNoId=").append(questionNoId);
         sb.append(", totalTime=").append(totalTime);
         sb.append(", totalNumber=").append(totalNumber);
         sb.append(", totalCorrect=").append(totalCorrect);
@@ -315,6 +340,16 @@ public class TextbookQuestion implements Serializable {
         }
 
         /**
+         * 设置题目编号id
+         *
+         * @param questionNoId 题目编号id
+         */
+        public Builder questionNoId(Integer questionNoId) {
+            obj.setQuestionNoId(questionNoId);
+            return this;
+        }
+
+        /**
          * 设置总做题时间
          *
          * @param totalTime 总做题时间

+ 12 - 12
server/data/src/main/java/com/qxgmat/data/dao/entity/UserCollectExperience.java

@@ -20,8 +20,8 @@ public class UserCollectExperience implements Serializable {
     /**
      * 经验id
      */
-    @Column(name = "`expericence_id`")
-    private Integer expericenceId;
+    @Column(name = "`experience_id`")
+    private Integer experienceId;
 
     @Column(name = "`create_time`")
     private Date createTime;
@@ -63,19 +63,19 @@ public class UserCollectExperience implements Serializable {
     /**
      * 获取经验id
      *
-     * @return expericence_id - 经验id
+     * @return experience_id - 经验id
      */
-    public Integer getExpericenceId() {
-        return expericenceId;
+    public Integer getExperienceId() {
+        return experienceId;
     }
 
     /**
      * 设置经验id
      *
-     * @param expericenceId 经验id
+     * @param experienceId 经验id
      */
-    public void setExpericenceId(Integer expericenceId) {
-        this.expericenceId = expericenceId;
+    public void setExperienceId(Integer experienceId) {
+        this.experienceId = experienceId;
     }
 
     /**
@@ -100,7 +100,7 @@ public class UserCollectExperience implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
-        sb.append(", expericenceId=").append(expericenceId);
+        sb.append(", experienceId=").append(experienceId);
         sb.append(", createTime=").append(createTime);
         sb.append("]");
         return sb.toString();
@@ -138,10 +138,10 @@ public class UserCollectExperience implements Serializable {
         /**
          * 设置经验id
          *
-         * @param expericenceId 经验id
+         * @param experienceId 经验id
          */
-        public Builder expericenceId(Integer expericenceId) {
-            obj.setExpericenceId(expericenceId);
+        public Builder experienceId(Integer experienceId) {
+            obj.setExperienceId(experienceId);
             return this;
         }
 

+ 187 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserExport.java

@@ -0,0 +1,187 @@
+package com.qxgmat.data.dao.entity;
+
+import com.alibaba.fastjson.JSONObject;
+import java.io.Serializable;
+import java.util.Date;
+import javax.persistence.*;
+
+@Table(name = "user_export")
+public class UserExport implements Serializable {
+    @Id
+    @Column(name = "`id`")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    @Column(name = "`user_id`")
+    private Integer userId;
+
+    /**
+     * 导出类型
+     */
+    @Column(name = "`type`")
+    private String type;
+
+    /**
+     * 导出设置
+     */
+    @Column(name = "`setting`")
+    private JSONObject setting;
+
+    @Column(name = "`create_time`")
+    private Date createTime;
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * @return id
+     */
+    public Integer getId() {
+        return id;
+    }
+
+    /**
+     * @param id
+     */
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    /**
+     * @return user_id
+     */
+    public Integer getUserId() {
+        return userId;
+    }
+
+    /**
+     * @param userId
+     */
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    /**
+     * 获取导出类型
+     *
+     * @return type - 导出类型
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * 设置导出类型
+     *
+     * @param type 导出类型
+     */
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    /**
+     * 获取导出设置
+     *
+     * @return setting - 导出设置
+     */
+    public JSONObject getSetting() {
+        return setting;
+    }
+
+    /**
+     * 设置导出设置
+     *
+     * @param setting 导出设置
+     */
+    public void setSetting(JSONObject setting) {
+        this.setting = setting;
+    }
+
+    /**
+     * @return create_time
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * @param createTime
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", userId=").append(userId);
+        sb.append(", type=").append(type);
+        sb.append(", setting=").append(setting);
+        sb.append(", createTime=").append(createTime);
+        sb.append("]");
+        return sb.toString();
+    }
+
+    public static UserExport.Builder builder() {
+        return new UserExport.Builder();
+    }
+
+    public static class Builder {
+        private UserExport obj;
+
+        public Builder() {
+            this.obj = new UserExport();
+        }
+
+        /**
+         * @param id
+         */
+        public Builder id(Integer id) {
+            obj.setId(id);
+            return this;
+        }
+
+        /**
+         * @param userId
+         */
+        public Builder userId(Integer userId) {
+            obj.setUserId(userId);
+            return this;
+        }
+
+        /**
+         * 设置导出类型
+         *
+         * @param type 导出类型
+         */
+        public Builder type(String type) {
+            obj.setType(type);
+            return this;
+        }
+
+        /**
+         * 设置导出设置
+         *
+         * @param setting 导出设置
+         */
+        public Builder setting(JSONObject setting) {
+            obj.setSetting(setting);
+            return this;
+        }
+
+        /**
+         * @param createTime
+         */
+        public Builder createTime(Date createTime) {
+            obj.setCreateTime(createTime);
+            return this;
+        }
+
+        public UserExport build() {
+            return this.obj;
+        }
+    }
+}

+ 77 - 7
server/data/src/main/java/com/qxgmat/data/dao/entity/UserPaper.java

@@ -60,6 +60,12 @@ public class UserPaper implements Serializable {
     private Integer paperNo;
 
     /**
+     * 区分qx试卷01
+     */
+    @Column(name = "`qx_cat`")
+    private Integer qxCat;
+
+    /**
      * 题目编号id列表:json
      */
     @Column(name = "`question_no_ids`")
@@ -84,12 +90,18 @@ public class UserPaper implements Serializable {
     private Integer time;
 
     /**
-     * 最近一次做题记录
+     * 最近一次做题时间
      */
     @Column(name = "`latest_time`")
     private Date latestTime;
 
     /**
+     * 最近一次做题记录
+     */
+    @Column(name = "`latest_report_id`")
+    private Integer latestReportId;
+
+    /**
      * 总作答时间
      */
     @Column(name = "`total_time`")
@@ -280,6 +292,24 @@ public class UserPaper implements Serializable {
     }
 
     /**
+     * 获取区分qx试卷01
+     *
+     * @return qx_cat - 区分qx试卷01
+     */
+    public Integer getQxCat() {
+        return qxCat;
+    }
+
+    /**
+     * 设置区分qx试卷01
+     *
+     * @param qxCat 区分qx试卷01
+     */
+    public void setQxCat(Integer qxCat) {
+        this.qxCat = qxCat;
+    }
+
+    /**
      * 获取题目编号id列表:json
      *
      * @return question_no_ids - 题目编号id列表:json
@@ -352,24 +382,42 @@ public class UserPaper implements Serializable {
     }
 
     /**
-     * 获取最近一次做题记录
+     * 获取最近一次做题时间
      *
-     * @return latest_time - 最近一次做题记录
+     * @return latest_time - 最近一次做题时间
      */
     public Date getLatestTime() {
         return latestTime;
     }
 
     /**
-     * 设置最近一次做题记录
+     * 设置最近一次做题时间
      *
-     * @param latestTime 最近一次做题记录
+     * @param latestTime 最近一次做题时间
      */
     public void setLatestTime(Date latestTime) {
         this.latestTime = latestTime;
     }
 
     /**
+     * 获取最近一次做题记录
+     *
+     * @return latest_report_id - 最近一次做题记录
+     */
+    public Integer getLatestReportId() {
+        return latestReportId;
+    }
+
+    /**
+     * 设置最近一次做题记录
+     *
+     * @param latestReportId 最近一次做题记录
+     */
+    public void setLatestReportId(Integer latestReportId) {
+        this.latestReportId = latestReportId;
+    }
+
+    /**
      * 获取总作答时间
      *
      * @return total_time - 总作答时间
@@ -474,11 +522,13 @@ public class UserPaper implements Serializable {
         sb.append(", originId=").append(originId);
         sb.append(", recordId=").append(recordId);
         sb.append(", paperNo=").append(paperNo);
+        sb.append(", qxCat=").append(qxCat);
         sb.append(", questionNoIds=").append(questionNoIds);
         sb.append(", questionNumber=").append(questionNumber);
         sb.append(", times=").append(times);
         sb.append(", time=").append(time);
         sb.append(", latestTime=").append(latestTime);
+        sb.append(", latestReportId=").append(latestReportId);
         sb.append(", totalTime=").append(totalTime);
         sb.append(", totalNumber=").append(totalNumber);
         sb.append(", totalCorrect=").append(totalCorrect);
@@ -588,6 +638,16 @@ public class UserPaper implements Serializable {
         }
 
         /**
+         * 设置区分qx试卷01
+         *
+         * @param qxCat 区分qx试卷01
+         */
+        public Builder qxCat(Integer qxCat) {
+            obj.setQxCat(qxCat);
+            return this;
+        }
+
+        /**
          * 设置题目编号id列表:json
          *
          * @param questionNoIds 题目编号id列表:json
@@ -628,9 +688,9 @@ public class UserPaper implements Serializable {
         }
 
         /**
-         * 设置最近一次做题记录
+         * 设置最近一次做题时间
          *
-         * @param latestTime 最近一次做题记录
+         * @param latestTime 最近一次做题时间
          */
         public Builder latestTime(Date latestTime) {
             obj.setLatestTime(latestTime);
@@ -638,6 +698,16 @@ public class UserPaper implements Serializable {
         }
 
         /**
+         * 设置最近一次做题记录
+         *
+         * @param latestReportId 最近一次做题记录
+         */
+        public Builder latestReportId(Integer latestReportId) {
+            obj.setLatestReportId(latestReportId);
+            return this;
+        }
+
+        /**
          * 设置总作答时间
          *
          * @param totalTime 总作答时间

+ 66 - 31
server/data/src/main/java/com/qxgmat/data/dao/entity/UserTextbookFeedback.java

@@ -18,18 +18,24 @@ public class UserTextbookFeedback implements Serializable {
     private Integer userId;
 
     /**
-     * 机经问题
-     */
-    @Column(name = "`topic_id`")
-    private Integer topicId;
-
-    /**
      * 学科
      */
     @Column(name = "`question_subject`")
     private String questionSubject;
 
     /**
+     * 题目序号
+     */
+    @Column(name = "`no`")
+    private Integer no;
+
+    /**
+     * 机经问题
+     */
+    @Column(name = "`topic_id`")
+    private Integer topicId;
+
+    /**
      * 换库表
      */
     @Column(name = "`library_id`")
@@ -97,39 +103,57 @@ public class UserTextbookFeedback implements Serializable {
     }
 
     /**
-     * 获取机经问题
+     * 获取学科
      *
-     * @return topic_id - 机经问题
+     * @return question_subject - 学科
      */
-    public Integer getTopicId() {
-        return topicId;
+    public String getQuestionSubject() {
+        return questionSubject;
     }
 
     /**
-     * 设置机经问题
+     * 设置学科
      *
-     * @param topicId 机经问题
+     * @param questionSubject 学科
      */
-    public void setTopicId(Integer topicId) {
-        this.topicId = topicId;
+    public void setQuestionSubject(String questionSubject) {
+        this.questionSubject = questionSubject;
     }
 
     /**
-     * 获取学科
+     * 获取题目序号
      *
-     * @return question_subject - 学科
+     * @return no - 题目序号
      */
-    public String getQuestionSubject() {
-        return questionSubject;
+    public Integer getNo() {
+        return no;
     }
 
     /**
-     * 设置学科
+     * 设置题目序号
      *
-     * @param questionSubject 学科
+     * @param no 题目序号
      */
-    public void setQuestionSubject(String questionSubject) {
-        this.questionSubject = questionSubject;
+    public void setNo(Integer no) {
+        this.no = no;
+    }
+
+    /**
+     * 获取机经问题
+     *
+     * @return topic_id - 机经问题
+     */
+    public Integer getTopicId() {
+        return topicId;
+    }
+
+    /**
+     * 设置机经问题
+     *
+     * @param topicId 机经问题
+     */
+    public void setTopicId(Integer topicId) {
+        this.topicId = topicId;
     }
 
     /**
@@ -244,8 +268,9 @@ public class UserTextbookFeedback implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
-        sb.append(", topicId=").append(topicId);
         sb.append(", questionSubject=").append(questionSubject);
+        sb.append(", no=").append(no);
+        sb.append(", topicId=").append(topicId);
         sb.append(", libraryId=").append(libraryId);
         sb.append(", target=").append(target);
         sb.append(", createTime=").append(createTime);
@@ -286,22 +311,32 @@ public class UserTextbookFeedback implements Serializable {
         }
 
         /**
-         * 设置机经问题
+         * 设置学科
          *
-         * @param topicId 机经问题
+         * @param questionSubject 学科
          */
-        public Builder topicId(Integer topicId) {
-            obj.setTopicId(topicId);
+        public Builder questionSubject(String questionSubject) {
+            obj.setQuestionSubject(questionSubject);
             return this;
         }
 
         /**
-         * 设置学科
+         * 设置题目序号
          *
-         * @param questionSubject 学科
+         * @param no 题目序号
          */
-        public Builder questionSubject(String questionSubject) {
-            obj.setQuestionSubject(questionSubject);
+        public Builder no(Integer no) {
+            obj.setNo(no);
+            return this;
+        }
+
+        /**
+         * 设置机经问题
+         *
+         * @param topicId 机经问题
+         */
+        public Builder topicId(Integer topicId) {
+            obj.setTopicId(topicId);
             return this;
         }
 

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

@@ -14,6 +14,7 @@
     <result column="total_time" jdbcType="INTEGER" property="totalTime" />
     <result column="total_number" jdbcType="INTEGER" property="totalNumber" />
     <result column="total_correct" jdbcType="INTEGER" property="totalCorrect" />
+    <result column="collect_number" jdbcType="INTEGER" property="collectNumber" />
     <result column="delete_time" jdbcType="TIMESTAMP" property="deleteTime" />
     <result column="relation_question" jdbcType="VARCHAR" property="relationQuestion" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayHandler" />
     <result column="relation_number" jdbcType="INTEGER" property="relationNumber" />
@@ -23,6 +24,6 @@
       WARNING - @mbg.generated
     -->
     `id`, `title`, `question_id`, `no`, `module`, `module_struct`, `total_time`, `total_number`, 
-    `total_correct`, `delete_time`, `relation_question`, `relation_number`
+    `total_correct`, `collect_number`, `delete_time`, `relation_question`, `relation_number`
   </sql>
 </mapper>

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

@@ -11,6 +11,7 @@
     <result column="logic" jdbcType="VARCHAR" property="logic" />
     <result column="question_number" jdbcType="INTEGER" property="questionNumber" />
     <result column="question_no_ids" jdbcType="VARCHAR" property="questionNoIds" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler" />
+    <result column="question_s_ids" jdbcType="VARCHAR" property="questionSIds" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler" />
     <result column="status" jdbcType="INTEGER" property="status" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
@@ -18,6 +19,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title`, `no`, `logic`, `question_number`, `question_no_ids`, `status`, `create_time`
+    `id`, `title`, `no`, `logic`, `question_number`, `question_no_ids`, `question_s_ids`, 
+    `status`, `create_time`
   </sql>
 </mapper>

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

@@ -12,6 +12,7 @@
     <result column="no" jdbcType="INTEGER" property="no" />
     <result column="subject" jdbcType="VARCHAR" property="subject" />
     <result column="question_id" jdbcType="INTEGER" property="questionId" />
+    <result column="question_no_id" jdbcType="INTEGER" property="questionNoId" />
     <result column="total_time" jdbcType="INTEGER" property="totalTime" />
     <result column="total_number" jdbcType="INTEGER" property="totalNumber" />
     <result column="total_correct" jdbcType="INTEGER" property="totalCorrect" />
@@ -27,8 +28,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title`, `is_trail`, `is_paper`, `no`, `subject`, `question_id`, `total_time`, 
-    `total_number`, `total_correct`, `collect_number`
+    `id`, `title`, `is_trail`, `is_paper`, `no`, `subject`, `question_id`, `question_no_id`, 
+    `total_time`, `total_number`, `total_correct`, `collect_number`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -11,6 +11,7 @@
     <result column="no" jdbcType="INTEGER" property="no" />
     <result column="library_id" jdbcType="INTEGER" property="libraryId" />
     <result column="question_no_ids" jdbcType="VARCHAR" property="questionNoIds" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler" />
+    <result column="question_t_ids" jdbcType="VARCHAR" property="questionTIds" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler" />
     <result column="question_number" jdbcType="INTEGER" property="questionNumber" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="status" jdbcType="INTEGER" property="status" />
@@ -20,7 +21,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title`, `logic`, `no`, `library_id`, `question_no_ids`, `question_number`, 
-    `create_time`, `status`, `year`
+    `id`, `title`, `logic`, `no`, `library_id`, `question_no_ids`, `question_t_ids`, 
+    `question_number`, `create_time`, `status`, `year`
   </sql>
 </mapper>

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

@@ -10,6 +10,7 @@
     <result column="no" jdbcType="INTEGER" property="no" />
     <result column="library_id" jdbcType="INTEGER" property="libraryId" />
     <result column="question_id" jdbcType="INTEGER" property="questionId" />
+    <result column="question_no_id" jdbcType="INTEGER" property="questionNoId" />
     <result column="total_time" jdbcType="INTEGER" property="totalTime" />
     <result column="total_number" jdbcType="INTEGER" property="totalNumber" />
     <result column="total_correct" jdbcType="INTEGER" property="totalCorrect" />
@@ -20,7 +21,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title`, `no`, `library_id`, `question_id`, `total_time`, `total_number`, `total_correct`, 
-    `year`, `collect_number`
+    `id`, `title`, `no`, `library_id`, `question_id`, `question_no_id`, `total_time`, 
+    `total_number`, `total_correct`, `year`, `collect_number`
   </sql>
 </mapper>

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

@@ -7,13 +7,13 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
-    <result column="expericence_id" jdbcType="INTEGER" property="expericenceId" />
+    <result column="experience_id" jdbcType="INTEGER" property="experienceId" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `expericence_id`, `create_time`
+    `id`, `user_id`, `experience_id`, `create_time`
   </sql>
 </mapper>

+ 20 - 0
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserExportMapper.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qxgmat.data.dao.UserExportMapper">
+  <resultMap id="BaseResultMap" type="com.qxgmat.data.dao.entity.UserExport">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+    <result column="user_id" jdbcType="INTEGER" property="userId" />
+    <result column="type" jdbcType="VARCHAR" property="type" />
+    <result column="setting" jdbcType="VARCHAR" property="setting" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `id`, `user_id`, `type`, `setting`, `create_time`
+  </sql>
+</mapper>

+ 5 - 2
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserPaperMapper.xml

@@ -14,11 +14,13 @@
     <result column="origin_id" jdbcType="INTEGER" property="originId" />
     <result column="record_id" jdbcType="INTEGER" property="recordId" />
     <result column="paper_no" jdbcType="INTEGER" property="paperNo" />
+    <result column="qx_cat" jdbcType="INTEGER" property="qxCat" />
     <result column="question_no_ids" jdbcType="VARCHAR" property="questionNoIds" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler" />
     <result column="question_number" jdbcType="INTEGER" property="questionNumber" />
     <result column="times" jdbcType="INTEGER" property="times" />
     <result column="time" jdbcType="INTEGER" property="time" />
     <result column="latest_time" jdbcType="DATE" property="latestTime" />
+    <result column="latest_report_id" jdbcType="INTEGER" property="latestReportId" />
     <result column="total_time" jdbcType="INTEGER" property="totalTime" />
     <result column="total_number" jdbcType="INTEGER" property="totalNumber" />
     <result column="total_correct" jdbcType="INTEGER" property="totalCorrect" />
@@ -30,7 +32,8 @@
       WARNING - @mbg.generated
     -->
     `id`, `user_id`, `title`, `paper_module`, `paper_origin`, `is_adapt`, `origin_id`, 
-    `record_id`, `paper_no`, `question_no_ids`, `question_number`, `times`, `time`, `latest_time`, 
-    `total_time`, `total_number`, `total_correct`, `delete_time`, `is_reset`
+    `record_id`, `paper_no`, `qx_cat`, `question_no_ids`, `question_number`, `times`, 
+    `time`, `latest_time`, `latest_report_id`, `total_time`, `total_number`, `total_correct`, 
+    `delete_time`, `is_reset`
   </sql>
 </mapper>

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

@@ -7,8 +7,9 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
-    <result column="topic_id" jdbcType="INTEGER" property="topicId" />
     <result column="question_subject" jdbcType="VARCHAR" property="questionSubject" />
+    <result column="no" jdbcType="INTEGER" property="no" />
+    <result column="topic_id" jdbcType="INTEGER" property="topicId" />
     <result column="library_id" jdbcType="INTEGER" property="libraryId" />
     <result column="target" jdbcType="VARCHAR" property="target" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
@@ -25,7 +26,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `topic_id`, `question_subject`, `library_id`, `target`, `create_time`, 
+    `id`, `user_id`, `question_subject`, `no`, `topic_id`, `library_id`, `target`, `create_time`, 
     `status`, `handle_time`
   </sql>
   <sql id="Blob_Column_List">

+ 11 - 0
server/data/src/main/java/com/qxgmat/data/relation/entity/SentenceQuestionRelation.java

@@ -1,11 +1,14 @@
 package com.qxgmat.data.relation.entity;
 
 import com.qxgmat.data.dao.entity.Question;
+import com.qxgmat.data.dao.entity.QuestionNo;
 import com.qxgmat.data.dao.entity.SentenceQuestion;
 
 public class SentenceQuestionRelation extends SentenceQuestion {
     private Question question;
 
+    private QuestionNo questionNo;
+
     public Question getQuestion() {
         return question;
     }
@@ -13,4 +16,12 @@ public class SentenceQuestionRelation extends SentenceQuestion {
     public void setQuestion(Question question) {
         this.question = question;
     }
+
+    public QuestionNo getQuestionNo() {
+        return questionNo;
+    }
+
+    public void setQuestionNo(QuestionNo questionNo) {
+        this.questionNo = questionNo;
+    }
 }

+ 11 - 0
server/data/src/main/java/com/qxgmat/data/relation/entity/TextbookQuestionRelation.java

@@ -1,11 +1,14 @@
 package com.qxgmat.data.relation.entity;
 
 import com.qxgmat.data.dao.entity.Question;
+import com.qxgmat.data.dao.entity.QuestionNo;
 import com.qxgmat.data.dao.entity.TextbookQuestion;
 
 public class TextbookQuestionRelation extends TextbookQuestion {
     private Question question;
 
+    private QuestionNo questionNo;
+
     public Question getQuestion() {
         return question;
     }
@@ -13,4 +16,12 @@ public class TextbookQuestionRelation extends TextbookQuestion {
     public void setQuestion(Question question) {
         this.question = question;
     }
+
+    public QuestionNo getQuestionNo() {
+        return questionNo;
+    }
+
+    public void setQuestionNo(QuestionNo questionNo) {
+        this.questionNo = questionNo;
+    }
 }

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

@@ -29,7 +29,7 @@
     <!--and `uo`.create_time &lt; cd.create_time-->
     where 1
     <if test="userId != null">
-    `uor`.`id` > 0
+    and `uor`.`id` > 0
     </if>
     <if test="dataId != null">
       and `cdh`.dataId = #{dataId,jdbcType=VARCHAR}

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

@@ -28,7 +28,9 @@
       <bind name="keywordLike" value="'%' + keyword + '%'" />
     </if>
     select
-    <include refid="Id_Column_List" />
+    <include refid="Id_Column_List" />,
+    uaq.`ask_time`,
+    uaq.`create_time`
     from `user_ask_question` uaq
     left join `question` q on q.`id` = uaq.`question_id`
     and (q.`question_module` = 'base' or q.`question_module` = 'sentence')
@@ -80,7 +82,9 @@
       <bind name="keywordLike" value="'%' + keyword + '%'" />
     </if>
     select
-    <include refid="Id_Column_List" />
+    <include refid="Id_Column_List" />,
+    uaq.`ask_time`,
+    uaq.`create_time`
     from `user_ask_question` uaq
     left join `question` q on q.`id` = uaq.`question_id`
       and (q.`question_module` = 'base' or q.`question_module` = 'textbook')

+ 14 - 12
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserCollectQuestionRelationMapper.xml

@@ -20,13 +20,14 @@
     </if>
     select max(ucq.`id`) as `id`,
     max(uq.`create_time`) as `latest_time`,
-    sum(uq.`user_correct`) / sum(uq.`user_number`) as `correct`,
-    sum(uq.`user_time`) / sum(uq.`user_number`) as `time`,
-    if(uq.`question_type`='sc', 1, if(uq.`question_type`='rc', 2, if(uq.`question_type`='cr', 3,if(uq.`question_type`='ds', 4, if(uq.`question_type`='ps', 5,if(uq.`question_type`='ir', 6, 7)))))) as `question_type`,
-    uq.`no` as `no`,
-    uq.`origin_id` as `pid`
+    sum(uq.`is_correct`) / count(uq.id) as `correct`,
+    sum(uq.`user_time`) / count(uq.id) as `time`,
+    if(q.`question_type`='sc', 1, if(q.`question_type`='rc', 2, if(q.`question_type`='cr', 3,if(q.`question_type`='ds', 4, if(q.`question_type`='ps', 5,if(q.`question_type`='ir', 6, 7)))))) as `question_type`,
+    max(uq.`no`) as `no`,
+    max(ur.`origin_id`) as `pid`
     from `user_collect_question` ucq
     left join `user_question` uq on uq.`question_id` = ucq.`question_id` and uq.`user_id`=#{userId,jdbcType=VARCHAR}
+    left join `user_report` ur on ur.`id` = uq.`report_id`
     left join `question` q on q.`id` = uq.`question_id`
     and (q.`question_module` = 'base' or q.`question_module` = 'sentence')
     <if test="questionTypes != null">
@@ -64,7 +65,7 @@
     <if test="endTime != null">
       and uq.`create_time` &lt; #{endTime,jdbcType=VARCHAR}
     </if>
-    group by ucq.`question_module`, ucq.`question_no_id`
+    group by ucq.`question_module`, ucq.`question_no_id`, q.`question_type`
     <if test="order != null">
       order by ${order}
     </if>
@@ -76,13 +77,14 @@
     </if>
     select max(ucq.`id`) as `id`,
     max(uq.`create_time`) as `latest_time`,
-    sum(uq.`user_correct`) / sum(uq.`user_number`) as `correct`,
-    sum(uq.`user_time`) / sum(uq.`user_number`) as `time`,
-    if(uq.`question_type`='sc', 1, if(uq.`question_type`='rc', 2, if(uq.`question_type`='cr', 3,if(uq.`question_type`='ds', 4, if(uq.`question_type`='ps', 5,if(uq.`question_type`='ir', 6, 7)))))) as `question_type`,
-    uq.`no` as `no`,
-    uq.`origin_id` as `pid`
+    sum(uq.`is_correct`) / count(uq.id) as `correct`,
+    sum(uq.`user_time`) / count(uq.id) as `time`,
+    if(q.`question_type`='sc', 1, if(q.`question_type`='rc', 2, if(q.`question_type`='cr', 3,if(q.`question_type`='ds', 4, if(q.`question_type`='ps', 5,if(q.`question_type`='ir', 6, 7)))))) as `question_type`,
+    max(uq.`no`) as `no`,
+    max(ur.`origin_id`) as `pid`
     from `user_collect_question` ucq
     left join `user_question` uq on uq.`question_id` = ucq.`question_id` and uq.`user_id`=#{userId,jdbcType=VARCHAR}
+    left join `user_report` ur on ur.`id` = uq.`report_id`
     left join `question` q on q.`id` = uq.`question_id`
     and (q.`question_module` = 'base' or q.`question_module` = 'textbook')
     <if test="questionTypes != null">
@@ -132,7 +134,7 @@
     <if test="endTime != null">
       and uq.`create_time` &lt; #{endTime,jdbcType=VARCHAR}
     </if>
-    group by ucq.`question_module`, ucq.`question_no_id`
+    group by ucq.`question_module`, ucq.`question_no_id`, q.`question_type`
     <if test="order != null">
       order by ${order}
     </if>

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

@@ -19,7 +19,8 @@
       <bind name="keywordLike" value="'%' + keyword + '%'" />
     </if>
     select
-    <include refid="Id_Column_List" />
+    <include refid="Id_Column_List" />,
+    unq.`update_time`
     from `user_note_question` unq
     left join `question` q on q.`id` = unq.`question_id`
     and (q.`question_module` = 'base' or q.`question_module` = 'sentence')
@@ -68,7 +69,8 @@
       <bind name="keywordLike" value="'%' + keyword + '%'" />
     </if>
     select
-    <include refid="Id_Column_List" />
+    <include refid="Id_Column_List" />,
+    unq.`update_time`
     from `user_note_question` unq
     left join `question` q on q.`id` = unq.`question_id`
     and (q.`question_module` = 'base' or q.`question_module` = 'textbook')

+ 19 - 5
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserPaperRelationMapper.xml

@@ -33,7 +33,12 @@
       <bind name="keywordLike" value="'%' + keyword + '%'" />
     </if>
     select
-    DISTINCT(up.`id`), ur.`user_correct` / up.`question_number` as `correct`, up.`user_time` / up.`question_number` as `time`
+    DISTINCT(up.`id`),
+    up.`title` as `title`,
+    up.`latest_time` as `latest_time`,
+    ur.`user_correct` / up.`question_number` as `correct`,
+    ur.`user_time` / up.`question_number` as `time`,
+    ur.`user_number` / up.`question_number` as `progress`
     from `user_paper` up
     left join `user_report` ur on ur.`id`= up.`latest_report_id`
     where up.`user_id` = #{userId,jdbcType=VARCHAR}
@@ -58,9 +63,10 @@
     select
     DISTINCT(up.`id`),
     up.`title` as `title`,
+    up.`latest_time` as `latest_time`,
     ur.`user_correct` / up.`question_number` as `correct`,
-    up.`user_time` / up.`question_number` as `time`,
-    ur.`user_number` / ur.`question_number` as `progress`,
+    ur.`user_time` / up.`question_number` as `time`,
+    ur.`user_number` / up.`question_number` as `progress`
     from `user_paper` up
     left join `user_report` ur on ur.`id`= up.`latest_report_id`
     left join `exercise_paper` ep on up.`paper_origin` = 'exercise' and ep.`id` = up.`origin_id`
@@ -133,11 +139,16 @@
       <bind name="keywordLike" value="'%' + keyword + '%'" />
     </if>
     select
-    DISTINCT(up.`id`), ur.`user_correct` / up.`question_number` as `correct`, up.`user_time` / up.`question_number` as `time`
+    DISTINCT(up.`id`),
+    up.`title` as `title`,
+    up.`latest_time` as `latest_time`,
+    ur.`user_correct` / up.`question_number` as `correct`,
+    ur.`user_time` / up.`question_number` as `time`,
+    ur.`user_number` / up.`question_number` as `progress`
     from `user_paper` up
     left join `user_report` ur on ur.`id`= up.`latest_report_id`
     left join `examination_paper` ep on up.`paper_origin` = 'examination' and ep.`id` = up.`origin_id`
-    <if test="structIds != null">
+    <if test="structIds != null and structIds.length>0">
       and (ep.`struct_two` in
       <foreach collection="structIds" item="item" index="index" open="(" close=")" separator=",">
         #{item}
@@ -166,6 +177,9 @@
     <if test="structIds == null">
       and (ep.`id` > 0 or tp.`id` > 0)
     </if>
+    <if test="libraryId == null">
+      and (ep.`id` > 0)
+    </if>
     <if test="libraryId != null">
       and (tp.`id` > 0)
     </if>

+ 18 - 18
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserQuestionRelationMapper.xml

@@ -26,12 +26,13 @@
     </if>
     select max(uq.`id`) as `id`,
     max(uq.`create_time`) as `latest_time`,
-    sum(uq.`user_correct`) / sum(uq.`user_number`) as `correct`,
-    sum(uq.`user_time`) / sum(uq.`user_number`) as `time`,
-    if(uq.`question_type`='sc', 1, if(uq.`question_type`='rc', 2, if(uq.`question_type`='cr', 3,if(uq.`question_type`='ds', 4, if(uq.`question_type`='ps', 5,if(uq.`question_type`='ir', 6, 7)))))) as `question_type`,
-    uq.`no` as `no`,
-    uq.`origin_id` as `pid`
+    sum(uq.`is_correct`) / count(uq.id) as `correct`,
+    sum(uq.`user_time`) / count(uq.id) as `time`,
+    if(q.`question_type`='sc', 1, if(q.`question_type`='rc', 2, if(q.`question_type`='cr', 3,if(q.`question_type`='ds', 4, if(q.`question_type`='ps', 5,if(q.`question_type`='ir', 6, 7)))))) as `question_type`,
+    max(uq.`no`) as `no`,
+    max(ur.`origin_id`) as `pid`
     from `user_question` uq
+    left join `user_report` ur on ur.`id`=uq.`report_id`
     left join `question` q on q.`id` = uq.`question_id`
       and (q.`question_module` = 'base' or q.`question_module` = 'sentence')
     <if test="questionTypes != null">
@@ -50,10 +51,9 @@
     </if>
     left join `sentence_question` sq on sq.`question_id` = q.`id`
       and (q.`question_module` = 'sentence')
-    left join `user_paper_question` upq on  on upq.`user_id` = #{userId,jdbcType=VARCHAR}
-      and upq.`question_id` = q.`id`
+    left join `user_paper_question` upq on upq.`user_id` = #{userId,jdbcType=VARCHAR}
+      and upq.`question_no_id` = qn.`id`
       and upq.`question_module` = uq.`question_module`
-      and upq.`question_no_id` = uq.`question_no_id`
       and upq.`question_origin` = 'remove_error'
     where
     q.`id` > 0 and upq.`id` = null and uq.`user_id` = #{userId,jdbcType=VARCHAR}
@@ -74,7 +74,7 @@
     <if test="endTime != null">
       and uq.`create_time` &lt; #{endTime,jdbcType=VARCHAR}
     </if>
-    group by uq.`question_module`, uq.`question_no_id`
+    group by uq.`question_module`, uq.`question_no_id`, q.`question_type`
     <if test="order != null">
       order by ${order}
     </if>
@@ -86,12 +86,13 @@
     </if>
     select max(uq.`id`) as `id`,
     max(uq.`create_time`) as `latest_time`,
-    sum(uq.`user_correct`) / sum(uq.`user_number`) as `correct`,
-    sum(uq.`user_time`) / sum(uq.`user_number`) as `time`,
-    if(uq.`question_type`='sc', 1, if(uq.`question_type`='rc', 2, if(uq.`question_type`='cr', 3,if(uq.`question_type`='ds', 4, if(uq.`question_type`='ps', 5,if(uq.`question_type`='ir', 6, 7)))))) as `question_type`,
-    uq.`no` as `no`,
-    uq.`origin_id` as `pid`
+    sum(uq.`is_correct`) / count(uq.id) as `correct`,
+    sum(uq.`user_time`) / count(uq.id) as `time`,
+    if(q.`question_type`='sc', 1, if(q.`question_type`='rc', 2, if(q.`question_type`='cr', 3,if(q.`question_type`='ds', 4, if(q.`question_type`='ps', 5,if(q.`question_type`='ir', 6, 7)))))) as `question_type`,
+    max(uq.`no`) as `no`,
+    max(ur.`origin_id`) as `pid`
     from `user_question` uq
+    left join `user_report` ur on ur.`id`=uq.`report_id`
     left join `question` q on q.`id` = uq.`question_id`
     and (q.`question_module` = 'base' or q.`question_module` = 'textbook')
     <if test="questionTypes != null">
@@ -100,7 +101,7 @@
         q.`question_type` = #{item}
       </foreach>
     </if>
-      left join `question_no` qn on qn.`question_id` = q.`id` and qn.`module` = 'examination'
+    left join `question_no` qn on qn.`question_id` = q.`id` and qn.`module` = 'examination'
     and (q.`question_module` = 'base')
     <if test="structIds != null">
       and
@@ -117,9 +118,8 @@
         and tq.`year` = #{year,jdbcType=VARCHAR}
       </if>
     left join `user_paper_question` upq on upq.`user_id` = #{userId,jdbcType=VARCHAR}
-      and upq.`question_id` = q.`id`
+      and upq.`question_no_id` = qn.`id`
       and upq.`question_module` = uq.`question_module`
-      and upq.`question_no_id` = uq.`question_no_id`
       and upq.`question_origin` = 'remove_error'
     where
     q.`id` > 0 and upq.`id` = null and uq.`user_id` = #{userId,jdbcType=VARCHAR}
@@ -146,7 +146,7 @@
     <if test="endTime != null">
       and uq.`create_time` &lt; #{endTime,jdbcType=VARCHAR}
     </if>
-    group by uq.`question_module`, uq.`question_no_id`
+    group by uq.`question_module`, uq.`question_no_id`, q.`question_type`
     <if test="order != null">
       order by ${order}
     </if>

+ 20 - 3
server/data/src/main/resources/db/migration/V1__init_table.sql

@@ -505,6 +505,7 @@ CREATE TABLE question_no (
   total_time int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总作答时间',
   total_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总作答次数',
   total_correct int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总正确次数',
+  collect_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总收藏数',
   delete_time datetime DEFAULT NULL,
   relation_question varchar(255) NOT NULL DEFAULT '' COMMENT '关联题目列表',
   relation_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '关联题目数量',
@@ -668,6 +669,7 @@ CREATE TABLE sentence_paper (
   logic varchar(20) NOT NULL DEFAULT '' COMMENT '组卷逻辑:no,trail',
   question_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '题目数量',
   question_no_ids text COMMENT '题目编号ids:json',
+  question_s_ids text COMMENT '长难句ids:json',
   status tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '开放状态:0关闭,1开启',
   create_time datetime DEFAULT NULL,
   PRIMARY KEY (id),
@@ -682,6 +684,7 @@ CREATE TABLE sentence_question (
   no int(11) unsigned NOT NULL DEFAULT '0' COMMENT '组卷序号',
   subject varchar(255) NOT NULL COMMENT '根据序号生成的固定标题',
   question_id int(11) unsigned NOT NULL COMMENT '题目id',
+  question_no_id int(11) unsigned NOT NULL COMMENT '题目编号id',
   chinese text,
   total_time int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总作答时间',
   total_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总做题次数',
@@ -850,6 +853,7 @@ CREATE TABLE textbook_paper (
   no int(11) unsigned NOT NULL COMMENT '序号',
   library_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '换库id',
   question_no_ids text COMMENT '题目编号ids:json',
+  question_t_ids text COMMENT '机经ids:json',
   question_number int(11) DEFAULT NULL,
   create_time datetime DEFAULT NULL,
   status tinyint(1) unsigned NOT NULL COMMENT '开放状态:0关闭,1开启',
@@ -864,6 +868,7 @@ CREATE TABLE textbook_question (
   no int(11) unsigned NOT NULL DEFAULT '0' COMMENT '题目序号',
   library_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '机经版本:关联换库表',
   question_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '题目id',
+  question_no_id int(11) unsigned NOT NULL COMMENT '题目编号id',
   total_time int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总做题时间',
   total_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总做题次数',
   total_correct int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总正确次数',
@@ -1013,7 +1018,7 @@ CREATE TABLE user_ask_question (
 CREATE TABLE user_collect_experience (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',
-  expericence_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '经验id',
+  experience_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '经验id',
   create_time datetime DEFAULT NULL,
   PRIMARY KEY (id),
   KEY user_id (user_id,expericence_id)
@@ -1101,6 +1106,15 @@ CREATE TABLE user_course_record (
   KEY user_id (user_id,course_id,record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-课程-访问记录';
 
+CREATE TABLE use_export (
+  id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  user_id int(11) unsigned NOT NULL DEFAULT '0',
+  type varchar(20) NOT NULL DEFAULT '' COMMENT '导出类型',
+  setting text COMMENT '导出设置',
+  create_time datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-导出-记录';
+
 CREATE TABLE user_feedback_error (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) unsigned NOT NULL COMMENT '用户id',
@@ -1281,11 +1295,13 @@ CREATE TABLE user_paper (
   origin_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '对应来源id',
   record_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '关联记录id',
   paper_no int(11) unsigned NOT NULL DEFAULT '0' COMMENT '相同试卷不同编号:对应千行cat次数',
+  qx_cat int(11) unsigned NOT NULL DEFAULT '0' COMMENT '区分qx试卷0,1',
   question_no_ids text COMMENT '题目编号id列表:json',
   question_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '题目数量',
   times int(11) unsigned NOT NULL DEFAULT '0' COMMENT '练习次数',
   time int(11) unsigned NOT NULL DEFAULT '0' COMMENT '单次时间:系统时间',
-  latest_time date DEFAULT NULL COMMENT '最近一次做题记录',
+  latest_time date DEFAULT NULL COMMENT '最近一次做题时间',
+  latest_report_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最近一次做题记录',
   total_time int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总作答时间',
   total_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总作答次数',
   total_correct int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总正确次数',
@@ -1401,8 +1417,9 @@ CREATE TABLE user_textbook_enroll (
 CREATE TABLE user_textbook_feedback (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) unsigned NOT NULL COMMENT '用户id',
-  topic_id int(11) unsigned NOT NULL COMMENT '机经问题',
   question_subject varchar(20) NOT NULL COMMENT '学科',
+  no int(11) unsigned NOT NULL DEFAULT 0 COMMENT '序号',
+  topic_id int(11) unsigned NOT NULL DEFAULT 0 COMMENT '机经问题',
   library_id int(11) unsigned NOT NULL COMMENT '换库表',
   target varchar(20) NOT NULL DEFAULT '' COMMENT '反馈类型',
   content text COMMENT '正确内容',

+ 6 - 0
server/data/src/main/resources/mybatis-generator.xml

@@ -108,10 +108,12 @@
         <table schema="qianxing" tableName="sentence_paper" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
             <columnOverride column="question_no_ids" javaType="Integer[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler"/>
+            <columnOverride column="question_s_ids" javaType="Integer[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler"/>
         </table>
         <table schema="qianxing" tableName="textbook_paper" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
             <columnOverride column="question_no_ids" javaType="Integer[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler"/>
+            <columnOverride column="question_t_ids" javaType="Integer[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler"/>
         </table>
         <table schema="qianxing" tableName="preview_paper" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
@@ -175,5 +177,9 @@
             <columnOverride column="promote" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler"/>
             <columnOverride column="gift" javaType="com.alibaba.fastjson.JSONArray" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonArrayHandler"/>
         </table>
+        <table schema="qianxing" tableName="user_export" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
+            <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
+            <columnOverride column="setting" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler"/>
+        </table>
     </context>
 </generatorConfiguration>

+ 186 - 211
server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java

@@ -12,6 +12,7 @@ import com.qxgmat.data.constants.enums.module.*;
 import com.qxgmat.data.constants.enums.status.AskStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.user.DataType;
+import com.qxgmat.data.constants.enums.user.ExportType;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.inline.PaperStat;
 import com.qxgmat.data.inline.UserQuestionStat;
@@ -120,6 +121,9 @@ public class MyController {
     private CourseTeacherService courseTeacherService;
 
     @Autowired
+    private ExaminationPaperService examinationPaperService;
+
+    @Autowired
     private FaqService faqService;
 
     @Autowired
@@ -153,6 +157,9 @@ public class MyController {
     private UserServiceService userServiceService;
 
     @Autowired
+    private UserExportService userExportService;
+
+    @Autowired
     private UserCollectQuestionService userCollectQuestionService;
 
     @Autowired
@@ -837,26 +844,13 @@ public class MyController {
     public Response<Boolean> addQuestionCollect(@RequestBody @Validated UserCollectQuestionDto dto)  {
         UserCollectQuestion entity = Transform.dtoToEntity(dto);
         User user = (User) shiroHelp.getLoginUser();
-        switch (QuestionModule.ValueOf(dto.getQuestionModule())){
-            case BASE:
-                entity.setQuestionModule(QuestionModule.BASE.key);
-                QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
-                entity.setQuestionId(questionNo.getQuestionId());
-                entity.setQuestionNoId(questionNo.getId());
-                break;
-            case SENTENCE:
-                entity.setQuestionModule(QuestionModule.SENTENCE.key);
-                SentenceQuestion sentenceQuestion = sentenceQuestionService.get(dto.getQuestionNoId());
-                entity.setQuestionId(sentenceQuestion.getQuestionId());
-                entity.setQuestionNoId(sentenceQuestion.getId());
-                break;
-            case TEXTBOOK:
-                entity.setQuestionModule(QuestionModule.TEXTBOOK.key);
-                TextbookQuestion textbookQuestion = textbookQuestionService.get(dto.getQuestionNoId());
-                entity.setQuestionId(textbookQuestion.getQuestionId());
-                entity.setQuestionNoId(textbookQuestion.getId());
-                break;
-        }
+
+        QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
+        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
+        QuestionModule questionModule = QuestionModule.WithQuestionNo(questionNoModule);
+        entity.setQuestionModule(questionModule.key);
+        entity.setQuestionId(questionNo.getQuestionId());
+        entity.setQuestionNoId(questionNo.getId());
         entity.setUserId(user.getId());
         userCollectQuestionService.addQuestion(entity);
 
@@ -865,36 +859,35 @@ public class MyController {
 
     @RequestMapping(value = "/collect/question/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "移除题目收藏", notes = "移除题目收藏", httpMethod = "DELETE")
-    public Response<Boolean> deleteQuestionCollect(String questionModule, Integer questionNoId)  {
-        User user = (User) shiroHelp.getLoginUser();
-        Integer questionId = null;
-        switch (QuestionModule.ValueOf(questionModule)){
-            case BASE:
-                QuestionNo questionNo = questionNoService.get(questionNoId);
-                questionId = questionNo.getQuestionId();
-                break;
-            case SENTENCE:
-                SentenceQuestion sentenceQuestion = sentenceQuestionService.get(questionNoId);
-                questionId = sentenceQuestion.getQuestionId();
-                break;
-            case TEXTBOOK:
-                TextbookQuestion textbookQuestion = textbookQuestionService.get(questionNoId);
-                questionId = textbookQuestion.getQuestionId();
-                break;
-        }
+    public Response<Boolean> deleteQuestionCollect(Integer questionNoId)  {
+        User user = (User) shiroHelp.getLoginUser();
+        QuestionNo questionNo = questionNoService.get(questionNoId);
+        Integer questionId = questionNo.getQuestionId();
         Boolean result = userCollectQuestionService.deleteQuestion(user.getId(), questionId);
 
         return ResponseHelp.success(result);
     }
 
-    @RequestMapping(value = "/collect/question/bind", method = RequestMethod.POST)
-    @ApiOperation(value = "收藏题目组卷", notes = "收藏题目组卷", httpMethod = "POST")
-    public Response<UserPaper> bindQuestionCollect(@RequestBody @Validated UserCustomBindDto dto)  {
+    @RequestMapping(value = "/collect/question/clear", method = RequestMethod.DELETE)
+    @ApiOperation(value = "移除题目收藏", notes = "移除题目收藏", httpMethod = "DELETE")
+    public Response<Boolean> deleteQuestionCollect(@RequestBody @Validated UserQuestionIdsDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
+        List<QuestionNo> questionNoList = questionNoService.select(dto.getQuestionNoIds());
+        for(QuestionNo questionNo : questionNoList){
+            userCollectQuestionService.deleteQuestion(user.getId(), questionNo.getQuestionId());
+        }
 
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/collect/question/group", method = RequestMethod.POST)
+    @ApiOperation(value = "收藏题目组卷", notes = "收藏题目组卷", httpMethod = "POST")
+    public Response<UserPaper> bindQuestionCollect(@RequestBody @Validated UserCustomGroupDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        QuestionModule questionModule = questionFlowService.validGroup(dto.getQuestionNoIds());
         UserPaper userPaper = questionFlowService.makePaper(
                 user.getId(),
-                QuestionModule.ValueOf(dto.getQuestionModule()),
+                questionModule,
                 PaperOrigin.COLLECT,
                 Arrays.stream(dto.getQuestionNoIds()).collect(Collectors.toList()),
                 dto.getFilterTimes()
@@ -913,7 +906,11 @@ public class MyController {
             throw new ParameterException("试卷未完成");
         }
         List<UserQuestion> questionList = userQuestionService.listByReport(user.getId(), dto.getUserReportId());
-        userPaperQuestionService.addRemoveError(questionList);
+        Collection questionNoIds = Transform.getIds(questionList, UserQuestion.class, "questionNoId");
+
+        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
+        List<QuestionNoRelation> relationList = questionNoService.relation(questionNoList);
+        userPaperQuestionService.addRemoveError(user.getId(), relationList);
 
         return ResponseHelp.success(true);
     }
@@ -960,20 +957,9 @@ public class MyController {
         List<Question> questionList = questionService.select(questionIds);
         Transform.combine(pr, questionList, UserCollectQuestionInfoDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);
 
-        List<UserCollectQuestionInfoDto> basePr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.BASE.key)).collect(Collectors.toList());
-        Collection baseQuestionNoIds = Transform.getIds(basePr, UserCollectQuestionInfoDto.class, "questionNoId");
-        List<QuestionNo> baseQuestionNoList = questionNoService.select(baseQuestionNoIds);
-        Transform.combine(basePr, baseQuestionNoList, UserCollectQuestionInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
-
-        List<UserCollectQuestionInfoDto> sentencePr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.SENTENCE.key)).collect(Collectors.toList());
-        Collection sentenceQuestionNoIds = Transform.getIds(sentencePr, UserCollectQuestionInfoDto.class, "questionNoId");
-        List<SentenceQuestion> sentenceQuestionList = sentenceQuestionService.select(sentenceQuestionNoIds);
-        Transform.combine(sentencePr, sentenceQuestionList, UserCollectQuestionInfoDto.class, "questionNoId", "questionNo", SentenceQuestion.class, "id", QuestionNoExtendDto.class);
-
-        List<UserCollectQuestionInfoDto> textbookPr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.TEXTBOOK.key)).collect(Collectors.toList());
-        Collection textbookQuestionNoIds = Transform.getIds(textbookPr, UserCollectQuestionInfoDto.class, "questionNoId");
-        List<TextbookQuestion> textbookQuestionList = textbookQuestionService.select(textbookQuestionNoIds);
-        Transform.combine(textbookPr, textbookQuestionList, UserCollectQuestionInfoDto.class, "questionNoId", "questionNo", TextbookQuestion.class, "id", QuestionNoExtendDto.class);
+        Collection questionNoIds = Transform.getIds(pr, UserCollectQuestionInfoDto.class, "questionNoId");
+        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
+        Transform.combine(pr, questionNoList, UserCollectQuestionInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
 
         // 绑定题目统计
         List<UserQuestion> userQuestionList = userQuestionService.listByQuestion(user.getId(), questionIds);
@@ -983,8 +969,19 @@ public class MyController {
         // 最近做题
         List<UserQuestion> lastList = userQuestionService.listWithLast(questionIds);
         Map lastMap = Transform.getMap(lastList, UserQuestion.class, "id", "createTime");
-        Transform.combine(pr, lastMap, UserQuestionErrorInfoDto.class, "questionId", "latestTime");
+        Transform.combine(pr, lastMap, UserCollectQuestionInfoDto.class, "questionId", "latestTime");
+
+        // 收藏、笔记
+        List<UserCollectQuestion> userCollectQuestionList = userCollectQuestionService.listByUserAndQuestions(user.getId(), questionIds);
+        Map collectMap = Transform.getMap(userCollectQuestionList, UserCollectQuestion.class, "questionId", "id");
 
+        List<UserNoteQuestion> userNoteQuestionList = userNoteQuestionService.listByUserAndQuestions(user.getId(), questionIds);
+        Map noteMap = Transform.getMap(userNoteQuestionList, UserNoteQuestion.class, "questionId", "id");
+
+        for(UserCollectQuestionInfoDto dto : pr){
+            dto.setCollect(collectMap.containsKey(dto.getQuestionId()));
+            dto.setNote(noteMap.containsKey(dto.getQuestionId()));
+        }
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
@@ -1030,20 +1027,9 @@ public class MyController {
         List<Question> questionList = questionService.select(questionIds);
         Transform.combine(pr, questionList, UserQuestionErrorInfoDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);
 
-        List<UserQuestionErrorInfoDto> basePr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.BASE.key)).collect(Collectors.toList());
-        Collection baseQuestionNoIds = Transform.getIds(basePr, UserQuestionErrorInfoDto.class, "questionNoId");
-        List<QuestionNo> baseQuestionNoList = questionNoService.select(baseQuestionNoIds);
-        Transform.combine(basePr, baseQuestionNoList, UserQuestionErrorInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
-
-        List<UserQuestionErrorInfoDto> sentencePr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.SENTENCE.key)).collect(Collectors.toList());
-        Collection sentenceQuestionNoIds = Transform.getIds(sentencePr, UserQuestionErrorInfoDto.class, "questionNoId");
-        List<SentenceQuestion> sentenceQuestionList = sentenceQuestionService.select(sentenceQuestionNoIds);
-        Transform.combine(sentencePr, sentenceQuestionList, UserQuestionErrorInfoDto.class, "questionNoId", "questionNo", SentenceQuestion.class, "id", QuestionNoExtendDto.class);
-
-        List<UserQuestionErrorInfoDto> textbookPr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.TEXTBOOK.key)).collect(Collectors.toList());
-        Collection textbookQuestionNoIds = Transform.getIds(textbookPr, UserQuestionErrorInfoDto.class, "questionNoId");
-        List<TextbookQuestion> textbookQuestionList = textbookQuestionService.select(textbookQuestionNoIds);
-        Transform.combine(textbookPr, textbookQuestionList, UserQuestionErrorInfoDto.class, "questionNoId", "questionNo", TextbookQuestion.class, "id", QuestionNoExtendDto.class);
+        Collection questionNoIds = Transform.getIds(pr, UserQuestionErrorInfoDto.class, "questionId");
+        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
+        Transform.combine(pr, questionNoList, UserQuestionErrorInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
 
         // 绑定题目统计
         List<UserQuestion> userQuestionList = userQuestionService.listByQuestion(user.getId(), questionIds);
@@ -1055,17 +1041,28 @@ public class MyController {
         Map lastMap = Transform.getMap(lastList, UserQuestion.class, "id", "createTime");
         Transform.combine(pr, lastMap, UserQuestionErrorInfoDto.class, "questionId", "latestTime");
 
+        // 收藏、笔记
+        List<UserCollectQuestion> userCollectQuestionList = userCollectQuestionService.listByUserAndQuestions(user.getId(), questionIds);
+        Map collectMap = Transform.getMap(userCollectQuestionList, UserCollectQuestion.class, "questionId", "id");
+
+        List<UserNoteQuestion> userNoteQuestionList = userNoteQuestionService.listByUserAndQuestions(user.getId(), questionIds);
+        Map noteMap = Transform.getMap(userNoteQuestionList, UserNoteQuestion.class, "questionId", "id");
+
+        for(UserQuestionErrorInfoDto dto : pr){
+            dto.setCollect(collectMap.containsKey(dto.getQuestionId()));
+            dto.setNote(noteMap.containsKey(dto.getQuestionId()));
+        }
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
-    @RequestMapping(value = "/error/bind", method = RequestMethod.POST)
+    @RequestMapping(value = "/error/group", method = RequestMethod.POST)
     @ApiOperation(value = "错题组卷", notes = "错题组卷", httpMethod = "POST")
-    public Response<UserPaper> bindError(@RequestBody @Validated UserCustomBindDto dto)  {
+    public Response<UserPaper> bindError(@RequestBody @Validated UserCustomGroupDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
-
+        QuestionModule questionModule = questionFlowService.validGroup(dto.getQuestionNoIds());
         UserPaper userPaper = questionFlowService.makePaper(
                 user.getId(),
-                QuestionModule.ValueOf(dto.getQuestionModule()),
+                questionModule,
                 PaperOrigin.ERROR,
                 Arrays.stream(dto.getQuestionNoIds()).collect(Collectors.toList()),
                 dto.getFilterTimes()
@@ -1079,8 +1076,9 @@ public class MyController {
     public Response<Boolean> clearError(@RequestBody @Validated UserQuestionIdsDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
 
-        List<UserQuestion> questionList = userQuestionService.select(dto.getQuestionNoIds());
-        userPaperQuestionService.addRemoveError(questionList);
+        List<QuestionNo> questionNoList = questionNoService.select(dto.getQuestionNoIds());
+        List<QuestionNoRelation> relationList = questionNoService.relation(questionNoList);
+        userPaperQuestionService.addRemoveError(user.getId(), relationList);
 
         return ResponseHelp.success(true);
     }
@@ -1095,7 +1093,11 @@ public class MyController {
             throw new ParameterException("试卷未完成");
         }
         List<UserQuestion> questionList = userQuestionService.listByReport(user.getId(), dto.getUserReportId());
-        userPaperQuestionService.addRemoveError(questionList);
+        Collection questionNoIds = Transform.getIds(questionList, UserQuestion.class, "questionNoId");
+
+        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
+        List<QuestionNoRelation> relationList = questionNoService.relation(questionNoList);
+        userPaperQuestionService.addRemoveError(user.getId(), relationList);
 
         return ResponseHelp.success(true);
     }
@@ -1106,32 +1108,30 @@ public class MyController {
         UserNoteQuestion entity = Transform.dtoToEntity(dto);
         User user = (User) shiroHelp.getLoginUser();
         entity.setUserId(user.getId());
-        switch (QuestionModule.ValueOf(dto.getQuestionModule())){
-            case BASE:
-                entity.setQuestionModule(QuestionModule.BASE.key);
-                QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
-                entity.setQuestionId(questionNo.getQuestionId());
-                entity.setQuestionNoId(questionNo.getId());
-                break;
-            case SENTENCE:
-                entity.setQuestionModule(QuestionModule.SENTENCE.key);
-                SentenceQuestion sentenceQuestion = sentenceQuestionService.get(dto.getQuestionNoId());
-                entity.setQuestionId(sentenceQuestion.getQuestionId());
-                entity.setQuestionNoId(sentenceQuestion.getId());
-                break;
-            case TEXTBOOK:
-                entity.setQuestionModule(QuestionModule.TEXTBOOK.key);
-                TextbookQuestion textbookQuestion = textbookQuestionService.get(dto.getQuestionNoId());
-                entity.setQuestionId(textbookQuestion.getQuestionId());
-                entity.setQuestionNoId(textbookQuestion.getId());
-                break;
-        }
-
-        userNoteQuestionService.update(entity);
+
+        QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
+        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
+        QuestionModule questionModule = QuestionModule.WithQuestionNo(questionNoModule);
+        entity.setQuestionModule(questionModule.key);
+        entity.setQuestionId(questionNo.getQuestionId());
+        entity.setQuestionNoId(questionNo.getId());
+
+        userNoteQuestionService.updateNote(entity);
 
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/note/question/clear", method = RequestMethod.POST)
+    @ApiOperation(value = "笔记移除", notes = "笔记移除", httpMethod = "POST")
+    public Response<Boolean> clearNoteQuestion(@RequestBody @Validated UserQuestionIdsDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        List<QuestionNo> questionNoList = questionNoService.select(dto.getQuestionNoIds());
+        for(QuestionNo questionNo : questionNoList){
+            userNoteQuestionService.deleteNote(user.getId(), questionNo.getQuestionId());
+        }
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/note/question/list", method = RequestMethod.GET)
     @ApiOperation(value = "获取题目笔记列表", notes = "获取笔记列表", httpMethod = "GET")
     public Response<PageMessage<UserNoteQuestionInfoDto>> listNoteQuestion(
@@ -1174,20 +1174,9 @@ public class MyController {
         List<Question> questionList = questionService.select(questionIds);
         Transform.combine(pr, questionList, UserNoteQuestionInfoDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);
 
-        List<UserNoteQuestionInfoDto> basePr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.BASE.key)).collect(Collectors.toList());
-        Collection baseQuestionNoIds = Transform.getIds(basePr, UserNoteQuestionInfoDto.class, "questionNoId");
-        List<QuestionNo> baseQuestionNoList = questionNoService.select(baseQuestionNoIds);
-        Transform.combine(basePr, baseQuestionNoList, UserNoteQuestionInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
-
-        List<UserNoteQuestionInfoDto> sentencePr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.SENTENCE.key)).collect(Collectors.toList());
-        Collection sentenceQuestionNoIds = Transform.getIds(sentencePr, UserNoteQuestionInfoDto.class, "questionNoId");
-        List<SentenceQuestion> sentenceQuestionList = sentenceQuestionService.select(sentenceQuestionNoIds);
-        Transform.combine(sentencePr, sentenceQuestionList, UserNoteQuestionInfoDto.class, "questionNoId", "questionNo", SentenceQuestion.class, "id", QuestionNoExtendDto.class);
-
-        List<UserNoteQuestionInfoDto> textbookPr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.TEXTBOOK.key)).collect(Collectors.toList());
-        Collection textbookQuestionNoIds = Transform.getIds(textbookPr, UserNoteQuestionInfoDto.class, "questionNoId");
-        List<TextbookQuestion> textbookQuestionList = textbookQuestionService.select(textbookQuestionNoIds);
-        Transform.combine(textbookPr, textbookQuestionList, UserNoteQuestionInfoDto.class, "questionNoId", "questionNo", TextbookQuestion.class, "id", QuestionNoExtendDto.class);
+        Collection questionNoIds = Transform.getIds(pr, UserNoteQuestionInfoDto.class, "questionNoId");
+        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
+        Transform.combine(pr, questionNoList, UserNoteQuestionInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
@@ -1224,7 +1213,7 @@ public class MyController {
         PaperOrigin paperOrigin = PaperOrigin.ValueOf(origin);
         QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(module);
         Page<UserPaper> p = null;
-        if (questionNoModule != null && (paperOrigin == PaperOrigin.COLLECT || paperOrigin == PaperOrigin.ERROR)){
+        if (paperOrigin == PaperOrigin.COLLECT || paperOrigin == PaperOrigin.ERROR){
             p = userPaperService.list(page, size, user.getId(), keyword, paperOrigin, startTime, endTime, order != null ? order.replace("|", " ") : null);
         }else if(questionNoModule == QuestionNoModule.EXERCISE){
             p = userPaperService.listExercise(page, size, user.getId(), keyword, questionTypes, structIds, courseModules, startTime, endTime, order != null ? order.replace("|", " ") : null);
@@ -1246,36 +1235,26 @@ public class MyController {
         }
         List<UserPaperDto> pr = Transform.convert(p, UserPaperDto.class);
 
+        if (questionNoModule == QuestionNoModule.EXAMINATION){
+            Collection originIds = Transform.getIds(p, UserPaper.class, "originId");
+            List<ExaminationPaper> examinationPapers = examinationPaperService.select(originIds);
+            Transform.combine(pr, examinationPapers, UserPaperDto.class, "originId", "origin", ExaminationPaper.class, "id", PaperExtendDto.class);
+        }
+
         Collection paperIds = Transform.getIds(p, UserPaper.class, "id");
         // 绑定用户报告
         Map<Object, Collection<UserReport>> reportByPaper = userReportService.mapByPaper(paperIds);
         Transform.combine(pr, reportByPaper, UserPaperDto.class, "id", "reports", UserReportExtendDto.class);
 
         // 获取试卷统计信息
-        List<UserPaperDto> basePr = pr.stream().filter((row)->QuestionModule.BASE == QuestionModule.WithPaper(PaperModule.ValueOf(row.getPaperModule()))).collect(Collectors.toList());
         Map<Integer, Integer[]> baseIdsMap = new HashMap<>();
-        for(UserPaperDto paper : basePr){
+        for(UserPaperDto paper : pr){
+            if (paper.getQuestionNoIds() == null) continue;
             baseIdsMap.put(paper.getId(), paper.getQuestionNoIds());
         }
         Map baseStatMap = questionNoService.statPaperMap(baseIdsMap);
         Transform.combine(pr, baseStatMap, UserPaperDto.class, "id", "stat");
 
-        List<UserPaperDto> sentencePr = pr.stream().filter((row)->QuestionModule.SENTENCE == QuestionModule.WithPaper(PaperModule.ValueOf(row.getPaperModule()))).collect(Collectors.toList());
-        Map<Integer, Integer[]> sentenceIdsMap = new HashMap<>();
-        for(UserPaperDto paper : sentencePr){
-            sentenceIdsMap.put(paper.getId(), paper.getQuestionNoIds());
-        }
-        Map sentenceStatMap = sentenceQuestionService.statPaperMap(sentenceIdsMap);
-        Transform.combine(pr, sentenceStatMap, UserPaperDto.class, "id", "stat");
-
-        List<UserPaperDto> textbookPr = pr.stream().filter((row)->QuestionModule.TEXTBOOK == QuestionModule.WithPaper(PaperModule.ValueOf(row.getPaperModule()))).collect(Collectors.toList());
-        Map<Integer, Integer[]> textbookIdsMap = new HashMap<>();
-        for(UserPaperDto paper : textbookPr){
-            textbookIdsMap.put(paper.getId(), paper.getQuestionNoIds());
-        }
-        Map textbookStatMap = textbookQuestionService.statPaperMap(textbookIdsMap);
-        Transform.combine(pr, textbookStatMap, UserPaperDto.class, "id", "stat");
-
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
@@ -1285,36 +1264,14 @@ public class MyController {
         UserAskQuestion entity = Transform.dtoToEntity(dto);
         User user = (User) shiroHelp.getLoginUser();
         entity.setUserId(user.getId());
-        QuestionModule questionModule = QuestionModule.ValueOf(dto.getQuestionModule());
-        Question question;
-        switch (questionModule){
-            case BASE:
-                entity.setQuestionModule(QuestionModule.BASE.key);
-                QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
-                entity.setQuestionId(questionNo.getQuestionId());
-                entity.setQuestionNoId(questionNo.getId());
-
-                question = questionService.get(questionNo.getQuestionId());
-                break;
-            case SENTENCE:
-                entity.setQuestionModule(QuestionModule.SENTENCE.key);
-                SentenceQuestion sentenceQuestion = sentenceQuestionService.get(dto.getQuestionNoId());
-                entity.setQuestionId(sentenceQuestion.getQuestionId());
-                entity.setQuestionNoId(sentenceQuestion.getId());
-
-                question = questionService.get(sentenceQuestion.getQuestionId());
-                break;
-            case TEXTBOOK:
-                entity.setQuestionModule(QuestionModule.TEXTBOOK.key);
-                TextbookQuestion textbookQuestion = textbookQuestionService.get(dto.getQuestionNoId());
-                entity.setQuestionId(textbookQuestion.getQuestionId());
-                entity.setQuestionNoId(textbookQuestion.getId());
-
-                question = questionService.get(textbookQuestion.getQuestionId());
-                break;
-            default:
-                throw new ParameterException("题目模块错误");
-        }
+
+        QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
+        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
+        QuestionModule questionModule = QuestionModule.WithQuestionNo(questionNoModule);
+        entity.setQuestionModule(questionModule.key);
+        entity.setQuestionId(questionNo.getQuestionId());
+        entity.setQuestionNoId(questionNo.getId());
+        Question question = questionService.get(questionNo.getQuestionId());
 
         Integer assignId = null;
         PaperModule paperModule = null;
@@ -1327,7 +1284,7 @@ public class MyController {
             }
             paperModule = PaperModule.ValueOf(userPaper.getPaperModule());
         }else{
-            paperModule = PaperModule.WithQuestion(questionModule);
+            paperModule = PaperModule.WithQuestionNo(questionNoModule);
         }
         entity.setAskModule(AskModule.WithPaper(paperModule).key);
         Integer recordId = questionFlowService.questionRelationCourse(user.getId(), assignId, QuestionType.ValueOf(question.getQuestionType()));
@@ -1409,20 +1366,9 @@ public class MyController {
         List<Question> questionList = questionService.select(questionIds);
         Transform.combine(pr, questionList, UserAskQuestionInfoDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);
 
-        List<UserAskQuestionInfoDto> basePr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.BASE.key)).collect(Collectors.toList());
-        Collection baseQuestionNoIds = Transform.getIds(basePr, UserAskQuestionInfoDto.class, "questionNoId");
-        List<QuestionNo> baseQuestionNoList = questionNoService.select(baseQuestionNoIds);
-        Transform.combine(basePr, baseQuestionNoList, UserAskQuestionInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
-
-        List<UserAskQuestionInfoDto> sentencePr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.SENTENCE.key)).collect(Collectors.toList());
-        Collection sentenceQuestionNoIds = Transform.getIds(sentencePr, UserAskQuestionInfoDto.class, "questionNoId");
-        List<SentenceQuestion> sentenceQuestionList = sentenceQuestionService.select(sentenceQuestionNoIds);
-        Transform.combine(sentencePr, sentenceQuestionList, UserAskQuestionInfoDto.class, "questionNoId", "questionNo", SentenceQuestion.class, "id", QuestionNoExtendDto.class);
-
-        List<UserAskQuestionInfoDto> textbookPr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.TEXTBOOK.key)).collect(Collectors.toList());
-        Collection textbookQuestionNoIds = Transform.getIds(textbookPr, UserAskQuestionInfoDto.class, "questionNoId");
-        List<TextbookQuestion> textbookQuestionList = textbookQuestionService.select(textbookQuestionNoIds);
-        Transform.combine(textbookPr, textbookQuestionList, UserAskQuestionInfoDto.class, "questionNoId", "questionNo", TextbookQuestion.class, "id", QuestionNoExtendDto.class);
+        Collection questionNoIds = Transform.getIds(pr, UserAskQuestionInfoDto.class, "questionNoId");
+        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
+        Transform.combine(pr, questionNoList, UserAskQuestionInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
@@ -1459,35 +1405,14 @@ public class MyController {
         entity.setModule(FeedbackModule.QUESTION.key);
         entity.setStatus(0);
 
-        Question question;
-        switch (QuestionModule.ValueOf(dto.getQuestionModule())){
-            case BASE:
-                entity.setQuestionModule(QuestionModule.BASE.key);
-                QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
-                entity.setModuleId(questionNo.getQuestionId());
-                entity.setQuestionNoId(questionNo.getId());
-
-                question = questionService.get(questionNo.getQuestionId());
-                break;
-            case SENTENCE:
-                entity.setQuestionModule(QuestionModule.SENTENCE.key);
-                SentenceQuestion sentenceQuestion = sentenceQuestionService.get(dto.getQuestionNoId());
-                entity.setModuleId(sentenceQuestion.getQuestionId());
-                entity.setQuestionNoId(sentenceQuestion.getId());
-
-                question = questionService.get(sentenceQuestion.getQuestionId());
-                break;
-            case TEXTBOOK:
-                entity.setQuestionModule(QuestionModule.SENTENCE.key);
-                TextbookQuestion textbookQuestion = textbookQuestionService.get(dto.getQuestionNoId());
-                entity.setModuleId(textbookQuestion.getQuestionId());
-                entity.setQuestionNoId(textbookQuestion.getId());
-
-                question = questionService.get(textbookQuestion.getQuestionId());
-                break;
-            default:
-                throw new ParameterException("题目模块错误");
-        }
+        QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
+        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
+        QuestionModule questionModule = QuestionModule.WithQuestionNo(questionNoModule);
+        entity.setQuestionModule(questionModule.key);
+        entity.setQuestionNoId(questionNo.getId());
+
+        Question question = questionService.get(questionNo.getQuestionId());
+
         entity.setQuestionType(question.getQuestionType());
         userFeedbackErrorService.add(entity);
 
@@ -1508,6 +1433,25 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/feedback/textbook", method = RequestMethod.POST)
+    @ApiOperation(value = "添加机经反馈", notes = "添加反馈", httpMethod = "POST")
+    public Response<Boolean> addFeedbackTextbook(@RequestBody @Validated UserTextbookFeedbackDto dto)  {
+        UserTextbookFeedback entity = Transform.dtoToEntity(dto);
+        User user = (User) shiroHelp.getLoginUser();
+
+        TextbookLibrary latest = textbookLibraryService.getLatest();
+        entity.setLibraryId(latest.getId());
+        entity.setUserId(user.getId());
+        entity.setStatus(0);
+        if (entity.getNo() != null && entity.getNo() > 0){
+            TextbookTopic textbookTopic = textbookTopicService.getByNo(entity.getLibraryId(), entity.getQuestionSubject(), entity.getNo());
+            entity.setTopicId(textbookTopic.getId());
+        }
+        userTextbookFeedbackService.add(entity);
+
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/faq", method = RequestMethod.POST)
     @ApiOperation(value = "添加faq", notes = "添加faq", httpMethod = "POST")
     public Response<Boolean> addFaq(@RequestBody @Validated FaqDto dto)  {
@@ -1812,6 +1756,12 @@ public class MyController {
         UserCourseAppointment appointment = userCourseAppointmentService.get(entity.getAppointmentId());
         entity.setUserId(user.getId());
         entity.setRecordId(appointment.getRecordId());
+        if (entity.getParentId() > 0){
+            UserCourseAppointmentComment comment = userCourseAppointmentCommentService.get(entity.getParentId());
+            if (comment != null){
+                entity.setReply(comment.getContent());
+            }
+        }
         entity = userCourseAppointmentCommentService.add(entity);
         return ResponseHelp.success(true);
     }
@@ -1933,12 +1883,37 @@ public class MyController {
         return ResponseHelp.success(dtos);
     }
 
-    @RequestMapping(value = "/export/question/error", method = RequestMethod.POST)
-    @ApiOperation(value = "关闭提示", notes = "关闭提示", httpMethod = "POST")
-    public Response<List<UserQuestionDetailDto>> exportQuestionError()  {
+    @RequestMapping(value = "/export/question", method = RequestMethod.POST)
+    @ApiOperation(value = "导出题目", notes = "导出题目", httpMethod = "POST")
+    public Response<UserExport> exportQuestion(@RequestBody @Validated UserExportDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
+        UserExport entity = Transform.dtoToEntity(dto);
+        entity.setUserId(user.getId());
+        entity.setType(ExportType.QUESTION.key);
+        entity = userExportService.add(entity);
+        return ResponseHelp.success(entity);
+    }
 
-        return ResponseHelp.success(null);
+    @RequestMapping(value = "/export/note/question", method = RequestMethod.POST)
+    @ApiOperation(value = "导出题目笔记", notes = "导出题目笔记", httpMethod = "POST")
+    public Response<UserExport> exportNoteQuestion(@RequestBody @Validated UserExportDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        UserExport entity = Transform.dtoToEntity(dto);
+        entity.setUserId(user.getId());
+        entity.setType(ExportType.NOTE_QUESTION.key);
+        entity = userExportService.add(entity);
+        return ResponseHelp.success(entity);
+    }
+
+    @RequestMapping(value = "/export/note/course", method = RequestMethod.POST)
+    @ApiOperation(value = "导出课程笔记", notes = "导出课程笔记", httpMethod = "POST")
+    public Response<UserExport> exportNoteCourse(@RequestBody @Validated UserExportDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        UserExport entity = Transform.dtoToEntity(dto);
+        entity.setUserId(user.getId());
+        entity.setType(ExportType.NOTE_COURSE.key);
+        entity = userExportService.add(entity);
+        return ResponseHelp.success(entity);
     }
 
     @RequestMapping(value = "/export/tips", method = RequestMethod.POST)

+ 3 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java

@@ -201,6 +201,9 @@ public class OrderController {
 
         Page<UserOrder> p = userOrderService.list(page, size, user.getId(), order, DirectionStatus.ValueOf(direction));
         Map<Integer, UserOrder> map = new HashMap<>();
+        for(UserOrder userOrder : p){
+            map.put(userOrder.getId(), userOrder);
+        }
 
         List<UserOrderDetailDto> pr = Transform.convert(p, UserOrderDetailDto.class);
         Collection orderIds = Transform.getIds(p, UserOrder.class, "id");

+ 17 - 72
server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java

@@ -169,18 +169,21 @@ public class QuestionController {
         User user = (User) shiroHelp.getLoginUser();
 
         QuestionNo questionNo = questionNoService.get(questionNoId);
+        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
+        QuestionModule module = QuestionModule.WithQuestionNo(questionNoModule);
 
         UserQuestion userQuestion = UserQuestion.builder()
                 .questionId(questionNo.getQuestionId())
                 .questionNoId(questionNoId)
-                .questionModule(QuestionModule.BASE.key)
+                .questionModule(module.key)
                 .build();
 
         UserQuestionDetailDto dto = Transform.convert(userQuestion, UserQuestionDetailDto.class);
+        dto.setQuestionNo(Transform.convert(questionNo, QuestionNoExtendDto.class));
 
         Question question = questionService.get(userQuestion.getQuestionId());
         dto.setQuestion(Transform.convert(question, QuestionDetailExtendDto.class));
-        dto.setQuestionNo(Transform.convert(questionNo, QuestionNoExtendDto.class));
+
 
         if (user!=null){
             UserCollectQuestion collect = userCollectQuestionService.getByUserAndQuestion(user.getId(), userQuestion.getQuestionId());
@@ -209,23 +212,9 @@ public class QuestionController {
                 dto.setQuestionStatus(1);
             }
         }
+
         if(dto.getQuestionStatus() == null){
-            Integer id = questionNo.getModuleStruct()[questionNo.getModuleStruct().length - 1];
-            QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
-            // 获取基本当前权限
-            switch(questionNoModule){
-                case EXAMINATION:
-                    ExaminationStruct examinationStruct = examinationStructService.get(id);
-                    dto.setQuestionStatus(examinationStruct.getQuestionStatus());
-                    break;
-                case EXERCISE:
-                    ExerciseStruct exerciseStruct = exerciseStructService.get(id);
-                    dto.setQuestionStatus(exerciseStruct.getQuestionStatus());
-                    break;
-                default:
-                    // 自由组卷,不提供提问显示
-                    dto.setQuestionStatus(-1);
-            }
+            dto.setQuestionStatus(questionFlowService.questionStatus(questionNo));
         }
 
         return ResponseHelp.success(dto);
@@ -606,23 +595,11 @@ public class QuestionController {
             Collection questions = Transform.getIds(associations, QuestionNoRelation.class, "question");
             dto.setAssociations(Transform.convert(questions, QuestionBaseExtendDto.class));
         }
+        List<QuestionNo> questionNoList = questionNoService.listByQuestion(userQuestion.getQuestionId());
+        dto.setQuestionNos(Transform.convert(questionNoList, QuestionNoExtendDto.class));
+        QuestionNo questionNo = questionNoService.get(userQuestion.getQuestionNoId());
+        dto.setQuestionNo(Transform.convert(questionNo, QuestionNoExtendDto.class));
 
-        switch (QuestionModule.ValueOf(userQuestion.getQuestionModule())){
-            case BASE:
-                List<QuestionNo> questionNoList = questionNoService.listByQuestion(userQuestion.getQuestionId());
-                dto.setQuestionNos(Transform.convert(questionNoList, QuestionNoExtendDto.class));
-                QuestionNo questionNo = questionNoService.get(userQuestion.getQuestionNoId());
-                dto.setQuestionNo(Transform.convert(questionNo, QuestionNoExtendDto.class));
-                break;
-            case SENTENCE:
-                SentenceQuestion sentenceQuestion = sentenceQuestionService.get(userQuestion.getQuestionNoId());
-                dto.setQuestionNo(Transform.convert(sentenceQuestion, QuestionNoExtendDto.class));
-                break;
-            case TEXTBOOK:
-                TextbookQuestion textbookQuestion = textbookQuestionService.get(userQuestion.getQuestionNoId());
-                dto.setQuestionNo(Transform.convert(textbookQuestion, QuestionNoExtendDto.class));
-                break;
-        }
         // 获取提问权限
         PaperOrigin origin = PaperOrigin.ValueOf(userReport.getPaperOrigin());
         if (user != null){
@@ -631,30 +608,9 @@ public class QuestionController {
                 dto.setQuestionStatus(1);
             }
         }
-        if (dto.getQuestionStatus() == null){
-            // 获取基本当前权限
-            switch(origin){
-                case EXAMINATION:
-                    ExaminationPaper examinationPaper = examinationPaperService.get(userReport.getOriginId());
-                    ExaminationStruct examinationStruct = examinationStructService.get(examinationPaper.getStructThree());
-                    dto.setQuestionStatus(examinationStruct.getQuestionStatus());
-                    break;
-                case EXERCISE:
-                    ExercisePaper exercisePaper = exercisePaperService.get(userReport.getOriginId());
-                    ExerciseStruct exerciseStruct = exerciseStructService.get(exercisePaper.getStructFour());
-                    dto.setQuestionStatus(exerciseStruct.getQuestionStatus());
-                    break;
-                case TEXTBOOK:
-                    TextbookQuestion textbookQuestion = textbookQuestionService.get(userQuestion.getQuestionNoId());
-                    TextbookLibrary textbookLibrary = textbookLibraryService.get(textbookQuestion.getLibraryId());
-                    dto.setQuestionStatus(textbookLibrary.getQuestionStatus());
-                    break;
-                case PREVIEW:
-                    // 上面questionRelationCourse包括了,不会执行到
-                default:
-                    // 自由组卷,不提供提问显示
-                    dto.setQuestionStatus(-1);
-            }
+
+        if(dto.getQuestionStatus() == null){
+            dto.setQuestionStatus(questionFlowService.questionStatus(questionNo));
         }
 
         return ResponseHelp.success(dto);
@@ -753,20 +709,9 @@ public class QuestionController {
         List<Question> questionList = questionService.select(questionIds);
         Transform.combine(pr, questionList, UserQuestionBaseDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);
 
-        List<UserQuestionBaseDto> basePr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.BASE.key)).collect(Collectors.toList());
-        Collection baseQuestionNoIds = Transform.getIds(basePr, UserQuestionBaseDto.class, "questionNoId");
-        List<QuestionNo> baseQuestionNoList = questionNoService.select(baseQuestionNoIds);
-        Transform.combine(basePr, baseQuestionNoList, UserQuestionBaseDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
-
-        List<UserQuestionBaseDto> sentencePr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.SENTENCE.key)).collect(Collectors.toList());
-        Collection sentenceQuestionNoIds = Transform.getIds(sentencePr, UserQuestionBaseDto.class, "questionNoId");
-        List<SentenceQuestion> sentenceQuestionList = sentenceQuestionService.select(sentenceQuestionNoIds);
-        Transform.combine(sentencePr, sentenceQuestionList, UserQuestionBaseDto.class, "questionNoId", "questionNo", SentenceQuestion.class, "id", QuestionNoExtendDto.class);
-
-        List<UserQuestionBaseDto> textbookPr = pr.stream().filter((row)->row.getQuestionModule().equals(QuestionModule.TEXTBOOK.key)).collect(Collectors.toList());
-        Collection textbookQuestionNoIds = Transform.getIds(textbookPr, UserQuestionBaseDto.class, "questionNoId");
-        List<TextbookQuestion> textbookQuestionList = textbookQuestionService.select(textbookQuestionNoIds);
-        Transform.combine(textbookPr, textbookQuestionList, UserQuestionBaseDto.class, "questionNoId", "questionNo", TextbookQuestion.class, "id", QuestionNoExtendDto.class);
+        Collection questionNoIds = Transform.getIds(pr, UserQuestionBaseDto.class, "questionNoId");
+        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
+        Transform.combine(pr, questionNoList, UserQuestionBaseDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
 
         return ResponseHelp.success(pr);
     }

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

@@ -61,6 +61,9 @@ public class SentenceController
     private QuestionService questionService;
 
     @Autowired
+    private QuestionNoService questionNoService;
+
+    @Autowired
     private SettingService settingService;
 
     @Autowired
@@ -221,7 +224,7 @@ public class SentenceController
         for(SentencePaper paper : p){
             questionNoIdsMap.put(paper.getId(), paper.getQuestionNoIds());
         }
-        Map statMap = sentenceQuestionService.statPaperMap(questionNoIdsMap);
+        Map statMap = questionNoService.statPaperMap(questionNoIdsMap);
         Transform.combine(pr, statMap, UserSentencePaperDto.class, "id", "stat");
 
         if (user != null){

+ 18 - 12
server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java

@@ -13,6 +13,7 @@ import com.qxgmat.data.constants.enums.module.QuestionModule;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.inline.UserQuestionStat;
+import com.qxgmat.data.relation.entity.QuestionNoRelation;
 import com.qxgmat.data.relation.entity.TextbookEnrollNumberRelation;
 import com.qxgmat.data.relation.entity.TextbookQuestionRelation;
 import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
@@ -57,6 +58,9 @@ public class TextbookController
     private QuestionService questionService;
 
     @Autowired
+    private QuestionNoService questionNoService;
+
+    @Autowired
     private SettingService settingService;
 
     @Autowired
@@ -112,25 +116,28 @@ public class TextbookController
         Integer latestId = latest.getId();
         for(TextbookLibrary library : new ArrayList<TextbookLibrary>(2){{add(latest);add(null);}}){
             UserTextbookGroupDto dto;
-            List<TextbookQuestion> list;
+            List<TextbookQuestion> textbookList;
             if (library == null){
                 dto = new UserTextbookGroupDto();
                 dto.setIsLatest(0);
                 dto.setNeedService(false);
                 dto.setHasService(false);
                 // 获取往期题目统计
-                list = textbookQuestionService.listByNoLibrary(latestId);
+                textbookList = textbookQuestionService.listByNoLibrary(latestId);
             }else{
                 dto = Transform.convert(library, UserTextbookGroupDto.class);
                 dto.setIsLatest(library.getEndDate() == null ? 1 : 0);
                 dto.setNeedService(library.getEndDate() == null);
                 dto.setHasService(true);
                 // 获取第三层所有题目,并获取题目统计
-                list = textbookQuestionService.listByLibrary(library.getId());
+                textbookList = textbookQuestionService.listByLibrary(library.getId());
             }
-            List<TextbookQuestionRelation> relations = textbookQuestionService.relation(list);
-            dto.setStat(textbookQuestionService.statPaper(list));
-            dto.setQuestionNumber(list.size());
+            Collection questionNoIds = Transform.getIds(textbookList, TextbookQuestion.class, "questionNoId");
+
+            List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
+            List<QuestionNoRelation> relations = questionNoService.relation(questionNoList);
+            dto.setStat(questionNoService.statPaper(questionNoList));
+            dto.setQuestionNumber(textbookList.size());
             Map<Object, UserQuestionStat> userQuestionStatMap = null;
             if(user != null){
                 if (dto.getNeedService()){
@@ -139,13 +146,12 @@ public class TextbookController
                     UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), ServiceKey.TEXTBOOK);
                     dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
                 }
-                Collection questionNoIds = Transform.getIds(list, TextbookQuestion.class, "id");
                 List<UserQuestion> userQuestionList = userQuestionService.listByQuestionNo(user.getId(), QuestionModule.TEXTBOOK, questionNoIds);
                 userQuestionStatMap = userQuestionService.statQuestionNoMap(userQuestionList);
 
                 dto.setUserStat(userQuestionService.statQuestion(userQuestionList));
 
-                if (list.size() > userQuestionStatMap.size()){
+                if (questionNoList.size() > userQuestionStatMap.size()){
                     dto.setUserNumber(userQuestionStatMap.size());
                     dto.setMinTimes(0);
                 }else{
@@ -168,13 +174,13 @@ public class TextbookController
                 UserTextbookGroupExtendDto extendDto = new UserTextbookGroupExtendDto();
                 extendDto.setLogic(logic.key);
                 extendDto.setTitle(logic.key.toUpperCase());
-                List<TextbookQuestionRelation> childQuestionList = relations.stream().filter((q)-> logic.contain(QuestionType.ValueOf(q.getQuestion().getQuestionType()))).collect(Collectors.toList());
+                List<QuestionNoRelation> childQuestionList = relations.stream().filter((q)-> logic.contain(QuestionType.ValueOf(q.getQuestion().getQuestionType()))).collect(Collectors.toList());
                 extendDto.setQuestionNumber(childQuestionList.size());
                 if (user != null){
                     int minTimes = 0;
                     int userQuestionNumber = 0;
                     boolean flag = true;
-                    for(TextbookQuestion questionNo : childQuestionList){
+                    for(QuestionNoRelation questionNo : childQuestionList){
                         UserQuestionStat stat = userQuestionStatMap.get(questionNo.getId());
                         if (stat == null) {
                             flag = false;
@@ -183,7 +189,7 @@ public class TextbookController
                         if (stat.getUserNumber() < minTimes || minTimes == 0) minTimes = stat.getUserNumber();
                     }
                     if (!flag) minTimes = 0;
-                    for(TextbookQuestion questionNo : childQuestionList){
+                    for(QuestionNoRelation questionNo : childQuestionList){
                         UserQuestionStat stat = userQuestionStatMap.get(questionNo.getId());
                         if (stat != null && stat.getUserNumber() > minTimes)  userQuestionNumber += 1;
                     }
@@ -330,7 +336,7 @@ public class TextbookController
         for(TextbookPaper paper : p){
             questionNoIdsMap.put(paper.getId(), paper.getQuestionNoIds());
         }
-        Map statMap = textbookQuestionService.statPaperMap(questionNoIdsMap);
+        Map statMap = questionNoService.statPaperMap(questionNoIdsMap);
         Transform.combine(pr, statMap, UserTextbookPaperDto.class, "id", "stat");
 
         if (user != null){

+ 107 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/PaperExtendDto.java

@@ -0,0 +1,107 @@
+package com.qxgmat.dto.extend;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.ExaminationPaper;
+
+@Dto(entity = ExaminationPaper.class)
+public class PaperExtendDto {
+    private Integer totalScore;
+
+    private Integer totalTimes;
+
+    private Integer quantScore;
+
+    private Integer verbalScore;
+
+    private Integer irScore;
+
+    private Integer secondTotalScore;
+
+    private Integer secondTotalTimes;
+
+    private Integer secondQuantScore;
+
+    private Integer secondVerbalScore;
+
+    private Integer secondIrScore;
+
+    public Integer getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Integer totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public Integer getTotalTimes() {
+        return totalTimes;
+    }
+
+    public void setTotalTimes(Integer totalTimes) {
+        this.totalTimes = totalTimes;
+    }
+
+    public Integer getQuantScore() {
+        return quantScore;
+    }
+
+    public void setQuantScore(Integer quantScore) {
+        this.quantScore = quantScore;
+    }
+
+    public Integer getVerbalScore() {
+        return verbalScore;
+    }
+
+    public void setVerbalScore(Integer verbalScore) {
+        this.verbalScore = verbalScore;
+    }
+
+    public Integer getIrScore() {
+        return irScore;
+    }
+
+    public void setIrScore(Integer irScore) {
+        this.irScore = irScore;
+    }
+
+    public Integer getSecondTotalScore() {
+        return secondTotalScore;
+    }
+
+    public void setSecondTotalScore(Integer secondTotalScore) {
+        this.secondTotalScore = secondTotalScore;
+    }
+
+    public Integer getSecondTotalTimes() {
+        return secondTotalTimes;
+    }
+
+    public void setSecondTotalTimes(Integer secondTotalTimes) {
+        this.secondTotalTimes = secondTotalTimes;
+    }
+
+    public Integer getSecondQuantScore() {
+        return secondQuantScore;
+    }
+
+    public void setSecondQuantScore(Integer secondQuantScore) {
+        this.secondQuantScore = secondQuantScore;
+    }
+
+    public Integer getSecondVerbalScore() {
+        return secondVerbalScore;
+    }
+
+    public void setSecondVerbalScore(Integer secondVerbalScore) {
+        this.secondVerbalScore = secondVerbalScore;
+    }
+
+    public Integer getSecondIrScore() {
+        return secondIrScore;
+    }
+
+    public void setSecondIrScore(Integer secondIrScore) {
+        this.secondIrScore = secondIrScore;
+    }
+}

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

@@ -9,6 +9,8 @@ import javax.validation.constraints.NotEmpty;
 public class QuestionNoExtendDto {
     private Integer id;
 
+    private Integer questionId;
+
     private String title;
 
     private Integer no;
@@ -86,4 +88,12 @@ public class QuestionNoExtendDto {
     public void setModule(String module) {
         this.module = module;
     }
+
+    public Integer getQuestionId() {
+        return questionId;
+    }
+
+    public void setQuestionId(Integer questionId) {
+        this.questionId = questionId;
+    }
 }

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

@@ -10,8 +10,6 @@ public class UserAskQuestionDto {
 
     private String target;
 
-    private String questionModule;
-
     private Integer questionNoId;
 
     private String originContent;
@@ -57,12 +55,4 @@ public class UserAskQuestionDto {
     public void setUserQuestionId(Integer userQuestionId) {
         this.userQuestionId = userQuestionId;
     }
-
-    public String getQuestionModule() {
-        return questionModule;
-    }
-
-    public void setQuestionModule(String questionModule) {
-        this.questionModule = questionModule;
-    }
 }

+ 0 - 11
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCollectQuestionDto.java

@@ -5,9 +5,6 @@ import com.qxgmat.data.dao.entity.UserCollectQuestion;
 
 @Dto(entity = UserCollectQuestion.class)
 public class UserCollectQuestionDto {
-
-    private String questionModule;
-
     private Integer questionNoId;
 
     public Integer getQuestionNoId() {
@@ -17,12 +14,4 @@ public class UserCollectQuestionDto {
     public void setQuestionNoId(Integer questionNoId) {
         this.questionNoId = questionNoId;
     }
-
-    public String getQuestionModule() {
-        return questionModule;
-    }
-
-    public void setQuestionModule(String questionModule) {
-        this.questionModule = questionModule;
-    }
 }

+ 5 - 15
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCustomBindDto.java

@@ -1,21 +1,11 @@
 package com.qxgmat.dto.request;
 
-public class UserCustomBindDto {
-    private String questionModule;
-
+public class UserCustomGroupDto {
     private Integer[] questionNoIds;
 
     // 过滤已组卷n次的题目
     private Integer filterTimes;
 
-    public Integer[] getQuestionNoIds() {
-        return questionNoIds;
-    }
-
-    public void setQuestionNoIds(Integer[] questionNoIds) {
-        this.questionNoIds = questionNoIds;
-    }
-
     public Integer getFilterTimes() {
         return filterTimes;
     }
@@ -24,11 +14,11 @@ public class UserCustomBindDto {
         this.filterTimes = filterTimes;
     }
 
-    public String getQuestionModule() {
-        return questionModule;
+    public Integer[] getQuestionNoIds() {
+        return questionNoIds;
     }
 
-    public void setQuestionModule(String questionModule) {
-        this.questionModule = questionModule;
+    public void setQuestionNoIds(Integer[] questionNoIds) {
+        this.questionNoIds = questionNoIds;
     }
 }

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

@@ -0,0 +1,27 @@
+package com.qxgmat.dto.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserExport;
+
+@Dto(entity = UserExport.class)
+public class UserExportDto {
+    private Object setting;
+
+    private String type;
+
+    public Object getSetting() {
+        return setting;
+    }
+
+    public void setSetting(Object setting) {
+        this.setting = setting;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+}

+ 0 - 9
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserFeedbackErrorQuestionDto.java

@@ -5,7 +5,6 @@ import com.qxgmat.data.dao.entity.UserFeedbackError;
 
 @Dto(entity = UserFeedbackError.class)
 public class UserFeedbackErrorQuestionDto {
-    private String questionModule;
 
     private Integer questionNoId;
 
@@ -49,14 +48,6 @@ public class UserFeedbackErrorQuestionDto {
         this.title = title;
     }
 
-    public String getQuestionModule() {
-        return questionModule;
-    }
-
-    public void setQuestionModule(String questionModule) {
-        this.questionModule = questionModule;
-    }
-
     public Integer getQuestionNoId() {
         return questionNoId;
     }

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

@@ -6,8 +6,6 @@ import com.qxgmat.data.dao.entity.UserNoteQuestion;
 @Dto(entity = UserNoteQuestion.class)
 public class UserNoteQuestionDto {
 
-    private String questionModule;
-
     private Integer questionNoId;
 
     private String questionContent;
@@ -52,14 +50,6 @@ public class UserNoteQuestionDto {
         this.associationContent = associationContent;
     }
 
-    public String getQuestionModule() {
-        return questionModule;
-    }
-
-    public void setQuestionModule(String questionModule) {
-        this.questionModule = questionModule;
-    }
-
     public String getQaContent() {
         return qaContent;
     }

+ 19 - 9
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserTextbookFeedbackDto.java

@@ -5,20 +5,14 @@ import com.qxgmat.data.dao.entity.UserTextbookFeedback;
 
 @Dto(entity = UserTextbookFeedback.class)
 public class UserTextbookFeedbackDto {
-    private Integer topicId;
+    private String questionSubject;
+
+    private Integer no;
 
     private String target;
 
     private String content;
 
-    public Integer getTopicId() {
-        return topicId;
-    }
-
-    public void setTopicId(Integer topicId) {
-        this.topicId = topicId;
-    }
-
     public String getTarget() {
         return target;
     }
@@ -34,4 +28,20 @@ public class UserTextbookFeedbackDto {
     public void setContent(String content) {
         this.content = content;
     }
+
+    public String getQuestionSubject() {
+        return questionSubject;
+    }
+
+    public void setQuestionSubject(String questionSubject) {
+        this.questionSubject = questionSubject;
+    }
+
+    public Integer getNo() {
+        return no;
+    }
+
+    public void setNo(Integer no) {
+        this.no = no;
+    }
 }

+ 20 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCollectQuestionInfoDto.java

@@ -28,6 +28,10 @@ public class UserCollectQuestionInfoDto {
 
     private UserQuestionStat stat;
 
+    private Boolean collect;
+
+    private Boolean note;
+
     public Integer getId() {
         return id;
     }
@@ -99,4 +103,20 @@ public class UserCollectQuestionInfoDto {
     public void setLatestTime(Date latestTime) {
         this.latestTime = latestTime;
     }
+
+    public Boolean getCollect() {
+        return collect;
+    }
+
+    public void setCollect(Boolean collect) {
+        this.collect = collect;
+    }
+
+    public Boolean getNote() {
+        return note;
+    }
+
+    public void setNote(Boolean note) {
+        this.note = note;
+    }
 }

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

@@ -2,6 +2,7 @@ package com.qxgmat.dto.response;
 
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.UserNoteQuestion;
+import com.qxgmat.dto.extend.QuestionExtendDto;
 import com.qxgmat.dto.extend.QuestionNoExtendDto;
 
 import java.util.Date;
@@ -18,6 +19,8 @@ public class UserNoteQuestionInfoDto {
 
     private QuestionNoExtendDto questionNo;
 
+    private QuestionExtendDto question;
+
     private String content;
 
     private String officialContent;
@@ -157,4 +160,12 @@ public class UserNoteQuestionInfoDto {
     public void setQaTime(Date qaTime) {
         this.qaTime = qaTime;
     }
+
+    public QuestionExtendDto getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(QuestionExtendDto question) {
+        this.question = question;
+    }
 }

+ 19 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderDetailDto.java

@@ -9,6 +9,7 @@ import com.qxgmat.dto.extend.CoursePackageExtendDto;
 import com.qxgmat.dto.extend.UserOrderRecordExtendDto;
 
 import java.math.BigDecimal;
+import java.util.Date;
 import java.util.List;
 
 public class UserOrderDetailDto {
@@ -26,6 +27,8 @@ public class UserOrderDetailDto {
 
     private JSONArray gift;
 
+    private Date createTime;
+
     private BigDecimal invoiceMoney;
 
     private Boolean hasInvoice;
@@ -143,4 +146,20 @@ public class UserOrderDetailDto {
     public void setCanInvoice(Boolean canInvoice) {
         this.canInvoice = canInvoice;
     }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public BigDecimal getInvoiceMoney() {
+        return invoiceMoney;
+    }
+
+    public void setInvoiceMoney(BigDecimal invoiceMoney) {
+        this.invoiceMoney = invoiceMoney;
+    }
 }

+ 61 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserPaperDto.java

@@ -4,6 +4,7 @@ import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.UserPaper;
 import com.qxgmat.data.dao.entity.UserReport;
 import com.qxgmat.data.inline.PaperStat;
+import com.qxgmat.dto.extend.PaperExtendDto;
 import com.qxgmat.dto.extend.UserReportExtendDto;
 
 import java.util.Date;
@@ -18,12 +19,24 @@ public class UserPaperDto {
 
     private String paperModule;
 
+    private String paperOrigin;
+
+    private Integer originId;
+
+    private Integer paperNo;
+
+    private Integer qxCat;
+
     private Integer[] questionNoIds;
 
+    private List<String> questionTypes;
+
     private PaperStat stat;
 
     private List<UserReportExtendDto> reports;
 
+    private PaperExtendDto origin;
+
     private Integer totalTime;
 
     private Integer totalNumber;
@@ -101,4 +114,52 @@ public class UserPaperDto {
     public void setQuestionNoIds(Integer[] questionNoIds) {
         this.questionNoIds = questionNoIds;
     }
+
+    public Integer getPaperNo() {
+        return paperNo;
+    }
+
+    public void setPaperNo(Integer paperNo) {
+        this.paperNo = paperNo;
+    }
+
+    public PaperExtendDto getOrigin() {
+        return origin;
+    }
+
+    public void setOrigin(PaperExtendDto origin) {
+        this.origin = origin;
+    }
+
+    public Integer getQxCat() {
+        return qxCat;
+    }
+
+    public void setQxCat(Integer qxCat) {
+        this.qxCat = qxCat;
+    }
+
+    public List<String> getQuestionTypes() {
+        return questionTypes;
+    }
+
+    public void setQuestionTypes(List<String> questionTypes) {
+        this.questionTypes = questionTypes;
+    }
+
+    public String getPaperOrigin() {
+        return paperOrigin;
+    }
+
+    public void setPaperOrigin(String paperOrigin) {
+        this.paperOrigin = paperOrigin;
+    }
+
+    public Integer getOriginId() {
+        return originId;
+    }
+
+    public void setOriginId(Integer originId) {
+        this.originId = originId;
+    }
 }

+ 20 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserQuestionErrorInfoDto.java

@@ -28,6 +28,10 @@ public class UserQuestionErrorInfoDto {
 
     private UserQuestionStat stat;
 
+    private Boolean collect;
+
+    private Boolean note;
+
     public Integer getId() {
         return id;
     }
@@ -99,4 +103,20 @@ public class UserQuestionErrorInfoDto {
     public void setStat(UserQuestionStat stat) {
         this.stat = stat;
     }
+
+    public Boolean getCollect() {
+        return collect;
+    }
+
+    public void setCollect(Boolean collect) {
+        this.collect = collect;
+    }
+
+    public Boolean getNote() {
+        return note;
+    }
+
+    public void setNote(Boolean note) {
+        this.note = note;
+    }
 }

+ 3 - 3
server/gateway-api/src/main/java/com/qxgmat/service/UserCollectExperienceService.java

@@ -81,14 +81,14 @@ public class UserCollectExperienceService extends AbstractService {
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", entity.getUserId())
-                        .andEqualTo("experienceId", entity.getExpericenceId())
+                        .andEqualTo("experienceId", entity.getExperienceId())
         );
         UserCollectExperience in = one(userCollectExperienceMapper, example);
         if (in != null){
             return in;
         }
         int result = insert(userCollectExperienceMapper, entity);
-        courseExperienceService.accumulation(entity.getExpericenceId(), 0, 1);
+        courseExperienceService.accumulation(entity.getExperienceId(), 0, 1);
         return entity;
     }
 
@@ -111,7 +111,7 @@ public class UserCollectExperienceService extends AbstractService {
             return true;
         }
         int result = delete(userCollectExperienceMapper, example);
-        courseExperienceService.accumulation(in.getExpericenceId(), 0, -1);
+        courseExperienceService.accumulation(in.getExperienceId(), 0, -1);
         return result > 0;
     }
 

+ 2 - 22
server/gateway-api/src/main/java/com/qxgmat/service/UserCollectQuestionService.java

@@ -145,17 +145,7 @@ public class UserCollectQuestionService extends AbstractService {
         }
         int result = insert(userCollectQuestionMapper, entity);
         questionService.accumulationCollect(entity, 1);
-        switch(QuestionModule.ValueOf(entity.getQuestionModule())){
-            case BASE:
-                questionNoService.accumulationCollect(in, 1);
-                break;
-            case SENTENCE:
-                sentenceQuestionService.accumulationCollect(in, 1);
-                break;
-            case TEXTBOOK:
-                textbookQuestionService.accumulationCollect(in, 1);
-                break;
-        }
+        questionNoService.accumulationCollect(in, 1);
         return entity;
     }
 
@@ -179,17 +169,7 @@ public class UserCollectQuestionService extends AbstractService {
         }
         boolean result = delete(in.getId());
         questionService.accumulationCollect(in, -1);
-        switch(QuestionModule.ValueOf(in.getQuestionModule())){
-            case BASE:
-                questionNoService.accumulationCollect(in, -1);
-                break;
-            case SENTENCE:
-                sentenceQuestionService.accumulationCollect(in, -1);
-                break;
-            case TEXTBOOK:
-                textbookQuestionService.accumulationCollect(in, -1);
-                break;
-        }
+        questionNoService.accumulationCollect(in, -1);
         return result;
     }
 

+ 32 - 11
server/gateway-api/src/main/java/com/qxgmat/service/UserNoteQuestionService.java

@@ -130,7 +130,7 @@ public class UserNoteQuestionService extends AbstractService {
      * @return
      */
     @Transactional
-    public UserNoteQuestion update(UserNoteQuestion note){
+    public UserNoteQuestion updateNote(UserNoteQuestion note){
         Example example = new Example(UserNoteQuestion.class);
         example.and(
                 example.createCriteria()
@@ -146,19 +146,19 @@ public class UserNoteQuestionService extends AbstractService {
         Date now = new Date();
         if(in == null){
             // 按实际更新更改更新时间
-            if (!note.getQuestionContent().isEmpty()){
+            if (note.getQuestionContent() != null && !note.getQuestionContent().isEmpty()){
                 note.setQuestionTime(now);
             }
-            if (!note.getOfficialContent().isEmpty()){
+            if (note.getOfficialContent() != null && !note.getOfficialContent().isEmpty()){
                 note.setOfficialTime(now);
             }
-            if (!note.getQxContent().isEmpty()){
+            if (note.getQxContent() != null && !note.getQxContent().isEmpty()){
                 note.setQxTime(now);
             }
-            if (!note.getAssociationContent().isEmpty()){
+            if (note.getAssociationContent() != null && !note.getAssociationContent().isEmpty()){
                 note.setAssociationTime(now);
             }
-            if (!note.getQaContent().isEmpty()){
+            if (note.getQaContent() != null && !note.getQaContent().isEmpty()){
                 note.setQaTime(now);
             }
             return add(note);
@@ -166,25 +166,46 @@ public class UserNoteQuestionService extends AbstractService {
             note.setId(in.getId());
 
             // 按实际更新更改更新时间
-            if (!note.getQuestionContent().equals(in.getQuestionContent())){
+            if (note.getQuestionContent() != null && !note.getQuestionContent().equals(in.getQuestionContent())){
                 note.setQuestionTime(now);
             }
-            if (!note.getOfficialContent().equals(in.getOfficialContent())){
+            if (note.getOfficialContent() != null && !note.getOfficialContent().equals(in.getOfficialContent())){
                 note.setOfficialTime(now);
             }
-            if (!note.getQxContent().equals(in.getQxContent())){
+            if (note.getQxContent() != null && !note.getQxContent().equals(in.getQxContent())){
                 note.setQxTime(now);
             }
-            if (!note.getAssociationContent().equals(in.getAssociationContent())){
+            if (note.getAssociationContent() != null && !note.getAssociationContent().equals(in.getAssociationContent())){
                 note.setAssociationTime(now);
             }
-            if (!note.getQaContent().equals(in.getQaContent())){
+            if (note.getQaContent() != null && !note.getQaContent().equals(in.getQaContent())){
                 note.setQaTime(now);
             }
             return edit(note);
         }
     }
 
+    /**
+     * 取消收藏题目编号
+     * @param userId
+     * @param questionId
+     * @return
+     */
+    @Transactional
+    public boolean deleteNote(Integer userId, Integer questionId){
+        Example example = new Example(UserNoteQuestion.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("questionId", questionId)
+        );
+        UserNoteQuestion in = one(userNoteQuestionMapper, example);
+        if (in == null){
+            return true;
+        }
+        return delete(in.getId());
+    }
+
     public UserNoteQuestion add(UserNoteQuestion note){
         int result = insert(userNoteQuestionMapper, note);
         note = one(userNoteQuestionMapper, note.getId());

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

@@ -229,7 +229,8 @@ public class UserPaperService extends AbstractService {
                             .andEqualTo("paperOrigin", origin.key)
                             .andEqualTo("originId", originId)
             );
-
+            // 如果是千行cat,则获取最后一次
+            example.orderBy("paperNo").desc();
             paper = one(userPaperMapper, example);
         }
 

+ 7 - 0
server/gateway-api/src/main/java/com/qxgmat/service/annotation/FinishAfterReport.java

@@ -0,0 +1,7 @@
+package com.qxgmat.service.annotation;
+
+import com.qxgmat.data.dao.entity.UserReport;
+
+public interface FinishAfterReport {
+    void callback(UserReport report);
+}

+ 5 - 3
server/gateway-api/src/main/java/com/qxgmat/service/extend/ExaminationService.java

@@ -223,10 +223,11 @@ public class ExaminationService extends AbstractService {
      * @return
      */
     public boolean isFinishCat(Integer userId){
+        User user = usersService.get(userId);
         ExaminationStruct struct = getCat();
         List<ExaminationPaper> paperList = examinationPaperService.listByTwo(struct.getId());
         Collection ids = Transform.getIds(paperList, ExaminationPaper.class, "id");
-        List<UserPaper> userPaperList = userPaperService.listWithOrigin(userId, PaperOrigin.EXAMINATION, ids, null);
+        List<UserPaper> userPaperList = userPaperService.listWithCat(userId, ids, user.getQxCat());
         if (paperList.size() != userPaperList.size()){
             return false;
         }
@@ -247,10 +248,11 @@ public class ExaminationService extends AbstractService {
      */
     @Transactional
     public Boolean resetCat(Integer userId, boolean force){
+        User user = usersService.get(userId);
         ExaminationStruct struct = getCat();
         List<ExaminationPaper> paperList = examinationPaperService.listByTwo(struct.getId());
         Collection ids = Transform.getIds(paperList, ExaminationPaper.class, "id");
-        List<UserPaper> userPaperList = userPaperService.listWithOrigin(userId, PaperOrigin.EXAMINATION, ids, null);
+        List<UserPaper> userPaperList = userPaperService.listWithCat(userId, ids, user.getQxCat());
         if (!force && paperList.size() != userPaperList.size()){
             throw new ParameterException("未完成所有");
         }
@@ -264,8 +266,8 @@ public class ExaminationService extends AbstractService {
             }
         }
         // 增加用户cat计数
-        User user = usersService.get(userId);
         usersService.edit(User.builder().id(userId).qxCat(user.getQxCat() + 1).build());
+        // 在获取start paper时,创建新的paper
         return userPaperService.reset(paperIds, userId);
     }
 

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

@@ -616,8 +616,8 @@ public class OrderFlowService {
                         if (userService.getIsReset() == 1 && examinationService.isFinishCat(record.getUserId()))  throw new ParameterException("已开通当前服务");
                     }
                     // 重置
-                    userService.setIsReset(0);
                     examinationService.resetCat(record.getUserId(), true);
+                    userService.setIsReset(0);
                 }else if (userService.getExpireTime() != null && userService.getExpireTime().before(time)){
                     // 已到期 - 续期
                     userService.setStartTime(startTime);

+ 81 - 13
server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java

@@ -25,6 +25,9 @@ public class QuestionFlowService {
     private ExercisePaperService exercisePaperService;
 
     @Resource
+    private ExerciseStructService exerciseStructService;
+
+    @Resource
     private PreviewService previewService;
 
     @Resource
@@ -52,6 +55,9 @@ public class QuestionFlowService {
     private TextbookQuestionService textbookQuestionService;
 
     @Resource
+    private TextbookLibraryService textbookLibraryService;
+
+    @Resource
     private TextbookPaperService textbookPaperService;
 
     @Resource
@@ -120,6 +126,8 @@ public class QuestionFlowService {
     // 完成试卷的callback,根据试卷模块进行后续处理
     private Map<PaperModule, StatReport> finishCallback = new HashMap<>();
 
+    private Map<PaperOrigin, FinishAfterReport> finishAfterCallback = new HashMap<>();
+
     public QuestionFlowService(){
         makePaperCallback.put(QuestionModule.BASE, (userPaper, id)->{
             // 获取考题时间
@@ -212,6 +220,8 @@ public class QuestionFlowService {
             if (serviceKey == ServiceKey.QX_CAT) {
                 User user = usersService.get(userPaper.getUserId());
                 userPaper.setPaperNo(user.getQxCat());
+                UserService userService = userServiceService.getService(user.getId(), serviceKey);
+                userPaper.setQxCat(userService.getIsReset() + 1);
             }
             return userPaper;
         });
@@ -264,10 +274,12 @@ public class QuestionFlowService {
                 User user = usersService.get(userPaper.getUserId());
 
                 // 创建新的paper
-                userPaper = userPaperService.newByPaper(userPaper.getUserId(), PaperOrigin.ValueOf(userPaper.getPaperOrigin()), PaperModule.ValueOf(userPaper.getPaperModule()), userPaper.getOriginId());
-                userPaper.setIsAdapt(userPaper.getIsAdapt());
-                userPaper.setTitle(userPaper.getTitle());
-                userPaper.setPaperNo(user.getQxCat());
+                UserPaper newPaper = userPaperService.newByPaper(userPaper.getUserId(), PaperOrigin.ValueOf(userPaper.getPaperOrigin()), PaperModule.ValueOf(userPaper.getPaperModule()), userPaper.getOriginId());
+                newPaper.setIsAdapt(userPaper.getIsAdapt());
+                newPaper.setTitle(userPaper.getTitle());
+                newPaper.setPaperNo(user.getQxCat());
+                UserService userService = userServiceService.getService(user.getId(), serviceKey);
+                newPaper.setQxCat(userService.getIsReset() + 1);
             }
             return userPaper;
         });
@@ -277,9 +289,7 @@ public class QuestionFlowService {
             if (questionNoId == 0) return false;
             this.bindQuestionNo(question, questionNoId);
             // 设定最后一次练习记录
-            usersService.edit(User.builder().id(question.getUserId()).latestExercise(report.getPaperId()).build());
-            // 设定最后一次错题记录
-            usersService.edit(User.builder().id(question.getUserId()).latestError(report.getPaperId()).build());
+            usersService.edit(User.builder().id(question.getUserId()).latestExercise(report.getId()).build());
             return true;
         });
         nextCallback.put(PaperModule.SENTENCE, (question, report, lastQuestion)->{
@@ -382,12 +392,12 @@ public class QuestionFlowService {
         });
         submitAfterCallback.put(QuestionModule.SENTENCE, (userQuestion, question)->{
             // 更新题目及长难句统计
-            sentenceQuestionService.accumulation(userQuestion);
+            questionNoService.accumulation(userQuestion);
             questionService.accumulation(userQuestion);
         });
         submitAfterCallback.put(QuestionModule.TEXTBOOK, (userQuestion, question)->{
             // 更新题目及机经统计
-            textbookQuestionService.accumulation(userQuestion);
+            questionNoService.accumulation(userQuestion);
             questionService.accumulation(userQuestion);
             this.answerDistributed(userQuestion, question);
         });
@@ -404,6 +414,11 @@ public class QuestionFlowService {
         finishCallback.put(PaperModule.EXAMINATION, (report, questionList)->{
             this.statExaminationReport(report, questionList);
         });
+
+        finishAfterCallback.put(PaperOrigin.ERROR, (report)->{
+            // 设定最后一次错题记录
+            usersService.edit(User.builder().id(report.getUserId()).latestError(report.getId()).build());
+        });
     }
 
     /**
@@ -420,8 +435,6 @@ public class QuestionFlowService {
         if (questionNoIds.size() == 0){
             throw new ParameterException("题目数为空");
         }
-        // todo 题目数不能超过50
-        // todo 不同类型不能一起组卷
         QuestionOrigin questionOrigin = QuestionOrigin.WithPaper(origin);
         if (filterTimes > 0){
             // 过滤重复多次的题目
@@ -444,6 +457,34 @@ public class QuestionFlowService {
         return paper;
     }
 
+    public QuestionModule validGroup(Integer[] questionNoIds){
+        if (questionNoIds == null || questionNoIds.length < 10) {
+            throw new ParameterException("不可小于10题,请重新选择");
+        }
+        if (questionNoIds.length > 50){
+            throw new ParameterException("不可多余50题,请重新选择");
+        }
+        QuestionModule questionModule=null;
+        List<QuestionNoRelation> questionNoList = questionNoService.relation(questionNoService.select(questionNoIds));
+        QuestionSubject subject = null;
+        for(QuestionNoRelation relation : questionNoList){
+            QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(relation.getModule());
+            QuestionModule currentModule = QuestionModule.WithQuestionNo(questionNoModule);
+            if(questionModule==null){
+                questionModule = currentModule;
+            }else if(questionModule !=currentModule){
+                throw new ParameterException("不可同时选中语文题和数学题,请重新选择。");
+            }
+            QuestionSubject currentSubject = QuestionSubject.FromType(QuestionType.ValueOf(relation.getQuestion().getQuestionType()));
+            if (subject == null){
+                subject = currentSubject;
+            }else if(subject!=currentSubject){
+                throw new ParameterException("不可同时选中语文题和数学题,请重新选择。");
+            }
+        }
+        return questionModule;
+    }
+
     /**
      * 获取做题paper
      * @param userId
@@ -696,11 +737,15 @@ public class QuestionFlowService {
 
         List<UserQuestion> userQuestionList = userQuestionService.listByReport(userId, userReportId);
         // 分析做题结果
-        StatReport callback = finishCallback.get(PaperModule.ValueOf(userReport.getPaperModule()));
-        callback.callback(userReport, userQuestionList);
+        StatReport statReportCallback = finishCallback.get(PaperModule.ValueOf(userReport.getPaperModule()));
+        statReportCallback.callback(userReport, userQuestionList);
         userReport.setIsStat(1);
         userReportService.edit(userReport);
 
+        FinishAfterReport finishAfterReportCallback = finishAfterCallback.get(PaperOrigin.ValueOf(userReport.getPaperOrigin()));
+        if(finishAfterCallback != null){
+            finishAfterReportCallback.callback(userReport);
+        }
         // 统计: 更新对应paper记录
         userPaperService.accumulation(userReport);
         return true;
@@ -740,6 +785,29 @@ public class QuestionFlowService {
         return courseExtendService.questionRelationCourse(userId, questionType);
     }
 
+    public Integer questionStatus(QuestionNo questionNo){
+        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
+        Integer id = null;
+        // 获取基本当前权限
+        switch(questionNoModule){
+            case EXAMINATION:
+                id = questionNo.getModuleStruct()[questionNo.getModuleStruct().length - 1];
+                ExaminationStruct examinationStruct = examinationStructService.get(id);
+                return examinationStruct.getQuestionStatus();
+            case EXERCISE:
+                id = questionNo.getModuleStruct()[questionNo.getModuleStruct().length - 1];
+                ExerciseStruct exerciseStruct = exerciseStructService.get(id);
+                return exerciseStruct.getQuestionStatus();
+            case TEXTBOOK:
+                TextbookQuestion textbookQuestion = textbookQuestionService.getByQuestionNo(questionNo.getId());
+                TextbookLibrary textbookLibrary = textbookLibraryService.get(textbookQuestion.getLibraryId());
+                return textbookLibrary.getQuestionStatus();
+            default:
+                // 自由组卷,不提供提问显示
+                return -1;
+        }
+    }
+
     /**
      * 累计考试学习时间
      * @param userId

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

@@ -5,6 +5,7 @@ import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.logic.SentenceLogic;
 import com.qxgmat.data.constants.enums.module.QuestionModule;
+import com.qxgmat.data.constants.enums.module.QuestionNoModule;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.SentenceQuestionRelation;
 import com.qxgmat.data.relation.entity.UserRecordStatRelation;
@@ -30,6 +31,9 @@ public class SentenceService {
     private QuestionService questionService;
 
     @Resource
+    private QuestionNoService questionNoService;
+
+    @Resource
     private SentenceQuestionService sentenceQuestionService;
 
     @Resource
@@ -63,8 +67,16 @@ public class SentenceService {
         question.setQuestionType(QuestionType.SENTENCE.key);
         question.setQuestionModule(QuestionModule.SENTENCE.key);
         question = questionService.add(question);
+        QuestionNo questionNo = QuestionNo.builder()
+                .questionId(question.getId())
+                .title(String.format("CNG%d", relation.getNo()))
+                .no(relation.getNo())
+                .module(QuestionNoModule.SENTENCE.key)
+                .build();
+        questionNo = questionNoService.add(questionNo);
         // 绑定关系
         relation.setQuestionId(question.getId());
+        relation.setQuestionNoId(questionNo.getId());
         relation.setSubject(String.format("CNG%d", relation.getNo()));
 
         SentenceQuestion sentenceQuestion =  sentenceQuestionService.add(relation);
@@ -97,6 +109,10 @@ public class SentenceService {
         if(in == null){
             in = sentenceQuestionService.get(relation.getId());
         }
+        QuestionNo questionNo = questionNoService.get(in.getQuestionNoId());
+        if(relation.getTitle() != null) questionNo.setTitle(relation.getTitle());
+        if(relation.getNo() != null) questionNo.setNo(relation.getNo());
+        questionNoService.edit(questionNo);
         relation.setSubject(String.format("CNG%d", relation.getNo()));
 
         // 根据序号调整分组:移出原有关系 - 绑定关系
@@ -130,31 +146,38 @@ public class SentenceService {
         Integer no = 0;
         List<SentencePaper> list = new ArrayList<>();
         if (questionList == null || questionList.size() == 0) return list;
-        List<Integer> tmp = new ArrayList<>(paperLength);
+        List<Integer> questionNoList = new ArrayList<>(paperLength);
+        List<Integer> sentenceList = new ArrayList<>(paperLength);
         for(SentenceQuestion question : questionList){
-            tmp.add(question.getId());
-            if (tmp.size() == paperLength){
+            questionNoList.add(question.getQuestionNoId());
+            sentenceList.add(question.getId());
+            if (sentenceList.size() == paperLength){
                 no += 1;
                 SentencePaper paper = SentencePaper.builder()
                         .logic(logic.key)
                         .no(no)
-                        .questionNumber(tmp.size())
+                        .questionNumber(sentenceList.size())
                         .status(0)
-                        .questionNoIds(tmp.toArray(new Integer[0])).build();
+                        .questionNoIds(questionNoList.toArray(new Integer[0]))
+                        .questionSIds(sentenceList.toArray(new Integer[0]))
+                        .build();
                 paper.setTitle(sentencePaperService.generateTitle(prefixTitle, paperLength, paper.getNo(), paper.getQuestionNumber()));
                 sentencePaperService.add(paper);
                 list.add(paper);
-                tmp.clear();
+                questionNoList.clear();
+                sentenceList.clear();
             }
         }
-        if (tmp.size() > 0){
+        if (sentenceList.size() > 0){
             no += 1;
             SentencePaper paper = SentencePaper.builder()
                     .logic(logic.key)
                     .no(no)
-                    .questionNumber(tmp.size())
+                    .questionNumber(sentenceList.size())
                     .status(0)
-                    .questionNoIds(tmp.toArray(new Integer[0])).build();
+                    .questionNoIds(questionNoList.toArray(new Integer[0]))
+                    .questionSIds(sentenceList.toArray(new Integer[0]))
+                    .build();
             paper.setTitle(sentencePaperService.generateTitle(prefixTitle, paperLength, paper.getNo(), paper.getQuestionNumber()));
             paper = sentencePaperService.add(paper);
             list.add(paper);

+ 31 - 7
server/gateway-api/src/main/java/com/qxgmat/service/extend/TextbookService.java

@@ -5,6 +5,7 @@ import com.qxgmat.data.constants.enums.TextbookSubject;
 import com.qxgmat.data.constants.enums.logic.SentenceLogic;
 import com.qxgmat.data.constants.enums.logic.TextbookLogic;
 import com.qxgmat.data.constants.enums.module.QuestionModule;
+import com.qxgmat.data.constants.enums.module.QuestionNoModule;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.TextbookQuestionRelation;
 import com.qxgmat.service.inline.*;
@@ -35,6 +36,9 @@ public class TextbookService {
     private QuestionService questionService;
 
     @Resource
+    private QuestionNoService questionNoService;
+
+    @Resource
     private TextbookQuestionService textbookQuestionService;
 
     @Resource
@@ -96,8 +100,16 @@ public class TextbookService {
         Question question = relation.getQuestion();
         question.setQuestionModule(QuestionModule.TEXTBOOK.key);
         question = questionService.add(question);
+        QuestionNo questionNo = QuestionNo.builder()
+                .questionId(question.getId())
+                .module(QuestionNoModule.TEXTBOOK.key)
+                .no(relation.getNo())
+                .title(relation.getTitle())
+                .build();
+        questionNo = questionNoService.add(questionNo);
         // 绑定关系
         relation.setQuestionId(question.getId());
+        relation.setQuestionNoId(questionNo.getId());
 
         TextbookLibrary library = textbookLibraryService.get(relation.getLibraryId());
         String prefixTitle = generatePrefixTitle(library);
@@ -128,10 +140,16 @@ public class TextbookService {
      */
     @Transactional
     public TextbookQuestion editQuestion(TextbookQuestionRelation relation){
+        TextbookQuestion textbookQuestion = textbookQuestionService.get(relation.getId());
         Question question = relation.getQuestion();
         question = questionService.edit(question);
 
-        TextbookQuestion textbookQuestion = textbookQuestionService.edit(relation);
+        QuestionNo questionNo = questionNoService.get(textbookQuestion.getQuestionNoId());
+        if (relation.getTitle() != null) questionNo.setTitle(relation.getTitle());
+        if (relation.getNo() != null) questionNo.setNo(relation.getNo());
+        questionNo = questionNoService.edit(questionNo);
+
+        textbookQuestion = textbookQuestionService.edit(relation);
 
         return textbookQuestion;
     }
@@ -191,10 +209,13 @@ public class TextbookService {
             );
         }else{
             // 加入
-            List<Integer> list = Arrays.stream(paper.getQuestionNoIds()).collect(Collectors.toList());
-            list.add(question.getId());
+            List<Integer> questionNoList = Arrays.stream(paper.getQuestionNoIds()).collect(Collectors.toList());
+            questionNoList.add(question.getQuestionNoId());
+            List<Integer> textbookList = Arrays.stream(paper.getQuestionTIds()).collect(Collectors.toList());
+            textbookList.add(question.getId());
             paper.setQuestionNumber(paper.getQuestionNumber() + 1);
-            paper.setQuestionNoIds(list.toArray(new Integer[0]));
+            paper.setQuestionNoIds(questionNoList.toArray(new Integer[0]));
+            paper.setQuestionTIds(textbookList.toArray(new Integer[0]));
             paper.setTitle(textbookPaperService.generateTitle(prefixTitle, paperLength, paper.getNo(), paper.getQuestionNumber()));
             paper = textbookPaperService.edit(paper);
         }
@@ -204,9 +225,12 @@ public class TextbookService {
         List<TextbookPaper> paperList = textbookPaperService.listByQuestion(question);
         for(TextbookPaper paper : paperList){
             paper.setQuestionNumber(paper.getQuestionNumber() - 1);
-            List<Integer> list = Arrays.stream(paper.getQuestionNoIds()).collect(Collectors.toList());
-            list.remove(question.getId());
-            paper.setQuestionNoIds(list.toArray(new Integer[0]));
+            List<Integer> questionNoList = Arrays.stream(paper.getQuestionNoIds()).collect(Collectors.toList());
+            questionNoList.remove(question.getQuestionNoId());
+            List<Integer> textbookList = Arrays.stream(paper.getQuestionNoIds()).collect(Collectors.toList());
+            textbookList.remove(question.getId());
+            paper.setQuestionNoIds(questionNoList.toArray(new Integer[0]));
+            paper.setQuestionTIds(textbookList.toArray(new Integer[0]));
             textbookPaperService.edit(paper);
         }
     }

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

@@ -467,7 +467,7 @@ public class QuestionNoService extends AbstractService {
         return question;
     }
 
-    @Deprecated
+    @Transactional
     public QuestionNo edit(QuestionNo question){
         QuestionNo in = one(questionNoMapper, question.getId());
         if(in == null){

+ 86 - 62
server/gateway-api/src/main/java/com/qxgmat/service/inline/SentenceQuestionService.java

@@ -88,68 +88,68 @@ public class SentenceQuestionService extends AbstractService {
         }
         return map;
     }
-
-    /**
-     * 累加做题记录到sentenceQuestion
-     * @param question
-     */
-    public void accumulation(UserQuestion question){
-        sentenceQuestionRelationMapper.accumulation(question.getQuestionNoId(), 1, question.getUserTime(), question.getIsCorrect());
-    }
-
-    /**
-     * 累加收藏记录到questionNo
-     * @param question
-     */
-    public void accumulationCollect(UserCollectQuestion question, int collect){
-        sentenceQuestionRelationMapper.accumulationCollect(question.getQuestionNoId(), collect);
-    }
-
-    /**
-     * 根据题目获取总试卷统计信息
-     * @param questionNoList
-     * @return
-     */
-    public PaperStat statPaper(List<SentenceQuestion> questionNoList){
-        PaperStat stat = new PaperStat();
-        Integer totalTime = 0;
-        Integer totalNumber = 0;
-        Integer totalCorrect = 0;
-        for(SentenceQuestion questionNo : questionNoList){
-            totalTime += questionNo.getTotalTime();
-            totalNumber += questionNo.getTotalNumber();
-            totalCorrect += questionNo.getTotalCorrect();
-        }
-        stat.setTotalCorrect(totalCorrect);
-        stat.setTotalNumber(totalNumber);
-        stat.setTotalTime(totalTime);
-        return stat;
-    }
-
-    /**
-     * 根据试卷分组获取统计信息
-     * @param questionNoIdsMap
-     * @return
-     */
-    public Map<Integer, PaperStat> statPaperMap(Map<Integer, Integer[]> questionNoIdsMap){
-        Map<Integer, PaperStat> relationMap = new HashMap<>();
-        List<Integer> ids = new ArrayList<>();
-        for(Integer[] questionNoIds : questionNoIdsMap.values()){
-            ids.addAll(Arrays.stream(questionNoIds).collect(Collectors.toList()));
-        }
-        List<SentenceQuestion> questionNoList = select(ids);
-        Map questionNoMap = Transform.getMap(questionNoList, SentenceQuestion.class, "id");
-        List<SentenceQuestion> l = new ArrayList<>();
-        for(Integer k: questionNoIdsMap.keySet()){
-            l.clear();
-            for (Integer questionNoId : questionNoIdsMap.get(k)){
-                l.add((SentenceQuestion)questionNoMap.get(questionNoId));
-            }
-            relationMap.put(k, statPaper(l));
-        }
-
-        return relationMap;
-    }
+//
+//    /**
+//     * 累加做题记录到sentenceQuestion
+//     * @param question
+//     */
+//    public void accumulation(UserQuestion question){
+//        sentenceQuestionRelationMapper.accumulation(question.getQuestionNoId(), 1, question.getUserTime(), question.getIsCorrect());
+//    }
+//
+//    /**
+//     * 累加收藏记录到questionNo
+//     * @param question
+//     */
+//    public void accumulationCollect(UserCollectQuestion question, int collect){
+//        sentenceQuestionRelationMapper.accumulationCollect(question.getQuestionNoId(), collect);
+//    }
+//
+//    /**
+//     * 根据题目获取总试卷统计信息
+//     * @param questionNoList
+//     * @return
+//     */
+//    public PaperStat statPaper(List<SentenceQuestion> questionNoList){
+//        PaperStat stat = new PaperStat();
+//        Integer totalTime = 0;
+//        Integer totalNumber = 0;
+//        Integer totalCorrect = 0;
+//        for(SentenceQuestion questionNo : questionNoList){
+//            totalTime += questionNo.getTotalTime();
+//            totalNumber += questionNo.getTotalNumber();
+//            totalCorrect += questionNo.getTotalCorrect();
+//        }
+//        stat.setTotalCorrect(totalCorrect);
+//        stat.setTotalNumber(totalNumber);
+//        stat.setTotalTime(totalTime);
+//        return stat;
+//    }
+//
+//    /**
+//     * 根据试卷分组获取统计信息
+//     * @param questionNoIdsMap
+//     * @return
+//     */
+//    public Map<Integer, PaperStat> statPaperMap(Map<Integer, Integer[]> questionNoIdsMap){
+//        Map<Integer, PaperStat> relationMap = new HashMap<>();
+//        List<Integer> ids = new ArrayList<>();
+//        for(Integer[] questionNoIds : questionNoIdsMap.values()){
+//            ids.addAll(Arrays.stream(questionNoIds).collect(Collectors.toList()));
+//        }
+//        List<SentenceQuestion> questionNoList = select(ids);
+//        Map questionNoMap = Transform.getMap(questionNoList, SentenceQuestion.class, "id");
+//        List<SentenceQuestion> l = new ArrayList<>();
+//        for(Integer k: questionNoIdsMap.keySet()){
+//            l.clear();
+//            for (Integer questionNoId : questionNoIdsMap.get(k)){
+//                l.add((SentenceQuestion)questionNoMap.get(questionNoId));
+//            }
+//            relationMap.put(k, statPaper(l));
+//        }
+//
+//        return relationMap;
+//    }
 
     /**
      * 根据长难句题目关系,获取完整题目:列表
@@ -162,6 +162,7 @@ public class SentenceQuestionService extends AbstractService {
         Collection questionIds = Transform.getIds(p, SentenceQuestion.class, "questionId");
         List<Question> questions = questionService.select(questionIds);
         Transform.combine(relationList, questions, SentenceQuestionRelation.class, "questionId", "question", Question.class, "id");
+
         return relationList;
     }
 
@@ -179,6 +180,29 @@ public class SentenceQuestionService extends AbstractService {
     }
 
     /**
+     * 根据题目编号获取题目
+     * @return
+     */
+    public List<SentenceQuestion> listByQuestionNo(Collection questionNoIds){
+        if (questionNoIds == null || questionNoIds.size() == 0) return new ArrayList<>();
+        Example example = new Example(SentenceQuestion.class);
+        example.and(
+                example.createCriteria()
+                        .andIn("questionNoId", questionNoIds)
+        );
+        return select(sentenceQuestionMapper, example);
+    }
+
+    public SentenceQuestion getByQuestionNo(Integer questionNoId){
+        Example example = new Example(SentenceQuestion.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("questionNoId", questionNoId)
+        );
+        return one(sentenceQuestionMapper, example);
+    }
+
+    /**
      * 根据序号查询
      * @param no
      * @return

+ 85 - 62
server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookQuestionService.java

@@ -213,68 +213,68 @@ public class TextbookQuestionService extends AbstractService {
         }
         return map;
     }
-
-    /**
-     * 累加做题记录到sentenceQuestion
-     * @param question
-     */
-    public void accumulation(UserQuestion question){
-        textbookQuestionRelationMapper.accumulation(question.getQuestionNoId(), 1, question.getTime(), question.getIsCorrect());
-    }
-
-    /**
-     * 累加收藏记录到questionNo
-     * @param question
-     */
-    public void accumulationCollect(UserCollectQuestion question, int collect){
-        textbookQuestionRelationMapper.accumulationCollect(question.getQuestionNoId(), collect);
-    }
-
-    /**
-     * 根据题目获取总试卷统计信息
-     * @param questionNoList
-     * @return
-     */
-    public PaperStat statPaper(List<TextbookQuestion> questionNoList){
-        PaperStat stat = new PaperStat();
-        Integer totalTime = 0;
-        Integer totalNumber = 0;
-        Integer totalCorrect = 0;
-        for(TextbookQuestion questionNo : questionNoList){
-            totalTime += questionNo.getTotalTime();
-            totalNumber += questionNo.getTotalNumber();
-            totalCorrect += questionNo.getTotalCorrect();
-        }
-        stat.setTotalCorrect(totalCorrect);
-        stat.setTotalNumber(totalNumber);
-        stat.setTotalTime(totalTime);
-        return stat;
-    }
-
-    /**
-     * 根据试卷分组获取统计信息
-     * @param questionNoIdsMap
-     * @return
-     */
-    public Map<Integer, PaperStat> statPaperMap(Map<Integer, Integer[]> questionNoIdsMap){
-        Map<Integer, PaperStat> relationMap = new HashMap<>();
-        List<Integer> ids = new ArrayList<>();
-        for(Integer[] questionNoIds : questionNoIdsMap.values()){
-            ids.addAll(Arrays.stream(questionNoIds).collect(Collectors.toList()));
-        }
-        List<TextbookQuestion> questionNoList = select(ids);
-        Map questionNoMap = Transform.getMap(questionNoList, TextbookQuestion.class, "id");
-        List<TextbookQuestion> l = new ArrayList<>();
-        for(Integer k: questionNoIdsMap.keySet()){
-            l.clear();
-            for (Integer questionNoId : questionNoIdsMap.get(k)){
-                l.add((TextbookQuestion)questionNoMap.get(questionNoId));
-            }
-            relationMap.put(k, statPaper(l));
-        }
-
-        return relationMap;
-    }
+//
+//    /**
+//     * 累加做题记录到sentenceQuestion
+//     * @param question
+//     */
+//    public void accumulation(UserQuestion question){
+//        textbookQuestionRelationMapper.accumulation(question.getQuestionNoId(), 1, question.getTime(), question.getIsCorrect());
+//    }
+//
+//    /**
+//     * 累加收藏记录到questionNo
+//     * @param question
+//     */
+//    public void accumulationCollect(UserCollectQuestion question, int collect){
+//        textbookQuestionRelationMapper.accumulationCollect(question.getQuestionNoId(), collect);
+//    }
+//
+//    /**
+//     * 根据题目获取总试卷统计信息
+//     * @param questionNoList
+//     * @return
+//     */
+//    public PaperStat statPaper(List<TextbookQuestion> questionNoList){
+//        PaperStat stat = new PaperStat();
+//        Integer totalTime = 0;
+//        Integer totalNumber = 0;
+//        Integer totalCorrect = 0;
+//        for(TextbookQuestion questionNo : questionNoList){
+//            totalTime += questionNo.getTotalTime();
+//            totalNumber += questionNo.getTotalNumber();
+//            totalCorrect += questionNo.getTotalCorrect();
+//        }
+//        stat.setTotalCorrect(totalCorrect);
+//        stat.setTotalNumber(totalNumber);
+//        stat.setTotalTime(totalTime);
+//        return stat;
+//    }
+//
+//    /**
+//     * 根据试卷分组获取统计信息
+//     * @param questionNoIdsMap
+//     * @return
+//     */
+//    public Map<Integer, PaperStat> statPaperMap(Map<Integer, Integer[]> questionNoIdsMap){
+//        Map<Integer, PaperStat> relationMap = new HashMap<>();
+//        List<Integer> ids = new ArrayList<>();
+//        for(Integer[] questionNoIds : questionNoIdsMap.values()){
+//            ids.addAll(Arrays.stream(questionNoIds).collect(Collectors.toList()));
+//        }
+//        List<TextbookQuestion> questionNoList = select(ids);
+//        Map questionNoMap = Transform.getMap(questionNoList, TextbookQuestion.class, "id");
+//        List<TextbookQuestion> l = new ArrayList<>();
+//        for(Integer k: questionNoIdsMap.keySet()){
+//            l.clear();
+//            for (Integer questionNoId : questionNoIdsMap.get(k)){
+//                l.add((TextbookQuestion)questionNoMap.get(questionNoId));
+//            }
+//            relationMap.put(k, statPaper(l));
+//        }
+//
+//        return relationMap;
+//    }
 
     /**
      * 根据题目关系,获取完整题目:列表
@@ -303,6 +303,29 @@ public class TextbookQuestionService extends AbstractService {
         return relation;
     }
 
+    /**
+     * 根据题目编号获取题目
+     * @return
+     */
+    public List<TextbookQuestion> listByQuestionNo(Collection questionNoIds){
+        if (questionNoIds == null || questionNoIds.size() == 0) return new ArrayList<>();
+        Example example = new Example(SentenceQuestion.class);
+        example.and(
+                example.createCriteria()
+                        .andIn("questionNoId", questionNoIds)
+        );
+        return select(textbookQuestionMapper, example);
+    }
+
+    public TextbookQuestion getByQuestionNo(Integer questionNoId){
+        Example example = new Example(SentenceQuestion.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("questionNoId", questionNoId)
+        );
+        return one(textbookQuestionMapper, example);
+    }
+
     public TextbookQuestion add(TextbookQuestion question){
         int result = insert(textbookQuestionMapper, question);
         question = one(textbookQuestionMapper, question.getId());

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

@@ -117,6 +117,18 @@ public class TextbookTopicService extends AbstractService {
         return select(textbookTopicMapper, example, page, size);
     }
 
+    public TextbookTopic getByNo(Integer libraryId, String subject, Integer no){
+        Example example = new Example(TextbookTopic.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("libraryId", libraryId)
+                        .andEqualTo("questionSubject", subject)
+                        .andEqualTo("no", no)
+        );
+        return one(textbookTopicMapper, example);
+    }
+
+
     public TextbookTopic add(TextbookTopic ad){
         int result = insert(textbookTopicMapper, ad);
         ad = one(textbookTopicMapper, ad.getId());

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

@@ -0,0 +1,73 @@
+package com.qxgmat.service.inline;
+
+import com.github.pagehelper.Page;
+import com.nuliji.tools.AbstractService;
+import com.nuliji.tools.exception.ParameterException;
+import com.nuliji.tools.exception.SystemException;
+import com.qxgmat.data.dao.UserExportMapper;
+import com.qxgmat.data.dao.entity.UserExport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class UserExportService extends AbstractService {
+    private static final Logger logger = LoggerFactory.getLogger(UserExportService.class);
+
+    @Resource
+    private UserExportMapper userExportMapper;
+
+    public UserExport add(UserExport entity){
+        int result = insert(userExportMapper, entity);
+        entity = one(userExportMapper, entity.getId());
+        if(entity == null){
+            throw new SystemException("记录添加失败");
+        }
+        return entity;
+    }
+
+    public UserExport edit(UserExport entity){
+        UserExport in = one(userExportMapper, entity.getId());
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        int result = update(userExportMapper, entity);
+        return entity;
+    }
+
+    public boolean delete(Number id){
+        UserExport in = one(userExportMapper, id);
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        int result = delete(userExportMapper, id);
+        return result > 0;
+    }
+
+    public UserExport get(Number id){
+        UserExport in = one(userExportMapper, id);
+
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        return in;
+    }
+
+    public Page<UserExport> select(int page, int pageSize){
+        return select(userExportMapper, page, pageSize);
+    }
+
+    public Page<UserExport> select(Integer[] ids){
+        return page(()->select(userExportMapper, ids), 1, ids.length);
+    }
+
+    public List<UserExport> select(Collection ids){
+        return select(userExportMapper, ids);
+    }
+
+}

+ 11 - 11
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserPaperQuestionService.java

@@ -8,13 +8,16 @@ import com.nuliji.tools.mybatis.Example;
 import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.module.PaperModule;
 import com.qxgmat.data.constants.enums.module.QuestionModule;
+import com.qxgmat.data.constants.enums.module.QuestionNoModule;
 import com.qxgmat.data.constants.enums.module.QuestionOrigin;
 import com.qxgmat.data.dao.UserPaperMapper;
 import com.qxgmat.data.dao.UserPaperQuestionMapper;
+import com.qxgmat.data.dao.entity.QuestionNo;
 import com.qxgmat.data.dao.entity.UserPaper;
 import com.qxgmat.data.dao.entity.UserPaperQuestion;
 import com.qxgmat.data.dao.entity.UserQuestion;
 import com.qxgmat.data.relation.UserPaperQuestionRelationMapper;
+import com.qxgmat.data.relation.entity.QuestionNoRelation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -37,24 +40,21 @@ public class UserPaperQuestionService extends AbstractService {
 
     /**
      * 添加需要移除的错题
-     * @param questionList
+     * @param questionNoList
      */
     @Transactional
-    public void addRemoveError(List<UserQuestion> questionList){
-        for(UserQuestion question : questionList){
-            if (question.getIsCorrect() > 0){
-                // 正确题过滤
-                continue;
-            }
-            if (question.getQuestionType().equals(QuestionType.AWA.key)){
+    public void addRemoveError(Integer userId, List<QuestionNoRelation> questionNoList){
+        for(QuestionNoRelation questionNo : questionNoList){
+            if (questionNo.getQuestion().getQuestionType().equals(QuestionType.AWA.key)){
                 // 作文不算错题
                 continue;
             }
+            QuestionModule questionModule = QuestionModule.WithQuestionNo(QuestionNoModule.ValueOf(questionNo.getModule()));
             UserPaperQuestion paperQuestion = UserPaperQuestion.builder()
-                    .userId(question.getUserId())
-                    .questionModule(question.getQuestionModule())
+                    .userId(userId)
+                    .questionModule(questionModule.key)
                     .questionOrigin(QuestionOrigin.REMOVE_ERROR.key)
-                    .questionNoId(question.getQuestionNoId())
+                    .questionNoId(questionNo.getId())
                     // 移出错题,没有对应paper
                     .paperId(0)
                     .build();