Browse Source

fix(all): 统一修复bug

Go 4 years ago
parent
commit
fea629d986
98 changed files with 1009 additions and 360 deletions
  1. 1 1
      front/project/Constant.js
  2. 21 11
      front/project/admin/routes/course/dataDetail/page.js
  3. 23 17
      front/project/admin/routes/course/detail/page.js
  4. 4 3
      front/project/admin/routes/course/invoice/page.js
  5. 5 0
      front/project/admin/routes/course/list/page.js
  6. 1 1
      front/project/admin/routes/course/package/page.js
  7. 5 2
      front/project/admin/routes/course/previewDetail/page.js
  8. 3 2
      front/project/admin/routes/course/student/page.js
  9. 1 1
      front/project/admin/routes/interaction/comment/page.js
  10. 1 1
      front/project/admin/routes/interaction/faq/page.js
  11. 1 1
      front/project/admin/routes/show/comment/page.js
  12. 1 1
      front/project/admin/routes/show/faq/page.js
  13. 1 0
      front/project/admin/routes/subject/examination/page.js
  14. 1 1
      front/project/admin/routes/subject/exercise/page.js
  15. 1 2
      front/project/admin/routes/subject/textbook/page.js
  16. 12 4
      front/project/admin/routes/user/detail/page.js
  17. 1 1
      front/project/h5/components/Input/index.js
  18. 2 1
      front/project/www/components/Date/index.js
  19. 1 1
      front/project/www/components/Input/index.js
  20. 1 1
      front/project/www/components/Item/index.js
  21. 3 0
      front/project/www/components/Login/index.js
  22. 8 3
      front/project/www/components/OtherModal/index.js
  23. 2 1
      front/project/www/components/PayModal/index.js
  24. 4 3
      front/project/www/routes/course/answer/page.js
  25. 2 1
      front/project/www/routes/course/data/page.js
  26. 5 4
      front/project/www/routes/course/dataDetail/page.js
  27. 17 12
      front/project/www/routes/course/detail/page.js
  28. 2 1
      front/project/www/routes/course/experience/page.js
  29. 2 1
      front/project/www/routes/course/note/page.js
  30. 34 30
      front/project/www/routes/course/packageDetail/page.js
  31. 3 3
      front/project/www/routes/course/vs/page.js
  32. 8 8
      front/project/www/routes/examination/list/page.js
  33. 10 1
      front/project/www/routes/exercise/list/page.js
  34. 14 8
      front/project/www/routes/exercise/main/page.js
  35. 4 2
      front/project/www/routes/my/answer/page.js
  36. 7 4
      front/project/www/routes/my/collect/page.js
  37. 1 1
      front/project/www/routes/my/course/page.js
  38. 1 1
      front/project/www/routes/my/data/page.js
  39. 30 11
      front/project/www/routes/my/error/page.js
  40. 106 15
      front/project/www/routes/my/index.js
  41. 9 1
      front/project/www/routes/my/main/page.js
  42. 31 20
      front/project/www/routes/my/note/page.js
  43. 65 29
      front/project/www/routes/my/report/page.js
  44. 1 1
      front/project/www/routes/page/cart/page.js
  45. 9 1
      front/project/www/routes/page/contract/index.less
  46. 3 1
      front/project/www/routes/page/export/page.js
  47. 33 13
      front/project/www/routes/paper/process/base/index.js
  48. 11 8
      front/project/www/routes/paper/process/page.js
  49. 3 4
      front/project/www/routes/paper/process/sentence/index.js
  50. 4 4
      front/project/www/routes/paper/question/detail/index.js
  51. 12 12
      front/project/www/routes/paper/report/page.js
  52. 6 0
      front/project/www/routes/question/search/index.less
  53. 10 1
      front/project/www/routes/question/search/page.js
  54. 1 1
      front/project/www/routes/sentence/read/index.less
  55. 1 1
      front/src/services/Tools.js
  56. 1 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/PaperModule.java
  57. 1 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/trade/RecordSource.java
  58. 1 0
      server/data/src/main/java/com/qxgmat/data/relation/PreviewPaperRelationMapper.java
  59. 7 0
      server/data/src/main/java/com/qxgmat/data/relation/QuestionNoRelationMapper.java
  60. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/UserAskCourseRelationMapper.java
  61. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/UserQuestionRelationMapper.java
  62. 6 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/PreviewPaperRelationMapper.xml
  63. 27 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/QuestionNoRelationMapper.xml
  64. 2 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/TextbookQuestionRelationMapper.xml
  65. 17 15
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserNoteQuestionRelationMapper.xml
  66. 13 14
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserQuestionRelationMapper.xml
  67. 2 0
      server/data/src/main/resources/db/migration/V1__init_table.sql
  68. 6 6
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java
  69. 3 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/PreviewController.java
  70. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/QuestionController.java
  71. 5 4
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  72. 18 5
      server/gateway-api/src/main/java/com/qxgmat/controller/api/AuthController.java
  73. 2 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  74. 10 6
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  75. 17 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  76. 40 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserAskCourseListDto.java
  77. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserListDto.java
  78. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/CourseNoExtendDto.java
  79. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseListDto.java
  80. 91 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserExportDto.java
  81. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserNoteQuestionInfoDto.java
  82. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserQuestionBaseDto.java
  83. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/UsersService.java
  84. 5 5
      server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java
  85. 7 2
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExaminationService.java
  86. 2 3
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExerciseService.java
  87. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExportService.java
  88. 4 5
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  89. 46 11
      server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java
  90. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewPaperService.java
  91. 20 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/QuestionNoService.java
  92. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/SentenceCodeService.java
  93. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookPaperService.java
  94. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseService.java
  95. 10 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderCheckoutService.java
  96. 18 3
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java
  97. 3 0
      server/gateway-api/src/main/java/com/qxgmat/util/shiro/OauthRealm.java
  98. 3 0
      server/gateway-api/src/main/java/com/qxgmat/util/shiro/UserRealm.java

+ 1 - 1
front/project/Constant.js

@@ -6,7 +6,7 @@ export const H5Url = __H5Url__;
 
 export const WechatH5AppId = __WechatH5AppId__;
 
-export const QrCode = __AssetsQrCode__;
+export const QrCodeAssets = __AssetsQrCode__;
 
 export const QuestionDifficult = [{ label: 'Easy', value: 'easy', sort: 2 }, { label: 'Medium', value: 'medium', sort: 1 }, { label: 'Hard', value: 'hard', sort: 0 }];
 

+ 21 - 11
front/project/admin/routes/course/dataDetail/page.js

@@ -39,7 +39,7 @@ export default class extends Page {
     if (id) {
       handler = Course.getData({ id });
     } else {
-      handler = Promise.resolve({ structId: 0 });
+      handler = Promise.resolve({ structId: '' });
     }
     handler
       .then(result => {
@@ -58,7 +58,14 @@ export default class extends Page {
         const data = form.getFieldsValue();
         data.parentStructId = this.exerciseMap[data.structId] ? this.exerciseMap[data.structId].parentId : 0;
         data.isSentence = this.exerciseMap[data.structId] ? (this.exerciseMap[data.structId].extend === 'sentence' ? 1 : 0) : 0;
-        Course.editData(data).then(() => {
+        let handler;
+        if (data.id) {
+          handler = Course.editData(data);
+        } else {
+          data.courseModule = module;
+          handler = Course.addData(data);
+        }
+        handler.then(() => {
           asyncSMessage('保存成功');
         }).catch((e) => {
           if (e.result) form.setFields(formatFormError(data, e.result));
@@ -70,17 +77,20 @@ export default class extends Page {
   renderBase() {
     const { exercise, data } = this.state;
     const { getFieldDecorator } = this.props.form;
+    const structIdConfig = {
+      rules: [{
+        required: true, message: '请选择学科',
+      }],
+    };
+    if (data.structId) {
+      structIdConfig.initialValue = data.structId;
+    }
     return <Block>
       <h1>基本信息</h1>
       <Form>
         {getFieldDecorator('id')(<input hidden />)}
-        {exercise && data.structId && <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='学科'>
-          {getFieldDecorator('structId', {
-            rules: [{
-              required: true, message: '请选择学科',
-            }],
-            initialValue: data.structId,
-          })(
+        {exercise && data && <Form.Item key={data.structId} labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='学科'>
+          {getFieldDecorator('structId', structIdConfig)(
             <TreeSelect treeData={exercise} />,
           )}
         </Form.Item>}
@@ -126,7 +136,7 @@ export default class extends Page {
               required: true, message: '请输入价格',
             }],
           })(
-            <InputNumber placeholder='请输入' />,
+            <InputNumber placeholder='请输入' min={0} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='页数'>
@@ -135,7 +145,7 @@ export default class extends Page {
               required: true, message: '请输入页数',
             }],
           })(
-            <InputNumber placeholder='请输入' />,
+            <InputNumber placeholder='请输入' min={0} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='链接地址'>

+ 23 - 17
front/project/admin/routes/course/detail/page.js

@@ -167,7 +167,7 @@ export default class extends Page {
     if (id) {
       handler = Course.get({ id });
     } else {
-      handler = Promise.resolve({ structId: 0, courseModule: module, expireDays: 180 });
+      handler = Promise.resolve({ structId: '', courseModule: module, expireDays: 180 });
     }
 
     handler
@@ -261,17 +261,20 @@ export default class extends Page {
   renderVideo() {
     const { exercise, data } = this.state;
     const { getFieldDecorator, getFieldValue, setFieldsValue } = this.props.form;
+    const structIdConfig = {
+      rules: [{
+        required: true, message: '请选择学科',
+      }],
+    };
+    if (data.structId) {
+      structIdConfig.initialValue = data.structId;
+    }
     const cover = getFieldValue('cover');
     return <Block>
       <Form>
         {getFieldDecorator('id')(<input hidden />)}
-        {exercise && data.structId && <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='学科'>
-          {getFieldDecorator('structId', {
-            rules: [{
-              required: true, message: '请选择学科',
-            }],
-            initialValue: data.structId,
-          })(
+        {exercise && data && <Form.Item key={data.structId} labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='学科'>
+          {getFieldDecorator('structId', structIdConfig)(
             <TreeSelect treeData={exercise} />,
           )}
         </Form.Item>}
@@ -317,7 +320,7 @@ export default class extends Page {
               { required: true, message: '请输入有效期' },
             ],
           })(
-            <InputNumber placeholder='天' />,
+            <InputNumber placeholder='天' min={0} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='使用有效期'>
@@ -326,7 +329,7 @@ export default class extends Page {
               { required: true, message: '请输入有效期' },
             ],
           })(
-            <InputNumber placeholder='天' />,
+            <InputNumber placeholder='天' min={0} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='教师'>
@@ -455,16 +458,19 @@ export default class extends Page {
   renderOnline() {
     const { exercise, data } = this.state;
     const { getFieldDecorator } = this.props.form;
+    const structIdConfig = {
+      rules: [{
+        required: true, message: '请选择学科',
+      }],
+    };
+    if (data.structId) {
+      structIdConfig.initialValue = data.structId;
+    }
     return <Block>
       <Form>
         {getFieldDecorator('id')(<input hidden />)}
-        {exercise && data.structId && <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='学科'>
-          {getFieldDecorator('structId', {
-            rules: [{
-              required: true, message: '请选择学科',
-            }],
-            initialValue: data.structId,
-          })(
+        {exercise && <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='学科'>
+          {getFieldDecorator('structId', structIdConfig)(
             <TreeSelect treeData={exercise} />,
           )}
         </Form.Item>}

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

@@ -18,11 +18,12 @@ export default class extends Page {
     this.exerciseMap = {};
     this.filterForm = [{
       key: 'userId',
-      type: 'tree',
+      type: 'select',
       allowClear: true,
-      tree: [],
       name: '用户',
-      placeholder: '用户',
+      select: [],
+      number: true,
+      placeholder: '请输入',
     }, {
       key: 'isDownload',
       type: 'select',

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

@@ -182,12 +182,14 @@ export default class extends Page {
       {this.state.detail && <Modal visible closable title='数据管理' onCancel={() => {
         this.close(false, 'detail');
       }} onOk={() => {
+        if (this.props.core.loading) return;
         this.submitInfo();
       }}>
         <Form>
           {this.state.uploadErr}
           <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 10 }} label={'在线课程'} help={this.state.onlineErr}>
             <Upload
+              accept={'.mp4'}
               listType="picture-card"
               showUploadList={false}
               beforeUpload={(file) => System.uploadVideo(file).then((result) => {
@@ -201,9 +203,11 @@ export default class extends Page {
                 <div className="ant-upload-text">已上传</div>
               </div> : <div><Icon type={this.props.core.loading ? 'loading' : 'plus'} /><div className="ant-upload-text">Upload</div></div>}
             </Upload>
+            {this.state.detail.onlineVideo && <a href={this.state.detail.onlineVideo} target='_blank'>访问</a>}
           </Form.Item>
           <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 10 }} label={'1v1课程'} help={this.state.vsErr}>
             <Upload
+              accept={'.mp4'}
               listType="picture-card"
               showUploadList={false}
               beforeUpload={(file) => System.uploadVideo(file).then((result) => {
@@ -217,6 +221,7 @@ export default class extends Page {
                 <div className="ant-upload-text">已上传</div>
               </div> : <div><Icon type={this.props.core.loading ? 'loading' : 'plus'} /><div className="ant-upload-text">Upload</div></div>}
             </Upload>
+            {this.state.detail.vsVideo && <a href={this.state.detail.vsVideo} target='_blank'>访问</a>}
           </Form.Item>
         </Form>
       </Modal>}

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

@@ -53,7 +53,7 @@ export default class extends Page {
       const item = {
         key: `gift.${row.value}`,
         type: 'number',
-        name: `送${row.label}`,
+        name: `送${row.label}`,
       };
       if (list) {
         item.select = list;

+ 5 - 2
front/project/admin/routes/course/previewDetail/page.js

@@ -25,6 +25,7 @@ export default class extends Page {
       type: 'select',
       select: [],
       name: '时间段',
+      required: true,
     }, {
       key: 'time',
       type: 'daterange',
@@ -36,6 +37,7 @@ export default class extends Page {
       type: 'select',
       select: [],
       name: '学生',
+      required: true,
     }, {
       key: 'time',
       type: 'daterange',
@@ -44,6 +46,7 @@ export default class extends Page {
       key: 'title',
       type: 'input',
       name: '作业标题',
+      required: true,
     }];
   }
 
@@ -184,7 +187,7 @@ export default class extends Page {
   addOnlineAction() {
     const { data } = this.state;
     asyncForm('添加', this.onlineList, {}, info => {
-      if (info.time.length > 0) {
+      if (info.time && info.time.length > 0) {
         info.startTime = info.time[0].format('YYYY-MM-DD');
         info.endTime = info.time[1].format('YYYY-MM-DD');
       }
@@ -200,7 +203,7 @@ export default class extends Page {
   addVsAction() {
     const { data } = this.state;
     asyncForm('添加', this.vsList, {}, info => {
-      if (info.time.length > 0) {
+      if (info.time && info.time.length > 0) {
         info.startTime = info.time[0].format('YYYY-MM-DD');
         info.endTime = info.time[1].format('YYYY-MM-DD');
       }

+ 3 - 2
front/project/admin/routes/course/student/page.js

@@ -121,7 +121,7 @@ export default class extends Page {
     }, {
       key: 'cctalkName',
       type: 'input',
-      name: 'CCTalk用户名',
+      name: 'CCTalk名',
     }];
     this.vsChangeList = [{
       key: 'id',
@@ -129,12 +129,13 @@ export default class extends Page {
     }, {
       key: 'teacherId',
       type: 'select',
+      required: true,
       select: [],
       name: '老师',
     }, {
       key: 'cctalkName',
       type: 'input',
-      name: 'CCTalk用户名',
+      name: 'CCTalk名',
     }];
     this.vsColumns = [{
       title: '学员id',

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

@@ -192,7 +192,7 @@ export default class extends Page {
         itemList={this.filterForm}
         data={Object.assign({}, search, { channel: search.channel ? search.channel.split('-') : '' })}
         onChange={data => {
-          data.channel = data.channel.join('-');
+          data.channel = data.channel ? data.channel.join('-') : data.channel;
           data.page = 1;
           this.search(data);
         }} />

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

@@ -260,7 +260,7 @@ export default class extends Page {
         itemList={this.filterForm}
         data={Object.assign({}, search, { channel: search.channel ? search.channel.split('-') : '' })}
         onChange={data => {
-          data.channel = data.channel.join('-');
+          data.channel = data.channel ? data.channel.join('-') : data.channel;
           data.page = 1;
           this.search(data);
         }} />

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

@@ -299,7 +299,7 @@ export default class extends Page {
         itemList={this.filterForm}
         data={Object.assign({}, search, { channel: search.channel ? search.channel.split('-') : '' })}
         onChange={data => {
-          data.channel = data.channel.join('-');
+          data.channel = data.channel ? data.channel.join('-') : data.channel;
           data.page = 1;
           this.search(data);
         }} />

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

@@ -259,7 +259,7 @@ export default class extends Page {
         itemList={this.filterForm}
         data={Object.assign({}, search, { channel: search.channel ? search.channel.split('-') : '' })}
         onChange={data => {
-          data.channel = data.channel.join('-');
+          data.channel = data.channel ? data.channel.join('-') : data.channel;
           data.page = 1;
           this.search(data);
         }} />

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

@@ -158,6 +158,7 @@ export default class extends Page {
       };
     }, this.state.search.paperId ? Number(this.state.search.paperId) : null, null);
     bindSearch(filterForm, 'questionNoId', this, (search) => {
+      search.module = 'examination';
       return Question.searchNo(search);
     }, (row) => {
       return {

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

@@ -178,7 +178,7 @@ export default class extends Page {
                 { title: '题源联想', ids: record.question.associationContent, field: 'associationContent', module: 'exercise', modal: true },
                 (data) => {
                   data.id = record.questionId;
-                  data.associationContent = data.associationContent.map(row => row.id);
+                  // data.associationContent = data.associationContent.map(row => row.id);
                   return Question.edit(data).then(() => {
                     asyncSMessage('修改成功!');
                   });

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

@@ -24,7 +24,6 @@ const filterForm = [
     name: '题型',
     select: TextbookType,
     placeholder: '请选择',
-    number: true,
   },
   {
     key: 'paperId',
@@ -82,7 +81,7 @@ export default class extends Page {
       render: (text, record) => {
         return <div className="table-button">
           {(
-            <Link to={`/subject/text/question/${record.id}`}>编辑</Link>
+            <Link to={`/subject/textbook/question/${record.id}`}>编辑</Link>
           )}
         </div>;
       },

+ 12 - 4
front/project/admin/routes/user/detail/page.js

@@ -4,6 +4,7 @@ import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import TableLayout from '@src/layouts/TableLayout';
+import ShowImage from '@src/components/ShowImage';
 import { formatDate, getMap, formatMoney, formatSeconds } from '@src/services/Tools';
 import { asyncSMessage, asyncForm, asyncDelConfirm } from '@src/services/AsyncTools';
 import { PcUrl, PrepareStatus, PrepareExaminationTime, ServiceKey } from '../../../../Constant';
@@ -77,6 +78,13 @@ export default class extends Page {
     this.setState({ real });
   }
 
+  submitReal() {
+    const { real = {} } = this.state;
+    User.real(real).then(() => {
+      this.close(true, 'real');
+    });
+  }
+
   addMoneyAction() {
     asyncForm('增加金额', this.moneyList, { id: this.state.data.id }, data => {
       return User.addMoney(data).then(() => {
@@ -112,7 +120,7 @@ export default class extends Page {
     page.current = p || 1;
     page.pageSize = size || 20;
     this.setState({ page });
-    User.listService({ userId: id, page: page.current, size: page.pageSize })
+    User.listService({ userId: id, page: page.current, size: page.pageSize, order: 'createTime', direction: 'desc' })
       .then(result => {
         this.setTableData(result.list, result.total);
       });
@@ -171,7 +179,7 @@ export default class extends Page {
         </div>
 
         <div className="group">
-          <h2>实名认证{data.realStatus === 0 && <Button onClick={() => {
+          <h2>实名认证{<Button onClick={() => {
             this.realAction();
           }}>人工认证</Button>}</h2>
 
@@ -193,8 +201,8 @@ export default class extends Page {
             </Col>
             <Col span={12}>
               <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='身份证照片'>
-                <Avatar src={data.realPhotoFront} />
-                <Avatar src={data.realPhotoBack} />
+                <ShowImage src={data.realPhotoFront} />
+                <ShowImage src={data.realPhotoBack} />
               </Form.Item>
             </Col>
           </Row>}

+ 1 - 1
front/project/h5/components/Input/index.js

@@ -39,7 +39,7 @@ export class SelectInput extends Component {
         left={
           <span className="g-input-left-select" onClick={() => this.setState({ showSelect: !showSelect })}>
             {selectValue}
-            <Icon type={showSelect ? 'down' : 'up'} />
+            <Icon type={showSelect ? 'up' : 'down'} />
             {showSelect && <ul className="select-list">{select.map((row) => {
               return <li onClick={() => {
                 this.setState({ showSelect: false });

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

@@ -30,7 +30,7 @@ export default class extends Component {
   }
 
   render() {
-    const { show, hideInput, disabledDate, theme = '', value, onChange } = this.props;
+    const { show, hideInput, disabledDate, theme = '', value, onChange, onClick } = this.props;
     return (
       <div
         ref={ref => {
@@ -39,6 +39,7 @@ export default class extends Component {
             this.setState({ load: false });
           }
         }}
+        onClick={(event) => onClick && onClick(event)}
         className={`g-date-block ${hideInput ? 'hide-input' : ''}`}
       >
         <DatePicker

+ 1 - 1
front/project/www/components/Input/index.js

@@ -51,7 +51,7 @@ export class SelectInput extends Component {
         left={
           <span className="g-input-left-select" onClick={() => this.setState({ showSelect: !showSelect })}>
             {selectValue}
-            <Icon type={showSelect ? 'down' : 'up'} />
+            <Icon type={showSelect ? 'up' : 'down'} />
             {showSelect && (
               <ul className="select-list">
                 {select.map(row => {

+ 1 - 1
front/project/www/components/Item/index.js

@@ -53,7 +53,7 @@ export class SingleItem extends Component {
             <span>{formatSeconds(data.time)}</span>
           </div>
           <div className="right">
-            <span>{data.trailNumber || 0}次观看</span>
+            <span>{(data.trailNumber || 0) + (data.saleNumber || 0)}次观看</span>
           </div>
         </div>
         <div className="name">

+ 3 - 0
front/project/www/components/Login/index.js

@@ -167,6 +167,8 @@ export default class Login extends Component {
       this.setState({ mobileError: '请输入正确的手机号' });
       return;
     }
+    this.setState({ mobileError: null });
+
     this.validMobileNumber += 1;
     const number = this.validMobileNumber;
     User.validWechat(area, mobile)
@@ -194,6 +196,7 @@ export default class Login extends Component {
       this.setState({ emailError: '请输入正确的邮箱' });
       return;
     }
+    this.setState({ emailError: null });
     this.validEmailNumber += 1;
     const number = this.validEmailNumber;
     User.validEmail(email)

+ 8 - 3
front/project/www/components/OtherModal/index.js

@@ -9,7 +9,7 @@ import scale from '@src/services/Scale';
 import { asyncSMessage } from '@src/services/AsyncTools';
 import AnswerButton from '../AnswerButton';
 import { SelectInput, VerificationInput, DefaultInput, Input, Textarea } from '../Input';
-import { MobileArea, TextbookFeedbackTarget, TextbookSubject, AskTarget, QrCode } from '../../../Constant';
+import { MobileArea, TextbookFeedbackTarget, TextbookSubject, AskTarget, QrCodeAssets } from '../../../Constant';
 import Invite from '../Invite';
 import Modal from '../Modal';
 import { Common } from '../../stores/common';
@@ -80,6 +80,7 @@ export class BindPhone extends Component {
       this.setState({ mobileError: '' });
       return;
     }
+    this.setState({ mobileError: '' });
     User.validMobile(area, mobile)
       .then(result => {
         if (number !== this.validNumber) return Promise.resolve();
@@ -261,6 +262,7 @@ export class BindEmail extends Component {
       this.setState({ error: '' });
       return;
     }
+    this.setState({ error: '' });
     User.validEmail(email)
       .then(result => {
         if (number !== this.validNumber) return Promise.resolve();
@@ -529,7 +531,7 @@ export class RealAuth extends Component {
             <div className="t3">扫码关注公众号,点击“我的-实名认证”</div>
           </div>
           <div className="real-auth-qrcode">
-            <Assets name={QrCode} width={150} height={150} />
+            <Assets name={QrCodeAssets} width={150} height={150} />
           </div>
         </div>
       </Modal>
@@ -1075,10 +1077,13 @@ export class CourseNoteModal extends Component {
   onConfirm() {
     const { course, onConfirm } = this.props;
     const { data } = this.state;
-    if (!data.content || !data.originContent) {
+    if (!data.content) {
       this.setState({ empty: { content: !data.content } });
       return Promise.reject();
     }
+    if (!data.courseNoId) {
+      data.courseNoId = this.state.courseNos[0].id;
+    }
     return My.updateCourseNote(course.id, data.courseNoId, data.content).then(() => {
       this.setState({ data: { content: '' } });
       if (onConfirm) onConfirm();

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

@@ -6,6 +6,7 @@ import { formatDate } from '@src/services/Tools';
 import Tabs from '../Tabs';
 import Modal from '../Modal';
 import QrCode from '../QrCode';
+import { QrCodeAssets } from '../../../Constant';
 
 import { Order } from '../../stores/order';
 import { Main } from '../../stores/main';
@@ -392,7 +393,7 @@ export class PayKBankModal extends Component {
           汇款成功后,请添加微信号XXX或扫描二维码联系小助手,进行核实。
         </div>
         <div className="p-a" style={{ right: 0, top: 50 }}>
-          <Assets name={QrCode} width={150} height={150} />
+          <Assets name={QrCodeAssets} width={150} height={150} />
         </div>
       </Modal>
     );

+ 4 - 3
front/project/www/routes/course/answer/page.js

@@ -1,4 +1,5 @@
 import React from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
 import Page from '@src/containers/Page';
 import { formatDate, getMap } from '@src/services/Tools';
@@ -173,7 +174,7 @@ export default class extends Page {
     return (
       <div>
         <div className="top content t-8">
-          千行课堂 > 全部课程 > {course.title} > <span className="t-1">全部问答</span>
+          <Link to="/course">千行课堂</Link> > <Link to="/course/online?tab=single">全部课程</Link> > <Link to={`/course/detail/${course.id}`}>{course.title}</Link> > <span className="t-1">全部问答</span>
           <div className="f-r" onClick={() => linkTo(`/course/detail/${course.id}`)}>
             返回课程
           </div>
@@ -203,9 +204,9 @@ export default class extends Page {
             return (
               <div className="answer-item">
                 <div className="t-2">
-                  课时{(courseNoMap[item.key] || {}).no} {item.position}:00~{item.position + 5}:00
+                  课时{(courseNoMap[item.courseNoId] || {}).no} {item.position}:00~{item.position + 5}:00
                 </div>
-                <div className="t-2">课程内容: {(courseNoMap[item.key] || {}).title}</div>
+                <div className="t-2">课程内容: {(courseNoMap[item.courseNoId] || {}).title}</div>
 
                 {tab === 'my' && item.answerStatus === 0 && (
                   <div className="f-r">

+ 2 - 1
front/project/www/routes/course/data/page.js

@@ -1,4 +1,5 @@
 import React from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
 import { Switch } from 'antd';
 import Page from '@src/containers/Page';
@@ -188,7 +189,7 @@ export default class extends Page {
     return (
       <div>
         <div className="top content t-8">
-          千行课堂 > <span className="t-1">资料列表</span>
+          <Link to="/course">千行课堂</Link> > <span className="t-1">资料列表</span>
           <div className="f-r"><a href="/my/tools?tab=data">我的资料</a></div>
         </div>
         <div className="center content">

+ 5 - 4
front/project/www/routes/course/dataDetail/page.js

@@ -1,4 +1,5 @@
 import React from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
@@ -61,7 +62,7 @@ export default class extends Page {
   }
 
   buy() {
-    const { data } = this.props;
+    const { data } = this.state;
     User.needLogin().then(() => {
       Order.speedPay({ productType: 'data', productId: data.id }).then(result => {
         User.needPay(result).then(() => {
@@ -72,7 +73,7 @@ export default class extends Page {
   }
 
   add() {
-    const { data } = this.props;
+    const { data } = this.state;
     User.needLogin().then(() => {
       Order.addCheckout({ productType: 'data', productId: data.id }).then(() => {
         this.setState({ add: true });
@@ -85,8 +86,8 @@ export default class extends Page {
     return (
       <div>
         <div className="top content t-8">
-          千行课堂 > 全部资料 > {data.parentStructId > 0 ? `${(dataStructMap[data.parentStructId] || {}).title} >` : ''}{' '}
-          {(dataStructMap[data.structId] || {}).title} > {data.title} > <span className="t-1">资料详情</span>
+          <Link to="/course">千行课堂</Link> > <Link to={'/course/data'}>全部资料</Link> > {data.parentStructId > 0 ? <Link to={`/course/online?tab=single&parentStructId=${(dataStructMap[data.parentStructId] || {}).key}`}>{(dataStructMap[data.parentStructId] || {}).title}</Link> : null}{data.parentStructId > 0 ? '> ' : ''}
+          <Link to={`/course/online?tab=single&parentStructId=${(dataStructMap[data.parentStructId] || {}).key}&structId=${(dataStructMap[data.structId] || {}).key}`}>{(dataStructMap[data.structId] || {}).title}</Link> > <Link to={`/course/data/detail/${data.id}`}>{data.title}</Link> > <span className="t-1">资料详情</span>
         </div>
         {this.renderDetail()}
         <Contact data={base.contact} />

+ 17 - 12
front/project/www/routes/course/detail/page.js

@@ -21,7 +21,7 @@ import { User } from '../../../stores/user';
 import { Order } from '../../../stores/order';
 import { Question } from '../../../stores/question';
 import { My } from '../../../stores/my';
-import { QrCode } from '../../../../Constant';
+import { QrCodeAssets } from '../../../../Constant';
 
 export default class extends Page {
   initState() {
@@ -208,6 +208,9 @@ export default class extends Page {
       } else {
         this.onChangeItem(1);
       }
+      if (!result.have) {
+        Course.trailView(id);
+      }
       this.refreshNote();
     });
     Main.listComment({ page: 1, size: 100, channel: 'course-video', position: id }).then(result => {
@@ -301,7 +304,8 @@ export default class extends Page {
 
   showWater(second, extend) {
     const { data, water = {} } = this.state;
-    const { waters = [] } = data;
+    let { waters = [] } = data;
+    waters = waters || [];
     if (!this.lastTime && water.show) {
       water.show = false;
       second -= 1;
@@ -395,7 +399,7 @@ export default class extends Page {
   }
 
   buy() {
-    const { data } = this.props;
+    const { data } = this.state;
     User.needLogin().then(() => {
       Order.speedPay({ productType: 'course', productId: data.id }).then(result => {
         User.needPay(result).then(() => {
@@ -406,7 +410,7 @@ export default class extends Page {
   }
 
   add() {
-    const { data } = this.props;
+    const { data } = this.state;
     User.needLogin().then(() => {
       Order.addCheckout({ productType: 'course', productId: data.id }).then(() => {
         this.setState({ add: true });
@@ -425,11 +429,12 @@ export default class extends Page {
   renderView() {
     const { base = {}, data = {}, item = {}, add, rightTab, showTab, showAsk, showNote, dataStructMap = {}, showComment, comment = {}, showFaq, faq = {}, showFinish, note = {}, ask = {}, timelineSelect = [], water = {} } = this.state;
     const { courseNos = [] } = data;
+    const { paper = {} } = item;
     return (
       <div>
         <div className="top content t-8">
-          千行课堂 > 全部课程 > {data.parentStructId > 0 ? `${(dataStructMap[data.parentStructId] || {}).title} >` : ''}{' '}
-          {(dataStructMap[data.structId] || {}).title} > {data.title} > <span className="t-1">课程详情</span>
+          <Link to="/course">千行课堂</Link> > <Link to="/course/online?tab=single">全部课程</Link> > {data.parentStructId > 0 ? <Link to={`/course/online?tab=single&parentStructId=${(dataStructMap[data.parentStructId] || {}).key}`}>{(dataStructMap[data.parentStructId] || {}).title}</Link> : null}{data.parentStructId > 0 ? '> ' : ''}
+          <Link to={`/course/online?tab=single&parentStructId=${(dataStructMap[data.parentStructId] || {}).key}&structId=${(dataStructMap[data.structId] || {}).key}`}>{(dataStructMap[data.structId] || {}).title}</Link> > <Link to={`/course/detail/${data.id}`}>{data.title}</Link> > <span className="t-1">课程详情</span>
         </div>
         <div className="center content">
           <div className="t-1 t-s-20">
@@ -449,19 +454,19 @@ export default class extends Page {
           <div className="t-2 m-b-1">授课老师:{data.teacher}</div>
           <div className={'detail'}>
             <div className="left">
-              {data.have && <div hidden={(item.paper && item.paper.finishTimes > 0)} className="left-top">
+              {data.have && <div hidden={(paper.paper && paper.paper.finishTimes > 0)} className="left-top">
                 <span className="d-i-b m-r-1">预习作业</span>
                 <span className="d-i-b m-r-2">
-                  <ProgressText width={480} size="small" progress={item.report ? formatPercent(item.report.userNumber, item.report.questionNumber) : 0} />
+                  <ProgressText width={480} size="small" progress={paper.report ? formatPercent(paper.report.userNumber, paper.report.questionNumber) : 0} />
                 </span>
-                <Button className="f-r" radius onClick={() => (item.report ? Question.continueLink('preview', item) : Question.startLink('preview', item))}>
+                <Button className="f-r" radius onClick={() => (paper.report ? Question.continueLink('preview', paper) : Question.startLink('preview', paper))}>
                   做题
                 </Button>
               </div>}
               <div className="video-layout">
-                {item && <Video
+                {item.resource && <Video
                   key={item.id}
-                  src={item.resource || '/1.mp4'}
+                  src={item.resource}
                   width={750}
                   height={467}
                   ref={ref => this.setVideo(ref)}
@@ -649,7 +654,7 @@ export default class extends Page {
     return (
       <div className="tab-layout">
         <div className="qr-layout">
-          <Assets name={QrCode} className="m-r-2 v-a-t" />
+          <Assets name={QrCodeAssets} className="m-r-2 v-a-t" />
           <div className="p-l-1 d-i-b t-l">
             <div className="t-1">千行小助手:</div>
             <div className="t-1 m-b-2">1232104-310431</div>

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

@@ -1,4 +1,5 @@
 import React, { Component } from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
 import Page from '@src/containers/Page';
 import { formatDate, formatMonth, getMap, formatPercent } from '@src/services/Tools';
@@ -116,7 +117,7 @@ export default class extends Page {
     return (
       <div>
         <div className="top content t-8">
-          千行课堂 > <span className="t-1">学员表现</span>
+          <Link to="/course">千行课堂</Link> > <span className="t-1">学员表现</span>
         </div>
         {this.renderDetail()}
       </div>

+ 2 - 1
front/project/www/routes/course/note/page.js

@@ -1,4 +1,5 @@
 import React, { Component } from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
 import { Icon } from 'antd';
 import Page from '@src/containers/Page';
@@ -201,7 +202,7 @@ export default class extends Page {
     return (
       <div>
         <div className="top content t-8">
-          千行课堂 > 全部课程 > {course.title} > <span className="t-1">我的笔记</span>
+          <Link to="/course">千行课堂</Link> > <Link to="/course/online?tab=single">全部课程</Link> > <Link to={`/course/detail/${course.id}`}>{course.title}</Link> > <span className="t-1">我的笔记</span>
           <div className="f-r" onClick={() => linkTo(`/course/detail/${course.id}`)}>返回课程</div>
         </div>
         <div className="center content">

+ 34 - 30
front/project/www/routes/course/packageDetail/page.js

@@ -1,4 +1,5 @@
 import React from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
 import Assets from '@src/components/Assets';
 import Page from '@src/containers/Page';
@@ -41,25 +42,22 @@ export default class extends Page {
   }
 
   buy() {
-    const { data } = this.props;
+    const { data } = this.state;
     User.needLogin().then(() => {
-      Order.speedPay({ productType: 'course_package', productId: data.id })
-        .then(result => {
-          User.needPay(result)
-            .then(() => {
-              linkTo('/my/course');
-            });
+      Order.speedPay({ productType: 'course_package', productId: data.id }).then(result => {
+        User.needPay(result).then(() => {
+          linkTo('/my/course');
         });
+      });
     });
   }
 
   add() {
-    const { data } = this.props;
+    const { data } = this.state;
     User.needLogin().then(() => {
-      Order.addCheckout({ productType: 'course_package', productId: data.id })
-        .then(() => {
-          this.setState({ add: true });
-        });
+      Order.addCheckout({ productType: 'course_package', productId: data.id }).then(() => {
+        this.setState({ add: true });
+      });
     });
   }
 
@@ -68,7 +66,7 @@ export default class extends Page {
     return (
       <div>
         <div className="top content t-8">
-          千行课堂 > 全部套餐 > {data.title} > <span className="t-1">套餐详情</span>
+          <Link to="/course">千行课堂</Link> > <Link to="/course/online?tab=package">全部套餐</Link> >  <Link to={`/course/package/detail/${data.id}`}>{data.title}</Link> > <span className="t-1">套餐详情</span>
         </div>
         <div className="center content">
           <div className="t-1 t-s-20 m-b-2">
@@ -77,8 +75,15 @@ export default class extends Page {
               <Button className="m-r-1" radius size="lager" onClick={() => this.buy()}>
                 立即购买
               </Button>
-              <Button theme="default" radius size="lager" disabled={data.add || add} onClick={() => this.add()}>
-                <Assets name="add" />
+              <Button
+                className="f-r"
+                theme="default"
+                radius
+                size="lager"
+                disabled={data.add || add}
+                onClick={() => this.add()}
+              >
+                <Assets name={data.add || add ? 'add_disabled' : 'add'} />
               </Button>
             </div>
           </div>
@@ -99,24 +104,23 @@ export default class extends Page {
               <Assets name="" />
             </div>
           </div>
-          <div className="main-title">赠送服务</div>
-          <div className="list">
-            {data.gift &&
-              ServiceKey.map(row => {
-                if (!data.gift[row.value]) return null;
-                const list = ServiceParamMap[row.value];
-                const map = getMap(list || [], 'value', 'label');
-                return <div className="service-item d-i-b">
-                  <Assets name={serviceIconMap[row.value]} />
-                  <div className="t t-13 t-s-20 f-w-b">{row.label}</div>
-                  <div className="t-13">{list ? map[data.gift[row.value]] : data.gift[row.value]}</div>
-                </div>;
-              })}
-          </div>
+          {data.gift && <div className="main-title">赠送服务</div>}
+          {data.gift && <div className="list">
+            {ServiceKey.map(row => {
+              if (!data.gift[row.value]) return null;
+              const list = ServiceParamMap[row.value];
+              const map = getMap(list || [], 'value', 'label');
+              return <div className="service-item d-i-b">
+                <Assets name={serviceIconMap[row.value]} />
+                <div className="t t-13 t-s-20 f-w-b">{row.label}</div>
+                <div className="t-13">{list ? map[data.gift[row.value]] : data.gift[row.value]}</div>
+              </div>;
+            })}
+          </div>}
         </div>
         <Contact data={base.contact} />
         <Footer />
-      </div>
+      </div >
     );
   }
 }

+ 3 - 3
front/project/www/routes/course/vs/page.js

@@ -96,7 +96,7 @@ export default class extends Page {
   }
 
   buy() {
-    const { data } = this.props;
+    const { data } = this.state;
     Order.speedPay({ productType: 'course', productId: data.id }).then(result => {
       User.needPay(result).then(() => {
         linkTo('/my/course');
@@ -105,7 +105,7 @@ export default class extends Page {
   }
 
   add() {
-    const { data } = this.props;
+    const { data } = this.state;
     Order.addCheckout({ productType: 'course', productId: data.id }).then(() => {
       this.setState({ add: true });
     });
@@ -161,7 +161,7 @@ export default class extends Page {
               const course = this.courseVsMap[t.value] || {};
               return (
                 <div className={`item ${key === t.value ? 'active' : ''}`} onClick={() => this.onChangeItem(t.value)}>
-                  <Assets name={t.icon} />
+                  <Assets src={t.cover} />
                   <div className="t-1 t-s-20 f-w-b">{course.title}</div>
                   <div className="t-2">{course.comment}</div>
                 </div>

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

@@ -57,14 +57,14 @@ export default class extends Page {
               <div className="night f-s-16 f-w-b">
                 {record.report ? `${record.report.score.totalScore}分${record.report.score.totalRank}th` : '-分-th'}
               </div>
-              <div className="f-s-12">全站: {Math.round(record.totalScore / record.totalTimes)}分</div>
+              <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.totalScore / record.totalTimes) : '-'}分</div>
             </div>,
             this.state.showPrev && record.prevReport && (
               <div className="table-row prev t-3">
                 <div className="night f-s-16 f-w-b">
                   {record.prevReport.score.totalScore}分{record.prevReport.score.totalRank}th
                 </div>
-                <div className="f-s-12">全站: {Math.round(record.secondTotalScore / record.secondTotalTimes)}分</div>
+                <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.secondTotalScore / record.secondTotalTimes) : '-'}分</div>
               </div>
             ),
           ];
@@ -80,14 +80,14 @@ export default class extends Page {
               <div className="night f-s-16 f-w-b">
                 {record.report ? `${record.report.score.verbalScore}分${record.report.score.verbalRank}th` : '-分-th'}
               </div>
-              <div className="f-s-12">全站: {Math.round(record.verbalScore / record.totalTimes)}分</div>
+              <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.verbalScore / record.totalTimes) : '-'}分</div>
             </div>,
             this.state.showPrev && record.prevReport && (
               <div className="table-row prev t-3">
                 <div className="night f-s-16 f-w-b">
                   {record.prevReport.score.verbalScore}分{record.prevReport.score.verbalRank}th
                 </div>
-                <div className="f-s-12">全站: {Math.round(record.secondVerbalScore / record.secondTotalTimes)}分</div>
+                <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.secondVerbalScore / record.secondTotalTimes) : '-'}分</div>
               </div>
             ),
           ];
@@ -102,14 +102,14 @@ export default class extends Page {
             <div className="night f-s-16 f-w-b">
               {record.report ? `${record.report.score.quantScore}分${record.report.score.quantRank}th` : '-分-th'}
             </div>
-            <div className="f-s-12">全站: {Math.round(record.quantScore / record.totalTimes)}分</div>
+            <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.quantScore / record.totalTimes) : '-'}分</div>
           </div>,
           this.state.showPrev && record.prevReport && (
             <div className="table-row prev t-3">
               <div className="night f-s-16 f-w-b">
                 {record.prevReport.score.quantScore}分{record.prevReport.score.quantRank}th
               </div>
-              <div className="f-s-12">全站: {Math.round(record.secondQuantScore / record.secondTotalTimes)}分</div>
+              <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.secondQuantScore / record.secondTotalTimes) : '-'}分</div>
             </div>
           ),
           ];
@@ -124,14 +124,14 @@ export default class extends Page {
             <div className="night f-s-16 f-w-b">
               {record.report ? `${record.report.score.irScore}分${record.report.score.irRank}th` : '-分-th'}
             </div>
-            <div className="f-s-12">全站: {Math.round(record.irScore / record.totalTimes)}分</div>
+            <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.irScore / record.totalTimes) : '-'}分</div>
           </div>,
           this.state.showPrev && record.prevReport && (
             <div className="table-row prev t-3">
               <div className="night f-s-16 f-w-b">
                 {record.prevReport.score.irScore}分{record.prevReport.score.irRank}th
               </div>
-              <div className="f-s-12">全站: {Math.round(record.secondIrScore / record.secondTotalTimes)}分</div>
+              <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.secondIrScore / record.secondTotalTimes) : '-'}分</div>
             </div>
           ),
           ];

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

@@ -140,8 +140,17 @@ export default class extends Page {
   init() {
     const { id } = this.params;
     Main.getExerciseParent(id).then(result => {
+      let tab1 = '';
+      let tab2 = '';
       const navs = result.map(row => {
         row.title = row.level > 2 ? row.titleZh : `${row.titleZh}${row.titleEn}`;
+        if (!tab1) {
+          tab1 = row.extend;
+        } else if (!tab2) {
+          tab2 = row.extend;
+        }
+        row.tab1 = tab1;
+        row.tab2 = tab2;
         return row;
       });
       this.inited = true;
@@ -254,7 +263,7 @@ export default class extends Page {
           <Breadcrumb separator=">">
             <Breadcrumb.Item href="/exercise">练习</Breadcrumb.Item>
             {(navs || []).map(row => {
-              return <Breadcrumb.Item>{row.title}</Breadcrumb.Item>;
+              return <Breadcrumb.Item href={`/exercise?tab1=${row.tab1}&tab2=${row.tab2}`}>{row.title}</Breadcrumb.Item>;
             })}
           </Breadcrumb>
           <Module className="m-t-2">

+ 14 - 8
front/project/www/routes/exercise/main/page.js

@@ -1,6 +1,6 @@
 import React from 'react';
 import './index.less';
-import { Modal } from 'antd';
+import { Modal, Tooltip } from 'antd';
 // import { Link } from 'react-router-dom';
 import Page from '@src/containers/Page';
 import { asyncConfirm } from '@src/services/AsyncTools';
@@ -349,6 +349,10 @@ export default class extends Page {
 
   refreshSentence() {
     const { sentence } = this.state;
+
+    Main.getSentence().then(result => {
+      this.setState({ sentenceInfo: result });
+    });
     if (!sentence) {
       User.clearSentenceTrail();
       Sentence.getInfo()
@@ -417,7 +421,7 @@ export default class extends Page {
             }
             index += 1;
             chapters.forEach(row => {
-              chapterSteps.push(`「${index}」${row.short}`);
+              chapterSteps.push(`「${index}」${row.short || ''}`);
               index += 1;
             });
             this.setState({ articleMap: map, trailArticles, chapterSteps, introduction, chapterMap, exerciseChapter });
@@ -1095,7 +1099,7 @@ export default class extends Page {
   }
 
   renderInputCodeModel() {
-    const { sentenceError } = this.state;
+    const { sentenceError, sentenceInfo = {} } = this.state;
     return (
       <Modal visible closable={false} footer={false} title={false}>
         <div className="code-module-modal">
@@ -1107,11 +1111,13 @@ export default class extends Page {
               }}
             />
             {sentenceError && <div className="error">{sentenceError}</div>}
-            {/* <div className="tip">
-              <Link to="/" className="right link">
-                什么是CODE?
-              </Link>
-            </div> */}
+            <div className="tip">
+              <Tooltip title={<span dangerouslySetInnerHTML={{ __html: sentenceInfo.detail }} />} trigger='click'>
+                <a target="_blank" className="right link">
+                  什么是CODE?
+              </a>
+              </Tooltip>
+            </div>
           </div>
           <div className="btn-list">
             <AnswerButton size="lager" theme="confirm" width={150} onClick={() => this.activeSentence()}>

+ 4 - 2
front/project/www/routes/my/answer/page.js

@@ -182,6 +182,8 @@ export default class extends Page {
       questionSubjectMap = {},
       oneSelect,
       twoSelectMap = {},
+      oneSelectPlaceholder,
+      twoSelectPlaceholder,
       filterMap = {},
       sortMap = {},
       list = [],
@@ -224,13 +226,13 @@ export default class extends Page {
               children: [
                 {
                   key: 'one',
-                  placeholder: '全部',
+                  placeholder: oneSelectPlaceholder,
                   select: oneSelect,
                 },
                 {
                   key: 'two',
                   be: 'one',
-                  placeholder: '全部',
+                  placeholder: twoSelectPlaceholder,
                   selectMap: twoSelectMap,
                 },
               ],

+ 7 - 4
front/project/www/routes/my/collect/page.js

@@ -90,7 +90,7 @@ export default class extends Page {
           const user = record.stat ? record.stat.userTime / record.stat.userNumber : 0;
           const all = record.questionNo.totalNumber ? record.questionNo.totalTime / record.questionNo.totalNumber : 0;
           return <div className="sub">
-            <div className="t-2 t-s-12">{user > 0 ? formatSeconds(user) : '-'}{user > 0 && <Assets height={10} width={10} name={user > all ? 'down' : 'up'} />}</div>
+            <div className="t-2 t-s-12">{user > 0 ? formatSeconds(user) : '-'}{user > 0 && <Assets height={10} width={10} name={user > all ? 'up' : 'down'} />}</div>
             <div className="t-6 t-s-12">全站{all > 0 ? formatSeconds(all) : '-'}</div>
           </div>;
         },
@@ -134,7 +134,7 @@ export default class extends Page {
     data.filterMap = this.state.search;
     if (data.order) {
       data.sortMap = { [data.order]: data.direction };
-    } else {
+    } else if (data.order !== null && data.order !== '') {
       data.sortMap = { collect_time: 'desc' };
     }
     if (data.timerange) {
@@ -306,7 +306,7 @@ export default class extends Page {
           this.setState({ showWarn: true, warn: { title: '组卷练习', content: '不可同时选中语文题和数学题,请重新选择。' } });
           return;
         }
-        this.setState({ showGroupConfirm: true, groupInfo: { questionNoIds: selectList, filterTimes: 2 } });
+        this.setState({ showGroupConfirm: true, groupInfo: { questionNoIds: selectList, filterTimes: 2, checked: true } });
         break;
       case 'export':
         if (!info.vip) {
@@ -565,7 +565,10 @@ export default class extends Page {
           您共选择了 <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" />
+          <Checkbox checked={groupInfo.checked} className="m-r-5" onChange={() => {
+            groupInfo.checked = !groupInfo.checked;
+            this.setState({ groupInfo });
+          }} />
           剔除已组卷练习 <Select
             theme="white"
             value={groupInfo.filterTimes}

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

@@ -804,7 +804,7 @@ class CourseOnline extends Component {
             </div>
           </div>
           <div className="open">
-            <GIcon name={open ? 'down' : 'up'} onClick={() => this.setState({ open: !open })} />
+            <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
           </div>
         </div>
         {open && this.renderTable()}

+ 1 - 1
front/project/www/routes/my/data/page.js

@@ -187,7 +187,7 @@ function barOption2(title, subTitle, data) {
     yAxis: {
       type: 'value',
       min: 0,
-      max: 300,
+      max: 100,
       axisLabel: { color: '#686872' },
       axisLine: { lineStyle: { color: '#D1D6DF' } },
     },

+ 30 - 11
front/project/www/routes/my/error/page.js

@@ -84,8 +84,8 @@ export default class extends Page {
         width: 100,
         sort: true,
         render: (text, record) => {
-          const user = record.stat ? record.stat.userTime / record.stat.userNumber : 0;
-          const all = record.questionNo.totalNumber ? record.questionNo.totalTime / record.questionNo.totalNumber : 0;
+          const user = Math.floor(record.stat ? record.stat.userTime / record.stat.userNumber : 0);
+          const all = Math.floor(record.questionNo.totalNumber ? record.questionNo.totalTime / record.questionNo.totalNumber : 0);
           return <div className="sub">
             <div className="t-2 t-s-12">{user > 0 ? formatSeconds(user) : '-'}{user > 0 && <Assets height={10} width={10} name={user > all ? 'down' : 'up'} />}</div>
             <div className="t-6 t-s-12">全站{all > 0 ? formatSeconds(all) : '-'}</div>
@@ -132,7 +132,7 @@ export default class extends Page {
     data.filterMap = this.state.search;
     if (data.order) {
       data.sortMap = { [data.order]: data.direction };
-    } else {
+    } else if (data.order !== null && data.order !== '') {
       data.sortMap = { latest_time: 'desc' };
     }
     if (data.timerange) {
@@ -150,11 +150,12 @@ export default class extends Page {
       all: true,
       needSentence: true,
       allSubject: true,
-    }).then(({ questionTypes }) => {
+    }).then(({ questionTypes, selectSentence }) => {
       return refreshStruct(this, questionTypes, data.tab, data.one, data.two, {
         all: true,
         needPreview: true,
         needTextbook: true,
+        selectSentence,
       }).then(({ courseModules, structIds, latest, year }) => {
         My.listError(
           Object.assign(
@@ -163,7 +164,7 @@ export default class extends Page {
             {
               order: Object.keys(data.sortMap)
                 .map(key => {
-                  if (key === 'title') return 'pid desc, no desc';
+                  if (key === 'title') return 'pid asc, no asc';
                   return `${key} ${data.sortMap[key]}`;
                 })
                 .join(','),
@@ -312,7 +313,7 @@ export default class extends Page {
           this.setState({ showWarn: true, warn: { title: '组卷练习', content: '不可同时选中语文题和数学题,请重新选择。' } });
           return;
         }
-        this.setState({ showGroupConfirm: true, groupInfo: { questionNoIds: selectList, filterTimes: 2 } });
+        this.setState({ showGroupConfirm: true, groupInfo: { questionNoIds: selectList, filterTimes: 2, checked: true } });
         break;
       case 'export':
         if (!info.vip) {
@@ -351,6 +352,9 @@ export default class extends Page {
 
   group() {
     const { groupInfo } = this.state;
+    if (!groupInfo.checked) {
+      delete groupInfo.filterTimes;
+    }
     My.groupError(groupInfo)
       .then((result) => {
         Question.startLink('error', result);
@@ -403,6 +407,8 @@ export default class extends Page {
       questionSubjectMap = {},
       oneSelect,
       twoSelectMap = {},
+      oneSelectPlaceholder,
+      twoSelectPlaceholder,
       filterMap = {},
       sortMap = {},
       list = [],
@@ -447,13 +453,13 @@ export default class extends Page {
               children: [
                 {
                   key: 'one',
-                  placeholder: '全部',
+                  placeholder: oneSelectPlaceholder,
                   select: oneSelect,
                 },
                 {
                   key: 'two',
                   be: 'one',
-                  placeholder: '全部',
+                  placeholder: twoSelectPlaceholder,
                   selectMap: twoSelectMap,
                 },
               ],
@@ -539,7 +545,10 @@ export default class extends Page {
         <div className="t-2 t-s-18">
           您共选择了 <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" />
+          <Checkbox checked={groupInfo.checked} className="m-r-5" onChange={() => {
+            groupInfo.checked = !groupInfo.checked;
+            this.setState({ groupInfo });
+          }} />
           剔除已组卷练习 <Select
             theme="white"
             value={groupInfo.filterTimes}
@@ -569,7 +578,12 @@ export default class extends Page {
             return (
               <div className="d-i-b m-b-5" style={{ width: 135 }}>
                 <Checkbox checked={exportInfo.include ? exportInfo.include.indexOf(item.key) >= 0 : false} className="m-r-5" onChange={() => {
-                  exportInfo.include.push(item.key);
+                  const index = exportInfo.include.indexOf(item.key);
+                  if (index >= 0) {
+                    exportInfo.include.splice(index, 1);
+                  } else {
+                    exportInfo.include.push(item.key);
+                  }
                   this.setState({ exportInfo });
                 }} />
                 {item.title}
@@ -589,7 +603,12 @@ export default class extends Page {
               return (
                 <div className="d-i-b m-b-2" style={{ width: 135 }}>
                   <Checkbox checked={exportInfo.include ? exportInfo.include.indexOf(item.key) >= 0 : false} className="m-r-5" onChange={() => {
-                    exportInfo.include.push(item.key);
+                    const index = exportInfo.include.indexOf(item.key);
+                    if (index >= 0) {
+                      exportInfo.include.splice(index, 1);
+                    } else {
+                      exportInfo.include.push(item.key);
+                    }
                     this.setState({ exportInfo });
                   }} />
                   {item.title}

+ 106 - 15
front/project/www/routes/my/index.js

@@ -34,12 +34,24 @@ export function refreshQuestionType(compontent, subject, questionType, { all, ne
             title: '全部',
             key: '',
           });
+        } else if (row.key === 'sentence') {
+          row.children.unshift({
+            title: '长难句',
+            key: 'sentence',
+          });
         }
       });
       compontent.questionSubjectSelect.unshift({
         title: '全部',
         key: '',
       });
+      compontent.questionSubjectMap[''] = [{
+        title: '全部',
+        key: '',
+      }];
+    }
+    if (questionType === subject) {
+      questionType = null;
     }
     compontent.setState({
       questionSubjectSelect: compontent.questionSubjectSelect,
@@ -47,11 +59,14 @@ export function refreshQuestionType(compontent, subject, questionType, { all, ne
     });
     return {
       questionTypes: questionType || (subject ? compontent.questionSubjectMap[subject].map(row => row.key).filter(row => row) : null),
+      selectSentence: subject === 'sentence',
     };
   });
 }
 
-export function refreshStruct(compontent, questionTypes, module, one, two, { all, needTextbook, needPreview }) {
+export function refreshStruct(compontent, questionTypes, module, one, two, { all, needTextbook, needPreview, selectSentence }) {
+  let oneSelectPlaceholder = '全部';
+  let twoSelectPlaceholder = '全部';
   switch (module) {
     case 'exercise':
       return Main.getExerciseAll().then(result => {
@@ -64,8 +79,8 @@ export function refreshStruct(compontent, questionTypes, module, one, two, { all
         const idsMap = getMap(tmp, 'id', 'key');
         const map = {};
         tmp.forEach(row => {
-          if (!map[row.u]) {
-            map[row.u] = {
+          if (!map[row.key]) {
+            map[row.key] = {
               title: row.title,
               key: row.key,
               structIds: [],
@@ -73,7 +88,7 @@ export function refreshStruct(compontent, questionTypes, module, one, two, { all
               questionTypes: [],
             };
           }
-          const item = map[row.u];
+          const item = map[row.key];
           item.structIds.push(row.id);
           if (item.questionTypes.indexOf(row.extend) < 0) {
             item.questionTypes.push(row.extend);
@@ -120,7 +135,7 @@ export function refreshStruct(compontent, questionTypes, module, one, two, { all
           }
         }
         const tree = formatTreeData(list, 'key', 'title', 'parentId');
-        const oneSelect = tree;
+        let oneSelect = tree;
         const twoSelectMap = getMap(tree, 'key', 'children');
         if (all) {
           oneSelect.forEach(row => {
@@ -129,14 +144,51 @@ export function refreshStruct(compontent, questionTypes, module, one, two, { all
                 title: '全部',
                 key: '',
               });
+            } else if (row.children.length === 0) {
+              row.children.unshift({
+                title: row.title,
+                key: '',
+              });
             }
           });
-          oneSelect.unshift({
-            title: '全部',
+          if (oneSelect.length > 1) {
+            oneSelect.unshift({
+              title: '全部',
+              key: '',
+            });
+          } else if (oneSelect.length === 1) {
+            oneSelectPlaceholder = null;
+          }
+          if (!one) {
+            twoSelectMap[''] = [{
+              title: '全部',
+              key: '',
+            }];
+          }
+        }
+        if (one && twoSelectMap[one]) {
+          if (twoSelectMap[one].length === 1) {
+            twoSelectPlaceholder = null;
+          } else if (twoSelectMap[one].length === 0 || !twoSelectMap[one]) {
+            twoSelectMap[one] = [{
+              title: '',
+              key: '',
+            }];
+          }
+        }
+        if (selectSentence) {
+          oneSelect = [{
+            title: '千行长难句',
             key: '',
-          });
+          }];
+          twoSelectMap[''] = [{
+            title: '千行长难句',
+            key: '',
+          }];
+          oneSelectPlaceholder = null;
+          twoSelectPlaceholder = null;
         }
-        compontent.setState({ oneSelect, twoSelectMap });
+        compontent.setState({ oneSelect, twoSelectMap, oneSelectPlaceholder, twoSelectPlaceholder });
 
         return {
           structIds,
@@ -191,19 +243,58 @@ export function refreshStruct(compontent, questionTypes, module, one, two, { all
         }
 
         const tree = formatTreeData(list, 'key', 'title', 'parentId');
-        const oneSelect = tree;
+        let oneSelect = tree;
         const twoSelectMap = getMap(tree, 'key', 'children');
         if (all) {
           oneSelect.forEach(row => {
-            row.children.unshift({
+            if (row.children.length > 1) {
+              row.children.unshift({
+                title: '全部',
+                key: '',
+              });
+            } else if (row.children.length === 0) {
+              row.children.unshift({
+                title: row.title,
+                key: '',
+              });
+            }
+          });
+          if (oneSelect.length > 1) {
+            oneSelect.unshift({
               title: '全部',
               key: '',
             });
-          });
-          oneSelect.unshift({
-            title: '全部',
+          } else if (oneSelect.length === 1) {
+            oneSelectPlaceholder = null;
+          }
+          if (!one) {
+            twoSelectMap[''] = [{
+              title: '全部',
+              key: '',
+            }];
+          }
+        }
+        if (one && twoSelectMap[one]) {
+          if (twoSelectMap[one].length === 1) {
+            twoSelectPlaceholder = null;
+          } else if (twoSelectMap[one].length === 0 || !twoSelectMap[one]) {
+            twoSelectMap[one] = [{
+              title: '',
+              key: '',
+            }];
+          }
+        }
+        if (selectSentence) {
+          oneSelect = [{
+            title: '千行长难句',
             key: '',
-          });
+          }];
+          twoSelectMap[''] = [{
+            title: '千行长难句',
+            key: '',
+          }];
+          oneSelectPlaceholder = null;
+          twoSelectPlaceholder = null;
         }
         compontent.setState({ oneSelect, twoSelectMap });
         return {

+ 9 - 1
front/project/www/routes/my/main/page.js

@@ -140,6 +140,8 @@ export default class extends Page {
   constructor(props) {
     super(props);
     this.colors = ['#3C39CC', '#9E9CFF', '#4292F0', '#4374EC', '#6865FD', '#8D65FD', '#6BABF6', '#7BBEFF', '#6BABF6'];
+    this.calMonth = null;
+    this.calYear = null;
   }
 
   initState() {
@@ -364,6 +366,9 @@ export default class extends Page {
       });
       this.setState({ study });
     });
+    time = moment(time);
+    this.calMonth = time.month();
+    this.calYear = time.year();
     this.setState({ day: value, time, showCal: false });
   }
 
@@ -462,8 +467,11 @@ export default class extends Page {
                 show
                 hideInput
                 theme="filled"
-                value={moment(time)}
+                value={time}
                 disabledDate={date => date.unix() <= moment.unix()}
+                onClick={(event) => {
+                  event.stopPropagation();
+                }}
                 onChange={date => {
                   this.refreshDay(date);
                   this.setState({ showCal: false });

+ 31 - 20
front/project/www/routes/my/note/page.js

@@ -111,7 +111,7 @@ export default class extends Page {
     data.filterMap = this.state.search;
     if (data.order) {
       data.sortMap = { [data.order]: data.direction };
-    } else {
+    } else if (data.order !== null && data.order !== '') {
       data.sortMap = { update_time: 'desc' };
     }
     if (data.timerange) {
@@ -154,7 +154,7 @@ export default class extends Page {
               row.list.push({
                 title: r.value,
                 key: `${row.key}|${r.value}`,
-                update_time: formatDate(row[`${r.value}Time`], 'YYYY-MM-DD HH:mm:ss'),
+                update_time: formatDate(row.updateTime, 'YYYY-MM-DD HH:mm:ss'),
                 content: row[`${r.value}Content`],
               });
             });
@@ -311,28 +311,27 @@ export default class extends Page {
 
   onAction(key) {
     const { info } = this.props.user;
-    const { selectList, contentSelectList } = this.state;
-    const questionNoMap = {};
-    // let questionNoIds;
+    const { selectPage = {} } = this.state;
+    const selectList = [].concat(...Object.values(selectPage).concat());
     switch (key) {
       case 'help':
         this.setState({ showTips: true });
         return;
       case 'clear':
-        if (selectList.length === 0 && contentSelectList === 0) {
+        if (selectList.length === 0) {
           this.setState({ showWarn: true, warn: { title: '移除', content: '不可少于1题,请重新选择' } });
           return;
         }
-        contentSelectList.forEach(row => {
-          const [questionNoIdStr, target] = row.split('|');
-          const questionNoId = Number(questionNoIdStr);
-          if (!questionNoMap[questionNoId]) {
-            questionNoMap[questionNoId] = { fields: 0 };
-          }
-          questionNoMap[questionNoId][target] = '';
-          questionNoMap[questionNoId].fields += 1;
-        });
-        this.clearNote(Object.keys(questionNoMap));
+        // contentSelectList.forEach(row => {
+        //   const [questionNoIdStr, target] = row.split('|');
+        //   const questionNoId = Number(questionNoIdStr);
+        //   if (!questionNoMap[questionNoId]) {
+        //     questionNoMap[questionNoId] = { fields: 0 };
+        //   }
+        //   questionNoMap[questionNoId][target] = '';
+        //   questionNoMap[questionNoId].fields += 1;
+        // });
+        this.clearNote(selectList);
         // this.setState({ showClearConfirm: true, clearInfo: { questionNoIds: selectList } });
         break;
       case 'export':
@@ -405,6 +404,8 @@ export default class extends Page {
       questionSubjectMap = {},
       oneSelect,
       twoSelectMap = {},
+      oneSelectPlaceholder,
+      twoSelectPlaceholder,
       filterMap = {},
       sortMap = {},
       list = [],
@@ -448,13 +449,13 @@ export default class extends Page {
               children: [
                 {
                   key: 'one',
-                  placeholder: '全部',
+                  placeholder: oneSelectPlaceholder,
                   select: oneSelect,
                 },
                 {
                   key: 'two',
                   be: 'one',
-                  placeholder: '全部',
+                  placeholder: twoSelectPlaceholder,
                   selectMap: twoSelectMap,
                 },
               ],
@@ -555,7 +556,12 @@ export default class extends Page {
             return (
               <div className="d-i-b m-b-5" style={{ width: 135 }}>
                 <Checkbox checked={exportInfo.include ? exportInfo.include.indexOf(item.key) >= 0 : false} className="m-r-5" onChange={() => {
-                  exportInfo.include.push(item.key);
+                  const index = exportInfo.include.indexOf(item.key);
+                  if (index >= 0) {
+                    exportInfo.include.splice(index, 1);
+                  } else {
+                    exportInfo.include.push(item.key);
+                  }
                   this.setState({ exportInfo });
                 }} />
                 {item.title}
@@ -575,7 +581,12 @@ export default class extends Page {
               return (
                 <div className="d-i-b m-b-2" style={{ width: 135 }}>
                   <Checkbox checked={exportInfo.include ? exportInfo.include.indexOf(item.key) >= 0 : false} className="m-r-5" onChange={() => {
-                    exportInfo.include.push(item.key);
+                    const index = exportInfo.include.indexOf(item.key);
+                    if (index >= 0) {
+                      exportInfo.include.splice(index, 1);
+                    } else {
+                      exportInfo.include.push(item.key);
+                    }
                     this.setState({ exportInfo });
                   }} />
                   {item.title}

+ 65 - 29
front/project/www/routes/my/report/page.js

@@ -107,7 +107,8 @@ export default class extends Page {
         fixSort: true,
         render: (text, record) => {
           if (text) {
-            return `${QuestionnTypeMap[record.questionTypes[0]]}-${text}`;
+            if (QuestionnTypeMap[record.questionTypes[0]]) return `${QuestionnTypeMap[record.questionTypes[0]]}-${text}`;
+            return `${text}`;
           }
           return '';
         },
@@ -167,7 +168,7 @@ export default class extends Page {
           const user = formatPercent(report.userCorrect, report.userNumber);
           const all = formatPercent(record.stat.totalCorrect, record.stat.totalNumber);
           return <div className="">
-            <div className="t-2 t-s-12">{user}%<Assets height={10} width={10} name={user > all ? 'down' : 'up'} /></div>
+            <div className="t-2 t-s-12">{user}%<Assets height={10} width={10} name={user > all ? 'up' : 'down'} /></div>
             <div className="t-6 t-s-12">全站{all}%</div>
           </div>;
         },
@@ -179,8 +180,8 @@ export default class extends Page {
         render: (text, record, index, childIndex) => {
           const { reports } = record;
           const report = childIndex === -1 ? reports[0] : record;
-          const user = report.userTime / report.userNumber;
-          const all = record.stat.totalTime / record.stat.totalNumber;
+          const user = Math.floor(report.userTime / report.userNumber);
+          const all = Math.floor(record.stat.totalTime / record.stat.totalNumber);
           return <div className="">
             <div className="t-2 t-s-12">{formatSeconds(user)}<Assets height={10} width={10} name={user > all ? 'down' : 'up'} /></div>
             <div className="t-6 t-s-12">全站{formatSeconds(all)}</div>
@@ -227,25 +228,53 @@ export default class extends Page {
         key: 'latest_time',
         title: '做题时间',
         sort: true,
-        render: (text, record) => {
-          const { reports } = record;
-          if (!reports) return null;
-          const report = reports[0];
-          if (!report) return null;
+        render: (text, record, index, childIndex) => {
+          const { reports, show } = record;
+          const report = childIndex === -1 ? reports[0] : record;
           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>
-          );
+          return <div className="">
+            <div className="t-2 t-s-12">{time.split(' ')[0]}
+              {childIndex === -1 && reports && reports.length > 1 && (
+                <Popover
+                  trigger="click"
+                  content={
+                    <table className="user-report-history-overlay">
+                      <tbody>
+                        {reports.map((child, ii) => {
+                          if (ii === 0) return null;
+                          return <tr>
+                            <td>{reports.length - ii}</td>
+                            <td>{formatDate(child.updateTime, 'YYYY-MM-DD HH:mm:ss')}</td>
+                            <td>{child.userNumber}/{child.questionNumber}</td>
+                            <td>
+                              <Checkbox
+                                checked={show.indexOf(child.id) >= 0}
+                                onChange={e => this.onChangeShowHistory(index, ii, e.target.checked)}
+                              />
+                            </td>
+                          </tr>;
+                        })}
+                      </tbody>
+                    </table>
+                  }
+                >
+                  <Tooltip overlayClassName="gray" title="历史数据">
+                    <span>
+                      <Assets className="m-l-5" name="down_normal" />
+                    </span>
+                  </Tooltip>
+                </Popover>
+              )}</div>
+            <div className="t-6 t-s-12">{time.split(' ')[1]}</div>
+          </div>;
         },
       },
       {
         key: 'overall',
         title: 'Overall',
-        render: (text, record) => {
-          const [report] = record.reports;
+        render: (text, record, index, childIndex) => {
+          const { reports } = record;
+          const report = childIndex === -1 ? reports[0] : record;
           if (!report) return null;
           if (!report.qxCat) {
             return (
@@ -271,8 +300,9 @@ export default class extends Page {
       {
         key: 'verbal',
         title: 'Verbal',
-        render: (text, record) => {
-          const [report] = record.reports;
+        render: (text, record, index, childIndex) => {
+          const { reports } = record;
+          const report = childIndex === -1 ? reports[0] : record;
           if (!report) return null;
           if (!report.qxCat) {
             return (
@@ -296,8 +326,9 @@ export default class extends Page {
       {
         key: 'quant',
         title: 'Quant',
-        render: (text, record) => {
-          const [report] = record.reports;
+        render: (text, record, index, childIndex) => {
+          const { reports } = record;
+          const report = childIndex === -1 ? reports[0] : record;
           if (!report) return null;
           if (!report.qxCat) {
             return (
@@ -319,8 +350,9 @@ export default class extends Page {
       {
         key: 'ir',
         title: 'IR',
-        render: (text, record) => {
-          const [report] = record.reports;
+        render: (text, record, index, childIndex) => {
+          const { reports } = record;
+          const report = childIndex === -1 ? reports[0] : record;
           if (!report) return null;
           if (!report.qxCat) {
             return <div className="f-s-12">{formatPercent(report.setting.number.ir, this.nums.ir.number, false)}</div>;
@@ -340,8 +372,9 @@ export default class extends Page {
       {
         key: 'report',
         title: '报告',
-        render: (text, record) => {
-          const [report] = record.reports;
+        render: (text, record, index, childIndex) => {
+          const { reports } = record;
+          const report = childIndex === -1 ? reports[0] : record;
           return (
             <IconButton
               type="report"
@@ -468,7 +501,7 @@ export default class extends Page {
     data.filterMap = this.state.search;
     if (data.order) {
       data.sortMap = { [data.order]: data.direction };
-    } else {
+    } else if (data.order !== '') {
       data.sortMap = { latest_time: 'desc' };
     }
     if (data.timerange) {
@@ -504,11 +537,12 @@ export default class extends Page {
           needSentence: true,
           allSubject: true,
           onlyPreview: data.one === 'preview',
-        }).then(({ questionTypes }) => {
+        }).then(({ questionTypes, selectSentence }) => {
           return refreshStruct(this, questionTypes, data.tab, data.one, data.two, {
             all: true,
             needPreview: true,
             needTextbook: false,
+            selectSentence,
           }).then(({ structIds, courseModules }) => {
             My.listReport(
               Object.assign(
@@ -608,6 +642,8 @@ export default class extends Page {
       questionSubjectMap = {},
       oneSelect,
       twoSelectMap = {},
+      oneSelectPlaceholder,
+      twoSelectPlaceholder,
       filterMap = {},
       sortMap = {},
       list = [],
@@ -637,13 +673,13 @@ export default class extends Page {
         children: [
           {
             key: 'one',
-            placeholder: '全部',
+            placeholder: oneSelectPlaceholder,
             select: oneSelect,
           },
           {
             key: 'two',
             be: 'one',
-            placeholder: '全部',
+            placeholder: twoSelectPlaceholder,
             selectMap: twoSelectMap,
           },
         ],

+ 1 - 1
front/project/www/routes/page/cart/page.js

@@ -166,7 +166,7 @@ export default class extends Page {
                 </div>
                 <div className="t-1">
                   原价<span className="m-l-5 t-8 t-d-l-t">¥ {order.originMoney || 0}</span>
-                  <span hidden={!((order.originMoney || 0) - (order.money || 0))} className="m-l-5  t-8">(优惠活动-¥{(order.originMoney || 0) - (order.money || 0)})</span>
+                  <span hidden={!(Math.round((order.originMoney || 0) - (order.money || 0)))} className="m-l-5  t-8">(优惠活动-¥{Math.round((order.originMoney || 0) - (order.money || 0))})</span>
                 </div>
               </div>
               <Button className="submit" onClick={() => this.pay()}>立即付款</Button>

+ 9 - 1
front/project/www/routes/page/contract/index.less

@@ -1,3 +1,11 @@
 @charset "utf-8";
 
-#contract {}
+#contract {
+  background: white;
+
+  >div {
+    margin: 0px auto;
+    width: 1000px;
+    padding: 20px 0;
+  }
+}

+ 3 - 1
front/project/www/routes/page/export/page.js

@@ -182,7 +182,9 @@ class BaseDetail extends Component {
     return [
       <div className="detail-item-block">
         <div className="t-1 t-s-16 m-b-2" dangerouslySetInnerHTML={{ __html: question.stem }} />
-        {this.renderSelect(question.content.questions[0].select)}
+        {question.content.questions.map(q => {
+          return [q.description && <div className="t-1 t-s-16 m-b-2" dangerouslySetInnerHTML={{ __html: q.description }} />, this.renderSelect(q.select)];
+        })}
       </div>,
       this.renderAnswer(),
     ];

+ 33 - 13
front/project/www/routes/paper/process/base/index.js

@@ -156,6 +156,10 @@ export default class extends Component {
     return text;
   }
 
+  showHelp() {
+    this.setState({ showHelp: true });
+  }
+
   next() {
     const { flow } = this.props;
     const { answer } = this.state;
@@ -174,7 +178,7 @@ export default class extends Component {
   }
 
   render() {
-    const { modal } = this.state;
+    const { modal, showHelp } = this.state;
     const { scene, paper } = this.props;
     let content = null;
     switch (scene) {
@@ -192,6 +196,7 @@ export default class extends Component {
       <div id="paper-process-base">
         {content}
         {modal ? this.renderModal() : ''}
+        {showHelp ? this.renderHelp() : ''}
       </div>
     );
   }
@@ -288,7 +293,7 @@ export default class extends Component {
               }}
             >
               <Assets name="subjectnumber_icon" />
-              {showNo && `${userQuestion.no} of ${totalNumber}`}
+              {showNo && `${userQuestion.stageNo} of ${totalNumber}`}
             </div>
           </div>
         </div>
@@ -319,7 +324,7 @@ export default class extends Component {
     // const { paper, userQuestion, singleTime, stageTime, flow } = this.props;
     const { showTime } = this.state;
     const { paper, flow, startTime, setting } = this.props;
-    const { disorder, order } = setting;
+    const { disorder = true, order } = setting;
     return (
       <div className="layout">
         <div className="fixed" />
@@ -355,7 +360,7 @@ export default class extends Component {
           <div className="full">
             <Assets name="fullscreen_icon" onClick={() => flow.toggleFullscreen()} />
           </div>
-          <div className="next" onClick={() => flow.start({ disorder: paper.finishTimes > 0 ? !disorder : false, order: order.filter(row => row) })}>
+          <div className="next" onClick={() => flow.start({ disorder: paper.finishTimes > 0 ? disorder : false, order: order.filter(row => row) })}>
             Next
             <Assets name="next_icon" />
           </div>
@@ -366,7 +371,7 @@ export default class extends Component {
 
   renderExerciseStart() {
     const { paper, flow, setting } = this.props;
-    const { disorder = false } = setting;
+    const { disorder = true } = setting;
     return (
       <div className="start">
         <div className="bg" />
@@ -404,13 +409,13 @@ export default class extends Component {
 
   renderExaminationStartCAT() {
     const { paper, flow, setting } = this.props;
-    const { disorder, order = [], orderIndex } = setting;
+    const { disorder = true, order = [], orderIndex } = setting;
     return (
       <div className="exercise-start default">
         <div className="title">Section Ordering</div>
         <div className="desc">Select the order in which the exam sections are to be administered.</div>
         <div className="desc tip">
-          NOTE: You have 1 minutes to make your selection. If you do not make your selection within 1 minutes, the first
+          NOTE: You have 1 minute to make your selection. If you do not make your selection within 1 minute, the first
           option listed will be selected and you will view the exam in the following order: Analytical Writing
           Assessment, Integrated Reasoning, Quantitative, Verbal.
         </div>
@@ -437,11 +442,11 @@ export default class extends Component {
         </div>
         <div className="bottom">
           {paper.finishTimes > 0 && <div className="text">
-            <Checkbox checked={!disorder} onChange={() => flow.setSetting({ disorder: !!disorder })} /> 题目选项乱序显示
+            <Checkbox checked={disorder} onChange={() => flow.setSetting({ disorder: !disorder })} /> 题目选项乱序显示
           </div>}
           <div className="text">
             Click{' '}
-            <div className="next" onClick={() => flow.start({ disorder: paper.finishTimes > 0 ? !disorder : false, order: order.filter(row => row) })}>
+            <div className="next" onClick={() => flow.start({ disorder: paper.finishTimes > 0 ? disorder : false, order: order.filter(row => row) })}>
               Next
               <Assets name="next_icon" />
             </div>{' '}
@@ -454,7 +459,7 @@ export default class extends Component {
 
   renderExaminationStartDefault() {
     const { paper, flow, setting } = this.props;
-    const { disorder = false, order = [], orderIndex } = setting;
+    const { disorder = true, order = [], orderIndex } = setting;
     return (
       <div className="exercise-start cat">
         <div className="title">Section Ordering</div>
@@ -540,7 +545,7 @@ export default class extends Component {
         </div>
         <div className="layout-footer">
           <div className="help">
-            <Assets name="help_icon" />
+            <Assets name="help_icon" onClick={() => this.showHelp()} />
             Help
           </div>
           <div className="full">
@@ -563,7 +568,7 @@ export default class extends Component {
         <div style={{ width: modal.width }} className="body">
           <div className="title">{modal.title}</div>
           <div className="desc">{modal.desc}</div>
-          {modal.type === 'confirm' ? (
+          {modal.type === 'confirm' && (
             <div className="btn-list">
               <div className="btn" onClick={() => this.hideModal(true)}>
                 <span className="t-d-l">Y</span>es
@@ -572,7 +577,8 @@ export default class extends Component {
                 <span className="t-d-l">N</span>o
               </div>
             </div>
-          ) : (<div className="btn-list">
+          )}
+          {modal.type === 'toast' && (<div className="btn-list">
             <div className="btn" onClick={() => this.hideModal(true)}>
               <span className="t-d-l">O</span>k
               </div>
@@ -581,4 +587,18 @@ export default class extends Component {
       </div>
     );
   }
+
+  renderHelp() {
+    // const { helpInfo } = this.state;
+
+    // return (
+    //   <div className="modal">
+    //     <div className="mask" />
+    //     <div style={{ width: modal.width }} className="body">
+    //       <div className="title">{modal.title}</div>
+    //       <div className="desc">{modal.desc}</div>
+    //     </div>
+    //   </div>
+    // );
+  }
 }

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

@@ -137,7 +137,7 @@ export default class extends Page {
     const { setting = {} } = report;
     if (scene === 'relax') {
       // 进入下一阶段
-      this.nextStage();
+      this.initNextStage();
     }
     // 更新模考做题进度
     if (report.paperModule === 'examination') {
@@ -153,7 +153,7 @@ export default class extends Page {
     return Question.next(report.id)
       .then(userQuestion => {
         const questionSetting = {};
-        if (setting.disorder) {
+        if (setting.disorder && userQuestion.questionType !== 'ds') {
           const { questions } = userQuestion.question.content;
           if (questions) {
             // 乱序显示选项
@@ -186,7 +186,7 @@ export default class extends Page {
   submit(answer) {
     const { report, userQuestion, questionSetting, singleTime } = this.state;
     const { setting = {} } = report;
-    if (setting.disorder) {
+    if (setting.disorder && userQuestion.questionType !== 'ds') {
       const { questions } = answer;
       if (questions) {
         // 还原乱序选项
@@ -207,7 +207,7 @@ export default class extends Page {
   }
 
   // 主动进入下一阶段
-  stage() {
+  nextStage() {
     const { report } = this.state;
     return Question.stage(report.id)
       .then(() => {
@@ -270,18 +270,19 @@ export default class extends Page {
       this.stageInterval = setInterval(() => {
         this.stageTime += 1;
         if (this.stageTime >= this.stageProcess.time) {
-          this.stageQuestionTime(0, true);
           const { scene } = this.state;
           if (scene === 'relax') {
             // 进入下一阶段,获取下一题
             this.next();
           } else {
             // 提交当前阶段
-            this.stage();
+            this.nextStage();
           }
+          return;
         }
         this.setState({ stageTime: this.stageProcess.time - this.stageTime });
       }, 1000);
+      this.setState({ stageTime: this.stageProcess.time - this.stageTime });
     }
   }
 
@@ -294,19 +295,21 @@ export default class extends Page {
       this.startTime += 1;
       // 1分钟等待: 自动提交第一选择
       const { scene } = this.state;
+      const { paper, setting } = this.state;
+      const { disorder = true, order = [] } = setting;
       if (scene !== 'start') {
         clearInterval(this.startInterval);
         this.startInterval = null;
       } else if (this.startTime >= 60) {
         clearInterval(this.startInterval);
         this.startInterval = null;
-        this.start(Object.assign({ order: ExaminationOrder[0].value }, this.state.setting));
+        this.start(Object.assign({ order: ExaminationOrder[0].value }, { disorder: paper.finishTimes > 0 ? disorder : false, order: order.filter(row => row) }));
       }
       this.setState({ startTime: 60 - this.startTime });
     }, 1000);
   }
 
-  nextStage() {
+  initNextStage() {
     const { report } = this.state;
     // 进入下一阶段
     const { order } = report.setting;

+ 3 - 4
front/project/www/routes/paper/process/sentence/index.js

@@ -303,11 +303,10 @@ export default class extends Component {
   }
 
   renderAnswer() {
-    const { mode, question, userQuestion } = this.props;
-    const { analysisTab, showAnswer } = this.state;
+    const { mode } = this.props;
+    const { analysisTab, userQuestion, question, showAnswer, stem } = this.state;
     const { userAnswer = {} } = userQuestion;
     const { answer } = question;
-    const { stem } = question;
     return (
       <div className="layout-body">
         {mode === 'question' ? (
@@ -322,7 +321,7 @@ export default class extends Component {
         ) : (<div className="title">
           <Icon name="question" />
           请分别找出句子中的主语,谓语和宾语,并做出逻辑关系判断。
-          </div>)}
+        </div>)}
 
         <div className="desc" dangerouslySetInnerHTML={{ __html: stem }} />
         <div className="label">主语</div>

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

@@ -19,7 +19,7 @@ import AnswerTable from '../../../../components/AnswerTable';
 import OtherAnswer from '../../../../components/OtherAnswer';
 import { Textarea } from '../../../../components/Input';
 import { QuestionNoteModal } from '../../../../components/OtherModal';
-import { AskTarget, QrCode } from '../../../../../Constant';
+import { AskTarget, QrCodeAssets } from '../../../../../Constant';
 import { Question } from '../../../../stores/question';
 import { My } from '../../../../stores/my';
 import { User } from '../../../../stores/user';
@@ -738,7 +738,7 @@ export default class extends Component {
               </div>
             </div>
             <div className="right">
-              <Assets name={QrCode} width={150} height={150} />
+              <Assets name={QrCodeAssets} width={150} height={150} />
               <div className="text">扫码关注公众号</div>
               <div className="text">千行GMAT</div>
             </div>
@@ -772,7 +772,7 @@ export default class extends Component {
               <Link to="/">了解更多></Link>
             </div>
             <div className="right">
-              <Assets name={QrCode} width={150} height={150} />
+              <Assets name={QrCodeAssets} width={150} height={150} />
               <div className="text">扫码关注公众号</div>
               <div className="text">千行GMAT</div>
             </div>
@@ -880,7 +880,7 @@ export default class extends Component {
               <div className="text">您也可以关注公众号及时获取结果。</div>
             </div>
             <div className="right">
-              <Assets name={QrCode} width={150} height={150} />
+              <Assets name={QrCodeAssets} width={150} height={150} />
               <div className="text">扫码关注公众号</div>
               <div className="text">千行GMAT</div>
             </div>

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

@@ -153,7 +153,7 @@ function BarOption2(title, data, legend, color) {
     yAxis: {
       type: 'value',
       min: 0,
-      max: 300,
+      max: 100,
       axisLabel: { color: '#686872' },
       axisLine: { lineStyle: { color: '#D1D6DF' } },
     },
@@ -897,7 +897,7 @@ export default class extends Page {
               <PieChart option={pieOption1('正确率', detail.info.userCorrect, detail.info.userNumber, detail.info.totalCorrect, detail.info.totalNumber)} />
             </div>
             <div className="block">
-              <BarChart option={barOption1('用时', detail.info.userTime / detail.info.userNumber, detail.info.totalTime / detail.info.totalNumber, detail.info.correctTime, detail.info.incorrectTime)} />
+              <BarChart option={barOption1('用时', detail.info.userTime / detail.info.userNumber, detail.info.totalTime / detail.info.totalNumber, detail.info.correctTime / detail.info.userCorrect, detail.info.incorrectTime / (detail.info.userNumber - detail.info.userCorrect))} />
             </div>
           </div>
         </div>
@@ -1024,7 +1024,7 @@ export default class extends Page {
               <PieChart option={pieOption1('正确率', info.userCorrect, info.userNumber, info.totalCorrect, info.totalNumber)} />
             </div>
             <div className="block">
-              <BarChart option={barOption1('用时', info.userTime / info.userNumber, info.totalTime / info.totalNumber, info.correctTime, info.incorrectTime)} />
+              <BarChart option={barOption1('用时', info.userTime / info.userNumber, info.totalTime / info.totalNumber, detail.info.correctTime / detail.info.userCorrect, detail.info.incorrectTime / (detail.info.userNumber - detail.info.userCorrect))} />
             </div>
           </div>
         </div>
@@ -1169,8 +1169,8 @@ export default class extends Page {
               this.setState({ tab: value });
             }}
           />
+          {!subjectDetail && <div className="title">完成情况</div>}
           {!subjectDetail && <div className="list">
-            <div className="title">完成情况</div>
             <div className="detail">
               <div className="block">
                 <div className="t1" />
@@ -1191,15 +1191,15 @@ export default class extends Page {
                 </div>}
               </div>
               <div className="block">
-                <div className="t1">超出建议用时</div>
+                <div className="t1">剩余时间</div>
                 {subject.verbal && <div className="t1">
-                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.verbal.info.userTime > subject.verbal.info.time ? subject.verbal.info.userTime - subject.verbal.info.time : 0).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
+                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.verbal.info.questionNumber === subject.verbal.info.userNumber && subject.verbal.info.time > subject.verbal.info.userTime ? subject.verbal.info.time - subject.verbal.info.userTime : 0).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
                 </div>}
                 {subject.quant && <div className="t1">
-                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.quant.info.userTime > subject.quant.info.time ? subject.quant.info.userTime - subject.quant.info.time : 0).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
+                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.quant.info.questionNumber === subject.quant.info.userNumber && subject.quant.info.time > subject.quant.info.userTime ? subject.quant.info.time - subject.quant.info.userTime : 0).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
                 </div>}
                 {subject.ir && <div className="t1">
-                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.ir.info.userTime > subject.ir.info.time ? subject.ir.info.userTime - subject.ir.info.time : 0).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
+                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.ir.info.questionNumber === subject.ir.info.userNumber && subject.ir.info.time > subject.ir.info.userTime ? subject.ir.info.time - subject.ir.info.userTime : 0).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
                 </div>}
               </div>
               <div className="block">
@@ -1287,7 +1287,7 @@ export default class extends Page {
       </div>
       {!subjectDetail && <div className="body gray">
         <div className="content">
-          <div className="title">成绩单</div>
+          <div className="title">整体表现</div>
           <table>
             <thead>
               <tr>
@@ -1451,7 +1451,7 @@ export default class extends Page {
   renderExaminationScore() {
     const { report = {} } = this.state;
     const { score, detail } = report;
-    const { subject } = detail;
+    const { subject, info } = detail;
     return <div className="body">
       <div className="content">
         <div className="title">成绩单</div>
@@ -1469,8 +1469,8 @@ export default class extends Page {
               if (!subject[row.value]) return null;
               return <tr>
                 <td>{row.long}</td>
-                <td>{row.ignore ? '--' : score[`${row.value}Score`]}</td>
-                <td>{row.ignore ? '--' : score[`${row.value}Rank`]}</td>
+                <td>{row.ignore || !info.cat ? '--' : score[`${row.value}Score`]}</td>
+                <td>{row.ignore || !info.cat ? '--' : score[`${row.value}Rank`]}</td>
                 <td><Button size="small" radius onClick={() => {
                   Question.getDetailByNo(report.id, 1, row.value).then((r) => {
                     openLink(`/paper/question/${r.id}`);

+ 6 - 0
front/project/www/routes/question/search/index.less

@@ -96,5 +96,11 @@
       height: 50px;
       border-bottom: 1px solid #eee;
     }
+
+    .search-content {
+      span {
+        color: #1890ff !important;
+      }
+    }
   }
 }

+ 10 - 1
front/project/www/routes/question/search/page.js

@@ -77,6 +77,15 @@ export default class extends Page {
           );
         }
         handler.then(result => {
+          const list = (this.state.search.keyword || '').split(' ').filter(row => row && row.length > 1);
+          result.list = result.list.map(row => {
+            if (list.length > 0 && row.question.description) {
+              for (let i = 0; i < list.length; i += 1) {
+                row.question.description = row.question.description.split(list[i]).join(`<span>${list[i]}</span>`);
+              }
+            }
+            return row;
+          });
           this.setState({ list: result.list, total: result.total, page: data.page, searchResult: !!data.keyword });
         });
       });
@@ -378,7 +387,7 @@ class SearchItem extends Component {
             <span>收藏 {data.collectNumber || 0}</span>
           </div>
         </div>
-        <div className="t-1 p-20" ><Typography.Paragraph ellipsis={{ rows: 3, expandable: false }}>{data.question.description}</Typography.Paragraph></div>
+        <div className="t-1 p-20" ><Typography.Paragraph ellipsis={{ rows: 3, expandable: false }}><div className="search-content" dangerouslySetInnerHTML={{ __html: data.question.description }} /></Typography.Paragraph></div>
       </div>
     );
   }

+ 1 - 1
front/project/www/routes/sentence/read/index.less

@@ -42,7 +42,7 @@
           position: absolute;
           left: 0;
           top: 0;
-          width: 960px;
+          width: 920px;
         }
       }
 

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

@@ -350,7 +350,7 @@ export function formatSeconds(seconds, rand = false) {
 
 export function formatPercent(child, mother, number = true) {
   if (!mother || !child) return number ? 0 : '0%';
-  return number ? Math.floor((child * 100) / mother) : `${Math.floor((child * 100) / mother)}%`;
+  return number ? Math.round((child * 100) / mother) : `${Math.round((child * 100) / mother)}%`;
 }
 
 export function formatTreeData(list, key = 'id', title = 'title', index = 'parent_id') {

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

@@ -29,6 +29,7 @@ public enum PaperModule {
             case TEXTBOOK:
                 return TEXTBOOK;
             case EXERCISE:
+            case PREVIEW:
                 return EXERCISE;
             case EXAMINATION:
                 return EXAMINATION;

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

@@ -21,7 +21,7 @@ public enum RecordSource {
     }
 
     public static RecordSource ValueOf(String name){
-        if (name == "") return null;
+        if (name == null || name == "") return null;
         return RecordSource.valueOf(name.toUpperCase());
     }
 

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

@@ -13,6 +13,7 @@ public interface PreviewPaperRelationMapper {
             @Param("courseModule") String courseModule,
             @Param("questionType") String questionType,
             @Param("structId") Integer structId,
+            @Param("courseId") Integer courseId,
             @Param("order") String order,
             @Param("direction") String direction
     );

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

@@ -63,6 +63,13 @@ public interface QuestionNoRelationMapper {
             @Param("size") Number size
     );
 
+    List<QuestionNo> nextQuestion(
+            @Param("structId") Number structId,
+            @Param("targetTypes") Collection targetTypes,
+            @Param("prevNo") Integer prevNo,
+            @Param("size") Number size
+    );
+
     List<QuestionDifficultRelation> allExaminationByType(
             @Param("structId") Number structId,
             @Param("targetTypes") Collection targetTypes

+ 1 - 1
server/data/src/main/java/com/qxgmat/data/relation/UserAskCourseRelationMapper.java

@@ -34,6 +34,6 @@ public interface UserAskCourseRelationMapper {
 
     void accumulation(
             @Param("id") Number id,
-            @Param("view") int view
+            @Param("viewNumber") int view
     );
 }

+ 1 - 1
server/data/src/main/java/com/qxgmat/data/relation/UserQuestionRelationMapper.java

@@ -39,7 +39,7 @@ public interface UserQuestionRelationMapper {
 
     List<UserQuestion> listLast(
             @Param("userId") Number userId,
-            @Param("questionIds") Collection questionIds
+            @Param("questionNoIds") Collection questionNoIds
     );
 
     List<UserRecordStatRelation> stat(

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

@@ -22,6 +22,9 @@
     <if test="structId != null">
       and (c.`struct_id` = #{structId,jdbcType=VARCHAR} or c.`parent_struct_id` = #{structId, jdbcType=VARCHAR} )
     </if>
+    <if test="courseId != null">
+      and c.`id` = #{courseId,jdbcType=VARCHAR}
+    </if>
     where c.`id` > 0
     <if test="courseModule != null">
       and pp.`course_module` = #{courseModule,jdbcType=VARCHAR}
@@ -29,6 +32,9 @@
     <if test="questionType != null">
       and pp.`question_type` = #{questionType,jdbcType=VARCHAR}
     </if>
+    <if test="structId != null or courseId != null">
+      and c.`id` is not null
+    </if>
     order by ${order} ${direction}
   </select>
 </mapper>

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

@@ -179,6 +179,32 @@
   </select>
 
   <!--
+    下一题
+  -->
+  <select id="nextQuestion" resultMap="IdMap">
+    select
+    <include refid="Id_Column_List" />
+    from `question_no` qn
+    left join `question` q on q.`id` = qn.`question_id`
+    where
+    q.id &gt; 0 and qn.question_id &gt; 0 and qn.`delete_time` is null
+    and qn.`module` = 'examination'
+    <if test="structId != null">
+      and find_in_set(#{structId,jdbcType=VARCHAR}, qn.`module_struct`)
+    </if>
+    <if test="targetTypes != null and targetTypes.size() > 0">
+      and q.`question_type` IN
+      <foreach collection="targetTypes" item="item" index="index" open="(" separator="," close=")">
+        #{item}
+      </foreach>
+    </if>
+    <if test="prevNo != null">
+      and qn.`no` > #{prevNo,jdbcType=INTEGER}
+    </if>
+    order by qn.`no` asc
+    limit #{size,jdbcType=VARCHAR}
+  </select>
+  <!--
     模考题目列表
   -->
   <select id="allExaminationByType" resultMap="DifficultMap">
@@ -257,7 +283,7 @@
     from `question_no` qn
     left join `question` q on q.`id` = qn.`question_id`
     <if test="paperId != null">
-      left join `examination_paper` ep on find_in_set(qn.`id`, trim(TRAILING ']' from trim(LEADING '[' from ep.`question_no_ids`)))
+      left join `examination_paper` ep on find_in_set(ep.`struct_three`, qn.`module_struct`)
       and ep.`id` = #{paperId,jdbcType=VARCHAR}
     </if>
     where q.`id` &gt; 0 and qn.`question_id` &gt; 0 and qn.`delete_time` is null

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

@@ -38,6 +38,7 @@
     select
     <include refid="Id_Column_List" />
     from `textbook_question` tq
+    left join `question` q on q.`id` = tq.`question_id`
     <if test="paperId != null">
       left join `textbook_paper` tp on find_in_set(tq.`id`, tp.`question_no_ids`)
       and tp.`id` = #{paperId,jdbcType=VARCHAR}
@@ -53,7 +54,7 @@
       and tq.`question_no_id` =#{questionNoId,jdbcType=VARCHAR}
     </if>
     <if test="questionType != null">
-      and tq.`question_type` =#{questionType,jdbcType=VARCHAR}
+      and q.`question_type` =#{questionType,jdbcType=VARCHAR}
     </if>
     order by ${order} ${direction}
   </select>

+ 17 - 15
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserNoteQuestionRelationMapper.xml

@@ -41,27 +41,28 @@
     left join `sentence_question` sq on sq.`question_id` = q.`id`
     and (q.`question_module` = 'sentence')
     left join `user_question` uq on uq.`question_id` = unq.`question_id`
-    left join `user_paper` up on up.`id` = uq.`paper_id`
-    left join `preview_assign` pa on up.`paper_origin` = 'preview' and pa.`id` = up.`origin_id`
+    left join `user_report` ur on ur.`id` = uq.`report_id`
+    left join `preview_assign` pa on ur.`paper_origin` = 'preview' and pa.`id` = ur.`origin_id` and ur.`paper_origin` = 'preview'
     <if test="courseModules != null">
       and pa.`course_module` in
       <foreach collection="courseModules" item="item" index="index" open="(" close=")" separator=",">
         #{item}
       </foreach>
     </if>
-    left join `preview_paper` pp on pa.`paper_id` = pp.`id`
-    <if test="questionTypes != null">
-      and
-      <foreach collection="questionTypes" item="item" index="index" open="(" close=")" separator=" or ">
-        pp.`question_type` = #{item}
-      </foreach>
-    </if>
+<!--    left join `preview_paper` pp on pa.`paper_id` = pp.`id`-->
+<!--    <if test="questionTypes != null">-->
+<!--      and-->
+<!--      <foreach collection="questionTypes" item="item" index="index" open="(" close=")" separator=" or ">-->
+<!--        pp.`question_type` = #{item}-->
+<!--      </foreach>-->
+<!--    </if>-->
     where
     q.`id` > 0 and unq.`user_id` = #{userId,jdbcType=VARCHAR}
     <if test="keyword != null">
       and (q.`stem` like #{keywordLike,jdbcType=VARCHAR}
       or qn.`title` like #{keywordLike,jdbcType=VARCHAR}
-      or sq.`title` like #{keywordLike,jdbcType=VARCHAR})
+      or sq.`title` like #{keywordLike,jdbcType=VARCHAR}
+      or unq.`question_content` like #{keywordLike,jdbcType=VARCHAR})
     </if>
     <if test="structIds != null">
       and qn.`id` > 0
@@ -73,10 +74,10 @@
       and (qn.`id` > 0 or sq.`id` > 0)
     </if>
     <if test="startTime != null">
-      and unq.`create_time` &gt; #{startTime,jdbcType=TIMESTAMP}
+      and unq.`update_time` &gt; #{startTime,jdbcType=TIMESTAMP}
     </if>
     <if test="endTime != null">
-      and unq.`create_time` &lt; #{endTime,jdbcType=TIMESTAMP}
+      and unq.`update_time` &lt; #{endTime,jdbcType=TIMESTAMP}
     </if>
     <if test="order != null">
       order by ${order}
@@ -120,7 +121,8 @@
     <if test="keyword != null">
       and (q.`stem` like #{keywordLike,jdbcType=VARCHAR}
       or qn.`title` like #{keywordLike,jdbcType=VARCHAR}
-      or tq.`title` like #{keywordLike,jdbcType=VARCHAR})
+      or tq.`title` like #{keywordLike,jdbcType=VARCHAR}
+      or unq.`question_content` like #{keywordLike,jdbcType=VARCHAR})
     </if>
     <if test="structIds != null">
       and qn.`id` > 0
@@ -135,10 +137,10 @@
       and (tq.`id` > 0)
     </if>
     <if test="startTime != null">
-      and unq.`create_time` &gt; #{startTime,jdbcType=TIMESTAMP}
+      and unq.`update_time` &gt; #{startTime,jdbcType=TIMESTAMP}
     </if>
     <if test="endTime != null">
-      and unq.`create_time` &lt; #{endTime,jdbcType=TIMESTAMP}
+      and unq.`update_time` &lt; #{endTime,jdbcType=TIMESTAMP}
     </if>
     <if test="order != null">
       order by ${order}

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

@@ -62,23 +62,22 @@
       and upq.`question_no_id` = qn.`id`
       and upq.`question_module` = uq.`question_module`
       and upq.`question_origin` = 'remove_error'
-    left join `user_paper` up on up.`id` = uq.`paper_id`
-    left join `preview_assign` pa on up.`paper_origin` = 'preview' and pa.`id` = up.`origin_id`
+    left join `preview_assign` pa on ur.`paper_origin` = 'preview' and pa.`id` = ur.`origin_id` and ur.`paper_origin`='preview'
     <if test="courseModules != null">
       and pa.`course_module` in
       <foreach collection="courseModules" item="item" index="index" open="(" close=")" separator=",">
         #{item}
       </foreach>
     </if>
-    left join `preview_paper` pp on pa.`paper_id` = pp.`id`
-    <if test="questionTypes != null">
-      and
-      <foreach collection="questionTypes" item="item" index="index" open="(" close=")" separator=" or ">
-        pp.`question_type` = #{item}
-      </foreach>
-    </if>
+<!--    left join `preview_paper` pp on pa.`paper_id` = pp.`id`-->
+<!--    <if test="questionTypes != null">-->
+<!--      and-->
+<!--      <foreach collection="questionTypes" item="item" index="index" open="(" close=")" separator=" or ">-->
+<!--        pp.`question_type` = #{item}-->
+<!--      </foreach>-->
+<!--    </if>-->
     where
-    q.`id` > 0 and qn.`id` > 0 and upq.`id` is null and uq.`user_id` = #{userId,jdbcType=VARCHAR}
+    q.`id` > 0 and upq.`id` is null and uq.`user_id` = #{userId,jdbcType=VARCHAR} and uq.`user_time` > 0
     <if test="keyword != null">
       and (q.`stem` like #{keywordLike,jdbcType=VARCHAR}
       or qn.`title` like #{keywordLike,jdbcType=VARCHAR}
@@ -147,7 +146,7 @@
       and upq.`question_module` = uq.`question_module`
       and upq.`question_origin` = 'remove_error'
     where
-    q.`id` > 0 and qn.`id` > 0 and upq.`id` = null and uq.`user_id` = #{userId,jdbcType=VARCHAR}
+    q.`id` > 0 and upq.`id` is null and uq.`user_id` = #{userId,jdbcType=VARCHAR} and uq.`user_time` > 0
     <if test="keyword != null">
       and (q.`stem` like #{keywordLike,jdbcType=VARCHAR}
       or qn.`title` like #{keywordLike,jdbcType=VARCHAR}
@@ -186,11 +185,11 @@
     <!--SUBSTRING_INDEX(GROUP_CONCAT(ur.`id` ORDER BY ur.`create_time` desc),',',1) as `id`-->
     max(uq.`id`) as `id`
     from `user_question` uq
-    where uq.`user_id` = #{userId,jdbcType=VARCHAR} and uq.`question_id` IN
-    <foreach collection="questionIds" item="item" index="index" open="(" close=")" separator=",">
+    where uq.`user_id` = #{userId,jdbcType=VARCHAR} and uq.`question_no_id` IN
+    <foreach collection="questionNoIds" item="item" index="index" open="(" close=")" separator=",">
       #{item}
     </foreach>
-    group by uq.`question_id`
+    group by uq.`question_no_id`
   </select>
 
   <!--

+ 2 - 0
server/data/src/main/resources/db/migration/V1__init_table.sql

@@ -143,6 +143,7 @@ CREATE TABLE course_experience (
   nickname varchar(255) DEFAULT NULL COMMENT '用户昵称',
   title varchar(255) NOT NULL DEFAULT '' COMMENT '题目',
   content text COMMENT '正文',
+  description text COMMENT '简介',
   link varchar(255) NOT NULL DEFAULT '' COMMENT '问卷链接',
   prepare_status varchar(255) NOT NULL DEFAULT '' COMMENT '备考身份',
   experience_day int(11) unsigned NOT NULL DEFAULT '0' COMMENT '备考周期: 天',
@@ -1051,6 +1052,7 @@ CREATE TABLE user_ask_course (
   manager_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '回答人id',
   show_status tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '展示状态:0关闭,1打开',
   answer_time datetime DEFAULT NULL COMMENT '回答时间',
+  view_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '查看人数',
   sort int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序:从大到小',
   create_time datetime DEFAULT NULL,
   update_time datetime DEFAULT NULL,

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

@@ -441,7 +441,7 @@ public class CourseController {
         entity = courseNoService.addNo(entity);
         // 统计课时数
         CourseNoStatRelation relation = courseNoService.statCourse(dto.getCourseId());
-        courseService.edit(Course.builder().id(dto.getCourseId()).noNumber(relation.getNumber()).time(relation.getNumber()).build());
+        courseService.edit(Course.builder().id(dto.getCourseId()).noNumber(relation.getNumber()).time(relation.getTime()).build());
         managerLogService.log(request);
         return ResponseHelp.success(Transform.convert(entity, CourseNo.class));
     }
@@ -453,7 +453,7 @@ public class CourseController {
         CourseNo entity = Transform.dtoToEntity(dto);
         entity = courseNoService.editNo(entity);// 统计课时数
         CourseNoStatRelation relation = courseNoService.statCourse(dto.getCourseId());
-        courseService.edit(Course.builder().id(dto.getCourseId()).noNumber(relation.getNumber()).time(relation.getNumber()).build());
+        courseService.edit(Course.builder().id(dto.getCourseId()).noNumber(relation.getNumber()).time(relation.getTime()).build());
 
         managerLogService.log(request);
         return ResponseHelp.success(true);
@@ -834,14 +834,14 @@ public class CourseController {
         List<UserAskCourseListDto> pr = Transform.convert(p, UserAskCourseListDto.class);
 
         // 绑定用户
-        Collection userIds = Transform.getIds(p, UserAskQuestion.class, "userId");
+        Collection userIds = Transform.getIds(p, UserAskCourse.class, "userId");
         List<User> userList = usersService.select(userIds);
-        Transform.combine(pr, userList, UserAskQuestionListDto.class, "userId", "user", User.class, "id", UserExtendDto.class);
+        Transform.combine(pr, userList, UserAskCourseListDto.class, "userId", "user", User.class, "id", UserExtendDto.class);
 
         // 绑定管理员
-        Collection managerIds = Transform.getIds(p, UserAskQuestion.class, "managerId");
+        Collection managerIds = Transform.getIds(p, UserAskCourse.class, "managerId");
         List<Manager> managerList = managerService.select(managerIds);
-        Transform.combine(pr, managerList, UserAskQuestionListDto.class, "managerId", "manager", Manager.class, "id", ManagerExtendDto.class);
+        Transform.combine(pr, managerList, UserAskCourseListDto.class, "managerId", "manager", Manager.class, "id", ManagerExtendDto.class);
 
         // 绑定课程
         Collection courseIds = Transform.getIds(p, UserAskCourse.class, "courseId");

+ 3 - 1
server/gateway-api/src/main/java/com/qxgmat/controller/admin/PreviewController.java

@@ -113,10 +113,11 @@ public class PreviewController {
             @RequestParam(required = false) String courseModule,
             @RequestParam(required = false) String questionType,
             @RequestParam(required = false) Integer structId,
+            @RequestParam(required = false) Integer courseId,
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<PreviewPaper> p = previewPaperService.listAdmin(page, size, CourseModule.ValueOf(courseModule), QuestionType.ValueOf(questionType), structId, order, DirectionStatus.ValueOf(direction));
+        Page<PreviewPaper> p = previewPaperService.listAdmin(page, size, CourseModule.ValueOf(courseModule), QuestionType.ValueOf(questionType), structId, courseId, order, DirectionStatus.ValueOf(direction));
         List<PreviewPaperListDto> pr = Transform.convert(p, PreviewPaperListDto.class);
 
         // 绑定用户
@@ -133,6 +134,7 @@ public class PreviewController {
         PreviewAssign entity = Transform.dtoToEntity(dto);
         PreviewPaper previewPaper = previewPaperService.get(entity.getPaperId());
         entity.setCourseId(previewPaper.getCourseId());
+        entity.setCourseModule(previewPaper.getCourseModule());
         previewAssignService.add(entity);
         managerLogService.log(request);
         return ResponseHelp.success(entity);

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/controller/admin/QuestionController.java

@@ -99,7 +99,7 @@ public class QuestionController {
         // 更新编号绑定
         questionNoService.bindQuestion(dto.getQuestionNoIds(), entity.getId());
         // 更新编号关联
-        questionNoService.relationQuestion(dto.getRelationQuestion());
+        if (dto.getRelationQuestion() != null) questionNoService.relationQuestion(dto.getRelationQuestion());
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }

+ 5 - 4
server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java

@@ -399,12 +399,13 @@ public class UserController {
             @RequestParam(required = false) String productType,
             @RequestParam(required = false) Integer productId,
             @RequestParam(required = false) String service,
+            @RequestParam(required = false) String source,
             @RequestParam(required = false) Boolean needMoney,
             @RequestParam(required = false) Boolean needPackage,
             @RequestParam(required = false) String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<UserOrderRecord> p = userOrderRecordService.listAdmin(page, size, orderId, userId, ProductType.ValueOf(productType), productId, ServiceKey.ValueOf(service), needMoney,needPackage, order, DirectionStatus.ValueOf(direction));
+        Page<UserOrderRecord> p = userOrderRecordService.listAdmin(page, size, orderId, userId, ProductType.ValueOf(productType), productId, ServiceKey.ValueOf(service), RecordSource.ValueOf(source),needMoney,needPackage, order, DirectionStatus.ValueOf(direction));
         List<UserOrderRecordListDto> pr = Transform.convert(p, UserOrderRecordListDto.class);
 
         // 绑定用户
@@ -439,7 +440,7 @@ public class UserController {
             User user = usersService.getByMobile(dto.getArea(), dto.getMobile());
             if (user == null){
                 // 创建user
-                user = usersService.register(dto.getArea(), dto.getMobile(), null,null, null,"", null);
+                user = usersService.register(dto.getArea(), dto.getMobile(), null,null, null,"", null, true);
             }
             entity.setUserId(user.getId());
         }
@@ -461,7 +462,7 @@ public class UserController {
             User user = usersService.getByMobile(dto.getArea(), dto.getMobile());
             if (user == null){
                 // 创建user
-                user = usersService.register(dto.getArea(), dto.getMobile(), null,null, null,"", null);
+                user = usersService.register(dto.getArea(), dto.getMobile(), null,null, null,"", null, true);
             }
             entity.setUserId(user.getId());
         }
@@ -482,7 +483,7 @@ public class UserController {
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction
     ){
-        Page<UserOrderRecord> p = userOrderRecordService.listWithServiceAdmin(page, size, ServiceKey.ValueOf(service), param, userId);
+        Page<UserOrderRecord> p = userOrderRecordService.listWithServiceAdmin(page, size, ServiceKey.ValueOf(service), param, userId, order, DirectionStatus.ValueOf(direction));
         List<UserServiceRecordInfoDto> pr = Transform.convert(p, UserServiceRecordInfoDto.class);
 
         // 绑定用户

+ 18 - 5
server/gateway-api/src/main/java/com/qxgmat/controller/api/AuthController.java

@@ -30,6 +30,7 @@ import com.qxgmat.service.inline.UserMessageService;
 import com.qxgmat.service.inline.UserOrderRecordService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.apache.shiro.authc.UnknownAccountException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
 import org.springframework.validation.annotation.Validated;
@@ -124,8 +125,12 @@ public class AuthController {
         }
         try {
             String ip = Tools.getClientIp(request);
-            usersService.register(userLoginDto.getArea(), userLoginDto.getMobile(), userLoginDto.getInviteCode(), userLoginDto.getEmail(), null, ip, aiHelp.parseIp(ip));
-        }catch (ParameterException e){
+            usersService.register(userLoginDto.getArea(), userLoginDto.getMobile(), userLoginDto.getInviteCode(), userLoginDto.getEmail(), null, ip, aiHelp.parseIp(ip), false);
+        }
+        catch( UnknownAccountException e){
+            throw new ParameterException(e.getMessage());
+        }
+        catch (ParameterException e){
             // 忽略已注册信息
         }
         shiroHelp.getSession().login(shiroHelp.user(userLoginDto.getArea()+":"+userLoginDto.getMobile(), "", true));
@@ -152,7 +157,11 @@ public class AuthController {
                 // 登录成功绑定二维码
                 usersService.Oauth(user, code, "wechat_pc", true);
             }
-        }catch (Exception e){
+        }
+        catch( UnknownAccountException e){
+            throw new ParameterException(e.getMessage());
+        }
+        catch (Exception e){
             throw new ParameterException("登录失败", e);
         }
         User openUser = (User) shiroHelp.getLoginUser();
@@ -176,7 +185,11 @@ public class AuthController {
             // userInfo:false,尝试登录
             // userInfo:true,第一次登录,尝试授权,等待绑定手机号
             shiroHelp.getSession().login(shiroHelp.oauth(code, "wechat_native", userInfo));
-        }catch (Exception e){
+        }
+        catch( UnknownAccountException e){
+            throw new ParameterException(e.getMessage());
+        }
+        catch (Exception e){
             throw new ParameterException("登录失败",e);
         }
         User openUser = (User) shiroHelp.getLoginUser();
@@ -215,7 +228,7 @@ public class AuthController {
         try{
             // 创建新的账号,设定手机号,绑定第三方登录
             String ip = Tools.getClientIp(request);
-            User user = usersService.register(userValidMobileDto.getArea(), userValidMobileDto.getMobile(), userValidMobileDto.getInviteCode(), userValidMobileDto.getEmail(), openUser, ip, aiHelp.parseIp(ip));
+            User user = usersService.register(userValidMobileDto.getArea(), userValidMobileDto.getMobile(), userValidMobileDto.getInviteCode(), userValidMobileDto.getEmail(), openUser, ip, aiHelp.parseIp(ip), false);
         }catch (ParameterException e){
             throw new ParameterException("该手机号绑定其他账号,请更换手机号码!");
         }

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

@@ -207,7 +207,7 @@ public class CourseController {
 
         // 课时
         List<CourseNo> courseNoList = courseNoService.allCourse(course.getId());
-        courseExtendService.refreshNoResource(user, course.getId(), courseNoList);
+        courseExtendService.refreshNoResource(user, course, courseNoList);
         dto.setCourseNos(Transform.convert(courseNoList, CourseNoExtendDto.class));
 
         // 评论
@@ -301,7 +301,7 @@ public class CourseController {
 
         // 课时
         List<CourseNo> courseNoList = courseNoService.allCourse(course.getId());
-        courseExtendService.refreshNoResource(user, course.getId(), courseNoList);
+        courseExtendService.refreshNoResource(user, course, courseNoList);
         dto.setCourseNos(Transform.convert(courseNoList, CourseNoExtendDto.class));
 
         return ResponseHelp.success(dto);

+ 10 - 6
server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java

@@ -23,6 +23,7 @@ import com.qxgmat.dto.request.FaqDto;
 import com.qxgmat.dto.request.UserCollectQuestionDto;
 import com.qxgmat.dto.request.UserNoteQuestionDto;
 import com.qxgmat.dto.response.*;
+import com.qxgmat.dto.response.UserExportDto;
 import com.qxgmat.help.*;
 import com.qxgmat.service.*;
 import com.qxgmat.service.extend.*;
@@ -1019,9 +1020,9 @@ public class MyController {
         Transform.combine(pr, stats, UserCollectQuestionInfoDto.class, "questionId", "stat");
 
         // 最近做题
-        List<UserQuestion> lastList = userQuestionService.listWithLast(user.getId(), questionIds);
-        Map lastMap = Transform.getMap(lastList, UserQuestion.class, "questionId", "createTime");
-        Transform.combine(pr, lastMap, UserCollectQuestionInfoDto.class, "questionId", "latestTime");
+        List<UserQuestion> lastList = userQuestionService.listWithLast(user.getId(), questionNoIds);
+        Map lastMap = Transform.getMap(lastList, UserQuestion.class, "questionNoId", "createTime");
+        Transform.combine(pr, lastMap, UserCollectQuestionInfoDto.class, "questionNoId", "latestTime");
 
         // 收藏、笔记
         List<UserCollectQuestion> userCollectQuestionList = userCollectQuestionService.listByUserAndQuestions(user.getId(), questionIds);
@@ -1090,9 +1091,9 @@ public class MyController {
         Transform.combine(pr, stats, UserQuestionErrorInfoDto.class, "questionId", "stat");
 
         // 最近做题
-        List<UserQuestion> lastList = userQuestionService.listWithLast(user.getId(), questionIds);
-        Map lastMap = Transform.getMap(lastList, UserQuestion.class, "questionId", "createTime");
-        Transform.combine(pr, lastMap, UserQuestionErrorInfoDto.class, "questionId", "latestTime");
+        List<UserQuestion> lastList = userQuestionService.listWithLast(user.getId(), questionNoIds);
+        Map lastMap = Transform.getMap(lastList, UserQuestion.class, "questionNoId", "createTime");
+        Transform.combine(pr, lastMap, UserQuestionErrorInfoDto.class, "questionNoId", "latestTime");
 
         // 收藏、笔记
         List<UserCollectQuestion> userCollectQuestionList = userCollectQuestionService.listByUserAndQuestions(user.getId(), questionIds);
@@ -2116,6 +2117,9 @@ public class MyController {
         if (!user.getId().equals(entity.getUserId())){
             throw new ParameterException("记录不存在");
         }
+        UserExportDto dto = Transform.convert(entity, UserExportDto.class);
+        // 签名信息:同pdfHelp
+        dto.setWaters(pdfHelp.getUserWater(user));
         return ResponseHelp.success(entity);
     }
 

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

@@ -753,7 +753,7 @@ public class QuestionController {
         }
         // 判断是否是cat模考,并且有模考权限
         ExaminationPaper examinationPaper = examinationPaperService.get(dto.getPaperId());
-        if(examinationService.isCat(examinationPaper)){
+        if(examinationService.isQxCat(examinationPaper)){
             UserService userService = userServiceService.getService(user.getId(), ServiceKey.QX_CAT);
             if (userService== null){
                 throw new ParameterException("请先开通模考");
@@ -781,6 +781,22 @@ public class QuestionController {
         return ResponseHelp.success(Transform.convert(report, UserReportBaseDto.class));
     }
 
+    @RequestMapping(value = "/preview/paper", method = RequestMethod.GET)
+    @ApiOperation(value = "开始: 预习作业", notes = "提交考试设置", httpMethod = "POST")
+    public Response<PaperBaseDto> startPreview(
+            @RequestParam(required = true) Integer paperId
+    )  {
+        User user = (User) shiroHelp.getLoginUser();
+        if (user == null) {
+            throw new AuthException("请先登录");
+        }
+        UserPaper paper = questionFlowService.paper(user.getId(), PaperOrigin.PREVIEW, paperId);
+
+        PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
+
+        return ResponseHelp.success(paperDto);
+    }
+
     @RequestMapping(value = "/preview/start", method = RequestMethod.POST)
     @ApiOperation(value = "开始: 预习作业", notes = "提交考试设置", httpMethod = "POST")
     public Response<UserReportBaseDto> startPreview(@RequestBody @Validated PreviewStartDto dto)  {

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

@@ -20,12 +20,18 @@ public class UserAskCourseListDto {
 
     private String position;
 
+    private Integer userId;
+
     private UserExtendDto user;
 
     private ManagerExtendDto manager;
 
+    private Integer courseId;
+
     private CourseExtendDto course;
 
+    private Integer courseNoId;
+
     private CourseNoExtendDto courseNo;
 
     private Integer askTime;
@@ -38,6 +44,8 @@ public class UserAskCourseListDto {
 
     private Date answerTime;
 
+    private Date createTime;
+
     public Integer getId() {
         return id;
     }
@@ -141,4 +149,36 @@ public class UserAskCourseListDto {
     public void setExpireTime(Date expireTime) {
         this.expireTime = expireTime;
     }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public Integer getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Integer courseId) {
+        this.courseId = courseId;
+    }
+
+    public Integer getCourseNoId() {
+        return courseNoId;
+    }
+
+    public void setCourseNoId(Integer courseNoId) {
+        this.courseNoId = courseNoId;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
 }

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

@@ -22,6 +22,8 @@ public class UserListDto {
 
     private Integer realStatus;
 
+    private String wechatUnionid;
+
     private String prepareStatus;
 
     private Integer inviteNumber;
@@ -86,6 +88,14 @@ public class UserListDto {
         this.inviteNumber = inviteNumber;
     }
 
+    public String getWechatUnionid() {
+        return wechatUnionid;
+    }
+
+    public void setWechatUnionid(String wechatUnionid) {
+        this.wechatUnionid = wechatUnionid;
+    }
+
     public String getPrepareStatus() {
         return prepareStatus;
     }

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

@@ -16,6 +16,8 @@ public class CourseNoExtendDto {
 
     private String title;
 
+    private String resource;
+
     private Integer time;
 
     private Integer askNumber;
@@ -56,6 +58,14 @@ public class CourseNoExtendDto {
         this.title = title;
     }
 
+    public String getResource() {
+        return resource;
+    }
+
+    public void setResource(String resource) {
+        this.resource = resource;
+    }
+
     public Integer getAskNumber() {
         return askNumber;
     }

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

@@ -67,6 +67,10 @@ public class CourseListDto {
 
     private Integer askNumber;
 
+    private Integer trailNumber;
+
+    private Integer saleNumber;
+
     private Collection<CommentExtendDto> comments;
 
     public Integer getId() {
@@ -308,4 +312,20 @@ public class CourseListDto {
     public void setComment(String comment) {
         this.comment = comment;
     }
+
+    public Integer getTrailNumber() {
+        return trailNumber;
+    }
+
+    public void setTrailNumber(Integer trailNumber) {
+        this.trailNumber = trailNumber;
+    }
+
+    public Integer getSaleNumber() {
+        return saleNumber;
+    }
+
+    public void setSaleNumber(Integer saleNumber) {
+        this.saleNumber = saleNumber;
+    }
 }

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

@@ -0,0 +1,91 @@
+package com.qxgmat.dto.response;
+
+import com.alibaba.fastjson.JSONObject;
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.Comment;
+import com.qxgmat.data.dao.entity.UserExport;
+
+import java.util.Date;
+
+@Dto(entity = UserExport.class)
+public class UserExportDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private Integer no;
+
+    private Integer type;
+
+    private JSONObject setting;
+
+    private JSONObject content;
+
+    private Date createTime;
+
+    private String[] waters;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public JSONObject getSetting() {
+        return setting;
+    }
+
+    public void setSetting(JSONObject setting) {
+        this.setting = setting;
+    }
+
+    public JSONObject getContent() {
+        return content;
+    }
+
+    public void setContent(JSONObject content) {
+        this.content = content;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    public Integer getNo() {
+        return no;
+    }
+
+    public void setNo(Integer no) {
+        this.no = no;
+    }
+
+    public String[] getWaters() {
+        return waters;
+    }
+
+    public void setWaters(String[] waters) {
+        this.waters = waters;
+    }
+}

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

@@ -41,6 +41,8 @@ public class UserNoteQuestionInfoDto {
 
     private Date qaTime;
 
+    private Date updateTime;
+
     public Integer getId() {
         return id;
     }
@@ -168,4 +170,12 @@ public class UserNoteQuestionInfoDto {
     public void setQuestion(QuestionExtendDto question) {
         this.question = question;
     }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
 }

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

@@ -15,6 +15,8 @@ public class UserQuestionBaseDto {
 
     private Integer no;
 
+    private Integer stageNo;
+
     private String questionModule;
 
     private String questionType;
@@ -160,4 +162,12 @@ public class UserQuestionBaseDto {
     public void setUserTime(Integer userTime) {
         this.userTime = userTime;
     }
+
+    public Integer getStageNo() {
+        return stageNo;
+    }
+
+    public void setStageNo(Integer stageNo) {
+        this.stageNo = stageNo;
+    }
 }

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

@@ -193,7 +193,7 @@ public class UsersService extends AbstractService {
      * @return
      */
     @Transactional
-    public User register(String area, String mobile, String inviteCode, String email, User openUser, String registerIp, String[] registerInfo){
+    public User register(String area, String mobile, String inviteCode, String email, User openUser, String registerIp, String[] registerInfo, boolean admin){
         boolean n = false;
         User user = getByMobile(area, mobile);
         if (user != null){
@@ -232,7 +232,7 @@ public class UsersService extends AbstractService {
         if (openUser != null){
             this.bind(user, openUser);
         }
-        if (n){
+        if (n && !admin){
             user = add(user);
             messageExtendService.sendRegister(user);
         }else{

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

@@ -269,14 +269,14 @@ public class CourseExtendService {
     /**
      * 根据用户权限更新资源信息
      * @param user
-     * @param courseId
+     * @param course
      * @param courseNoList
      */
-    public void refreshNoResource(User user, Integer courseId, List<CourseNo> courseNoList){
+    public void refreshNoResource(User user, Course course, List<CourseNo> courseNoList){
         if (user != null){
-            if (userCourseService.getCourse(user.getId(), courseId) == null){
+            if (userCourseService.getCourse(user.getId(), course.getId()) == null){
                 for(CourseNo courseNo : courseNoList){
-                    courseNo.setResource(courseNo.getTrailResource());
+                    courseNo.setResource(courseNo.getIsTrail() > 0 ? courseNo.getTrailResource() :"");
                 }
             }else{
                 for(CourseNo courseNo : courseNoList){
@@ -285,7 +285,7 @@ public class CourseExtendService {
             }
         }else{
             for(CourseNo courseNo : courseNoList){
-                courseNo.setResource(courseNo.getTrailResource());
+                courseNo.setResource(courseNo.getIsTrail() > 0 ? courseNo.getTrailResource() :"");
             }
         }
     }

+ 7 - 2
server/gateway-api/src/main/java/com/qxgmat/service/extend/ExaminationService.java

@@ -292,11 +292,16 @@ public class ExaminationService extends AbstractService {
         throw new SystemException("没有找到cat模考节点");
     }
 
-    public boolean isCat(ExaminationPaper paper){
+    public boolean isQxCat(ExaminationPaper paper){
         ExaminationStruct struct = examinationStructService.get(paper.getStructTwo());
         return struct.getExtend().equals(ServiceKey.QX_CAT.key);
     }
 
+    public boolean isCat(ExaminationPaper paper){
+        ExaminationStruct struct = examinationStructService.get(paper.getStructTwo());
+        return struct.getExtend().equals(ServiceKey.QX_CAT.key) || struct.getExtend().equals("cat");
+    }
+
     /**
      * 获取下一阶段难度分
      * @param currentLevel
@@ -582,7 +587,7 @@ public class ExaminationService extends AbstractService {
                 list.add(target);
                 indexSet.add(index);
             }
-        }while(list.size() == size);
+        }while(list.size() < size);
         return list;
     }
 

+ 2 - 3
server/gateway-api/src/main/java/com/qxgmat/service/extend/ExerciseService.java

@@ -168,10 +168,10 @@ public class ExerciseService extends AbstractService {
         int min = 0;
         int max = 0;
         for(QuestionNo question : questionList){
-            tmp.add(question.getId());
-            if (tmp.size() == 0){
+            if (tmp.size() == 0 || min == 0){
                 min = question.getNo();
             }
+            tmp.add(question.getId());
             max = question.getNo();
             if (tmp.size() == length){
                 no += 1;
@@ -212,7 +212,6 @@ public class ExerciseService extends AbstractService {
             }else{
                 paper.setTitle(exercisePaperService.generateTitle(prefixTitle, length, paper.getNo(), paper.getQuestionNumber()));
             }
-            paper.setTitle(exercisePaperService.generateTitle(prefixTitle, length, paper.getNo(), paper.getQuestionNumber()));
             paper = exercisePaperService.add(paper);
             list.add(paper);
         }

+ 2 - 2
server/gateway-api/src/main/java/com/qxgmat/service/extend/ExportService.java

@@ -238,12 +238,12 @@ public class ExportService {
 
     private List<UserQuestionRelation> listByQuestionNoIds(Integer userId, Collection questionNoIds, boolean needAnswer){
         List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
-        Collection questionIds = Transform.getIds(questionNoList, QuestionNo.class, "questionId");
+        Collection questionIds = Transform.getIds(questionNoList, QuestionNo.class, "id");
 
         List<UserQuestion> userQuestionList = null;
         if (needAnswer){
             // 查询所有id的最后一次做题记录
-            userQuestionList = userQuestionService.listWithLast(userId, questionIds);
+            userQuestionList = userQuestionService.listWithLast(userId, questionNoIds);
         }else{
             userQuestionList = new ArrayList<>();
             for(Object questionNoId : questionNoIds){

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

@@ -875,7 +875,7 @@ public class OrderFlowService {
         }
 
         // 计算购物车数量
-        return userOrderCheckoutService.allByUser(userId, 0).size();
+        return userOrderCheckoutService.allByUserBase(userId, 0).size();
     }
 
     /**
@@ -899,7 +899,7 @@ public class OrderFlowService {
         }
 
         // 计算购物车数量
-        return userOrderCheckoutService.allByUser(userId, 0).size();
+        return userOrderCheckoutService.allByUserBase(userId, 0).size();
     }
 
 
@@ -937,7 +937,7 @@ public class OrderFlowService {
         callback.callback(in, list);
 
         // 计算购物车数量
-        return userOrderCheckoutService.allByUser(userId, 0).size();
+        return userOrderCheckoutService.allByUserBase(userId, 0).size();
     }
 
     /**
@@ -1093,8 +1093,7 @@ public class OrderFlowService {
             throw new ParameterException("已停用");
         }
 
-        UseRecord callback = useRecordCallback.get(ProductType.ValueOf(in.getProductType()));
-        in = callback.callback(in);
+        in.setIsStop(1);
         userOrderRecordService.edit(in);
     }
 

+ 46 - 11
server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java

@@ -165,7 +165,7 @@ public class QuestionFlowService {
                     userPaper.setTitle(assign.getTitle());
             }
             // 新paper绑定当前记录关系
-            if (userPaper.getRecordId() == 0){
+            if (userPaper.getRecordId() ==null || userPaper.getRecordId() == 0){
                 // 获取用户当前课程记录
                 Integer recordId = previewService.getRecord(userPaper.getUserId(), id);
                 userPaper.setRecordId(recordId);
@@ -319,8 +319,15 @@ public class QuestionFlowService {
         });
         nextCallback.put(PaperModule.EXAMINATION, (question, report, lastQuestion)->{
             ExaminationPaper paper = examinationPaperService.get(report.getOriginId());
-            if (!examinationCompute(paper, report, lastQuestion, question)){
-                return false;
+            if (paper.getIsAdapt() > 0){
+                if (!examinationCompute(paper, report, lastQuestion, question)){
+                    return false;
+                }
+            }else{
+                // 固定顺序出题
+                if (!examinationCompute(paper, report, lastQuestion, question)){
+                    return false;
+                }
             }
 
             // 保存report: 更新setting
@@ -917,8 +924,10 @@ public class QuestionFlowService {
                 default:
                     throw new ParameterException("模考出题流程错误:"+subject.key+"不支持适应性判断");
             }
-        }else{
+        }else if (paper.getIsAdapt() > 0){
             questionNoId = randomCompute(subject, paper, questionTypes, setting, subnumber, ids, userQuestionList);
+        }else{
+            questionNoId = fixOrderCompute(subject, paper, questionTypes, setting, subnumber, ids, userQuestionList);
         }
         if (questionNoId ==null || questionNoId == 0) {
             throw new ParameterException("模考出题流程错误:题目生成错误");
@@ -946,7 +955,7 @@ public class QuestionFlowService {
         // 一共分为4个阶段:每个阶段9题,包含一题阅读
         // 其中句改SC有14题,阅读RC有13题(共4篇阅读,每篇题数为3、3、3、4),逻辑CR为9题
         // verbal: { "steps": [{"ids": [], "level": 0}, {}] }
-        List questionNoIds = new ArrayList<>();
+        List<Integer> questionNoIds = new ArrayList<>();
         JSONObject verbal = setting.getJSONObject("verbal");
         if (verbal == null) {
             verbal = new JSONObject();
@@ -955,7 +964,7 @@ public class QuestionFlowService {
         }
         JSONArray steps = verbal.getJSONArray("steps");
         // 判断当前是第几阶段
-        Integer step = subnumber / examinationService.verbalPre + 1;
+        Integer step = subnumber / examinationService.verbalPre;
         Integer questionIndex = 0;
         JSONObject info;
         if (subnumber == 0){
@@ -977,7 +986,11 @@ public class QuestionFlowService {
             questionIndex = subnumber % examinationService.verbalPre;
         }
         //获取下一题
-        questionNoIds = info.getJSONArray("ids").toJavaObject(questionNoIds.getClass());
+        questionNoIds.clear();
+        JSONArray lastIds = info.getJSONArray("ids");
+        for(int i = 0; i<lastIds.size(); i++){
+            questionNoIds.add(lastIds.getInteger(i));
+        }
         return (Integer) questionNoIds.get(questionIndex);
     }
 
@@ -993,7 +1006,7 @@ public class QuestionFlowService {
         // 一共分为4个阶段:每个阶段题分别为8、8、8、7题,合计31题
         // 其中数学PS有17题,数学DS有14题
         // quant: { "steps": [{"ids": [], "level": 0}, {}] }
-        List questionNoIds = new ArrayList<>();
+        List<Integer> questionNoIds = new ArrayList<>();
         JSONObject quant = setting.getJSONObject("quant");
         if (quant == null) {
             quant = new JSONObject();
@@ -1025,7 +1038,11 @@ public class QuestionFlowService {
             questionIndex = subnumber - examinationService.quantNumber[step];
         }
         //获取下一题
-        questionNoIds = info.getJSONArray("ids").toJavaObject(questionNoIds.getClass());
+        questionNoIds.clear();
+        JSONArray lastIds = info.getJSONArray("ids");
+        for(int i = 0; i<lastIds.size(); i++){
+            questionNoIds.add(lastIds.getInteger(i));
+        }
         return (Integer) questionNoIds.get(questionIndex);
     }
 
@@ -1077,6 +1094,17 @@ public class QuestionFlowService {
         return questionNoService.randomExamination(paper.getStructThree(), targetTypes, ids);
     }
 
+    public Integer fixOrderCompute(QuestionSubject subject, ExaminationPaper paper, List<String> questionTypes, JSONObject setting, Integer subnumber, Collection ids, List<UserQuestion> subQuestionList){
+        List<String> targetTypes = QuestionType.FromSubject(subject);
+        Integer prevNo = null;
+        if (subQuestionList != null && subQuestionList.size() > 0){
+            Integer questionNoId = subQuestionList.get(subQuestionList.size() - 1).getQuestionNoId();
+            QuestionNo questionNo = questionNoService.get(questionNoId);
+            prevNo = questionNo.getNo();
+        }
+        return questionNoService.nextQuestion(paper.getStructThree(), targetTypes, prevNo);
+    }
+
     /**
      * 获取报告题目列表
      * @param userReportId
@@ -1160,12 +1188,15 @@ public class QuestionFlowService {
      * @return
      */
     private Boolean baseAnswer(JSONObject userAnswer, JSONObject answer, Question question){
+        if (userAnswer == null) return false;
         String type = question.getContent().getString("type");
         QuestionContentType contentType = QuestionContentType.ValueOf(type);
         JSONArray userQuestions = userAnswer.getJSONArray("questions");
+        if (userQuestions == null) return false;
         JSONArray questions = answer.getJSONArray("questions");
         for(int i = 0; i< questions.size(); i++){
             JSONObject userOne = userQuestions.getJSONObject(i);
+            if (userOne == null) return false;
             JSONObject one = questions.getJSONObject(i);
             switch(contentType){
                 case DOUBLE:
@@ -1263,6 +1294,9 @@ public class QuestionFlowService {
         }
         JSONObject answer = question.getAnswer();
         JSONObject userAnswer = userQuestion.getUserAnswer();
+        if (userAnswer == null){
+            return;
+        }
 
         String type = question.getContent().getString("type");
         QuestionContentType contentType = QuestionContentType.ValueOf(type);
@@ -1641,6 +1675,7 @@ public class QuestionFlowService {
         }
         Map<Number, QuestionNoRelation> relationMap = questionNoService.mapWithRelationByIds(tmpIds);
 
+        ExaminationPaper examinationPaper = examinationPaperService.get(report.getOriginId());
         // report
         JSONObject detail = new JSONObject();
         JSONObject score = new JSONObject();
@@ -1866,6 +1901,7 @@ public class QuestionFlowService {
         info.put("questionNumber", report.getQuestionNumber());
         info.put("userNumber", report.getUserNumber());
         info.put("userCorrect", report.getUserCorrect());
+        info.put("cat", examinationService.isCat(examinationPaper));
         detail.put("info", info);
 
         report.setDetail(detail);
@@ -1874,8 +1910,7 @@ public class QuestionFlowService {
         // 统计
         UserService userService = null;
         // 判断是否是cat模考:记录到第二次
-        ExaminationPaper examinationPaper = examinationPaperService.get(report.getOriginId());
-        if(examinationService.isCat(examinationPaper)){
+        if(examinationService.isQxCat(examinationPaper)){
             userService = userServiceService.getService(report.getUserId(), ServiceKey.QX_CAT);
         }
         examinationPaperService.accumulation(report, userService != null && userService.getIsReset() > 0);

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

@@ -48,7 +48,7 @@ public class PreviewPaperService extends AbstractService {
         put("", "pp");
     }};
 
-    public Page<PreviewPaper> listAdmin(int page, int size, CourseModule courseModule, QuestionType questionType, Integer structId, String order, DirectionStatus direction){
+    public Page<PreviewPaper> listAdmin(int page, int size, CourseModule courseModule, QuestionType questionType, Integer structId, Integer courseId, String order, DirectionStatus direction){
         if(order == null || order.isEmpty()){
             order = "id";
         }
@@ -63,7 +63,7 @@ public class PreviewPaperService extends AbstractService {
         String finalOrder = order;
         DirectionStatus finalDirection = direction;
         Page<PreviewPaper> p = page(() -> {
-            previewPaperRelationMapper.listAdmin(courseModule != null ? courseModule.key : null, questionType != null ? questionType.key : null, structId, finalOrder, finalDirection.key);
+            previewPaperRelationMapper.listAdmin(courseModule != null ? courseModule.key : null, questionType != null ? questionType.key : null, structId, courseId, finalOrder, finalDirection.key);
         }, page, size);
 
         Collection ids = Transform.getIds(p, PreviewPaper.class, "id");

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

@@ -303,6 +303,22 @@ public class QuestionNoService extends AbstractService {
     }
 
     /**
+     * 按顺序获取下一题
+     * @param structId
+     * @param targetTypes
+     * @param prevNo
+     * @return
+     */
+    public Integer nextQuestion(Integer structId, Collection targetTypes, Integer prevNo){
+        List<QuestionNo> questionNoList = questionNoRelationMapper.nextQuestion(structId, targetTypes, prevNo == null ? 0 : prevNo, 1);
+        if (questionNoList.size() > 0){
+            return questionNoList.get(0).getId();
+        }else{
+            return null;
+        }
+    }
+
+    /**
      * 随机批量获取对应模块下的试题:排除已做试题
      * @param structId
      * @param targetTypes
@@ -545,8 +561,10 @@ public class QuestionNoService extends AbstractService {
     @Transactional
     public QuestionNo add(QuestionNo question){
         List<String> list = new ArrayList<>();
-        for (Integer item : question.getModuleStruct()) {
-            list.add(String.valueOf(item));
+        if (question.getModuleStruct() != null){
+            for (Integer item : question.getModuleStruct()) {
+                list.add(String.valueOf(item));
+            }
         }
         QuestionNo in = getByNo(question.getTitle(), question.getModule(), String.join(",", list));
         if (in != null){

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

@@ -40,10 +40,10 @@ public class SentenceCodeService extends AbstractService {
         );
         SentenceCode entity = one(sentenceCodeMapper, example);
         if (entity==null){
-            throw new ParameterException("您输入的code有误,请重新数据");
+            throw new ParameterException("您输入的code有误,请重新输入");
         }
         if (entity.getUserId() > 0){
-            throw new ParameterException("您输入的code有误,请重新数据");
+            throw new ParameterException("您输入的code有误,请重新输入");
         }
         entity.setUserId(userId);
         edit(entity);

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

@@ -86,7 +86,7 @@ public class TextbookPaperService extends AbstractService {
      * @return
      */
     public Page<TextbookPaper> listAdmin(int page, int size, String keyword){
-        Example example = new Example(TextbookLibrary.class);
+        Example example = new Example(TextbookPaper.class);
         if(keyword != null)
             example.and(
                     example.createCriteria()

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

@@ -62,7 +62,7 @@ public class UserCourseService extends AbstractService {
      */
     public boolean hasService(Integer userId, Integer courseId){
         if (courseId == null) return false;
-        Example example = new Example(UserService.class);
+        Example example = new Example(UserCourse.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)

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

@@ -43,6 +43,16 @@ public class UserOrderCheckoutService extends AbstractService {
         return select(userOrderCheckoutMapper, example);
     }
 
+    public List<UserOrderCheckout> allByUserBase(Integer userId, Integer orderId){
+        Example example = new Example(UserOrderCheckout.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("orderId", orderId)
+                        .andEqualTo("parentId", 0)
+        );
+        return select(userOrderCheckoutMapper, example);
+    }
 
     /**
      * 列出对应类型的购物信息

+ 18 - 3
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java

@@ -12,6 +12,7 @@ import com.qxgmat.data.constants.enums.module.CourseModule;
 import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.module.VsCourseType;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
+import com.qxgmat.data.constants.enums.trade.RecordSource;
 import com.qxgmat.data.dao.UserOrderRecordMapper;
 import com.qxgmat.data.dao.entity.UserOrder;
 import com.qxgmat.data.dao.entity.UserOrderRecord;
@@ -339,7 +340,7 @@ public class UserOrderRecordService extends AbstractService {
         return add(course);
     }
 
-    public Page<UserOrderRecord> listAdmin(int page, int size, Integer orderId, Integer userId, ProductType productType, Integer productId, ServiceKey service, Boolean needMoney, Boolean needPackage, String order, DirectionStatus direction){
+    public Page<UserOrderRecord> listAdmin(int page, int size, Integer orderId, Integer userId, ProductType productType, Integer productId, ServiceKey service, RecordSource source, Boolean needMoney, Boolean needPackage, String order, DirectionStatus direction){
         Example example = new Example(UserOrderRecord.class);
         if(orderId != null){
             example.and(
@@ -353,6 +354,12 @@ public class UserOrderRecordService extends AbstractService {
                             .andEqualTo("userId", userId)
             );
         }
+        if (source != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("source", source.key)
+            );
+        }
         if(needMoney != null){
             example.and(
                     needMoney ?
@@ -514,7 +521,7 @@ public class UserOrderRecordService extends AbstractService {
      * @param userId
      * @return
      */
-    public Page<UserOrderRecord> listWithServiceAdmin(int page, int size, ServiceKey service, String param, Integer userId){
+    public Page<UserOrderRecord> listWithServiceAdmin(int page, int size, ServiceKey service, String param, Integer userId, String order, DirectionStatus direction){
         Example example = new Example(UserOrderRecord.class);
         example.and(
                 example.createCriteria().andEqualTo("productType", ProductType.SERVICE.key)
@@ -534,7 +541,15 @@ public class UserOrderRecordService extends AbstractService {
                     example.createCriteria().andEqualTo("userId", userId)
             );
         }
-
+        if(order == null || order.isEmpty()) order = "id";
+        switch(direction){
+            case ASC:
+                example.orderBy(order).asc();
+                break;
+            case DESC:
+            default:
+                example.orderBy(order).desc();
+        }
         return select(userOrderRecordMapper, example, page, size);
     }
 

+ 3 - 0
server/gateway-api/src/main/java/com/qxgmat/util/shiro/OauthRealm.java

@@ -33,6 +33,9 @@ public class OauthRealm extends AuthorizingRealm {
         String platform = new String(token.getPassword());
 
         User user = usersService.Oauth(null, code, platform, userInfo);
+        if (user.getIsFrozen() > 0){
+            throw new UnknownAccountException("账号异常,请练习管理员!");
+        }
         return new SimpleAuthenticationInfo(user, platform, getName());
     }
 }

+ 3 - 0
server/gateway-api/src/main/java/com/qxgmat/util/shiro/UserRealm.java

@@ -56,6 +56,9 @@ public class UserRealm extends AuthorizingRealm {
         if (user == null || user.getId() <= 0) {
             throw new UnknownAccountException("用户不存在!");
         }
+        if (user.getIsFrozen() > 0){
+            throw new UnknownAccountException("账号异常,请练习管理员!");
+        }
 //        if(!usersService.equalsPassword(user, password)){
 //            throw new IncorrectCredentialsException("用户名或密码错误!");
 //        }