Browse Source

Merge branch 'master' of www.gitinn.com:zaixianjiaoyu/sourcecode

KaysonCui 5 years ago
parent
commit
3daf7d1bec
100 changed files with 4506 additions and 2314 deletions
  1. 40 5
      front/project/Constant.js
  2. 40 32
      front/project/admin/routes/course/experienceDetail/page.js
  3. 9 8
      front/project/admin/routes/course/preview/page.js
  4. 10 1
      front/project/admin/routes/course/previewDetail/page.js
  5. 2 2
      front/project/admin/routes/course/student/page.js
  6. 42 19
      front/project/admin/routes/interaction/askQuestion/page.js
  7. 5 1
      front/project/admin/routes/interaction/askQuestionDetail/page.js
  8. 99 136
      front/project/admin/routes/interaction/comment/page.js
  9. 84 53
      front/project/admin/routes/interaction/faq/page.js
  10. 120 43
      front/project/admin/routes/interaction/feedback/page.js
  11. 89 105
      front/project/admin/routes/show/comment/page.js
  12. 54 336
      front/project/admin/routes/show/deploy/page.js
  13. 65 45
      front/project/admin/routes/show/faq/page.js
  14. 0 16
      front/project/admin/routes/show/readDetail/index.js
  15. 0 3
      front/project/admin/routes/show/readDetail/index.less
  16. 0 169
      front/project/admin/routes/show/readDetail/page.js
  17. 7 6
      front/project/admin/routes/show/tips/page.js
  18. 24 18
      front/project/admin/routes/student/askCourse/page.js
  19. 42 19
      front/project/admin/routes/student/askQuestion/page.js
  20. 6 2
      front/project/admin/routes/student/askQuestionDetail/page.js
  21. 42 29
      front/project/admin/routes/student/studyDetail/page.js
  22. 1 3
      front/project/admin/routes/subject/textbookQuestion/page.js
  23. 81 12
      front/project/admin/routes/textbook/library/page.js
  24. 142 1
      front/project/admin/routes/user/order/page.js
  25. 106 0
      front/project/admin/routes/user/orderDetail/page.js
  26. 34 39
      front/project/admin/routes/user/recordAll/page.js
  27. 23 33
      front/project/admin/routes/user/recordBuy/page.js
  28. 1 1
      front/project/admin/stores/system.js
  29. 4 0
      front/project/admin/stores/textbook.js
  30. 9 1
      front/project/admin/stores/user.js
  31. 11 9
      front/project/h5/stores/my.js
  32. 4 0
      front/project/www/components/ListTable/index.less
  33. 1 0
      front/project/www/components/Select/index.js
  34. 2 1
      front/project/www/routes/examination/index.js
  35. 5 0
      front/project/www/routes/examination/list/index.less
  36. 432 220
      front/project/www/routes/examination/list/page.js
  37. 15 103
      front/project/www/routes/examination/main/page.js
  38. 4 0
      front/project/www/routes/exercise/list/index.less
  39. 106 102
      front/project/www/routes/exercise/list/page.js
  40. 217 221
      front/project/www/routes/exercise/main/page.js
  41. 9 10
      front/project/www/routes/paper/question/page.js
  42. 10 0
      front/project/www/routes/question/detail/index.js
  43. 472 0
      front/project/www/routes/question/detail/index.less
  44. 648 0
      front/project/www/routes/question/detail/page.js
  45. 3 0
      front/project/www/routes/question/index.js
  46. 2 2
      front/project/www/routes/textbook/list/index.js
  47. 16 0
      front/project/www/routes/textbook/list/index.less
  48. 170 227
      front/project/www/routes/textbook/list/page.js
  49. 3 2
      front/project/www/static/login.html
  50. 4 0
      front/project/www/stores/course.js
  51. 23 0
      front/project/www/stores/main.js
  52. 95 22
      front/project/www/stores/my.js
  53. 22 4
      front/project/www/stores/question.js
  54. 6 2
      front/project/www/stores/textbook.js
  55. 3 1
      front/src/layouts/FormLayout/index.js
  56. 1 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/logic/TextbookLogic.java
  57. 37 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/AskModule.java
  58. 0 18
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/FaqModule.java
  59. 18 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/InsideModule.java
  60. 175 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/ExaminationPaper.java
  61. 0 70
      server/data/src/main/java/com/qxgmat/data/dao/entity/Faq.java
  62. 70 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/PreviewPaper.java
  63. 61 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/Question.java
  64. 12 12
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookLibraryHistory.java
  65. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserAskCourse.java
  66. 86 16
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserAskQuestion.java
  67. 0 35
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserCourseAppointment.java
  68. 105 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserFeedbackError.java
  69. 75 49
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserNoteQuestion.java
  70. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrder.java
  71. 53 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrderRecord.java
  72. 7 7
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserQuestion.java
  73. 7 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/ExaminationPaperMapper.xml
  74. 2 5
      server/data/src/main/java/com/qxgmat/data/dao/mapping/FaqMapper.xml
  75. 4 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/PreviewPaperMapper.xml
  76. 6 4
      server/data/src/main/java/com/qxgmat/data/dao/mapping/QuestionMapper.xml
  77. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookLibraryHistoryMapper.xml
  78. 4 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserAskCourseMapper.xml
  79. 6 4
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserAskQuestionMapper.xml
  80. 1 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserCourseAppointmentMapper.xml
  81. 5 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserFeedbackErrorMapper.xml
  82. 6 4
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserNoteQuestionMapper.xml
  83. 3 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderMapper.xml
  84. 3 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderRecordMapper.xml
  85. 24 0
      server/data/src/main/java/com/qxgmat/data/relation/CommentRelationMapper.java
  86. 9 0
      server/data/src/main/java/com/qxgmat/data/relation/CourseExperienceRelationMapper.java
  87. 9 0
      server/data/src/main/java/com/qxgmat/data/relation/ExaminationPaperRelationMapper.java
  88. 25 0
      server/data/src/main/java/com/qxgmat/data/relation/FaqRelationMapper.java
  89. 1 0
      server/data/src/main/java/com/qxgmat/data/relation/PreviewPaperRelationMapper.java
  90. 5 0
      server/data/src/main/java/com/qxgmat/data/relation/QuestionRelationMapper.java
  91. 1 0
      server/data/src/main/java/com/qxgmat/data/relation/TextbookPaperRelationMapper.java
  92. 25 1
      server/data/src/main/java/com/qxgmat/data/relation/UserAskQuestionRelationMapper.java
  93. 17 5
      server/data/src/main/java/com/qxgmat/data/relation/UserCollectQuestionRelationMapper.java
  94. 6 0
      server/data/src/main/java/com/qxgmat/data/relation/UserCourseRecordRelationMapper.java
  95. 3 0
      server/data/src/main/java/com/qxgmat/data/relation/UserFeedbackErrorRelationMapper.java
  96. 16 5
      server/data/src/main/java/com/qxgmat/data/relation/UserNoteQuestionRelationMapper.java
  97. 25 0
      server/data/src/main/java/com/qxgmat/data/relation/UserOrderRelationMapper.java
  98. 33 1
      server/data/src/main/java/com/qxgmat/data/relation/UserPaperRelationMapper.java
  99. 24 0
      server/data/src/main/java/com/qxgmat/data/relation/UserQuestionRelationMapper.java
  100. 0 0
      server/data/src/main/java/com/qxgmat/data/relation/UserReportRelationMapper.java

File diff suppressed because it is too large
+ 40 - 5
front/project/Constant.js


+ 40 - 32
front/project/admin/routes/course/experienceDetail/page.js

@@ -100,41 +100,49 @@ export default class extends Page {
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='作者信息'>
           <Row>
-            <Col span={6}>
-              {getFieldDecorator('prepareStatus', {
-                rules: [{
-                  required: true, message: '请选择',
-                }],
-              })(
-                <Select select={PrepareStatus} placeholder='身份' />,
-              )}
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 14 }} label='身份'>
+                {getFieldDecorator('prepareStatus', {
+                  rules: [{
+                    required: true, message: '请选择',
+                  }],
+                })(
+                  <Select select={PrepareStatus} placeholder='身份' />,
+                )}
+              </Form.Item>
             </Col>
-            <Col span={6}>
-              {getFieldDecorator('experienceDay', {
-                rules: [{
-                  required: true, message: '请输入备考天数',
-                }],
-              })(
-                <InputNumber placeholder='备考周期: 天' />,
-              )}
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 14 }} label='备考周期'>
+                {getFieldDecorator('experienceDay', {
+                  rules: [{
+                    required: true, message: '请输入备考天数',
+                  }],
+                })(
+                  <InputNumber placeholder='备考周期: 天' />,
+                )}
+              </Form.Item>
             </Col>
-            <Col span={6}>
-              {getFieldDecorator('experienceScore', {
-                rules: [{
-                  required: true, message: '请选择',
-                }],
-              })(
-                <Select select={ExperienceScore} placeholder='分手成绩' />,
-              )}
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 14 }} label='分手成绩'>
+                {getFieldDecorator('experienceScore', {
+                  rules: [{
+                    required: true, message: '请选择',
+                  }],
+                })(
+                  <Select select={ExperienceScore} placeholder='分手成绩' />,
+                )}
+              </Form.Item>
             </Col>
-            <Col span={6}>
-              {getFieldDecorator('experiencePercent', {
-                rules: [{
-                  required: true, message: '请选择',
-                }],
-              })(
-                <Select select={ExperiencePercent} placeholder='提分范围' />,
-              )}
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 14 }} label='提分范围'>
+                {getFieldDecorator('experiencePercent', {
+                  rules: [{
+                    required: true, message: '请选择',
+                  }],
+                })(
+                  <Select select={ExperiencePercent} placeholder='提分范围' />,
+                )}
+              </Form.Item>
             </Col>
           </Row>
         </Form.Item>

+ 9 - 8
front/project/admin/routes/course/preview/page.js

@@ -9,12 +9,13 @@ import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
 import { getMap, formatTreeData, bindSearch } from '@src/services/Tools';
 import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
-import { CourseModule } from '../../../../Constant';
+import { CourseModule, QuestionType } from '../../../../Constant';
 import { Exercise } from '../../../stores/exercise';
 import { Course } from '../../../stores/course';
 import { Preview } from '../../../stores/preview';
 
 const CourseModuleMap = getMap(CourseModule, 'value', 'label');
+const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
 
 export default class extends Page {
   init() {
@@ -50,12 +51,12 @@ export default class extends Page {
       placeholder: '请选择',
       select: CourseModule,
     }, {
-      key: 'structId',
-      type: 'tree',
+      key: 'questionType',
+      type: 'select',
       allowClear: true,
-      name: '单项',
+      name: '题型',
       placeholder: '请选择',
-      select: [],
+      select: QuestionType,
     }, {
       key: 'courseId',
       type: 'select',
@@ -74,10 +75,10 @@ export default class extends Page {
       title: '课程名称',
       dataIndex: 'course.title',
     }, {
-      title: '单项',
-      dataIndex: 'course.structId',
+      title: '题型',
+      dataIndex: 'questionType',
       render: (text) => {
-        return this.exerciseMap[text] || '';
+        return QuestionTypeMap[text] || '';
       },
     }, {
       title: '作业标题',

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

@@ -6,7 +6,7 @@ import Block from '@src/components/Block';
 import Select from '@src/components/Select';
 import { formatFormError, bindSearch, formatDate, generateSearch } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
-// import { PreviewMode } from '../../../../Constant';
+import { QuestionType } from '../../../../Constant';
 import Association from '../../../components/Association';
 import { Preview } from '../../../stores/preview';
 import { Course } from '../../../stores/course';
@@ -221,6 +221,15 @@ export default class extends Page {
             <Select select={this.state.courseNo} disabled={data.id || !data.courseId} placeholder='请选择课时' />,
           )}
         </Form.Item>}
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题型'>
+          {getFieldDecorator('questionType', {
+            rules: [
+              { required: true, message: '请选择题型' },
+            ],
+          })(
+            <Select select={QuestionType} placeholder='请选择题型' />,
+          )}
+        </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='作业标题'>
           {getFieldDecorator('title', {
             rules: [

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

@@ -143,7 +143,7 @@ export default class extends Page {
       title: '学员名称',
       dataIndex: 'user.nickname',
       render: (text, record) => {
-        return `${text}(${record.mobile})`;
+        return `${text}(${record.user.mobile})`;
       },
     }, {
       title: '课时数',
@@ -293,7 +293,7 @@ export default class extends Page {
   addVsStudentAction() {
     const { id } = this.params;
     const { data } = this.state;
-    this.vsAddList[4].type = data.vsType === 'novice' ? 'hidden' : 'number';
+    this.vsAddList[3].type = data.vsType === 'novice' ? 'hidden' : 'number';
     asyncForm('添加', this.vsAddList, {}, info => {
       if (data.vsType === 'novice') {
         // 写死:新手每次1课时

+ 42 - 19
front/project/admin/routes/interaction/askQuestion/page.js

@@ -6,15 +6,16 @@ import Block from '@src/components/Block';
 import FilterLayout from '@src/layouts/FilterLayout';
 import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { getMap, bindSearch, formatDate } from '@src/services/Tools';
+import { getMap, bindSearch, formatDate, formatSeconds } from '@src/services/Tools';
 import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
-import { QuestionType, AskStatus, MoneyRange, SwitchSelect, AskTarget } from '../../../../Constant';
+import { QuestionType, AskStatus, MoneyRange, SwitchSelect, AskTarget, AskModule } from '../../../../Constant';
 import { User } from '../../../stores/user';
 import { Question } from '../../../stores/question';
 
 const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
 const AskStatusMap = getMap(AskStatus, 'value', 'label');
 const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
+const AskModuleMap = getMap(AskModule, 'value', 'label');
 export default class extends Page {
   init() {
     this.actionList = [{
@@ -24,6 +25,13 @@ export default class extends Page {
       needSelect: 1,
     }];
     this.filterForm = [{
+      key: 'askModule',
+      type: 'select',
+      allowClear: true,
+      name: '板块',
+      select: AskModule,
+      placeholder: '请选择',
+    }, {
       key: 'questionType',
       type: 'select',
       allowClear: true,
@@ -38,7 +46,7 @@ export default class extends Page {
       select: AskStatus,
       number: true,
     }, {
-      key: 'money',
+      key: 'moneyRang',
       type: 'select',
       allowClear: true,
       name: '消费金额',
@@ -75,34 +83,43 @@ export default class extends Page {
       placeholder: '请输入',
     }];
     this.columns = [{
+      title: '板块',
+      dataIndex: 'askModule',
+      render: (text) => {
+        return AskModuleMap[text];
+      },
+    }, {
       title: '题型',
       dataIndex: 'type',
       render: (text, record) => {
-        return QuestionTypeMap[record.question.type];
+        return QuestionTypeMap[record.question.questionType];
       },
-    },
-    {
+    }, {
       title: '题目id',
       dataIndex: 'questionNo.no',
-    },
-    {
+    }, {
+      title: '提问者',
+      dataIndex: 'user.nickname',
+    }, {
+      title: '消费金额',
+      dataIndex: 'user.totalMoney',
+    }, {
       title: '提问时间',
       dataIndex: 'createTime',
       render: (text) => {
         return formatDate(text);
       },
-    },
-    {
-      title: '提问摘要',
-      dataIndex: 'content',
     }, {
-      title: '提问者',
-      dataIndex: 'user.nickname',
-    }, {
-      title: '回答状态',
-      dataIndex: 'answerStatus',
-      render: (text) => {
-        return AskStatusMap[text] || text;
+      title: '倒计时',
+      dataIndex: 'askTime',
+      render: (text, record) => {
+        const end = new Date(record.answerTime) || new Date();
+        const cost = (end.getTime() - new Date(record.createTime).getTime()) / 1000;
+        if (text) {
+          if (text - cost > 0) return `${formatSeconds(text - cost)}/${formatSeconds(text)}`;
+          return `0/${formatSeconds(text)}`;
+        }
+        return '-';
       },
     }, {
       title: '回答者',
@@ -114,6 +131,12 @@ export default class extends Page {
         return text ? formatDate(text) : '';
       },
     }, {
+      title: '回答状态',
+      dataIndex: 'answerStatus',
+      render: (text) => {
+        return AskStatusMap[text] || text;
+      },
+    }, {
       title: '展示状态',
       dataIndex: 'showStatus',
       render: (text) => {

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

@@ -8,12 +8,13 @@ import DragList from '@src/components/DragList';
 // import FileUpload from '@src/components/FileUpload';
 import { formatFormError, formatDate, getMap } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
-import { UserUrl, AskTarget, QuestionType } from '../../../../Constant';
+import { UserUrl, AskTarget, QuestionType, AskModule } from '../../../../Constant';
 // import { User } from '../../../stores/user';
 import { Question } from '../../../stores/question';
 
 const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
 const AskTargetMap = getMap(AskTarget, 'value', 'label');
+const AskModuleMap = getMap(AskModule, 'value', 'label');
 export default class extends Page {
   init() {
     // Exercise.allStruct().then(result => {
@@ -88,6 +89,9 @@ export default class extends Page {
     return <Block>
       <h1>题目信息</h1>
       <Form>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='板块'>
+          {AskModuleMap[data.askModule]}
+        </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题型'>
           {QuestionTypeMap[question.type]}
         </Form.Item>

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

@@ -3,91 +3,44 @@ import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import FilterLayout from '@src/layouts/FilterLayout';
-import ActionLayout from '@src/layouts/ActionLayout';
+// import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { getMap, bindSearch, formatDate } from '@src/services/Tools';
+import { getMap, bindSearch, formatDate, formatTreeData } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
-import { ChannelModule, SwitchSelect } from '../../../../Constant';
+import { CommentChannel, SwitchSelect, MoneyRange } from '../../../../Constant';
 import { System } from '../../../stores/system';
 import { User } from '../../../stores/user';
+import { Course } from '../../../stores/course';
 
 const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
-const ChannelModuleMap = getMap(ChannelModule, 'value', 'label');
+const CommentChannelMap = getMap(CommentChannel, 'value', 'label');
 export default class extends Page {
   init() {
-    this.actionList = [{
-      key: 'add',
-      type: 'primary',
-      name: '创建',
-    }];
     this.itemList = [{
       key: 'id',
       type: 'hidden',
     }, {
-      key: 'channel',
-      type: 'select',
-      allowClear: true,
-      name: '频道',
-      select: ChannelModule,
-      placeholder: '请选择',
-    }, {
-      key: 'position',
-      type: 'select',
-      allowClear: true,
-      name: '位置',
-      select: ChannelModule,
-      placeholder: '请选择',
-    }, {
-      key: 'nickname',
-      type: 'input',
-      name: '学员昵称',
-    }, {
-      key: 'avatar',
-      type: 'image',
-      name: '学员头像',
-      onUpload: ({ file }) => {
-        return System.uploadImage(file).then(result => { return result; });
-      },
-    }, {
-      key: 'content',
-      type: 'textarea',
-      name: '评价内容',
-    }];
-    this.userItemList = [{
-      key: 'id',
-      type: 'hidden',
-    }, {
-      key: 'channel',
-      type: 'select',
-      allowClear: true,
-      name: '频道',
-      select: ChannelModule,
-      placeholder: '请选择',
-    }, {
-      key: 'position',
-      type: 'select',
-      allowClear: true,
-      name: '位置',
-      select: ChannelModule,
-      placeholder: '请选择',
-    }, {
       key: 'content',
       type: 'textarea',
       name: '评价内容',
     }];
+    this.filterF = null;
     this.filterForm = [{
       key: 'channel',
-      type: 'select',
+      type: 'cascader',
       allowClear: true,
       name: '频道',
-      select: ChannelModule,
+      select: formatTreeData(CommentChannel.filter(row => row.type !== 'manual'), 'value', 'label', 'parent'),
       placeholder: '请选择',
+      onChange: (value) => {
+        this.changeSearch(this.filterForm, this, value.join('-'), null);
+      },
     }, {
       key: 'position',
       type: 'select',
       allowClear: true,
       name: '位置',
-      select: ChannelModule,
+      select: [],
       placeholder: '请选择',
     }, {
       key: 'userId',
@@ -98,74 +51,73 @@ export default class extends Page {
       number: true,
       placeholder: '请输入',
     }, {
+      key: 'moneyRang',
+      type: 'select',
+      allowClear: true,
+      name: '消费金额',
+      select: MoneyRange,
+      number: true,
+    }, {
       key: 'isSpecial',
       type: 'select',
       allowClear: true,
-      name: '精选',
+      name: '展示',
       number: true,
       select: SwitchSelect,
     }];
-    this.columns = [
-      {
-        title: '频道',
-        dataIndex: 'channel',
-        render: (text, record) => {
-          return ChannelModuleMap[record.channel];
-        },
+    this.columns = [{
+      title: '频道',
+      dataIndex: 'channel',
+      render: (text, record) => {
+        return CommentChannelMap[record.channel];
       },
-      {
-        title: '位置',
-        dataIndex: 'position',
+    }, {
+      title: '位置',
+      dataIndex: 'position',
+    }, {
+      title: '用户',
+      dataIndex: 'user',
+      render: (text, record) => {
+        let extend = '';
+        if (record.isSystem) extend = '系统创建';
+        else if (!record.userId) extend = '未注册';
+        return `${text.nickname || record.nickname}${extend ? `(${extend})` : ''}`;
       },
-      {
-        title: '内容',
-        dataIndex: 'content',
+    }, {
+      title: '时间',
+      dataIndex: 'createTime',
+      render: (text) => {
+        return formatDate(text);
       },
-      {
-        title: '用户',
-        dataIndex: 'user',
-        render: (text, record) => {
-          let extend = '';
-          if (record.isSystem) extend = '系统创建';
-          else if (!record.userId) extend = '未注册';
-          return `${text.nickname || record.nickname}${extend ? `(${extend})` : ''}`;
-        },
-      }, {
-        title: '时间',
-        dataIndex: 'createTime',
-        render: (text) => {
-          return formatDate(text);
-        },
-      }, {
-        title: '精选',
-        dataIndex: 'isSpecial',
-        render: (text) => {
-          return SwitchSelectMap[text] || text;
-        },
-      }, {
-        title: '操作',
-        dataIndex: 'handler',
-        render: (text, record) => {
-          return <div className="table-button">
-            {(
-              <a onClick={() => {
-                this.editAction(record);
-              }}>编辑</a>
-            )}
-            {!!record.isSpecial && (
-              <a onClick={() => {
-                this.special(record, 0);
-              }}>取消精选</a>
-            )}
-            {!record.isSpecial && (
-              <a onClick={() => {
-                this.special(record, 1);
-              }}>精选</a>
-            )}
-          </div>;
-        },
+    }, {
+      title: '展示',
+      dataIndex: 'isSpecial',
+      render: (text) => {
+        return SwitchSelectMap[text] || text;
+      },
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          {(
+            <a onClick={() => {
+              this.editAction(record);
+            }}>编辑</a>
+          )}
+          {!!record.isSpecial && (
+            <a onClick={() => {
+              this.special(record, 0);
+            }}>取消展示</a>
+          )}
+          {!record.isSpecial && (
+            <a onClick={() => {
+              this.special(record, 1);
+            }}>展示</a>
+          )}
+        </div>;
       },
-    ];
+    }];
     bindSearch(this.filterForm, 'userId', this, (search) => {
       return User.list(search);
     }, (row) => {
@@ -174,29 +126,39 @@ export default class extends Page {
         value: row.id,
       };
     }, this.state.search.userId ? Number(this.state.search.userId) : null, null);
+    this.changeSearch(this.filterForm, this, this.state.search.channel, this.state.search.position);
+    this.state.search.channel = this.state.search.channel ? this.state.search.channel.split('-') : '';
   }
 
-  initData() {
-    System.listComment(this.state.search).then(result => {
-      this.setTableData(result.list, result.total);
-    });
+  changeSearch(list, component, key, value) {
+    if (key === 'course-video' || key === 'course-vs' || key === 'course_data') {
+      bindSearch(list, 'position', component, (search) => {
+        if (key === 'course-video') {
+          return Course.list(Object.assign({ courseModule: 'video' }, search));
+        } if (key === 'course-vs') {
+          return Course.list(Object.assign({ courseModule: 'vs' }, search));
+        }
+        return Course.listData(search);
+      }, (row) => {
+        return {
+          title: row.title,
+          value: row.id,
+        };
+      }, value ? Number(value) : null, null);
+      list[1].disabled = false;
+    } else {
+      list[1].disabled = true;
+    }
   }
 
-  addAction() {
-    asyncForm('创建评价', this.itemList, {}, data => {
-      return System.addComment(data).then(() => {
-        asyncSMessage('添加成功!');
-        this.refresh();
-      });
+  initData() {
+    System.listComment(Object.assign({ user: true }, this.state.search)).then(result => {
+      this.setTableData(result.list, result.total);
     });
   }
 
   editAction(row) {
-    let item = this.itemList;
-    if (row.userId) {
-      item = this.userItemList;
-    }
-    asyncForm('编辑', item, row, data => {
+    asyncForm('编辑', this.itemList, row, data => {
       return System.editComment(data).then(() => {
         asyncSMessage('编辑成功!');
         this.refresh();
@@ -205,7 +167,7 @@ export default class extends Page {
   }
 
   special(row, isSpecial) {
-    System.editComment({ id: row.id, isSpecial }).then(() => {
+    System.editComment({ id: row.id, isSpecial, isShow: isSpecial }).then(() => {
       asyncSMessage('操作成功!');
       this.refresh();
     });
@@ -215,18 +177,19 @@ export default class extends Page {
     return <Block flex>
       <FilterLayout
         show
+        ref={(ref) => { this.filterF = ref; }}
         itemList={this.filterForm}
         data={this.state.search}
         onChange={data => {
+          data.channel = data.channel.join('-');
           this.search(data);
         }} />
-      <ActionLayout
+      {/* <ActionLayout
         itemList={this.actionList}
         selectedKeys={this.state.selectedKeys}
         onAction={key => this.onAction(key)}
-      />
+      /> */}
       <TableLayout
-        select
         columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}

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

@@ -3,15 +3,17 @@ import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import FilterLayout from '@src/layouts/FilterLayout';
-import ActionLayout from '@src/layouts/ActionLayout';
+// import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { getMap, formatDate } from '@src/services/Tools';
+import { getMap, formatDate, formatTreeData, bindSearch } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
-import { ChannelModule, SwitchSelect, AskStatus } from '../../../../Constant';
+import { FaqChannel, SwitchSelect, AskStatus, MoneyRange } from '../../../../Constant';
 import { System } from '../../../stores/system';
+import { User } from '../../../stores/user';
+import { Course } from '../../../stores/course';
 
 const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
-const ChannelModuleMap = getMap(ChannelModule, 'value', 'label');
+const FaqChannelMap = getMap(FaqChannel, 'value', 'label');
 const AskStatusMap = getMap(AskStatus, 'value', 'label');
 export default class extends Page {
   init() {
@@ -20,20 +22,6 @@ export default class extends Page {
       key: 'id',
       type: 'hidden',
     }, {
-      key: 'channel',
-      type: 'select',
-      allowClear: true,
-      name: '频道',
-      select: ChannelModule,
-      placeholder: '请选择',
-    }, {
-      key: 'position',
-      type: 'select',
-      allowClear: true,
-      name: '位置',
-      select: ChannelModule,
-      placeholder: '请选择',
-    }, {
       key: 'content',
       type: 'textarea',
       name: '用户留言',
@@ -55,19 +43,24 @@ export default class extends Page {
       type: 'textarea',
       name: '编辑回复',
     }];
+    this.filterF = null;
     this.filterForm = [{
       key: 'channel',
-      type: 'select',
+      type: 'cascader',
       allowClear: true,
       name: '频道',
-      select: ChannelModule,
+      select: formatTreeData(FaqChannel, 'value', 'label', 'parent'),
       placeholder: '请选择',
+      onChange: (value) => {
+        this.changeSearch(this.filterForm, this, value.join('-'), null);
+      },
     }, {
       key: 'position',
       type: 'select',
       allowClear: true,
       name: '位置',
-      select: ChannelModule,
+      select: [],
+      number: true,
       placeholder: '请选择',
     }, {
       key: 'answerStatus',
@@ -77,10 +70,25 @@ export default class extends Page {
       name: '状态',
       select: AskStatus,
     }, {
+      key: 'userId',
+      type: 'select',
+      allowClear: true,
+      name: '用户',
+      select: [],
+      number: true,
+      placeholder: '请输入',
+    }, {
+      key: 'moneyRang',
+      type: 'select',
+      allowClear: true,
+      name: '消费金额',
+      select: MoneyRange,
+      number: true,
+    }, {
       key: 'isSpecial',
       type: 'select',
       allowClear: true,
-      name: '精选',
+      name: '展示',
       number: true,
       select: SwitchSelect,
     }];
@@ -88,28 +96,24 @@ export default class extends Page {
       title: '频道',
       dataIndex: 'channel',
       render: (text, record) => {
-        return ChannelModuleMap[record.channel];
+        return FaqChannelMap[record.channel];
       },
     }, {
       title: '位置',
-      dataIndex: 'position',
+      dataIndex: 'positionDetail.title',
     }, {
       title: '提问时间',
+      sorter: true,
       dataIndex: 'createTime',
       render: (text) => {
         return formatDate(text);
       },
     }, {
       title: '提问者',
-      dataIndex: 'user',
-      render: (text, record) => {
-        if (record.isSystem) return '系统创建';
-        if (!record.userId) return '未注册';
-        return text ? text.nickname : '';
-      },
+      dataIndex: 'user.nickname',
     }, {
-      title: '问题摘要',
-      dataIndex: 'content',
+      title: '消费金额',
+      dataIndex: 'user.totalMoney',
     }, {
       title: '回答状态',
       dataIndex: 'answerStatus',
@@ -117,10 +121,17 @@ export default class extends Page {
         return AskStatusMap[text] || '';
       },
     }, {
-      title: '精选',
+      title: '回答时间',
+      sorter: true,
+      dataIndex: 'answerTime',
+      render: (text) => {
+        return text ? formatDate(text) : '';
+      },
+    }, {
+      title: '展示',
       dataIndex: 'isSpecial',
-      render: (text, record) => {
-        return record.status > 0 ? SwitchSelectMap[text] || text : '-';
+      render: (text) => {
+        return SwitchSelectMap[text] || text;
       },
     }, {
       title: '操作',
@@ -132,7 +143,7 @@ export default class extends Page {
               this.editAction(record);
             }}>编辑</a>
           )}
-          {!record.isSystem && record.status === 0 && (
+          {record.answerStatus === 0 && (
             <a onClick={() => {
               this.answerAction(record);
             }}>回复</a>
@@ -140,32 +151,50 @@ export default class extends Page {
           {!!record.isSpecial && (
             <a onClick={() => {
               this.special(record, 0);
-            }}>取消精选</a>
+            }}>取消展示</a>
           )}
           {!record.isSpecial && (
             <a onClick={() => {
               this.special(record, 1);
-            }}>精选</a>
+            }}>展示</a>
           )}
         </div>;
       },
     }];
+    bindSearch(this.filterForm, 'userId', this, (search) => {
+      return User.list(search);
+    }, (row) => {
+      return {
+        title: `${row.nickname}(${row.mobile})`,
+        value: row.id,
+      };
+    }, this.state.search.userId ? Number(this.state.search.userId) : null, null);
+    this.changeSearch(this.filterForm, this, this.state.search.channel, this.state.search.position);
+    this.state.search.channel = this.state.search.channel ? this.state.search.channel.split('-') : '';
   }
 
-  initData() {
-    System.listFAQ(Object.assign({ faqModule: 'consult' }, this.state.search)).then(result => {
-      this.setTableData(result.list, result.total);
-    });
+  changeSearch(list, component, key, value) {
+    if (key === 'course-video' || key === 'course_data') {
+      bindSearch(list, 'position', component, (search) => {
+        if (key === 'course-video') {
+          return Course.list(Object.assign({ courseModule: 'video' }, search));
+        }
+        return Course.listData(search);
+      }, (row) => {
+        return {
+          title: row.title,
+          value: row.id,
+        };
+      }, value ? Number(value) : null, null);
+      list[1].disabled = false;
+    } else {
+      list[1].disabled = true;
+    }
   }
 
-  addAction() {
-    asyncForm('创建', this.itemList, {}, data => {
-      return System.addFAQ(data).then(() => {
-        asyncSMessage('添加成功!');
-        this.refresh();
-      });
-    }).then(component => {
-      this.formF = component;
+  initData() {
+    System.listFAQ(Object.assign({ user: true }, this.state.search)).then(result => {
+      this.setTableData(result.list, result.total);
     });
   }
 
@@ -193,7 +222,7 @@ export default class extends Page {
   }
 
   special(row, isSpecial) {
-    System.editFAQ({ id: row.id, isSpecial }).then(() => {
+    System.editFAQ({ id: row.id, isSpecial, isShow: isSpecial }).then(() => {
       asyncSMessage('编辑成功!');
       this.refresh();
     });
@@ -203,16 +232,18 @@ export default class extends Page {
     return <Block flex>
       <FilterLayout
         show
+        ref={(ref) => { this.filterF = ref; }}
         itemList={this.filterForm}
         data={this.state.search}
         onChange={data => {
+          data.channel = data.channel.join('-');
           this.search(data);
         }} />
-      <ActionLayout
+      {/* <ActionLayout
         itemList={this.actionList}
         selectedKeys={this.state.selectedKeys}
         onAction={key => this.onAction(key)}
-      />
+      /> */}
       <TableLayout
         columns={this.tableSort(this.columns)}
         list={this.state.list}

+ 120 - 43
front/project/admin/routes/interaction/feedback/page.js

@@ -9,11 +9,14 @@ import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
 import { getMap, formatDate, bindSearch } from '@src/services/Tools';
 import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
-import { FeedbackStatus, FeedbackModule, MoneyRange, AskTarget } from '../../../../Constant';
+import { FeedbackStatus, FeedbackModule, MoneyRange, AskTarget, FeedbackQuestionType } from '../../../../Constant';
 import { User } from '../../../stores/user';
+import { Question } from '../../../stores/question';
+import { Course } from '../../../stores/course';
 
 const FeedbackStatusMap = getMap(FeedbackStatus, 'value', 'label');
 const FeedbackModuleMap = getMap(FeedbackModule, 'value', 'label');
+const FeedbackQuestionTypeMap = getMap(FeedbackQuestionType, 'value', 'label');
 const AskTargetMap = getMap(AskTarget, 'value', 'label');
 export default class extends Page {
   init() {
@@ -33,6 +36,7 @@ export default class extends Page {
       name: '批量忽略',
       needSelect: 1,
     }];
+    this.filterF = null;
     this.filterForm = [{
       key: 'module',
       type: 'select',
@@ -40,6 +44,35 @@ export default class extends Page {
       name: '类型',
       select: FeedbackModule,
       placeholder: '请选择',
+      onChange: (value) => {
+        this.changeSearch(this.filterForm, this, value, null);
+        this.filterF.setFieldsValue({ questionType: null, keyword: null, target: null, moduleId: null });
+      },
+    }, {
+      key: 'questionType',
+      type: 'select',
+      allowClear: true,
+      name: '单项',
+      select: FeedbackQuestionType,
+    }, {
+      key: 'keyword',
+      type: 'input',
+      allowClear: true,
+      name: '题目id',
+      placeholder: '题目ID',
+    }, {
+      key: 'target',
+      type: 'select',
+      allowClear: true,
+      name: '勘误区域',
+      select: AskTarget,
+    }, {
+      key: 'moduleId',
+      type: 'select',
+      allowClear: true,
+      name: '资料',
+      select: [],
+      placeholder: '资料',
     }, {
       key: 'status',
       type: 'select',
@@ -56,7 +89,7 @@ export default class extends Page {
       number: true,
       placeholder: '请输入',
     }, {
-      key: 'money',
+      key: 'moneyRang',
       type: 'select',
       allowClear: true,
       name: '消费金额',
@@ -68,52 +101,58 @@ export default class extends Page {
       allowClear: true,
       name: '勘误名称',
     }];
-    this.columns = [
-      {
-        title: '类型',
-        dataIndex: 'module',
-        render: (text) => {
-          return FeedbackModuleMap[text];
-        },
+    this.columns = [{
+      title: '类型',
+      dataIndex: 'module',
+      render: (text) => {
+        return FeedbackModuleMap[text];
       },
-      {
-        title: '提交时间',
-        dataIndex: 'createTime',
-        render: (text) => {
-          return text ? formatDate(text) : '-';
-        },
+    }, {
+      title: '学科单项',
+      dataIndex: 'questionType',
+      render: (text) => {
+        return FeedbackQuestionTypeMap[text];
       },
-      {
-        title: '提交人',
-        dataIndex: 'user.nickname',
+    }, {
+      title: '对象',
+      dataIndex: 'title',
+    }, {
+      title: '勘误区域',
+      dataIndex: 'target',
+      render: (text) => {
+        return AskTargetMap[text] || '';
       },
-      {
-        title: '消费金额',
-        dataIndex: 'user.totalMoney',
+    }, {
+      title: '提交时间',
+      dataIndex: 'createTime',
+      render: (text) => {
+        return text ? formatDate(text) : '-';
       },
-      {
-        title: '勘误对象',
-        dataIndex: 'title',
-      }, {
-        title: '处理状态',
-        dataIndex: 'status',
-        render: (text) => {
-          return FeedbackStatusMap[text] || text;
-        },
-      }, {
-        title: '操作',
-        dataIndex: 'handler',
-        render: (text, record) => {
-          return <div className="table-button">
-            {(
-              <a onClick={() => {
-                this.detailAction(record);
-              }}>查看</a>
-            )}
-          </div>;
-        },
+    }, {
+      title: '用户',
+      dataIndex: 'user.nickname',
+    }, {
+      title: '消费金额',
+      dataIndex: 'user.totalMoney',
+    }, {
+      title: '处理状态',
+      dataIndex: 'status',
+      render: (text) => {
+        return FeedbackStatusMap[text] || text;
+      },
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          {(
+            <a onClick={() => {
+              this.detailAction(record);
+            }}>查看</a>
+          )}
+        </div>;
       },
-    ];
+    }];
     bindSearch(this.filterForm, 'userId', this, (search) => {
       return User.list(search);
     }, (row) => {
@@ -122,6 +161,43 @@ export default class extends Page {
         value: row.id,
       };
     }, this.state.search.userId ? Number(this.state.search.userId) : null, null);
+    this.changeSearch(this.filterForm, this, this.state.search.module, this.state.search.moduleId);
+  }
+
+  changeSearch(list, component, key, value) {
+    if (key === 'data') {
+      bindSearch(list, 'moduleId', component, (search) => {
+        if (key === 'question') {
+          return Question.listNo(search);
+        }
+        return Course.listData(search);
+      }, (row) => {
+        if (key === 'question') {
+          return {
+            title: row.title,
+            value: row.questionId,
+          };
+        }
+        return {
+          title: row.title,
+          value: row.id,
+        };
+      }, value ? Number(value) : null, null);
+      list[1].disabled = true;
+      list[2].disabled = true;
+      list[3].disabled = true;
+      list[4].disabled = false;
+    } else if (key === 'question') {
+      list[1].disabled = false;
+      list[2].disabled = false;
+      list[3].disabled = false;
+      list[4].disabled = true;
+    } else {
+      list[1].disabled = true;
+      list[2].disabled = true;
+      list[3].disabled = true;
+      list[4].disabled = true;
+    }
   }
 
   initData() {
@@ -205,6 +281,7 @@ export default class extends Page {
     return <Block flex>
       <FilterLayout
         show
+        ref={(ref) => { this.filterF = ref; }}
         itemList={this.filterForm}
         data={this.state.search}
         onChange={data => {

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

@@ -5,14 +5,14 @@ import Block from '@src/components/Block';
 import FilterLayout from '@src/layouts/FilterLayout';
 import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { getMap, bindSearch, formatDate } from '@src/services/Tools';
+import { getMap, formatDate, bindSearch, formatTreeData } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
-import { ChannelModule, SwitchSelect } from '../../../../Constant';
+import { CommentChannel, SystemSelect } from '../../../../Constant';
 import { System } from '../../../stores/system';
-import { User } from '../../../stores/user';
+import { Course } from '../../../stores/course';
 
-const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
-const ChannelModuleMap = getMap(ChannelModule, 'value', 'label');
+const CommentChannelMap = getMap(CommentChannel, 'value', 'label');
+const SystemSelectMap = getMap(SystemSelect, 'value', 'label');
 export default class extends Page {
   init() {
     this.actionList = [{
@@ -20,22 +20,26 @@ export default class extends Page {
       type: 'primary',
       name: '创建',
     }];
+    this.formF = null;
     this.itemList = [{
       key: 'id',
       type: 'hidden',
     }, {
       key: 'channel',
-      type: 'select',
+      type: 'cascader',
       allowClear: true,
       name: '频道',
-      select: ChannelModule,
+      select: formatTreeData(CommentChannel, 'value', 'label', 'parent'),
       placeholder: '请选择',
+      onChange: (value) => {
+        this.changeSearch(this.itemList, this.formF, value.join('-'), null, 2);
+      },
     }, {
       key: 'position',
       type: 'select',
       allowClear: true,
       name: '位置',
-      select: ChannelModule,
+      select: [],
       placeholder: '请选择',
     }, {
       key: 'nickname',
@@ -57,137 +61,113 @@ export default class extends Page {
       key: 'id',
       type: 'hidden',
     }, {
-      key: 'channel',
-      type: 'select',
-      allowClear: true,
-      name: '频道',
-      select: ChannelModule,
-      placeholder: '请选择',
-    }, {
-      key: 'position',
-      type: 'select',
-      allowClear: true,
-      name: '位置',
-      select: ChannelModule,
-      placeholder: '请选择',
-    }, {
       key: 'content',
       type: 'textarea',
       name: '评价内容',
     }];
+    this.filterF = null;
     this.filterForm = [{
       key: 'channel',
-      type: 'select',
+      type: 'cascader',
       allowClear: true,
       name: '频道',
-      select: ChannelModule,
+      select: formatTreeData(CommentChannel, 'value', 'label', 'parent'),
       placeholder: '请选择',
+      onChange: (value) => {
+        this.changeSearch(this.filterForm, this, value.join('-'), null);
+      },
     }, {
       key: 'position',
       type: 'select',
       allowClear: true,
       name: '位置',
-      select: ChannelModule,
-      placeholder: '请选择',
-    }, {
-      key: 'userId',
-      type: 'select',
-      allowClear: true,
-      name: '用户',
       select: [],
-      number: true,
-      placeholder: '请输入',
+      placeholder: '请选择',
     }, {
-      key: 'isSpecial',
+      key: 'isSystem',
       type: 'select',
       allowClear: true,
-      name: '精选',
       number: true,
-      select: SwitchSelect,
+      name: '来源',
+      select: SystemSelect,
     }];
-    this.columns = [
-      {
-        title: '频道',
-        dataIndex: 'channel',
-        render: (text, record) => {
-          return ChannelModuleMap[record.channel];
-        },
+    this.columns = [{
+      title: '频道',
+      dataIndex: 'channel',
+      render: (text, record) => {
+        return CommentChannelMap[record.channel];
       },
-      {
-        title: '位置',
-        dataIndex: 'position',
+    }, {
+      title: '商品名称',
+      dataIndex: 'positionDetail.title',
+    }, {
+      title: '创建时间',
+      sorter: true,
+      dataIndex: 'createTime',
+      render: (text) => {
+        return formatDate(text);
       },
-      {
-        title: '内容',
-        dataIndex: 'content',
+    }, {
+      title: '来源',
+      dataIndex: 'isSystem',
+      render: (text) => {
+        return SystemSelectMap[text];
       },
-      {
-        title: '用户',
-        dataIndex: 'user',
-        render: (text, record) => {
-          let extend = '';
-          if (record.isSystem) extend = '系统创建';
-          else if (!record.userId) extend = '未注册';
-          return `${text.nickname || record.nickname}${extend ? `(${extend})` : ''}`;
-        },
-      }, {
-        title: '时间',
-        dataIndex: 'createTime',
-        render: (text) => {
-          return formatDate(text);
-        },
-      }, {
-        title: '精选',
-        dataIndex: 'isSpecial',
-        render: (text) => {
-          return SwitchSelectMap[text] || text;
-        },
-      }, {
-        title: '操作',
-        dataIndex: 'handler',
-        render: (text, record) => {
-          return <div className="table-button">
-            {(
-              <a onClick={() => {
-                this.editAction(record);
-              }}>编辑</a>
-            )}
-            {!!record.isSpecial && (
-              <a onClick={() => {
-                this.special(record, 0);
-              }}>取消精选</a>
-            )}
-            {!record.isSpecial && (
-              <a onClick={() => {
-                this.special(record, 1);
-              }}>精选</a>
-            )}
-          </div>;
-        },
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          {(
+            <a onClick={() => {
+              this.editAction(record);
+            }}>编辑</a>
+          )}
+        </div>;
       },
-    ];
-    bindSearch(this.filterForm, 'userId', this, (search) => {
-      return User.list(search);
-    }, (row) => {
-      return {
-        title: `${row.nickname}(${row.mobile})`,
-        value: row.id,
-      };
-    }, this.state.search.userId ? Number(this.state.search.userId) : null, null);
+    }];
+    this.changeSearch(this.filterForm, this, this.state.search.channel, this.state.search.position);
+    this.state.search.channel = this.state.search.channel ? this.state.search.channel.split('-') : '';
+  }
+
+  changeSearch(list, component, key, value, index = 1) {
+    if (key === 'course-video' || key === 'course-vs' || key === 'course_data') {
+      bindSearch(list, 'position', component, (search) => {
+        if (key === 'course-video') {
+          return Course.list(Object.assign({ courseModule: 'video' }, search));
+        } if (key === 'course-vs') {
+          return Course.list(Object.assign({ courseModule: 'vs' }, search));
+        }
+        return Course.listData(search);
+      }, (row) => {
+        return {
+          title: row.title,
+          value: row.id,
+        };
+      }, value ? Number(value) : null, null);
+      list[index].disabled = false;
+    } else {
+      list[index].disabled = true;
+    }
   }
 
   initData() {
-    System.listComment(this.state.search).then(result => {
+    System.listComment(Object.assign({ isSpecial: true }, this.state.search)).then(result => {
       this.setTableData(result.list, result.total);
     });
   }
 
   addAction() {
     asyncForm('创建评价', this.itemList, {}, data => {
+      data.isShow = 1;
+      data.isSystem = 1;
+      data.isSpecial = 1;
       return System.addComment(data).then(() => {
         asyncSMessage('添加成功!');
         this.refresh();
       });
+    }).then(component => {
+      this.formF = component;
     });
   }
 
@@ -201,11 +181,14 @@ export default class extends Page {
         asyncSMessage('编辑成功!');
         this.refresh();
       });
+    }).then(component => {
+      this.formF = component;
+      this.changeSearch(this.filterForm, this, row.channel, row.position, 2);
     });
   }
 
-  special(row, isSpecial) {
-    System.editComment({ id: row.id, isSpecial }).then(() => {
+  special(row, isShow) {
+    System.editComment({ id: row.id, isShow }).then(() => {
       asyncSMessage('操作成功!');
       this.refresh();
     });
@@ -215,9 +198,11 @@ export default class extends Page {
     return <Block flex>
       <FilterLayout
         show
+        ref={(ref) => { this.filterF = ref; }}
         itemList={this.filterForm}
         data={this.state.search}
         onChange={data => {
+          data.channel = data.channel.join('-');
           this.search(data);
         }} />
       <ActionLayout
@@ -226,7 +211,6 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        select
         columns={this.tableSort(this.columns)}
         list={this.state.list}
         pagination={this.state.page}

+ 54 - 336
front/project/admin/routes/show/deploy/page.js

@@ -1,17 +1,17 @@
 import React from 'react';
-import { Tabs, Form, Row, Col, Input, InputNumber, Button, Upload, Icon } from 'antd';
+import { Tabs, Form, Row, Col, Input, InputNumber, Button } from 'antd';
 import './index.less';
+import Editor from '@src/components/Editor';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
-import { flattenObject } from '@src/services/Tools';
+import { flattenObject, formatFormError } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
-import { ServiceParamMap } from '../../../../Constant';
 import { System } from '../../../stores/system';
 
 export default class extends Page {
   constructor(props) {
     super(props);
-    this.vipList = ServiceParamMap.vip;
+    this.state.tab = 'sentence';
   }
 
   initData() {
@@ -19,39 +19,17 @@ export default class extends Page {
   }
 
   refresh(tab) {
-    if (tab === 'qx_cat') {
-      return this.refreshQxCat();
-    }
-    if (tab === 'textbook') {
-      return this.refreshTextbook();
-    }
-    if (tab === 'vip') {
-      return this.refreshVip();
+    if (tab === 'sentence') {
+      return this.refreshSentence();
     }
     return Promise.reject();
   }
 
-  refreshQxCat() {
-    return System.getServiceQxCat().then(result => {
-      this.setState({ qx_cat: result || {} });
+  refreshSentence() {
+    return System.getSentenceInfo().then(result => {
+      this.setState({ sentence: result || {} });
       const { form } = this.props;
-      form.setFieldsValue(flattenObject(result, 'qx_cat'));
-    });
-  }
-
-  refreshTextbook() {
-    return System.getServiceTextbook().then(result => {
-      this.setState({ textbook: result || {} });
-      const { form } = this.props;
-      form.setFieldsValue(flattenObject(result, 'textbook'));
-    });
-  }
-
-  refreshVip() {
-    return System.getServiceVip().then(result => {
-      this.setState({ vip: result || {} });
-      const { form } = this.props;
-      form.setFieldsValue(flattenObject(result, 'vip'));
+      form.setFieldsValue(flattenObject(result, 'sentence'));
     });
   }
 
@@ -63,328 +41,74 @@ export default class extends Page {
     this.setState({ [field]: data });
   }
 
+  changeValue(field, key, value) {
+    const { data } = this.state;
+    data[field] = data[field] || {};
+    data[field][key] = value;
+    this.setState({ data });
+  }
+
   submit(tab) {
-    let handler;
-    if (tab === 'qx_cat') {
-      handler = this.submitQxCat();
-    }
-    if (tab === 'textbook') {
-      handler = this.submitTextbook();
-    }
-    if (tab === 'vip') {
-      handler = this.submitVip();
+    let handler = Promise.reject();
+    if (tab === 'sentence') {
+      handler = this.submitSentence();
     }
     handler.then(() => {
       asyncSMessage('保存成功');
     });
   }
 
-  submitQxCat() {
-    const { qx_cat } = this.state;
-    return System.setServiceQxCat(qx_cat);
-  }
-
-  submitTextbook() {
-    const { textbook } = this.state;
-    return System.setServiceTextbook(textbook);
-  }
-
-  submitVip() {
-    const { vip } = this.state;
-    return System.setServiceVip(vip);
-  }
-
-  renderQxCat() {
-    const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
-    const image = getFieldValue('qx_cat.image') || null;
-    return <Form>
-      <Row>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='商品价格'>
-          {getFieldDecorator('qx_cat.package[0].price', {
-            rules: [
-              { required: true, message: '输入千行Cat价格' },
-            ],
-          })(
-            <InputNumber placeholder='请输入千行Cat价格' onChange={(value) => {
-              this.changeMapValue('qx_cat', 'package', 0, 'price', value);
-            }} style={{ width: '200px' }} />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='服务名称'>
-          {getFieldDecorator('qx_cat.package[0].title', {
-            rules: [
-              { required: true, message: '输入千行Cat名称' },
-            ],
-          })(
-            <Input placeholder='请输入千行Cat名称' onChange={(e) => {
-              this.changeMapValue('qx_cat', 'package', 0, 'title', e.target.value);
-            }} style={{ width: '200px' }} />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='服务简介'>
-          {getFieldDecorator('qx_cat.package[0].description', {
-            rules: [
-              { required: true, message: '输入千行Cat服务简介' },
-            ],
-          })(
-            <Input placeholder='请输入千行Cat服务简介' onChange={(e) => {
-              this.changeMapValue('qx_cat', 'package', 0, 'description', e.target.value);
-            }} style={{ width: '200px' }} />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='有效期说明'>
-          {getFieldDecorator('qx_cat.package[0].expire_info', {
-            rules: [
-              { required: true, message: '输入千行Cat有效期说明' },
-            ],
-          })(
-            <Input placeholder='请输入千行Cat有效期说明' onChange={(e) => {
-              this.changeMapValue('qx_cat', 'package', 0, 'expire_info', e.target.value);
-            }} style={{ width: '200px' }} />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='退款政策'>
-          {getFieldDecorator('qx_cat.package[0].refund_policy', {
-            rules: [
-              { required: true, message: '输入千行Cat退款政策' },
-            ],
-          })(
-            <Input placeholder='请输入千行Cat退款政策' onChange={(e) => {
-              this.changeMapValue('qx_cat', 'package', 0, 'refund_policy', e.target.value);
-            }} style={{ width: '200px' }} />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='版权说明'>
-          {getFieldDecorator('qx_cat.package[0].copyright_notes', {
-            rules: [
-              { required: true, message: '输入千行Cat版权说明' },
-            ],
-          })(
-            <Input placeholder='请输入千行Cat版权说明' onChange={(e) => {
-              this.changeMapValue('qx_cat', 'package', 0, 'copyright_notes', e.target.value);
-            }} style={{ width: '200px' }} />,
-          )}
-        </Form.Item>
-
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='商品图片'>
-          {getFieldDecorator('qx_cat.image', {
-            rules: [
-              { required: true, message: '上传图片' },
-            ],
-          })(
-            <Upload
-              listType="picture-card"
-              showUploadList={false}
-              beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                setFieldsValue({ 'qx_cat.image': result.url });
-                return Promise.reject();
-              })}
-            >
-              {image ? <img src={image} alt="avatar" /> : <div>
-                <Icon type={this.state.loading ? 'loading' : 'plus'} />
-                <div className="ant-upload-text">Upload</div>
-              </div>}
-            </Upload>,
-          )}
-        </Form.Item>
-      </Row>
-    </Form>;
+  submitSentence() {
+    const { form } = this.props;
+    return new Promise((resolve, reject) => {
+      form.validateFields(['sentence'], (err, values) => {
+        if (err) {
+          reject(err);
+        }
+        const data = values.sentence;
+        return System.setSentenceInfo(data)
+          .then(() => {
+            resolve();
+          }).catch((e) => {
+            form.setFields(formatFormError(data, e.result));
+            reject(e);
+          });
+      });
+    });
   }
 
-  renderTextbook() {
-    const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
-    const image = getFieldValue('textbook.image') || null;
+  renderSentence() {
+    const { getFieldDecorator } = this.props.form;
     return <Form>
       <Row>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='商品图片'>
-          {getFieldDecorator('textbook.image', {
+        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='购买链接'>
+          {getFieldDecorator('sentence.link', {
             rules: [
-              { required: true, message: '上传图片' },
+              { required: true, message: '输入购买链接' },
             ],
           })(
-            <Upload
-              listType="picture-card"
-              showUploadList={false}
-              beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                setFieldsValue({ 'textbook.image': result.url });
-                return Promise.reject();
-              })}
-            >
-              {image ? <img src={image} alt="avatar" /> : <div>
-                <Icon type={this.state.loading ? 'loading' : 'plus'} />
-                <div className="ant-upload-text">Upload</div>
-              </div>}
-            </Upload>,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='商品价格'>
-          {getFieldDecorator('textbook.package[0].price', {
-            rules: [
-              { required: true, message: '输入数学机经价格' },
-            ],
-          })(
-            <InputNumber placeholder='请输入数学机经价格' onChange={(value) => {
-              this.changeMapValue('textbook', 'package', 0, 'price', value);
+            <Input placeholder='请输入购买链接' onChange={(e) => {
+              this.changeValue('sentence', 'link', e.target.value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='服务名称'>
-          {getFieldDecorator('textbook.package[0].title', {
+        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='购买价格'>
+          {getFieldDecorator('sentence.price', {
             rules: [
-              { required: true, message: '输入数学机经名称' },
+              { required: true, message: '输入购买价格' },
             ],
           })(
-            <Input placeholder='请输入数学机经名称' onChange={(e) => {
-              this.changeMapValue('textbook', 'package', 0, 'title', e.target.value);
+            <InputNumber placeholder='请输入购买价格' onChange={(value) => {
+              this.changeValue('sentence', 'price', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='服务简介'>
-          {getFieldDecorator('textbook.package[0].description', {
-            rules: [
-              { required: true, message: '输入数学机经服务简介' },
-            ],
+        <Form.Item label='Code说明'>
+          {getFieldDecorator('sentence.detail', {
           })(
-            <Input placeholder='请输入数学机经服务简介' onChange={(e) => {
-              this.changeMapValue('textbook', 'package', 0, 'description', e.target.value);
-            }} style={{ width: '200px' }} />,
+            <Editor placeholder='输入内容' />,
           )}
         </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='有效期说明'>
-          {getFieldDecorator('textbook.package[0].expire_info', {
-            rules: [
-              { required: true, message: '输入数学机经有效期说明' },
-            ],
-          })(
-            <Input placeholder='请输入数学机经有效期说明' onChange={(e) => {
-              this.changeMapValue('textbook', 'package', 0, 'expire_info', e.target.value);
-            }} style={{ width: '200px' }} />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='退款政策'>
-          {getFieldDecorator('textbook.package[0].refund_policy', {
-            rules: [
-              { required: true, message: '输入数学机经退款政策' },
-            ],
-          })(
-            <Input placeholder='请输入数学机经退款政策' onChange={(e) => {
-              this.changeMapValue('textbook', 'package', 0, 'refund_policy', e.target.value);
-            }} style={{ width: '200px' }} />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='版权说明'>
-          {getFieldDecorator('textbook.package[0].copyright_notes', {
-            rules: [
-              { required: true, message: '输入数学机经版权说明' },
-            ],
-          })(
-            <Input placeholder='请输入数学机经版权说明' onChange={(e) => {
-              this.changeMapValue('textbook', 'package', 0, 'copyright_notes', e.target.value);
-            }} style={{ width: '200px' }} />,
-          )}
-        </Form.Item>
-      </Row>
-    </Form>;
-  }
-
-  renderVip() {
-    const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
-    const image = getFieldValue('vip.image') || null;
-    return <Form>
-      <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='商品图片'>
-        {getFieldDecorator('vip.image', {
-          rules: [
-            { required: true, message: '上传图片' },
-          ],
-        })(
-          <Upload
-            listType="picture-card"
-            showUploadList={false}
-            beforeUpload={(file) => System.uploadImage(file).then((result) => {
-              setFieldsValue({ 'vip.image': result.url });
-              return Promise.reject();
-            })}
-          >
-            {image ? <img src={image} alt="avatar" /> : <div>
-              <Icon type={this.state.loading ? 'loading' : 'plus'} />
-              <div className="ant-upload-text">Upload</div>
-            </div>}
-          </Upload>,
-        )}
-      </Form.Item>
-      <Row>
-        {this.vipList.map((row, index) => {
-          return <Col span={12}>
-            <h1>{row.label}</h1>
-            <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='商品价格'>
-              {getFieldDecorator(`vip.package[${index}].price`, {
-                rules: [
-                  { required: true, message: '输入价格' },
-                ],
-              })(
-                <InputNumber placeholder={'输入价格'} onChange={(value) => {
-                  this.changeMapValue('vip', 'package', index, 'price', value);
-                }} style={{ width: '200px' }} />,
-              )}
-            </Form.Item>
-            <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='服务名称'>
-              {getFieldDecorator(`vip.package[${index}].title`, {
-                rules: [
-                  { required: true, message: '输入名称' },
-                ],
-              })(
-                <Input placeholder={'输入名称'} onChange={(e) => {
-                  this.changeMapValue('vip', 'package', index, 'title', e.target.value);
-                }} style={{ width: '200px' }} />,
-              )}
-            </Form.Item>
-            <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='服务简介'>
-              {getFieldDecorator(`vip.package[${index}].description`, {
-                rules: [
-                  { required: true, message: '输入服务简介' },
-                ],
-              })(
-                <Input placeholder='请输入服务简介' onChange={(e) => {
-                  this.changeMapValue('vip', 'package', index, 'description', e.target.value);
-                }} style={{ width: '200px' }} />,
-              )}
-            </Form.Item>
-            <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='有效期说明'>
-              {getFieldDecorator(`vip.package[${index}].expire_info`, {
-                rules: [
-                  { required: true, message: '输入有效期说明' },
-                ],
-              })(
-                <Input placeholder='请输入有效期说明' onChange={(e) => {
-                  this.changeMapValue('vip', 'package', index, 'expire_info', e.target.value);
-                }} style={{ width: '200px' }} />,
-              )}
-            </Form.Item>
-            <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='退款政策'>
-              {getFieldDecorator(`vip.package[${index}].refund_policy`, {
-                rules: [
-                  { required: true, message: '输入退款政策' },
-                ],
-              })(
-                <Input placeholder='请输入退款政策' onChange={(e) => {
-                  this.changeMapValue('vip', 'package', index, 'refund_policy', e.target.value);
-                }} style={{ width: '200px' }} />,
-              )}
-            </Form.Item>
-            <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='版权说明'>
-              {getFieldDecorator(`vip.package[${index}].copyright_notes`, {
-                rules: [
-                  { required: true, message: '输入版权说明' },
-                ],
-              })(
-                <Input placeholder='请输入版权说明' onChange={(e) => {
-                  this.changeMapValue('vip', 'package', index, 'copyright_notes', e.target.value);
-                }} style={{ width: '200px' }} />,
-              )}
-            </Form.Item>
-          </Col>;
-        })}
-
       </Row>
     </Form>;
   }
@@ -395,14 +119,8 @@ export default class extends Page {
       this.setState({ tab: value, selectedKeys: [], checkedKeys: [] });
       this.refresh(value);
     }}>
-      <Tabs.TabPane tab="千行Cat" key="qx_cat">
-        {this.renderQxCat()}
-      </Tabs.TabPane>
-      <Tabs.TabPane tab="数学机经" key="textbook">
-        {this.renderTextbook()}
-      </Tabs.TabPane>
-      <Tabs.TabPane tab="Vip" key="vip">
-        {this.renderVip()}
+      <Tabs.TabPane tab="长难句购买" key="sentence">
+        {this.renderSentence()}
       </Tabs.TabPane>
     </Tabs>
       <Row type="flex" justify="center">

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

@@ -5,13 +5,14 @@ import Block from '@src/components/Block';
 import FilterLayout from '@src/layouts/FilterLayout';
 import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { getMap } from '@src/services/Tools';
+import { getMap, formatDate, bindSearch, formatTreeData } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
-import { ChannelModule, SwitchSelect, AskStatus } from '../../../../Constant';
+import { FaqChannel, SystemSelect } from '../../../../Constant';
 import { System } from '../../../stores/system';
+import { Course } from '../../../stores/course';
 
-const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
-const ChannelModuleMap = getMap(ChannelModule, 'value', 'label');
+const FaqChannelMap = getMap(FaqChannel, 'value', 'label');
+const SystemSelectMap = getMap(SystemSelect, 'value', 'label');
 export default class extends Page {
   init() {
     this.actionList = [{
@@ -25,17 +26,18 @@ export default class extends Page {
       type: 'hidden',
     }, {
       key: 'channel',
-      type: 'select',
-      allowClear: true,
+      type: 'cascader',
       name: '频道',
-      select: ChannelModule,
+      select: formatTreeData(FaqChannel, 'value', 'label', 'parent'),
       placeholder: '请选择',
+      onChange: (value) => {
+        this.changeSearch(this.itemList, this.formF, value.join('-'), null, 2);
+      },
     }, {
       key: 'position',
       type: 'select',
-      allowClear: true,
       name: '位置',
-      select: ChannelModule,
+      select: [],
       placeholder: '请选择',
     }, {
       key: 'content',
@@ -46,52 +48,54 @@ export default class extends Page {
       type: 'textarea',
       name: '编辑回复',
     }];
+    this.filterF = null;
     this.filterForm = [{
       key: 'channel',
-      type: 'select',
+      type: 'cascader',
       allowClear: true,
       name: '频道',
-      select: ChannelModule,
+      select: formatTreeData(FaqChannel, 'value', 'label', 'parent'),
       placeholder: '请选择',
+      onChange: (value) => {
+        this.changeSearch(this.filterForm, this, value.join('-'), null);
+      },
     }, {
       key: 'position',
       type: 'select',
       allowClear: true,
       name: '位置',
-      select: ChannelModule,
-      placeholder: '请选择',
-    }, {
-      key: 'answerStatus',
-      type: 'select',
-      allowClear: true,
       number: true,
-      name: '状态',
-      select: AskStatus,
+      select: [],
+      placeholder: '请选择',
     }, {
-      key: 'isSpecial',
+      key: 'isSystem',
       type: 'select',
       allowClear: true,
-      name: '精选',
       number: true,
-      select: SwitchSelect,
+      name: '来源',
+      select: SystemSelect,
     }];
     this.columns = [{
       title: '频道',
       dataIndex: 'channel',
       render: (text, record) => {
-        return ChannelModuleMap[record.channel];
+        return FaqChannelMap[record.channel];
       },
     }, {
       title: '位置',
-      dataIndex: 'position',
+      dataIndex: 'positionDetail.title',
     }, {
-      title: '问题摘要',
-      dataIndex: 'content',
+      title: '创建时间',
+      sorter: true,
+      dataIndex: 'createTime',
+      render: (text) => {
+        return formatDate(text);
+      },
     }, {
-      title: '精选',
-      dataIndex: 'isSpecial',
-      render: (text, record) => {
-        return record.status > 0 ? SwitchSelectMap[text] || text : '-';
+      title: '来源',
+      dataIndex: 'isSystem',
+      render: (text) => {
+        return SystemSelectMap[text];
       },
     }, {
       title: '操作',
@@ -103,30 +107,44 @@ export default class extends Page {
               this.editAction(record);
             }}>编辑</a>
           )}
-          {!!record.isSpecial && (
-            <a onClick={() => {
-              this.special(record, 0);
-            }}>取消精选</a>
-          )}
-          {!record.isSpecial && (
-            <a onClick={() => {
-              this.special(record, 1);
-            }}>精选</a>
-          )}
         </div>;
       },
     }];
+
+    this.changeSearch(this.filterForm, this, this.state.search.channel, this.state.search.position);
+    this.state.search.channel = this.state.search.channel ? this.state.search.channel.split('-') : '';
+  }
+
+  changeSearch(list, component, key, value, index = 1) {
+    if (key === 'course-video' || key === 'course_data') {
+      bindSearch(list, 'position', component, (search) => {
+        if (key === 'course-video') {
+          return Course.list(Object.assign({ courseModule: 'video' }, search));
+        }
+        return Course.listData(search);
+      }, (row) => {
+        return {
+          title: row.title,
+          value: row.id,
+        };
+      }, value ? Number(value) : null, null);
+      list[index].disabled = false;
+    } else {
+      list[index].disabled = true;
+    }
   }
 
   initData() {
-    System.listFAQ(Object.assign({ faqModule: 'system' }, this.state.search)).then(result => {
+    System.listFAQ(Object.assign({ isSpecial: true }, this.state.search)).then(result => {
       this.setTableData(result.list, result.total);
     });
   }
 
   addAction() {
     asyncForm('创建', this.itemList, {}, data => {
-      data.faqModule = 'system';
+      data.isShow = 1;
+      data.isSystem = 1;
+      data.isSpecial = 1;
       return System.addFAQ(data).then(() => {
         asyncSMessage('添加成功!');
         this.refresh();
@@ -138,18 +156,18 @@ export default class extends Page {
 
   editAction(row) {
     asyncForm('编辑', this.itemList, row, data => {
-      data.faqModule = 'system';
       return System.editFAQ(data).then(() => {
         asyncSMessage('编辑成功!');
         this.refresh();
       });
     }).then(component => {
       this.formF = component;
+      this.changeSearch(this.filterForm, this, row.channel, row.position, 2);
     });
   }
 
-  special(row, isSpecial) {
-    System.editFAQ({ id: row.id, isSpecial }).then(() => {
+  show(row, isShow) {
+    System.editFAQ({ id: row.id, isShow }).then(() => {
       asyncSMessage('编辑成功!');
       this.refresh();
     });
@@ -159,9 +177,11 @@ export default class extends Page {
     return <Block flex>
       <FilterLayout
         show
+        ref={(ref) => { this.filterF = ref; }}
         itemList={this.filterForm}
         data={this.state.search}
         onChange={data => {
+          data.channel = data.channel.join('-');
           this.search(data);
         }} />
       <ActionLayout

+ 0 - 16
front/project/admin/routes/show/readDetail/index.js

@@ -1,16 +0,0 @@
-import module from '../../module';
-import group from '../group';
-
-export default {
-  path: '/show/read/detail',
-  matchPath: '/show/read/detail/:id?',
-  key: 'show-read-detail',
-  title: '推荐阅读',
-  needLogin: true,
-  module,
-  group,
-  showKey: 'show-read',
-  component() {
-    return import('./page');
-  },
-};

+ 0 - 3
front/project/admin/routes/show/readDetail/index.less

@@ -1,3 +0,0 @@
-@charset "utf-8";
-
-#course-experience-detail {}

+ 0 - 169
front/project/admin/routes/show/readDetail/page.js

@@ -1,169 +0,0 @@
-import React from 'react';
-import { Form, Input, Button, Row, Col, InputNumber } from 'antd';
-import './index.less';
-import Editor from '@src/components/Editor';
-import Page from '@src/containers/Page';
-import Block from '@src/components/Block';
-import Select from '@src/components/Select';
-// import FileUpload from '@src/components/FileUpload';
-import { formatFormError, generateSearch } from '@src/services/Tools';
-import { asyncSMessage } from '@src/services/AsyncTools';
-import { PrepareStatus, ExperienceScoreRange, ExperiencePercent } from '../../../../Constant';
-import { Course } from '../../../stores/course';
-import { User } from '../../../stores/user';
-
-const [minScore, maxScore, step] = ExperienceScoreRange;
-let tmp = minScore;
-const ExperienceScore = [];
-while (tmp <= maxScore) {
-  ExperienceScore.push({ label: `${tmp}`, value: tmp });
-  tmp += step;
-}
-
-export default class extends Page {
-  initData() {
-    const { id } = this.params;
-    const { form } = this.props;
-    let handler;
-    if (id) {
-      handler = Course.getExperience({ id });
-    } else {
-      handler = Promise.resolve({});
-    }
-
-    handler
-      .then(result => {
-        generateSearch('userId', {}, this, (search) => {
-          return User.list(search);
-        }, (row) => {
-          return {
-            title: `${row.nickname}(${row.mobile})`,
-            value: row.id,
-          };
-        }, result.userId, null);
-        form.setFieldsValue(result);
-      });
-  }
-
-  submit() {
-    const { form } = this.props;
-    form.validateFields((err) => {
-      if (!err) {
-        const data = form.getFieldsValue();
-        let handler;
-        if (data.id) {
-          handler = Course.editExperience(data);
-        } else {
-          handler = Course.addExperience(data);
-        }
-        handler.then(() => {
-          asyncSMessage('保存成功');
-          goBack();
-        }).catch((e) => {
-          if (e.result) form.setFields(formatFormError(data, e.result));
-        });
-      }
-    });
-  }
-
-  renderBase() {
-    const { getFieldDecorator } = this.props.form;
-    return <Block>
-      <Form>
-        {getFieldDecorator('id')(<input hidden />)}
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题目'>
-          {getFieldDecorator('title', {
-            rules: [
-              { required: true, message: '请输入名称' },
-            ],
-          })(
-            <Input placeholder='请输入名称' />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='作者'>
-          {getFieldDecorator('userId', {
-            rules: [
-              { required: true, message: '请选择作者' },
-            ],
-          })(
-            <Select {...this.state.userId} placeholder='请选择作者' />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='昵称'>
-          {getFieldDecorator('nickname', {
-            rules: [
-              { required: true, message: '请输入昵称' },
-            ],
-          })(
-            <Input placeholder='输入昵称' />,
-          )}
-        </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='作者信息'>
-          <Row>
-            <Col span={6}>
-              {getFieldDecorator('prepareStatus', {
-                rules: [{
-                  required: true, message: '请选择',
-                }],
-              })(
-                <Select select={PrepareStatus} placeholder='身份' />,
-              )}
-            </Col>
-            <Col span={6}>
-              {getFieldDecorator('experienceDay', {
-                rules: [{
-                  required: true, message: '请输入备考天数',
-                }],
-              })(
-                <InputNumber placeholder='备考周期: 天' />,
-              )}
-            </Col>
-            <Col span={6}>
-              {getFieldDecorator('experienceScore', {
-                rules: [{
-                  required: true, message: '请选择',
-                }],
-              })(
-                <Select select={ExperienceScore} placeholder='分手成绩' />,
-              )}
-            </Col>
-            <Col span={6}>
-              {getFieldDecorator('experiencePercent', {
-                rules: [{
-                  required: true, message: '请选择',
-                }],
-              })(
-                <Select select={ExperiencePercent} placeholder='提分范围' />,
-              )}
-            </Col>
-          </Row>
-        </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='问卷链接'>
-          {getFieldDecorator('link', {
-          })(
-            <Input placeholder='输入内容' />,
-          )}
-        </Form.Item>
-        <Form.Item label='正文'>
-          {getFieldDecorator('content', {
-          })(
-            <Editor placeholder='输入内容' />,
-          )}
-        </Form.Item>
-      </Form>
-    </Block>;
-  }
-
-  renderView() {
-    return <div flex>
-      {this.renderBase()}
-      <Row type="flex" justify="center">
-        <Col>
-          <Button type="primary" onClick={() => {
-            this.submit();
-          }}>保存</Button>
-        </Col>
-      </Row>
-    </div>;
-  }
-}

+ 7 - 6
front/project/admin/routes/show/tips/page.js

@@ -9,15 +9,16 @@ import { asyncSMessage } from '@src/services/AsyncTools';
 import { System } from '../../../stores/system';
 
 const locations = [
-  { page: '4-1/3模考-千行CAT', location: '显示第一次模考成绩', value: '4-1/3-show' },
-  { page: '4-1-1 做题结束-练习报告', location: '建议用时', value: '4-1-1-suggest' },
-  { page: '4-2-2模考-数学机经-选择习题册', location: '已更新至邮箱', value: '4-2-2-update' },
-  { page: '4-2-2模考-数学机经-选择习题册', location: '下载弹框', value: '4-2-2-download' },
-  { page: '4-2-2模考-数学机经-选择习题册', location: '邮箱处', value: '4-2-2-email' },
+  { page: '4-1/3模考-千行CAT/加入第二套', location: '显示第一次模考成绩', value: '4-1/3-show' },
+  { page: '4-3-1 做题结束-练习报告', location: '建议用时', value: '4-3-1-suggest' },
+  { page: '4-3.2模考-数学机经-最新列表页', location: '邮箱订阅', value: '4-3.2-update' },
   { page: '4-4 模考-休息', location: 'Optional Break', value: '4-4-optional' },
-  { page: '4-2-3 模考-数学机经-购买', location: '购买方式', value: '4-2-3-buy' },
   { page: '4-2-3 模考-数学机经-购买', location: '填写邮箱', value: '4-2-3-email' },
   { page: '5-1/3主页/弹框', location: '备注6', value: '5-1/3-note' },
+  { page: '5-7/1 数据-练习', location: '平均用时', value: '5-7/1-data' },
+  { page: '5-1 显示答案', location: '解释30%', value: '4-2-2-answer' },
+  { page: '5-6 数据', location: '平均用时+?号', value: '5-6-data' },
+  { page: '5-9 显示答案', location: '课程开发票', value: '5-9-invoice' },
 ];
 
 export default class extends Page {

+ 24 - 18
front/project/admin/routes/student/askCourse/page.js

@@ -62,7 +62,7 @@ export default class extends Page {
       number: true,
       placeholder: '请输入',
     }, {
-      key: 'money',
+      key: 'moneyRang',
       type: 'select',
       allowClear: true,
       name: '消费金额',
@@ -75,26 +75,32 @@ export default class extends Page {
       render: (text, record) => {
         return `${record.course.parentStructId ? `${this.exerciseMap[record.course.parentStructId]}-` : ''}${this.exerciseMap[record.course.structId]}`;
       },
-    },
-    {
+    }, {
       title: '课程',
       dataIndex: 'course.title',
-    },
-    {
+    }, {
+      title: '课时',
+      dataIndex: 'courseNo.no',
+    }, {
       title: '位置',
       dataIndex: 'position',
-      render: (text, record) => {
-        return `P${record.courseNo.no}:${text}`;
+      render: (text) => {
+        return `${text}`;
       },
-    },
-    {
-      title: '提问摘要',
-      dataIndex: 'content',
     }, {
       title: '提问者',
       dataIndex: 'user.nickname',
     }, {
-      title: '承诺时间',
+      title: '消费金额',
+      dataIndex: 'user.totalMoney',
+    }, {
+      title: '提问时间',
+      dataIndex: 'createTime',
+      render: (text) => {
+        return text ? formatDate(text) : '';
+      },
+    }, {
+      title: '倒计时',
       dataIndex: 'askTime',
       render: (text, record) => {
         const end = new Date(record.answerTime) || new Date();
@@ -106,12 +112,6 @@ export default class extends Page {
         return '-';
       },
     }, {
-      title: '回答状态',
-      dataIndex: 'answerStatus',
-      render: (text) => {
-        return AskStatusMap[text] || text;
-      },
-    }, {
       title: '回答者',
       dataIndex: 'manager.username',
     }, {
@@ -121,6 +121,12 @@ export default class extends Page {
         return text ? formatDate(text) : '';
       },
     }, {
+      title: '回答状态',
+      dataIndex: 'answerStatus',
+      render: (text) => {
+        return AskStatusMap[text] || text;
+      },
+    }, {
       title: '展示状态',
       dataIndex: 'showStatus',
       render: (text) => {

+ 42 - 19
front/project/admin/routes/student/askQuestion/page.js

@@ -6,15 +6,16 @@ import Block from '@src/components/Block';
 import FilterLayout from '@src/layouts/FilterLayout';
 import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { getMap, bindSearch, formatDate } from '@src/services/Tools';
+import { getMap, bindSearch, formatDate, formatSeconds } from '@src/services/Tools';
 import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
-import { QuestionType, AskStatus, MoneyRange, SwitchSelect, AskTarget } from '../../../../Constant';
+import { QuestionType, AskStatus, MoneyRange, SwitchSelect, AskTarget, AskModule } from '../../../../Constant';
 import { User } from '../../../stores/user';
 import { Question } from '../../../stores/question';
 
 const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
 const AskStatusMap = getMap(AskStatus, 'value', 'label');
 const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
+const AskModuleMap = getMap(AskModule, 'value', 'label');
 export default class extends Page {
   init() {
     this.actionList = [{
@@ -24,6 +25,13 @@ export default class extends Page {
       needSelect: 1,
     }];
     this.filterForm = [{
+      key: 'askModule',
+      type: 'select',
+      allowClear: true,
+      name: '板块',
+      select: AskModule,
+      placeholder: '请选择',
+    }, {
       key: 'questionType',
       type: 'select',
       allowClear: true,
@@ -38,7 +46,7 @@ export default class extends Page {
       select: AskStatus,
       number: true,
     }, {
-      key: 'money',
+      key: 'moneyRang',
       type: 'select',
       allowClear: true,
       name: '消费金额',
@@ -75,34 +83,43 @@ export default class extends Page {
       placeholder: '请输入',
     }];
     this.columns = [{
+      title: '板块',
+      dataIndex: 'askModule',
+      render: (text) => {
+        return AskModuleMap[text];
+      },
+    }, {
       title: '题型',
       dataIndex: 'type',
       render: (text, record) => {
-        return QuestionTypeMap[record.question.type];
+        return QuestionTypeMap[record.question.questionType];
       },
-    },
-    {
+    }, {
       title: '题目id',
       dataIndex: 'questionNo.no',
-    },
-    {
+    }, {
+      title: '提问者',
+      dataIndex: 'user.nickname',
+    }, {
+      title: '消费金额',
+      dataIndex: 'user.totalMoney',
+    }, {
       title: '提问时间',
       dataIndex: 'createTime',
       render: (text) => {
         return formatDate(text);
       },
-    },
-    {
-      title: '提问摘要',
-      dataIndex: 'content',
     }, {
-      title: '提问者',
-      dataIndex: 'user.nickname',
-    }, {
-      title: '回答状态',
-      dataIndex: 'answerStatus',
-      render: (text) => {
-        return AskStatusMap[text] || text;
+      title: '倒计时',
+      dataIndex: 'askTime',
+      render: (text, record) => {
+        const end = new Date(record.answerTime) || new Date();
+        const cost = (end.getTime() - new Date(record.createTime).getTime()) / 1000;
+        if (text) {
+          if (text - cost > 0) return `${formatSeconds(text - cost)}/${formatSeconds(text)}`;
+          return `0/${formatSeconds(text)}`;
+        }
+        return '-';
       },
     }, {
       title: '回答者',
@@ -114,6 +131,12 @@ export default class extends Page {
         return text ? formatDate(text) : '';
       },
     }, {
+      title: '回答状态',
+      dataIndex: 'answerStatus',
+      render: (text) => {
+        return AskStatusMap[text] || text;
+      },
+    }, {
       title: '展示状态',
       dataIndex: 'showStatus',
       render: (text) => {

+ 6 - 2
front/project/admin/routes/student/askQuestionDetail/page.js

@@ -8,12 +8,13 @@ import DragList from '@src/components/DragList';
 // import FileUpload from '@src/components/FileUpload';
 import { formatFormError, formatDate, getMap } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
-import { UserUrl, AskTarget, QuestionType } from '../../../../Constant';
+import { UserUrl, AskTarget, QuestionType, AskModule } from '../../../../Constant';
 // import { User } from '../../../stores/user';
 import { Question } from '../../../stores/question';
 
 const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
 const AskTargetMap = getMap(AskTarget, 'value', 'label');
+const AskModuleMap = getMap(AskModule, 'value', 'label');
 export default class extends Page {
   init() {
     // Exercise.allStruct().then(result => {
@@ -88,8 +89,11 @@ export default class extends Page {
     return <Block>
       <h1>题目信息</h1>
       <Form>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='板块'>
+          {AskModuleMap[data.askModule]}
+        </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题型'>
-          {QuestionTypeMap[question.type]}
+          {QuestionTypeMap[question.questionType]}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题目id'>
           <a href='' target='_blank'>{questionNo.no}</a>

+ 42 - 29
front/project/admin/routes/student/studyDetail/page.js

@@ -7,7 +7,7 @@ import Block from '@src/components/Block';
 // import FilterLayout from '@src/layouts/FilterLayout';
 import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
-import { formatDate } from '@src/services/Tools';
+import { formatDate, formatSecond, formatPercent } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
 import { UserUrl } from '../../../../Constant';
 import { Course } from '../../../stores/course';
@@ -18,28 +18,33 @@ import { System } from '../../../stores/system';
 export default class extends Page {
   init() {
     this.videoColumns = [{
-      title: '时间段',
-      dataIndex: 'time',
-      render: (text) => {
-        return `${formatDate(text.startTime, 'YYYY-MM-DD')}~${formatDate(text.endTime, 'YYYY-MM-DD')}`;
-      },
+      title: '课时',
+      dataIndex: 'courseNo.no',
     }, {
-      title: '学员id',
-      dataIndex: 'userId',
+      title: '课时名称',
+      dataIndex: 'courseNo.title',
     }, {
-      title: '学员名称',
-      dataIndex: 'user.nickname',
+      title: '学习记录',
+      dataIndex: 'records',
+      render: (text) => {
+        return (text || []).map(row => {
+          return <p>{formatDate(row.createTime, 'YYYY-MM-DD HH:mm:ss')} {formatSecond(row.userTime)}</p>;
+        });
+      },
     }, {
-      title: '手机号',
-      dataIndex: 'user.mobile',
+      title: '预习作业进度',
+      dataIndex: 'userPaper',
+      render: (text, record) => {
+        return `${text.times}遍+${formatPercent(record.userReport.userNumber, record.userReport.questionNumber)}`;
+      },
     }, {
       title: '操作',
       dataIndex: 'handler',
       render: (text, record) => {
         return <div className="table-button">
           {<a onClick={() => {
-            this.deleteOnlineStudent(record);
-          }}>删除</a>}
+            User.locationUser(record.userId, `${UserUrl}/my/report`);
+          }}>查看</a>}
         </div>;
       },
     }];
@@ -61,7 +66,7 @@ export default class extends Page {
       showTime: true,
       name: '上课时间',
     }, {
-      key: 'channel',
+      key: 'cctalkChannel',
       type: 'input',
       name: '频道号',
     }];
@@ -85,13 +90,13 @@ export default class extends Page {
       dataIndex: 'title',
     }, {
       title: '频道号',
-      dataIndex: 'channel',
+      dataIndex: 'cctalkChannel',
     }, {
       title: '学习进度',
       dataIndex: 'isFinish',
       render: (text, record) => {
         return <Checkbox checked={text > 0} onChange={(e) => {
-          this.changeAppointment(record.id, { isFinish: e.target.value ? 1 : 0 });
+          this.changeAppointment(record.id, { isFinish: e.target.checked ? 1 : 0 });
         }} />;
       },
     }, {
@@ -136,8 +141,7 @@ export default class extends Page {
           return;
         }
         const { course } = row;
-        this.setState({ module: course.courseModule });
-        this.setState({ data: row });
+        this.setState({ data: row, module: course.courseModule });
         this.refresh();
       });
   }
@@ -159,15 +163,15 @@ export default class extends Page {
   }
 
   refreshVideo() {
-    // const { id } = this.params;
-    // Course.listStudentOnline(Object.assign({ courseId: id }, this.state.search)).then(result => {
-    //   this.setTableData(result.list, result.total);
-    // });
+    const { id } = this.params;
+    User.allCourseRecord(Object.assign({ recordId: id }, this.state.search)).then(result => {
+      this.setTableData(result, result ? result.length : 0);
+    });
   }
 
   refreshVs() {
     const { id } = this.params;
-    User.listCourseAppointment(Object.assign({ courseId: id }, this.state.search)).then(result => {
+    User.listCourseAppointment(Object.assign({ recordId: id }, this.state.search)).then(result => {
       result.list = result.list.map(row => {
         row.startTime = moment(row.startTime);
         row.endTime = moment(row.endTime);
@@ -225,7 +229,7 @@ export default class extends Page {
 
   renderVs() {
     const { data, page } = this.state;
-    return <Block flex>
+    return <div flex>
       {data.number > page.total && <ActionLayout
         itemList={this.vsAction}
         selectedKeys={this.state.selectedKeys}
@@ -301,11 +305,11 @@ export default class extends Page {
           </Row>
         </Form>
       </Modal>}
-    </Block>;
+    </div>;
   }
 
   renderVideo() {
-    return <Block flex>
+    return <div flex>
       <TableLayout
         columns={this.videoColumns}
         list={this.state.list}
@@ -315,10 +319,10 @@ export default class extends Page {
         onSelect={(keys, rows) => this.tableSelect(keys, rows)}
         selectedKeys={this.state.selectedKeys}
       />
-    </Block>;
+    </div>;
   }
 
-  renderView() {
+  renderDetail() {
     switch (this.state.module) {
       case 'online':
         return [];
@@ -330,4 +334,13 @@ export default class extends Page {
         return <div />;
     }
   }
+
+  renderView() {
+    const { data = {} } = this.state;
+    const { course = {} } = data;
+    return <Block flex>
+      <h1>{course.title}{data.vsNo > 0 ? `V${data.vsNo}` : ''}{data.number > 0 ? `(${data.number}课时)` : ''}</h1>
+      {this.renderDetail()}
+    </Block>;
+  }
 }

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

@@ -228,9 +228,7 @@ export default class extends Page {
               { required: true, message: '请选择考点' },
             ],
           })(
-            <Select select={this.state.placeList} placeholder='请选择考点' onChange={(v) => {
-              this.refreshPart(v);
-            }} />,
+            <Select select={this.state.placeList} placeholder='请选择考点' />,
           )}
         </Form.Item>
       </Form>

+ 81 - 12
front/project/admin/routes/textbook/library/page.js

@@ -1,5 +1,5 @@
 import React from 'react';
-import { Button, Form, Modal, DatePicker, Input, Upload } from 'antd';
+import { Button, Form, Modal, DatePicker, Input, Upload, Checkbox, Switch } from 'antd';
 import { Link } from 'react-router-dom';
 import moment from 'moment';
 import './index.less';
@@ -13,6 +13,7 @@ import { asyncDelConfirm, asyncSMessage } from '@src/services/AsyncTools';
 // import { TextbookType } from '../../../../Constant';
 import { Textbook } from '../../../stores/textbook';
 import { System } from '../../../stores/system';
+import { formatDate } from '../../../../../src/services/Tools';
 // import { Question } from '../../../stores/question';
 // import { Slient } from '../../../stores/slient';
 
@@ -31,9 +32,15 @@ export default class extends Page {
     this.columns = [{
       title: '库头',
       dataIndex: 'startDate',
+      render: (text) => {
+        return formatDate(text, 'YYYY-MM-DD');
+      },
     }, {
       title: '库尾',
       dataIndex: 'endDate',
+      render: (text) => {
+        return text ? formatDate(text, 'YYYY-MM-DD') : '-';
+      },
     }, {
       title: '发布次数',
       dataIndex: 'historyNumber',
@@ -43,6 +50,14 @@ export default class extends Page {
         }}>{text}</a> : 0;
       },
     }, {
+      title: '开放问答',
+      dataIndex: 'questionStatus',
+      render: (text, record) => {
+        return <Checkbox checked={text > 0} onChange={(e) => {
+          this.changeLibrary(record.id, { questionStatus: e.target.checked ? 1 : 0 });
+        }} />;
+      },
+    }, {
       title: '操作',
       dataIndex: 'handler',
       render: (text, record) => {
@@ -106,9 +121,16 @@ export default class extends Page {
     });
   }
 
+  changeLibrary(id, data) {
+    data.id = id;
+    return Textbook.editLibrary(data).then(() => {
+      this.refresh();
+    });
+  }
+
   renderView() {
     const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
-
+    const { postSwitch = {} } = this.state;
     const quant = getFieldValue('post.quant') || null;
     const ir = getFieldValue('post.ir') || null;
     const rc = getFieldValue('post.rc') || null;
@@ -134,28 +156,47 @@ export default class extends Page {
         onSelect={(keys, rows) => this.tableSelect(keys, rows)}
         selectedKeys={this.state.selectedKeys}
       />
-      {this.state.history && <Modal visible closable title='发布历史' onCancel={() => {
+      {this.state.history && <Modal visible closable title='发布历史' okButtonDisabled onCancel={() => {
         this.close(false, 'history');
       }} onOk={() => {
         this.close(false, 'history');
       }}>
         {this.state.history.map(row => {
-          return <p>{row.content}</p>;
+          const list = [];
+          if (row.quant) {
+            list.push(<p>{formatDate(row.createTime, 'YYYY-MM-DD HH:mm:ss')} - {row.quantContent}</p>);
+          }
+          if (row.ir) {
+            list.push(<p>{formatDate(row.createTime, 'YYYY-MM-DD HH:mm:ss')} - {row.irContent}</p>);
+          }
+          if (row.rc) {
+            list.push(<p>{formatDate(row.createTime, 'YYYY-MM-DD HH:mm:ss')} - {row.rcContent}</p>);
+          }
+          return list;
         })}
       </Modal>}
-      {this.state.post && <Modal visible closable title='发布机经' onCancel={() => {
+      {this.state.post && <Modal visible closable title='发布机经:请上传机经文件,并点击发布' onCancel={() => {
         this.close(false, 'post');
       }} onOk={() => {
         this.submitPost();
       }}>
         <Form>
           {getFieldDecorator('post.libraryId')}
-          <Form.Item label='请上传机经文件,并点击发布'>
-            {getFieldDecorator('post.content')(
+          <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 15 }} label='数学机经'>
+            不更新,使用上一版本
+            <Switch checked={!postSwitch.quant} onChange={() => {
+              postSwitch.quant = !postSwitch.quant;
+              this.setState({ postSwitch });
+            }} />
+            <br />
+            {!postSwitch.quant && !!this.state.post.quantVersion && `版本:${this.state.post.quantVersion}`}
+          </Form.Item>
+          {postSwitch.quant && <Form.Item>
+            {getFieldDecorator('post.quantContent')(
               <Input.TextArea placeholder='更新日志' />,
             )}
-          </Form.Item>
-          <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 15 }} label='数学机经'>
+          </Form.Item>}
+          {postSwitch.quant && <Form.Item>
             {getFieldDecorator('post.quant')(
               <Upload
                 listType="text"
@@ -171,8 +212,22 @@ export default class extends Page {
                 </div>}
               </Upload>,
             )}
-          </Form.Item>
+          </Form.Item>}
           <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 15 }} label='阅读机经'>
+            不更新,使用上一版本
+            <Switch checked={!postSwitch.rc} onChange={() => {
+              postSwitch.rc = !postSwitch.rc;
+              this.setState({ postSwitch });
+            }} />
+            <br />
+            {!postSwitch.rc && !!this.state.post.rcVersion && `版本:${this.state.post.rcVersion}`}
+          </Form.Item>
+          {postSwitch.rc && <Form.Item>
+            {getFieldDecorator('post.rcContent')(
+              <Input.TextArea placeholder='更新日志' />,
+            )}
+          </Form.Item>}
+          {postSwitch.rc && <Form.Item>
             {getFieldDecorator('post.rc')(
               <Upload
                 listType="text"
@@ -188,8 +243,22 @@ export default class extends Page {
                 </div>}
               </Upload>,
             )}
-          </Form.Item>
+          </Form.Item>}
           <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 15 }} label='逻辑机经'>
+            不更新,使用上一版本
+            <Switch checked={!postSwitch.ir} onChange={() => {
+              postSwitch.ir = !postSwitch.ir;
+              this.setState({ postSwitch });
+            }} />
+            <br />
+            {!postSwitch.ir && !!this.state.post.irVersion && `版本:${this.state.post.irVersion}`}
+          </Form.Item>
+          {postSwitch.ir && <Form.Item >
+            {getFieldDecorator('post.irContent')(
+              <Input.TextArea placeholder='更新日志' />,
+            )}
+          </Form.Item>}
+          {postSwitch.ir && <Form.Item>
             {getFieldDecorator('post.ir')(
               <Upload
                 listType="text"
@@ -205,7 +274,7 @@ export default class extends Page {
                 </div>}
               </Upload>,
             )}
-          </Form.Item>
+          </Form.Item>}
         </Form>
       </Modal>}
     </Block>;

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

@@ -1,10 +1,151 @@
 import React from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
+import FilterLayout from '@src/layouts/FilterLayout';
+// import ActionLayout from '@src/layouts/ActionLayout';
+import TableLayout from '@src/layouts/TableLayout';
+import { getMap, formatDate, formatMoney, bindSearch } from '@src/services/Tools';
+import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
+import { ProductTypeMain, RecordBuySource } from '../../../../Constant';
+import { User } from '../../../stores/user';
 
+const ProductTypeMainMap = getMap(ProductTypeMain, 'value', 'label');
+const RecordBuySourceMap = getMap(RecordBuySource, 'value', 'label');
 export default class extends Page {
+  init() {
+    this.itemList = [{
+      key: 'id',
+      type: 'hidden',
+    }, {
+      key: 'transactionNo',
+      type: 'input',
+      name: '请输入支付流水号',
+    }];
+    this.filterF = null;
+    this.filterForm = [{
+      key: 'userId',
+      type: 'select',
+      allowClear: true,
+      name: '用户',
+      select: [],
+      number: true,
+      placeholder: '请输入',
+    }, {
+      key: 'productType',
+      type: 'select',
+      allowClear: true,
+      name: '种类',
+      select: ProductTypeMain,
+    }, {
+      key: 'payMethod',
+      type: 'select',
+      allowClear: true,
+      name: '购买方式',
+      select: RecordBuySource,
+    }, {
+      key: 'orderId',
+      type: 'input',
+      allowClear: true,
+      name: '订单id',
+    }];
+    this.columns = [{
+      title: '订单号',
+      dataIndex: 'id',
+    }, {
+      title: '下单时间',
+      dataIndex: 'createTime',
+      render: (text) => {
+        return text ? formatDate(text) : '';
+      },
+    }, {
+      title: '用户姓名',
+      dataIndex: 'user.nickname',
+    }, {
+      title: '种类',
+      dataIndex: 'productTypes',
+      render: (text) => {
+        return (text || []).map(row => `${ProductTypeMainMap[row]}`).join(', ');
+      },
+    }, {
+      title: '支付金额',
+      dataIndex: 'money',
+      render: (text, record) => {
+        return `${formatMoney(text)}${text !== record.originMoney ? `(${formatMoney(record.originMoney)})` : ''}`;
+      },
+    }, {
+      title: '支付方式',
+      dataIndex: 'payMethod',
+      render: (text) => {
+        return RecordBuySourceMap[text] || '';
+      },
+    }, {
+      title: '支付时间',
+      dataIndex: 'payTime',
+      render: (text) => {
+        return text ? formatDate(text) : '';
+      },
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          <Link to={`/user/order/detail/${record.id}`}>查看</Link>
+          {record.payStatus === 0 && (
+            <a onClick={() => {
+              this.finishActionn(record);
+            }}>确认收款</a>
+          )}
+        </div>;
+      },
+    }];
+    bindSearch(this.filterForm, 'userId', this, (search) => {
+      return User.list(search);
+    }, (row) => {
+      return {
+        title: `${row.nickname}(${row.mobile})`,
+        value: row.id,
+      };
+    }, this.state.search.userId ? Number(this.state.search.userId) : null, null);
+  }
+
+  initData() {
+    User.listOrder(Object.assign({}, this.state.search)).then(result => {
+      this.setTableData(result.list, result.total);
+    });
+  }
+
+  finishAction(row) {
+    asyncForm('确认收款', this.itemList, row, data => {
+      return User.finishOrder(data).then(() => {
+        asyncSMessage('支付成功!');
+        this.refresh();
+      });
+    }).then(component => {
+      this.formF = component;
+    });
+  }
+
   renderView() {
-    return <Block flex />;
+    return <Block flex>
+      <FilterLayout
+        show
+        ref={(ref) => { this.filterF = ref; }}
+        itemList={this.filterForm}
+        data={this.state.search}
+        onChange={data => {
+          this.search(data);
+        }} />
+      <TableLayout
+        columns={this.tableSort(this.columns)}
+        list={this.state.list}
+        pagination={this.state.page}
+        loading={this.props.core.loading}
+        onChange={(pagination, filters, sorter) => this.tableChange(pagination, filters, sorter)}
+        onSelect={(keys, rows) => this.tableSelect(keys, rows)}
+        selectedKeys={this.state.selectedKeys}
+      />
+    </Block>;
   }
 }

+ 106 - 0
front/project/admin/routes/user/orderDetail/page.js

@@ -1,30 +1,136 @@
 import React from 'react';
 import './index.less';
+import { Form, Row, Col } from 'antd';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
+import { getMap, formatDate, formatMoney } from '@src/services/Tools';
+import { RecordBuySource, ServiceKey, ServiceParamMap } from '../../../../Constant';
+import { User } from '../../../stores/user';
 
+const RecordBuySourceMap = getMap(RecordBuySource, 'value', 'label');
+
+const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
+const ServiceParamRelation = getMap(Object.keys(ServiceParamMap).map(key => {
+  return { map: getMap(ServiceParamMap[key], 'value', 'label'), key };
+}), 'key', 'map');
+const promoteInfoMap = {
+  'textbook-half': '半价机经券',
+};
 export default class extends Page {
+  initData() {
+    const { id } = this.params;
+    let handler;
+    if (id) {
+      handler = User.getOrder({ id });
+    } else {
+      handler = Promise.resolve({});
+    }
+    handler
+      .then(result => {
+        const courseMap = getMap(result.courses, 'id');
+        const dataMap = getMap(result.datas, 'id');
+        const packageMap = getMap(result.packages, 'id');
+        this.setState({ courseMap, dataMap, packageMap, data: result });
+      });
+  }
+
   renderProduct() {
+    const { data = {}, courseMap, dataMap, packageMap } = this.state;
+    const { checkouts = [] } = data;
     return <Block>
       <h1>包含商品</h1>
+      <Form>
+        {(checkouts || []).map(row => {
+          let title = '';
+          switch (row.productType) {
+            case 'course':
+              ({ title } = (courseMap[row.productId] || {}));
+              break;
+            case 'data':
+              ({ title } = (dataMap[row.productId] || {}));
+              break;
+            case 'package':
+              ({ title } = (packageMap[row.productId] || {}));
+              break;
+            case 'service':
+              title = `服务:${ServiceKeyMap[row.service]}${row.param ? (ServiceParamRelation[row.service] || {})[row.param] || '' : ''}`;
+              break;
+            default:
+              break;
+          }
+          return <Row>
+            <Col span={10} offset={2}>
+              {title}{row.number > 1 ? `X ${row.number}` : ''}
+            </Col>
+            <Col span={12}>
+              {`${formatMoney(row.money)}${row.money !== row.originMoney ? `(${formatMoney(row.originMoney)})` : ''}`}
+            </Col>
+          </Row>;
+        })}
+      </Form>
     </Block>;
   }
 
   renderMoney() {
+    const { data = {} } = this.state;
     return <Block>
       <h1>订单金额</h1>
+      <Form>
+        <Form.Item labelCol={{ span: 3 }} wrapperCol={{ span: 16 }} label='原价'>
+          {formatMoney(data.originMoney)}
+        </Form.Item>
+        {Object.keys(data.promote || {}).map(key => {
+          const info = data.promote[key];
+          return <Form.Item labelCol={{ span: 3 }} wrapperCol={{ span: 16 }} label='优惠金额'>
+            <Row>
+              <Col span={12} >
+                -{formatMoney(info.originMoney - info.money)}
+              </Col>
+              <Col span={12}>
+                {info.message || promoteInfoMap[`${key}-${info.key}`]}
+              </Col>
+            </Row>
+          </Form.Item>;
+        })}
+        <Form.Item labelCol={{ span: 3 }} wrapperCol={{ span: 16 }} label='总支付'>
+          {formatMoney(data.money)}
+        </Form.Item>
+      </Form>
     </Block>;
   }
 
   renderGift() {
+    const { data = {} } = this.state;
     return <Block>
       <h1>赠送服务</h1>
+      <Form>
+        {(data.gift || []).map(row => {
+          return <Form.Item >
+            {row.message}
+          </Form.Item>;
+        })}
+      </Form>
     </Block>;
   }
 
   renderTime() {
+    const { data = {} } = this.state;
     return <Block>
       <h1>订单时间</h1>
+      <Form>
+        <Form.Item labelCol={{ span: 3 }} wrapperCol={{ span: 16 }} label='提交时间'>
+          {data.createTime ? formatDate(data.createTime) : ''}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 3 }} wrapperCol={{ span: 16 }} label='支付时间'>
+          {data.payTime ? formatDate(data.payTime) : ''}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 3 }} wrapperCol={{ span: 16 }} label='支付方式'>
+          {RecordBuySourceMap[data.payMethod] || ''}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 3 }} wrapperCol={{ span: 16 }} label='付款流水号'>
+          {data.transactionNo}
+        </Form.Item>
+      </Form>
     </Block>;
   }
 

+ 34 - 39
front/project/admin/routes/user/recordAll/page.js

@@ -6,7 +6,7 @@ import FilterLayout from '@src/layouts/FilterLayout';
 import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
 import { getMap, formatDate, formatMoney, bindSearch } from '@src/services/Tools';
-import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
+import { asyncSMessage, asyncForm, asyncDelConfirm } from '@src/services/AsyncTools';
 import { ServiceParamMap, ServiceKey, MobileArea, RecordSource, ProductTypeMain } from '../../../../Constant';
 import { User } from '../../../stores/user';
 import { Course } from '../../../stores/course';
@@ -136,31 +136,15 @@ export default class extends Page {
       select: ProductTypeMain,
       onChange: (value) => {
         // 根据服务
-        if (value === 'service') {
-          this.filterForm[2].disabled = true;
-          this.filterForm[3].disabled = false;
-          this.filterForm[3].select = ServiceParamList[value] || [];
-        } else {
-          this.filterForm[2].disabled = false;
-          this.filterForm[3].disabled = true;
-          bindSearch(this.filterForm, 'productId', this.filterF, (search) => {
-            if (value === 'course') {
-              return Course.list(search);
-            }
-            return Course.listData(search);
-          }, (row) => {
-            return {
-              title: row.title,
-              key: row.id,
-            };
-          }, null, null);
-        }
+        this.changeSearch(this.filterForm, this.filterF, value, null);
+        this.filterF.setFieldsValue({ productId: null, service: null });
       },
     }, {
       key: 'productId',
       type: 'select',
       allowClear: true,
       name: '具体名称',
+      number: true,
       select: [],
     }, {
       key: 'service',
@@ -201,7 +185,7 @@ export default class extends Page {
           return (record.data || {}).title;
         }
         if (record.productType === 'service') {
-          return `${ServiceKeyMap[text]}${(ServiceParamRelation[record.service] || {})[text] || ''}`;
+          return `${ServiceKeyMap[text]}${record.param ? (ServiceParamRelation[record.service] || {})[record.param] || '' : ''}`;
         }
         return '';
       },
@@ -209,7 +193,7 @@ export default class extends Page {
       title: '开通时间',
       dataIndex: 'time',
       render: (text, record) => {
-        return `${record.startTime ? formatDate(record.startTime) : ''} - ${record.endTime ? formatDate(record.endTime) : ''}`;
+        return `${record.startTime ? formatDate(record.startTime) : ''} - ${record.endTime ? formatDate(record.endTime) : ''} `;
       },
     }, {
       title: '开通方式',
@@ -235,35 +219,44 @@ export default class extends Page {
           )}
         </div>;
       },
-    },
-    ];
+    }];
     bindSearch(this.filterForm, 'userId', this, (search) => {
       return User.list(search);
     }, (row) => {
       return {
-        title: `${row.nickname}(${row.mobile})`,
+        title: `${row.nickname} (${row.mobile})`,
         value: row.id,
       };
     }, this.state.search.userId ? Number(this.state.search.userId) : null, null);
 
-    if (this.state.search.productId) {
-      bindSearch(this.filterForm, 'productId', this.filterF, (search) => {
-        if (this.state.search.productType === 'course') {
+    this.changeSearch(this.filterForm, this, this.state.search.productType, this.state.search.productId);
+  }
+
+  initFilter() {
+
+  }
+
+  changeSearch(list, component, key, value) {
+    if (key === 'service') {
+      list[2].disabled = true;
+      list[3].disabled = false;
+    } else if (key === 'course' || key === 'data') {
+      list[2].disabled = false;
+      list[3].disabled = true;
+      bindSearch(list, 'productId', component, (search) => {
+        if (key === 'course') {
           return Course.list(search);
         }
         return Course.listData(search);
       }, (row) => {
         return {
           title: row.title,
-          key: row.id,
+          value: row.id,
         };
-      }, Number(this.state.search.productId), null);
-      this.filterForm[3].disabled = true;
-    } else if (this.state.search.service) {
-      this.filterForm[2].disabled = true;
+      }, value ? Number(value) : null, null);
     } else {
-      this.filterForm[2].disabled = true;
-      this.filterForm[3].disabled = true;
+      list[2].disabled = true;
+      list[3].disabled = true;
     }
   }
 
@@ -304,9 +297,11 @@ export default class extends Page {
   }
 
   stopAction(id) {
-    return User.stopRecord({ id }).then(() => {
-      asyncSMessage('停用成功!');
-      this.refresh();
+    asyncDelConfirm('停用确认', '是否停用选中记录?', () => {
+      return User.stopRecord({ id }).then(() => {
+        asyncSMessage('停用成功!');
+        this.refresh();
+      });
     });
   }
 
@@ -314,7 +309,7 @@ export default class extends Page {
     return <Block flex>
       <FilterLayout
         show
-        ref={(ref) => { this.filterF = ref; }}
+        ref={(ref) => { if (!this.filterF) { this.filterF = ref; } }}
         itemList={this.filterForm}
         data={this.state.search}
         onChange={data => {

+ 23 - 33
front/project/admin/routes/user/recordBuy/page.js

@@ -14,9 +14,9 @@ import { Course } from '../../../stores/course';
 const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
 const ProductTypeMainMap = getMap(ProductTypeMain, 'value', 'label');
 const RecordBuySourceMap = getMap(RecordBuySource, 'value', 'label');
-const ServiceParamList = getMap(Object.keys(ServiceParamMap).map(key => {
-  return { list: ServiceParamMap[key], key };
-}), 'key', 'list');
+// const ServiceParamList = getMap(Object.keys(ServiceParamMap).map(key => {
+//   return { list: ServiceParamMap[key], key };
+// }), 'key', 'list');
 const ServiceParamRelation = getMap(Object.keys(ServiceParamMap).map(key => {
   return { map: getMap(ServiceParamMap[key], 'value', 'label'), key };
 }), 'key', 'map');
@@ -39,31 +39,14 @@ export default class extends Page {
       select: ProductTypeMain,
       onChange: (value) => {
         // 根据服务
-        if (value === 'service') {
-          this.filterForm[2].disabled = true;
-          this.filterForm[3].disabled = false;
-          this.filterForm[3].select = ServiceParamList[value] || [];
-        } else {
-          this.filterForm[2].disabled = false;
-          this.filterForm[3].disabled = true;
-          bindSearch(this.filterForm, 'productId', this.filterF, (search) => {
-            if (value === 'course') {
-              return Course.list(search);
-            }
-            return Course.listData(search);
-          }, (row) => {
-            return {
-              title: row.title,
-              key: row.id,
-            };
-          }, null, null);
-        }
+        this.changeSearch(this.filterForm, this, value, null);
       },
     }, {
       key: 'productId',
       type: 'select',
       allowClear: true,
       name: '具体名称',
+      number: true,
       select: [],
     }, {
       key: 'service',
@@ -109,7 +92,7 @@ export default class extends Page {
           return (record.data || {}).title;
         }
         if (record.productType === 'service') {
-          return `${ServiceKeyMap[text]}${(ServiceParamRelation[record.service] || {})[text] || ''}`;
+          return `${ServiceKeyMap[text]}${record.param ? (ServiceParamRelation[record.service] || {})[record.param] || '' : ''}`;
         }
         return '';
       },
@@ -147,24 +130,30 @@ export default class extends Page {
       };
     }, this.state.search.userId ? Number(this.state.search.userId) : null, null);
 
-    if (this.state.search.productId) {
-      bindSearch(this.filterForm, 'productId', this.filterF, (search) => {
-        if (this.state.search.productType === 'course') {
+    this.changeSearch(this.filterForm, this, this.state.search.productType, this.state.search.productId);
+  }
+
+  changeSearch(list, component, key, value) {
+    if (key === 'service') {
+      list[2].disabled = true;
+      list[3].disabled = false;
+    } else if (key === 'course' || key === 'data') {
+      list[2].disabled = false;
+      list[3].disabled = true;
+      bindSearch(list, 'productId', component, (search) => {
+        if (key === 'course') {
           return Course.list(search);
         }
         return Course.listData(search);
       }, (row) => {
         return {
           title: row.title,
-          key: row.id,
+          value: row.id,
         };
-      }, Number(this.state.search.productId), null);
-      this.filterForm[3].disabled = true;
-    } else if (this.state.search.service) {
-      this.filterForm[2].disabled = true;
+      }, value ? Number(value) : null, null);
     } else {
-      this.filterForm[2].disabled = true;
-      this.filterForm[3].disabled = true;
+      list[2].disabled = true;
+      list[3].disabled = true;
     }
   }
 
@@ -178,6 +167,7 @@ export default class extends Page {
     return <Block flex>
       <FilterLayout
         show
+        ref={(ref) => { this.filterF = ref; }}
         itemList={this.filterForm}
         data={this.state.search}
         onChange={data => {

+ 1 - 1
front/project/admin/stores/system.js

@@ -169,7 +169,7 @@ export default class SystemStore extends BaseStore {
     return this.apiGet('/setting/sentence_info');
   }
 
-  setTSentenceInfo(params) {
+  setSentenceInfo(params) {
     return this.apiPut('/setting/sentence_info', params);
   }
 

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

@@ -41,6 +41,10 @@ export default class TextbookStore extends BaseStore {
     return this.apiPost('/textbook/library/add', params);
   }
 
+  editLibrary(params) {
+    return this.apiPut('/textbook/library/edit', params);
+  }
+
   listHistory(params) {
     return this.apiGet('/textbook/library/history/list', params);
   }

+ 9 - 1
front/project/admin/stores/user.js

@@ -81,6 +81,10 @@ export default class UserStore extends BaseStore {
     return this.apiGet('/user/valid/mobile', params);
   }
 
+  allCourseRecord(params) {
+    return this.apiGet('/user/course/record/all', params);
+  }
+
   listCourseAppointment(params) {
     return this.apiGet('/user/course/appointment/list', params);
   }
@@ -121,8 +125,12 @@ export default class UserStore extends BaseStore {
     return this.apiGet('/user/order/list', params);
   }
 
+  finishOrder(params) {
+    return this.apiPut('/user/order/finish', params);
+  }
+
   stopRecord(params) {
-    return this.apiGet('/user/record/stop', params);
+    return this.apiPut('/user/record/stop', params);
   }
 
   listRecord(params) {

+ 11 - 9
front/project/h5/stores/my.js

@@ -246,38 +246,40 @@ export default class MyStore extends BaseStore {
   }
 
   /**
-   * 添加提问
+   * 添加题目提问
+   * @param {*} userPaperId : 用于获取预习作业,判断权限
    * @param {*} target
    * @param {*} questionModule
    * @param {*} questionNoId
    * @param {*} content
    */
-  addQuestionAsk(userQuestionId, target, questionModule, questionNoId, originContent, content) {
-    return this.apiPost('/my/ask/question', { userQuestionId, target, questionModule, questionNoId, originContent, content });
+  addQuestionAsk(userPaperId, target, questionModule, questionNoId, originContent, content) {
+    return this.apiPost('/my/ask/question', { userPaperId, target, questionModule, questionNoId, originContent, content });
   }
 
   /**
    * 添加题目勘误
-   * @param {*} moduleId
+   * @param {*} questionModule
+   * @param {*} questionNoId
    * @param {*} title
    * @param {*} position
    * @param {*} originContent
    * @param {*} content
    */
-  addFeedbackErrorQuestion(moduleId, title, position, originContent, content) {
-    return this.apiPost('/my/feedback/error/question', { moduleId, title, position, originContent, content });
+  addFeedbackErrorQuestion(questionModule, questionNoId, title, position, originContent, content) {
+    return this.apiPost('/my/feedback/error/question', { questionModule, questionNoId, title, position, originContent, content });
   }
 
   /**
    * 添加数据勘误
-   * @param {*} moduleId
+   * @param {*} dataId
    * @param {*} title
    * @param {*} position
    * @param {*} originContent
    * @param {*} content
    */
-  addErrorData(moduleId, title, position, originContent, content) {
-    return this.apiPost('/my/feedback/error/question', { moduleId, title, position, originContent, content });
+  addErrorData(dataId, title, position, originContent, content) {
+    return this.apiPost('/my/feedback/error/question', { dataId, title, position, originContent, content });
   }
 
   /**

+ 4 - 0
front/project/www/components/ListTable/index.less

@@ -40,6 +40,10 @@
     .right-action {
       float: right;
       margin-right: 24px;
+
+      span {
+        margin-right: 10px;
+      }
     }
   }
 

+ 1 - 0
front/project/www/components/Select/index.js

@@ -31,6 +31,7 @@ export default class Select extends Component {
       }
     }
     const title = list.length > 0 ? list[index].title : '';
+    console.log(list);
     return (
       <div className={`select ${theme || ''}`}>
         <div hidden={!selecting} className="mask" onClick={() => this.close()} />

+ 2 - 1
front/project/www/routes/examination/index.js

@@ -1,3 +1,4 @@
 import main from './main';
+import list from './list';
 
-export default [main];
+export default [main, list];

+ 5 - 0
front/project/www/routes/examination/list/index.less

@@ -1,6 +1,11 @@
 @charset "utf-8";
 
 #examination-list {
+  .ant-breadcrumb {
+    margin-top: 20px;
+    margin-bottom: 20px;
+  }
+
   .code-module {
     padding: 80px 250px;
     text-align: center;

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

@@ -1,246 +1,429 @@
 import React from 'react';
+import { Breadcrumb, Tooltip, Switch } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
-import { asyncConfirm } from '@src/services/AsyncTools';
-import { formatPercent, formatSeconds, formatDate } from '@src/services/Tools';
-import Tabs from '../../../components/Tabs';
-import Module from '../../../components/Module';
+import { asyncConfirm, asyncSMessage } from '@src/services/AsyncTools';
+import { formatPercent, formatDate } from '@src/services/Tools';
 import ListTable from '../../../components/ListTable';
 import ProgressText from '../../../components/ProgressText';
 import IconButton from '../../../components/IconButton';
+import Button from '../../../components/Button';
 import { Main } from '../../../stores/main';
 import { Question } from '../../../stores/question';
-import { QuestionDifficult } from '../../../../Constant';
 
-const LOGIC_NO = 'no';
-const LOGIC_PLACE = 'place';
-const LOGIC_DIFFICULT = 'difficult';
-const LOGIC_ERROR = 'error';
 
 export default class extends Page {
   initState() {
-    this.columns = [
-      {
-        title: '练习册',
-        width: 250,
-        align: 'left',
-        render: (record) => {
-          let progress = 0;
-          if (record.report) {
-            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16">{record.title}</div>
-              <div>
-                <ProgressText progress={progress} size="small" />
-              </div>
+    this.qxCatColumns = [{
+      title: '模考',
+      width: 250,
+      align: 'left',
+      render: (record) => {
+        let progress = 0;
+        if (record.report) {
+          progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16">{record.title}{record.paper && record.paper.paperNo > 0 ? String.fromCharCode(64 + record.paper.paperNo) : ''}</div>
+            <div>
+              <ProgressText progress={progress} size="small" />
             </div>
-          );
-        },
+            {this.state.showPrev && <div className="prev">
+              <div className="night f-s-16">{record.title}{record.prevPaper && record.prevPaper.paperNo > 0 ? String.fromCharCode(64 + record.prevPaper.paperNo) : ''}</div>
+            </div>}
+          </div>
+        );
       },
-      {
-        title: '正确率',
-        width: 150,
-        align: 'left',
-        render: (record) => {
-          let correct = '--';
-          if (record.report) {
-            correct = formatPercent(record.report.userCorrect, record.report.userNumber, false);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16 f-w-b">{correct}</div>
-              <div className="f-s-12">全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}</div>
-            </div>
-          );
-        },
+    }, {
+      title: 'Total',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <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>
+            {this.state.showPrev && record.prevReport && <div className="prev">
+              <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>}
+          </div>
+        );
       },
-      {
-        title: '全站用时',
-        width: 150,
-        align: 'left',
-        render: (record) => {
-          let time = '--';
-          if (record.paper) {
-            time = formatSeconds(record.paper.report.userTime / record.paper.report.userNumber);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16 f-w-b">{time}</div>
-              <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
-            </div>
-          );
-        },
+    }, {
+      title: 'Verbal',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <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>
+            {this.state.showPrev && record.prevReport && <div className="prev">
+              <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>}
+          </div>
+        );
       },
-      {
-        title: '最近做题',
-        width: 150,
-        align: 'left',
-        render: (record) => {
-          if (!record.report) return null;
-          return (
-            <div className="table-row">
-              <div>{formatDate(record.report.updateTime, 'YYYY-MM-DD')}</div>
-              <div>{formatDate(record.report.updateTime, 'HH:mm')}</div>
-            </div>
-          );
-        },
+    }, {
+      title: 'Quant',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <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>
+            {this.state.showPrev && record.prevReport && <div className="prev">
+              <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>}
+          </div>
+        );
       },
-      {
-        title: '操作',
-        width: 180,
-        align: 'left',
-        render: (record) => {
-          return (
-            <div className="table-row p-t-1">
-              {!record.report && <IconButton type="start" tip="Start" onClick={() => {
-                Question.startLink('exercise', record);
-              }} />}
-              {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
-                Question.continueLink('exercise', record);
+    }, {
+      title: 'IR',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <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>
+            {this.state.showPrev && record.prevReport && <div className="prev">
+              <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>}
+          </div>
+        );
+      },
+    }, {
+      title: '做题时间',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <div>{record.report && formatDate(record.report.updateTime, 'YYYY-MM-DD')}</div>
+            <div>{record.report && formatDate(record.report.updateTime, 'HH:mm')}</div>
+            {this.state.showPrev && record.prevReport && <div className="prev">
+              <div>{formatDate(record.prevReport.updateTime, 'YYYY-MM-DD')}</div>
+              <div>{formatDate(record.prevReport.updateTime, 'HH:mm')}</div>
+            </div>}
+          </div>
+        );
+      },
+    }, {
+      title: '操作',
+      width: 180,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row p-t-1">
+            {!record.report && <IconButton type="start" tip="Start" onClick={() => {
+              Question.startLink('examination', record);
+            }} />}
+            {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
+              Question.continueLink('examination', record);
+            }} />}
+          </div>
+        );
+      },
+    }, {
+      title: '报告',
+      width: 30,
+      align: 'right',
+      render: (record) => {
+        return (
+          <div className="table-row p-t-1">
+            {record.report && record.report.isFinish && <IconButton type="report" tip="Report" onClick={() => {
+              Question.reportLink(record);
+            }} />}
+            {this.state.showPrev && <div className="prev">
+              {record.prevReport && <IconButton type="report" tip="Report" onClick={() => {
+                Question.reportPrevLink(record);
               }} />}
-              <IconButton type="restart" tip="Restart" onClick={() => {
-                this.restart(record);
-              }} />
+            </div>}
+          </div>
+        );
+      },
+    }];
+    this.catColumns = [{
+      title: '模考',
+      width: 250,
+      align: 'left',
+      render: (record) => {
+        let progress = 0;
+        if (record.report) {
+          progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16">{record.title}{record.paper && record.paper.paperNo > 0 ? String.fromCharCode(64 + record.paper.paperNo) : ''}</div>
+            <div>
+              <ProgressText progress={progress} size="small" />
             </div>
-          );
-        },
+          </div>
+        );
+      },
+    }, {
+      title: 'Total',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <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>
+        );
+      },
+    }, {
+      title: 'Verbal',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <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>
+        );
+      },
+    }, {
+      title: 'Quant',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <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>
+        );
+      },
+    }, {
+      title: 'IR',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <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>
+        );
       },
-      {
-        title: '报告',
-        width: 30,
-        align: 'right',
-        render: (record) => {
-          if (!record.report || !record.report.isFinish) return null;
-          return (
-            <div className="table-row p-t-1">
-              <IconButton type="report" tip="Report" onClick={() => {
-                Question.reportLink(record);
-              }} />
+    }, {
+      title: '做题时间',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <div>{record.report && formatDate(record.report.updateTime, 'YYYY-MM-DD')}</div>
+            <div>{record.report && formatDate(record.report.updateTime, 'HH:mm')}</div>
+          </div>
+        );
+      },
+    }, {
+      title: '操作',
+      width: 180,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row p-t-1">
+            {!record.report && <IconButton type="start" tip="Start" onClick={() => {
+              Question.startLink('examination', record);
+            }} />}
+            {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
+              Question.continueLink('examination', record);
+            }} />}
+            <IconButton type="restart" tip="Restart" onClick={() => {
+              this.restart(record);
+            }} />
+          </div>
+        );
+      },
+    }, {
+      title: '报告',
+      width: 30,
+      align: 'right',
+      render: (record) => {
+        return (
+          <div className="table-row p-t-1">
+            {record.report && record.report.isFinish && <IconButton type="report" tip="Report" onClick={() => {
+              Question.reportLink(record);
+            }} />}
+            {this.state.showPrev && <div className="prev">
+              {record.prevReport && <IconButton type="report" tip="Report" onClick={() => {
+                Question.reportPrevLink(record);
+              }} />}
+            </div>}
+          </div>
+        );
+      },
+    }];
+    this.columns = [{
+      title: '模考',
+      width: 250,
+      align: 'left',
+      render: (record) => {
+        let progress = 0;
+        if (record.report) {
+          progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16">{record.title}</div>
+            <div>
+              <ProgressText progress={progress} times={record.paper ? record.paper.times : 0} size="small" />
             </div>
-          );
-        },
+          </div>
+        );
+      },
+    }, {
+      title: 'Total',
+      width: 150,
+      align: 'left',
+      render: () => {
+        return (
+          <div className="table-row">
+            <div className="f-s-12">仅CAT模考</div>
+          </div>
+        );
+      },
+    }, {
+      title: 'Verbal',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <div className="f-s-12">{record.report ? formatPercent(record.report.setting.number.verbal, this.nums.verbal.number, false) : '0%'}</div>
+          </div>
+        );
+      },
+    }, {
+      title: 'Quant',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <div className="f-s-12">{record.report ? formatPercent(record.report.setting.number.quant, this.nums.quant.number, false) : '0%'}</div>
+          </div>
+        );
+      },
+    }, {
+      title: 'IR',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row">
+            <div className="f-s-12">{record.report ? formatPercent(record.report.setting.number.ir, this.nums.ir.number, false) : '0%'}</div>
+          </div>
+        );
+      },
+    }, {
+      title: '做题时间',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        if (!record.report) return null;
+        return (
+          <div className="table-row">
+            <div>{formatDate(record.report.updateTime, 'YYYY-MM-DD')}</div>
+            <div>{formatDate(record.report.updateTime, 'HH:mm')}</div>
+          </div>
+        );
+      },
+    }, {
+      title: '操作',
+      width: 180,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row p-t-1">
+            {!record.report && <IconButton type="start" tip="Start" onClick={() => {
+              Question.startLink('examination', record);
+            }} />}
+            {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
+              Question.continueLink('examination', record);
+            }} />}
+            <IconButton type="restart" tip="Restart" onClick={() => {
+              this.restart(record);
+            }} />
+          </div>
+        );
+      },
+    }, {
+      title: '报告',
+      width: 30,
+      align: 'right',
+      render: (record) => {
+        if (!record.report || !record.report.isFinish) return null;
+        return (
+          <div className="table-row p-t-1">
+            <IconButton type="report" tip="Report" onClick={() => {
+              Question.reportLink(record);
+            }} />
+          </div>
+        );
       },
-    ];
-    this.placeList = [];
+    }];
     this.inited = false;
     return {
-      logic: LOGIC_NO,
-      logicExtend: '',
-      logics: [{
-        key: LOGIC_NO,
-        title: '按顺序练习',
-      }, {
-        key: LOGIC_PLACE,
-        title: '按考点练习',
-      }, {
-        key: LOGIC_DIFFICULT,
-        title: '按难度练习',
-      }, {
-        key: LOGIC_ERROR,
-        title: '按易错度练习',
-      }],
     };
   }
 
   init() {
     const { id } = this.params;
-    Main.getExerciseParent(id).then(result => {
-      const navs = result;
-      this.inited = true;
-      this.setState({ navs });
+    Main.getExaminationParent(id).then(result => {
+      const navs = result.map(row => {
+        row.title = `${row.titleZh}${row.titleEn}`;
+        return row;
+      });
+      this.cat = navs.filter(row => row.isAdapt > 0).length > 0;
+      this.qxCat = navs.filter(row => row.isAdapt > 1).length > 0;
+      Main.getExaminationNumber().then(nums => {
+        this.nums = nums;
+        this.inited = true;
+        this.setState({ navs });
+        this.refreshData();
+      });
     });
   }
 
   initData() {
     const data = Object.assign(this.state, this.state.search);
     this.setState(data);
-    this.refreshData();
-  }
-
-  refreshData(newLogic) {
-    const { logic } = this.state;
-    let handler = null;
-    switch (newLogic || logic) {
-      case LOGIC_PLACE:
-        handler = this.refreshPlace();
-        break;
-      case LOGIC_DIFFICULT:
-        handler = this.refreshDifficult();
-        break;
-      default:
-        handler = Promise.resolve();
-    }
-    handler.then(() => {
-      this.refreshExercise();
-    });
+    if (this.inited) this.refreshData();
   }
 
-  refreshPlace() {
-    const { id } = this.params;
-    let handler;
-    if (this.placeList.length > 0) {
-      this.setState({ logicExtends: this.placeList });
-      handler = Promise.resolve();
-    } else {
-      handler = Question.getExercisePlace(id).then(result => {
-        this.placeList = result.map(row => {
-          return {
-            name: row,
-            key: row,
-          };
-        });
-        this.setState({ logicExtends: this.placeList });
+  refreshData() {
+    if (this.cat) {
+      Question.getExaminationInfo().then((result) => {
+        this.setState({ examination: result });
       });
     }
-    return handler.then(() => {
-      let { logicExtend } = this.state;
-      if (logicExtend === '') {
-        logicExtend = this.placeList[0].key;
-        this.setState({ logicExtend });
-      }
-    });
-  }
-
-  refreshDifficult() {
-    let { logicExtend } = this.state;
-    this.setState({
-      logicExtends: QuestionDifficult.map(difficult => {
-        difficult.name = difficult.label;
-        difficult.key = difficult.value;
-        return difficult;
-      }),
-    });
-    return Promise.resolve().then(() => {
-      if (logicExtend === '') {
-        logicExtend = QuestionDifficult[0].key;
-        this.setState({ logicExtend });
-      }
-    });
+    this.refreshExamination();
   }
 
-  refreshExercise() {
-    const { logic, logicExtend } = this.state;
-    Question.getExerciseList(Object.assign({ structId: this.params.id, logic, logicExtend }, this.state.search))
+  refreshExamination() {
+    Question.getExaminationList(Object.assign({ structId: this.params.id }, this.state.search))
       .then((result) => {
         this.setState({ list: result.list, total: result.total });
+      })
+      .catch(err => {
+        asyncSMessage(err.message, 'warn');
+        // linkTo('/examination');
       });
   }
 
-  onChangeTab(key, value) {
-    const { logic } = this.state;
-    const data = {};
-    if (key === 'logicExtend') {
-      data.logic = logic;
-      data.logicExtend = value;
-    } else {
-      data.logic = value;
-    }
-    // this.refreshData(tab);
-    this.refreshQuery(data);
-  }
-
   restart(item) {
     asyncConfirm('提示', '是否重置', () => {
       Question.restart(item.paper.id).then(() => {
@@ -249,35 +432,64 @@ export default class extends Page {
     });
   }
 
+  resetCat() {
+    asyncConfirm('提示', '是否重置', () => {
+      Question.resetCat().then(() => {
+        this.refresh();
+      });
+    });
+  }
+
   renderView() {
-    const { logic, logicExtend, logics = [], logicExtends = [], list } = this.state;
+    const { list, navs, search, examination = {} } = this.state;
+    const { finish } = search;
     return (
       <div>
         <div className="content">
-          <Module className="m-t-2">
-            <Tabs
-              active={logic}
-              border
-              width="180px"
-              space="0"
-              tabs={logics}
-              onChange={(key) => {
-                this.onChangeTab('logic', key);
-              }}
-            />
-            {logicExtends.length > 0 && <Tabs
-              active={logicExtend}
-              type="text"
-              tabs={logicExtends}
-              onChange={(key) => {
-                this.onChangeTab('logicExtend', key);
-              }}
-            />}
-          </Module>
-
+          <Breadcrumb separator=">">
+            <Breadcrumb.Item href="/examination">模考</Breadcrumb.Item>
+            {(navs || []).map(row => {
+              return <Breadcrumb.Item>{row.title}</Breadcrumb.Item>;
+            })}
+          </Breadcrumb>
           <ListTable
+            rightAction={<div hidden={!this.qxCat}>
+              <span>有效期至:{examination.expireTime && formatDate(examination.expireTime, 'YYYY-MM-DD')}</span>
+              {examination.reset && <span>第一遍模考成绩<Switch checked={this.state.showPrev} onChange={() => {
+                this.setState({ showPrev: !this.state.showPrev });
+              }} /></span>}
+              {!examination.reset && examination.canReset && <Button size="small" radius onClick={() => {
+                this.resetExamination();
+              }}>
+                Reset</Button>}
+              {!examination.reset && !examination.canReset && <Tooltip overlayClassName="gray" placement="top" title="全部模考做完才可重置">
+                <a>
+                  <Button size="small" disabled radius>
+                    Reset
+                  </Button>
+                </a >
+              </Tooltip>}
+            </div>}
+            filters={[
+              {
+                type: 'radio',
+                checked: finish,
+                list: [{ key: '0', title: '未完成' }, { key: '1', title: '已完成' }],
+                onChange: item => {
+                  if (item.key === finish) {
+                    this.search({ finish: null });
+                  } else if (item.key === '0') {
+                    this.search({ finish: 0 });
+                  } else if (item.key === '1') {
+                    this.search({ finish: 1 });
+                  } else {
+                    this.search({ finish: null });
+                  }
+                },
+              },
+            ]}
             data={list}
-            columns={this.columns}
+            columns={this.qxCat ? this.qxCatColumns : (this.cat ? this.catColumns : this.columns)}
           />
         </div>
       </div>

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

@@ -2,13 +2,11 @@ import React from 'react';
 import './index.less';
 import Page from '@src/containers/Page';
 import { asyncConfirm, asyncSMessage } from '@src/services/AsyncTools';
-import { formatTreeData, formatSeconds, formatPercent, formatDate } from '@src/services/Tools';
+import { formatTreeData, formatPercent, formatDate } from '@src/services/Tools';
 import Panel, { WaitPanel, BuyPanel, SmallPanel, SmallWaitPanel, SmallBuyPanel } from '../../../components/Panel';
 import Tabs from '../../../components/Tabs';
 import Module from '../../../components/Module';
 import Division from '../../../components/Division';
-import ProgressText from '../../../components/ProgressText';
-import IconButton from '../../../components/IconButton';
 import { Main } from '../../../stores/main';
 // import { My } from '../../../stores/my';
 import { Question } from '../../../stores/question';
@@ -16,104 +14,11 @@ import { Textbook } from '../../../stores/textbook';
 // import { User } from '../../../stores/user';
 // import { CourseModuleShow, CourseModule } from '../../../../Constant';
 import { Order } from '../../../stores/order';
+import { User } from '../../../stores/user';
 
 const TEXTBOOK = 'textbook';
 
 export default class extends Page {
-  constructor(props) {
-    super(props);
-    this.examinationColumns = [{
-      title: '练习册',
-      width: 250,
-      align: 'left',
-      render: item => {
-        return (
-          <div className="table-row">
-            <div className="night f-s-16">{item.title}</div>
-            <div>
-              <ProgressText
-                progress={item.report.id ? formatPercent(item.repport.userNumber, item.report.questionNumber) : 0}
-                size="small"
-              />
-            </div>
-          </div>
-        );
-      },
-    },
-    {
-      title: '正确率',
-      width: 150,
-      align: 'left',
-      render: item => {
-        return (
-          <div className="table-row">
-            <div className="night f-s-16 f-w-b">--</div>
-            <div className="f-s-12">{formatPercent(item.stat.totalCorrect, item.stat.totalNumber, false)}</div>
-          </div>
-        );
-      },
-    },
-    {
-      title: '全站用时',
-      width: 150,
-      align: 'left',
-      render: item => {
-        return (
-          <div className="table-row">
-            <div className="night f-s-16 f-w-b">--</div>
-            <div className="f-s-12">全站{formatSeconds(item.stat.totalTime / item.stat.totalNumber)}</div>
-          </div>
-        );
-      },
-    },
-    {
-      title: '最近做题',
-      width: 150,
-      align: 'left',
-      render: () => {
-        return (
-          <div className="table-row">
-            <div>2019-04-28</div>
-            <div>07:30</div>
-          </div>
-        );
-      },
-    },
-    {
-      title: '操作',
-      width: 180,
-      align: 'left',
-      render: item => {
-        return (
-          <div className="table-row p-t-1">
-            {!item.report && <IconButton type="start" tip="Start" onClick={() => Question.startLink('preview', item)} />}
-            {item.report.id && !item.report.isFinish && (
-              <IconButton
-                className="m-r-2"
-                type="continue"
-                tip="Continue"
-                onClick={() => Question.continueLink('preview', item)}
-              />
-            )}
-            {item.report.id && <IconButton type="restart" tip="Restart" onClick={() => this.restart('preview', item)} />}
-          </div>
-        );
-      },
-    },
-    {
-      title: '报告',
-      width: 30,
-      align: 'right',
-      render: item => {
-        return (
-          <div className="table-row p-t-1">
-            {item.report.isFinish && <IconButton type="report" tip="Report" onClick={() => Question.reportLink(item)} />}
-          </div>
-        );
-      },
-    }];
-  }
-
   initState() {
     this.examinationProgress = {};
     this.textbookProgress = {};
@@ -145,7 +50,7 @@ export default class extends Page {
   initData() {
     const data = Object.assign(this.state, this.state.search);
     if (!data.tab1) {
-      data.tab1 = TEXTBOOK;
+      data.tab1 = 'cat';
     }
     this.setState(data);
     if (this.inited) this.refreshData();
@@ -275,11 +180,15 @@ export default class extends Page {
   }
 
   examinationList(item) {
-    linkTo(`/examination/list/${item.id}`);
+    User.needLogin().then(() => {
+      linkTo(`/examination/list/${item.id}`);
+    });
   }
 
-  textbookList(item) {
-    linkTo(`/textbook/list/${item.id}`);
+  textbookList(item, isLatest) {
+    User.needLogin().then(() => {
+      linkTo(`/textbook/list?logic=${encodeURIComponent(item.logic)}&latest=${isLatest ? 1 : 0}`);
+    });
   }
 
   renderView() {
@@ -321,6 +230,9 @@ export default class extends Page {
                   title={struct.isLatest ? '最新' : '往期'}
                   col="3"
                   data={struct}
+                  onOpen={() => {
+                    this.open(struct.unUseRecord.id);
+                  }}
                 />;
               }
               return <BuyPanel
@@ -335,7 +247,7 @@ export default class extends Page {
               col="3"
               data={struct}
               onClick={(item) => {
-                this.textbookList(item);
+                this.textbookList(item, struct.isLatest);
               }}
             />;
           })}
@@ -363,7 +275,7 @@ export default class extends Page {
                 title={struct.title}
                 data={struct}
                 onOpen={() => {
-                  this.open(struct.unUseRecord);
+                  this.open(struct.unUseRecord.id);
                 }}
               />;
             }

+ 4 - 0
front/project/www/routes/exercise/list/index.less

@@ -1,6 +1,10 @@
 @charset "utf-8";
 
 #exercise-list {
+  .ant-breadcrumb {
+    margin-top: 20px;
+  }
+
   .code-module {
     padding: 80px 250px;
     text-align: center;

+ 106 - 102
front/project/www/routes/exercise/list/page.js

@@ -1,4 +1,5 @@
 import React from 'react';
+import { Breadcrumb } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import { asyncConfirm } from '@src/services/AsyncTools';
@@ -19,110 +20,102 @@ const LOGIC_ERROR = 'error';
 
 export default class extends Page {
   initState() {
-    this.columns = [
-      {
-        title: '练习册',
-        width: 250,
-        align: 'left',
-        render: (record) => {
-          let progress = 0;
-          if (record.report) {
-            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16">{record.title}</div>
-              <div>
-                <ProgressText progress={progress} size="small" />
-              </div>
+    this.columns = [{
+      title: '练习册',
+      width: 250,
+      align: 'left',
+      render: (record) => {
+        let progress = 0;
+        if (record.report) {
+          progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16">{record.title}</div>
+            <div>
+              <ProgressText progress={progress} size="small" />
             </div>
-          );
-        },
+          </div>
+        );
       },
-      {
-        title: '正确率',
-        width: 150,
-        align: 'left',
-        render: (record) => {
-          let correct = '--';
-          if (record.report) {
-            correct = formatPercent(record.report.userCorrect, record.report.userNumber, false);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16 f-w-b">{correct}</div>
-              <div className="f-s-12">全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}</div>
-            </div>
-          );
-        },
+    }, {
+      title: '正确率',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        let correct = '--';
+        if (record.report) {
+          correct = formatPercent(record.report.userCorrect, record.report.userNumber, false);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16 f-w-b">{correct}</div>
+            <div className="f-s-12">全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}</div>
+          </div>
+        );
       },
-      {
-        title: '全站用时',
-        width: 150,
-        align: 'left',
-        render: (record) => {
-          let time = '--';
-          if (record.paper) {
-            time = formatSeconds(record.paper.report.userTime / record.paper.report.userNumber);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16 f-w-b">{time}</div>
-              <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
-            </div>
-          );
-        },
+    }, {
+      title: '全站用时',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        let time = '--';
+        if (record.paper) {
+          time = formatSeconds(record.paper.report.userTime / record.paper.report.userNumber);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16 f-w-b">{time}</div>
+            <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
+          </div>
+        );
       },
-      {
-        title: '最近做题',
-        width: 150,
-        align: 'left',
-        render: (record) => {
-          if (!record.report) return null;
-          return (
-            <div className="table-row">
-              <div>{formatDate(record.report.updateTime, 'YYYY-MM-DD')}</div>
-              <div>{formatDate(record.report.updateTime, 'HH:mm')}</div>
-            </div>
-          );
-        },
+    }, {
+      title: '最近做题',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        const time = record.report ? record.report.updateTime : record.paper ? record.paper.latestTime : null;
+        return (
+          <div className="table-row">
+            <div>{time && formatDate(time, 'YYYY-MM-DD')}</div>
+            <div>{time && formatDate(time, 'HH:mm')}</div>
+          </div>
+        );
       },
-      {
-        title: '操作',
-        width: 180,
-        align: 'left',
-        render: (record) => {
-          return (
-            <div className="table-row p-t-1">
-              {!record.report && <IconButton type="start" tip="Start" onClick={() => {
-                Question.startLink('exercise', record);
-              }} />}
-              {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
-                Question.continueLink('exercise', record);
-              }} />}
-              <IconButton type="restart" tip="Restart" onClick={() => {
-                this.restart(record);
-              }} />
-            </div>
-          );
-        },
+    }, {
+      title: '操作',
+      width: 180,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row p-t-1">
+            {!record.report && <IconButton type="start" tip="Start" onClick={() => {
+              Question.startLink('exercise', record);
+            }} />}
+            {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
+              Question.continueLink('exercise', record);
+            }} />}
+            <IconButton type="restart" tip="Restart" onClick={() => {
+              this.restart(record);
+            }} />
+          </div>
+        );
       },
-      {
-        title: '报告',
-        width: 30,
-        align: 'right',
-        render: (record) => {
-          if (!record.report || !record.report.isFinish) return null;
-          return (
-            <div className="table-row p-t-1">
-              <IconButton type="report" tip="Report" onClick={() => {
-                Question.reportLink(record);
-              }} />
-            </div>
-          );
-        },
+    }, {
+      title: '报告',
+      width: 30,
+      align: 'right',
+      render: (record) => {
+        return (
+          <div className="table-row p-t-1">
+            {record.report && record.report.isFinish && <IconButton type="report" tip="Report" onClick={() => {
+              Question.reportLink(record);
+            }} />}
+          </div>
+        );
       },
-    ];
+    }];
     this.placeList = [];
     this.inited = false;
     return {
@@ -147,7 +140,10 @@ export default class extends Page {
   init() {
     const { id } = this.params;
     Main.getExerciseParent(id).then(result => {
-      const navs = result;
+      const navs = result.map(row => {
+        row.title = `${row.titleZh}${row.titleEn}`;
+        return row;
+      });
       this.inited = true;
       this.setState({ navs });
     });
@@ -250,11 +246,17 @@ export default class extends Page {
   }
 
   renderView() {
-    const { logic, logicExtend, logics = [], logicExtends = [], list, search } = this.state;
+    const { logic, logicExtend, logics = [], logicExtends = [], list, search, navs } = this.state;
     const { finish } = search;
     return (
       <div>
         <div className="content">
+          <Breadcrumb separator=">">
+            <Breadcrumb.Item href="/exercise">练习</Breadcrumb.Item>
+            {(navs || []).map(row => {
+              return <Breadcrumb.Item>{row.title}</Breadcrumb.Item>;
+            })}
+          </Breadcrumb>
           <Module className="m-t-2">
             <Tabs
               active={logic}
@@ -280,11 +282,13 @@ export default class extends Page {
               {
                 type: 'radio',
                 checked: finish,
-                list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
+                list: [{ key: '0', title: '未完成' }, { key: '1', title: '已完成' }],
                 onChange: item => {
-                  if (item.key === 0) {
+                  if (item.key === finish) {
+                    this.search({ finish: null });
+                  } else if (item.key === '0') {
                     this.search({ finish: 0 });
-                  } else if (item.key === 1) {
+                  } else if (item.key === '1') {
                     this.search({ finish: 1 });
                   } else {
                     this.search({ finish: null });

+ 217 - 221
front/project/www/routes/exercise/main/page.js

@@ -35,232 +35,219 @@ const PREVIEW_LIST = 'PREVIEW_LIST';
 
 const CourseModuleMap = getMap(CourseModule, 'value', 'label');
 
-const exerciseColumns = [
-  {
-    title: '练习册',
-    width: 250,
-    align: 'left',
-    render: item => {
-      return (
-        <div className="table-row">
-          <div className="night f-s-16">{item.title}</div>
-          <div>
-            <ProgressText
-              progress={item.report.id ? formatPercent(item.repport.userNumber, item.report.questionNumber) : 0}
-              size="small"
-            />
-          </div>
+const exerciseColumns = [{
+  title: '练习册',
+  width: 250,
+  align: 'left',
+  render: record => {
+    let progress = 0;
+    if (record.report) {
+      progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+    }
+    return (
+      <div className="table-row">
+        <div className="night f-s-16">{record.title}</div>
+        <div>
+          <ProgressText progress={progress} times={record.paper ? record.paper.times : 0} size="small" />
         </div>
-      );
-    },
+      </div>
+    );
   },
-  {
-    title: '正确率',
-    width: 150,
-    align: 'left',
-    render: item => {
-      return (
-        <div className="table-row">
-          <div className="night f-s-16 f-w-b">--</div>
-          <div className="f-s-12">{formatPercent(item.stat.totalCorrect, item.stat.totalNumber, false)}</div>
-        </div>
-      );
-    },
+}, {
+  title: '正确率',
+  width: 150,
+  align: 'left',
+  render: item => {
+    return (
+      <div className="table-row">
+        <div className="night f-s-16 f-w-b">--</div>
+        <div className="f-s-12">{formatPercent(item.stat.totalCorrect, item.stat.totalNumber, false)}</div>
+      </div>
+    );
   },
-  {
-    title: '全站用时',
-    width: 150,
-    align: 'left',
-    render: item => {
-      return (
-        <div className="table-row">
-          <div className="night f-s-16 f-w-b">--</div>
-          <div className="f-s-12">全站{formatSeconds(item.stat.totalTime / item.stat.totalNumber)}</div>
-        </div>
-      );
-    },
+}, {
+  title: '全站用时',
+  width: 150,
+  align: 'left',
+  render: record => {
+    let time = '--';
+    if (record.paper) {
+      time = formatSeconds(record.paper.report.userTime / record.paper.report.userNumber);
+    }
+    return (
+      <div className="table-row">
+        <div className="night f-s-16 f-w-b">{time}</div>
+        <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
+      </div>
+    );
   },
-  {
-    title: '最近做题',
-    width: 150,
-    align: 'left',
-    render: () => {
-      return (
-        <div className="table-row">
-          <div>2019-04-28</div>
-          <div>07:30</div>
-        </div>
-      );
-    },
+}, {
+  title: '最近做题',
+  width: 150,
+  align: 'left',
+  render: (record) => {
+    const time = record.report ? record.report.updateTime : record.paper ? record.paper.latestTime : null;
+    return (
+      <div className="table-row">
+        <div>{time && formatDate(time, 'YYYY-MM-DD')}</div>
+        <div>{time && formatDate(time, 'HH:mm')}</div>
+      </div>
+    );
   },
-  {
-    title: '操作',
-    width: 180,
-    align: 'left',
-    render: item => {
-      return (
-        <div className="table-row p-t-1">
-          {!item.report && <IconButton type="start" tip="Start" onClick={() => Question.startLink('preview', item)} />}
-          {item.report.id && !item.report.isFinish && (
-            <IconButton
-              className="m-r-2"
-              type="continue"
-              tip="Continue"
-              onClick={() => Question.continueLink('preview', item)}
-            />
-          )}
-          {item.report.id && <IconButton type="restart" tip="Restart" onClick={() => this.restart('preview', item)} />}
-        </div>
-      );
-    },
+}, {
+  title: '操作',
+  width: 180,
+  align: 'left',
+  render: record => {
+    return (
+      <div className="table-row p-t-1">
+        {!record.report && <IconButton type="start" tip="Start" onClick={() => {
+          Question.startLink('exercise', record);
+        }} />}
+        {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
+          Question.continueLink('exercise', record);
+        }} />}
+        <IconButton type="restart" tip="Restart" onClick={() => {
+          this.restart(record);
+        }} />
+      </div>
+    );
   },
-  {
-    title: '报告',
-    width: 30,
-    align: 'right',
-    render: item => {
-      return (
-        <div className="table-row p-t-1">
-          {item.report.isFinish && <IconButton type="report" tip="Report" onClick={() => Question.reportLink(item)} />}
-        </div>
-      );
-    },
+}, {
+  title: '报告',
+  width: 30,
+  align: 'right',
+  render: record => {
+    return (
+      <div className="table-row p-t-1">
+        {record.report && record.report.isFinish && <IconButton type="report" tip="Report" onClick={() => {
+          Question.reportLink(record);
+        }} />}
+      </div>
+    );
   },
-];
+}];
 
 export default class extends Page {
   constructor(props) {
     super(props);
-    this.sentenceColums = [
-      {
-        title: '练习册',
-        width: 250,
-        align: 'left',
-        render: record => {
-          let progress = 0;
-          if (record.report) {
-            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16">{record.title}</div>
-              <div>
-                <ProgressText progress={progress} size="small" />
-              </div>
-            </div>
-          );
-        },
-      },
-      {
-        title: '正确率',
-        width: 150,
-        align: 'left',
-        render: record => {
-          let correct = '--';
-          if (record.report) {
-            correct = formatPercent(record.report.userCorrect, record.report.userNumber, false);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16 f-w-b">{correct}</div>
-              <div className="f-s-12">
-                全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}
-              </div>
+    this.sentenceColums = [{
+      title: '练习册',
+      width: 250,
+      align: 'left',
+      render: record => {
+        let progress = 0;
+        if (record.report) {
+          progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16">{record.title}</div>
+            <div>
+              <ProgressText progress={progress} times={record.paper ? record.paper.times : 0} size="small" />
             </div>
-          );
-        },
+          </div>
+        );
       },
-      {
-        title: '全站用时',
-        width: 150,
-        align: 'left',
-        render: record => {
-          let time = '--';
-          if (record.report) {
-            time = formatSeconds(record.report.userTime / record.report.userNumber);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16 f-w-b">{time}</div>
-              <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
+    }, {
+      title: '正确率',
+      width: 150,
+      align: 'left',
+      render: record => {
+        let correct = '--';
+        if (record.report) {
+          correct = formatPercent(record.report.userCorrect, record.report.userNumber, false);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16 f-w-b">{correct}</div>
+            <div className="f-s-12">
+              全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}
             </div>
-          );
-        },
+          </div>
+        );
       },
-      {
-        title: '最近做题',
-        width: 150,
-        align: 'left',
-        render: record => {
-          const time = record.report ? record.report.updateTime : record.paper ? record.paper.latestTime : null;
-          if (!time) return null;
-          return (
-            <div className="table-row">
-              <div>{formatDate(time, 'YYYY-MM-DD')}</div>
-              <div>{formatDate(time, 'HH:mm')}</div>
-            </div>
-          );
-        },
+    }, {
+      title: '全站用时',
+      width: 150,
+      align: 'left',
+      render: record => {
+        let time = '--';
+        if (record.report) {
+          time = formatSeconds(record.report.userTime / record.report.userNumber);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16 f-w-b">{time}</div>
+            <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
+          </div>
+        );
       },
-      {
-        title: '操作',
-        width: 180,
-        align: 'left',
-        render: record => {
-          return (
-            <div className="table-row p-t-1">
-              {!record.report && (
-                <IconButton
-                  type="start"
-                  tip="Start"
-                  onClick={() => {
-                    Question.startLink('sentence', record);
-                  }}
-                />
-              )}
-              {record.report && !record.report.isFinish && (
-                <IconButton
-                  className="m-r-2"
-                  type="continue"
-                  tip="Continue"
-                  onClick={() => {
-                    Question.continueLink('sentence', record);
-                  }}
-                />
-              )}
-              {record.report && !!record.report.isFinish && (
-                <IconButton
-                  type="restart"
-                  tip="Restart"
-                  onClick={() => {
-                    this.restart(record);
-                  }}
-                />
-              )}
-            </div>
-          );
-        },
+    }, {
+      title: '最近做题',
+      width: 150,
+      align: 'left',
+      render: record => {
+        const time = record.report ? record.report.updateTime : record.paper ? record.paper.latestTime : null;
+        return (
+          <div className="table-row">
+            <div>{time && formatDate(time, 'YYYY-MM-DD')}</div>
+            <div>{time && formatDate(time, 'HH:mm')}</div>
+          </div>
+        );
       },
-      {
-        title: '报告',
-        width: 30,
-        align: 'right',
-        render: record => {
-          if (!record.report || !record.report.isFinish) return null;
-          return (
-            <div className="table-row p-t-1">
+    }, {
+      title: '操作',
+      width: 180,
+      align: 'left',
+      render: record => {
+        return (
+          <div className="table-row p-t-1">
+            {!record.report && (
               <IconButton
-                type="report"
-                tip="Report"
+                type="start"
+                tip="Start"
                 onClick={() => {
-                  Question.reportLink(record);
+                  Question.startLink('sentence', record);
                 }}
               />
-            </div>
-          );
-        },
+            )}
+            {record.report && !record.report.isFinish && (
+              <IconButton
+                className="m-r-2"
+                type="continue"
+                tip="Continue"
+                onClick={() => {
+                  Question.continueLink('sentence', record);
+                }}
+              />
+            )}
+            {record.report && !!record.report.isFinish && (
+              <IconButton
+                type="restart"
+                tip="Restart"
+                onClick={() => {
+                  this.restart(record);
+                }}
+              />
+            )}
+          </div>
+        );
+      },
+    }, {
+      title: '报告',
+      width: 30,
+      align: 'right',
+      render: record => {
+        return (
+          <div className="table-row p-t-1">
+            {record.report && record.report.isFinish && <IconButton type="report" tip="Report" onClick={() => {
+              Question.reportLink(record);
+            }} />}
+          </div>
+        );
       },
-    ];
+    }];
   }
 
   initState() {
@@ -485,12 +472,12 @@ export default class extends Page {
         row.info = [
           {
             title: '已做',
-            number: row.userNumber || 0,
+            number: row.userNumber || '-',
             unit: '题',
           },
           {
             title: '剩余',
-            number: row.questionNumber - row.userNumber || 0,
+            number: row.userNumber ? row.questionNumber - row.userNumber : '-',
             unit: '题',
           },
           {
@@ -588,25 +575,31 @@ export default class extends Page {
   }
 
   exerciseList(item) {
-    linkTo(`/exercise/list/${item.id}`);
+    User.needLogin().then(() => {
+      linkTo(`/exercise/list/${item.id}`);
+    });
   }
 
   activeSentence() {
-    Sentence.active(this.code)
-      .then(() => {
-        // 重新获取长难句信息
-        User.clearSentenceTrail();
-        this.setState({ sentence: null, articleMap: null, paperList: null });
-        this.refresh();
-      })
-      .catch(err => {
-        this.setState({ sentenceError: err.message });
-      });
+    User.needLogin().then(() => {
+      Sentence.active(this.code)
+        .then(() => {
+          // 重新获取长难句信息
+          User.clearSentenceTrail();
+          this.setState({ sentence: null, articleMap: null, paperList: null });
+          this.refresh();
+        })
+        .catch(err => {
+          this.setState({ sentenceError: err.message });
+        });
+    });
   }
 
   trailSentence() {
-    User.sentenceTrail();
-    this.setState({ sentenceError: null });
+    User.needLogin().then(() => {
+      User.sentenceTrail();
+      this.setState({ sentenceError: null });
+    });
   }
 
   sentenceRead(article) {
@@ -618,13 +611,16 @@ export default class extends Page {
   }
 
   sentenceFilter(value) {
-    const { paperList } = this.state;
+    const { paperChecked, paperList } = this.state;
+    value = paperChecked === value ? null : value;
     const list = paperList.filter(row => {
       const finish = row.paper ? row.paper.times > 0 : false;
       if (value === 0) {
         return !finish;
+      } if (value === 1) {
+        return finish;
       }
-      return finish;
+      return true;
     });
     this.setState({ paperFilterList: list, paperChecked: value });
   }

+ 9 - 10
front/project/www/routes/paper/question/page.js

@@ -113,9 +113,9 @@ export default class extends Page {
   }
 
   submitAsk() {
-    const { userQuestion = {}, ask = {} } = this.state;
+    const { question = {}, questionNo = {}, paper = {}, ask = {} } = this.state;
     if (ask.originContent === '' || ask.content === '' || ask.target === '') return;
-    My.addQuestionAsk(userQuestion.id, ask.target, userQuestion.questionModule, ask.originContent, ask.content).then(() => {
+    My.addQuestionAsk(paper.id, ask.target, question.questionModule, questionNo.id, ask.originContent, ask.content).then(() => {
       this.setState({ askModal: false, askOkModal: true });
     }).catch(err => {
       this.setState({ askError: err.message });
@@ -125,7 +125,7 @@ export default class extends Page {
   submitFeedbackError() {
     const { feedback = {}, question = {}, questionNo = {} } = this.state;
     if (feedback.originContent === '' || feedback.content === '' || feedback.target === '') return;
-    My.addFeedbackErrorQuestion(question.id, questionNo.title, feedback.target, feedback.originContent, feedback.content).then(() => {
+    My.addFeedbackErrorQuestion(question.questionModule, questionNo.id, questionNo.title, feedback.target, feedback.originContent, feedback.content).then(() => {
       this.setState({ feedbackModal: false, feedbackOkModal: true });
     }).catch(err => {
       this.setState({ feedbackError: err.message });
@@ -133,8 +133,8 @@ export default class extends Page {
   }
 
   submitNote(close) {
-    const { userQuestion = {}, note = {} } = this.state;
-    My.updateQuestionNote(userQuestion.questionModule, userQuestion.questionNoId, note).then(() => {
+    const { question = {}, questionNo = {}, note = {} } = this.state;
+    My.updateQuestionNote(question.questionModule, questionNo.id, note).then(() => {
       if (close) this.setState({ noteModal: false });
     }).catch(err => {
       this.setState({ noteError: err.message });
@@ -147,14 +147,14 @@ export default class extends Page {
   }
 
   toggleCollect() {
-    const { userQuestion = {} } = this.state;
+    const { userQuestion = {}, question = {}, questionNo = {} } = this.state;
     if (!userQuestion.collect) {
-      My.addQuestionCollect(userQuestion.questionModule, userQuestion.questionNoId).then(() => {
+      My.addQuestionCollect(question.questionModule, questionNo.id).then(() => {
         userQuestion.collect = true;
         this.setState({ userQuestion });
       });
     } else {
-      My.delQuestionCollect(userQuestion.questionModule, userQuestion.questionNoId).then(() => {
+      My.delQuestionCollect(question.questionModule, questionNo.id).then(() => {
         userQuestion.collect = false;
         this.setState({ userQuestion });
       });
@@ -250,7 +250,7 @@ export default class extends Page {
         </div>
       </div>
       <div className="right" hidden={question.questionType === 'awa'}>
-        <span className="b">
+        <span className="b" hidden={!userQuestion.id}>
           用时:<span dangerouslySetInnerHTML={{ __html: formatSeconds(userQuestion.userTime).replace(/([0-9]+)([msh])/g, '<span class="s">$1</span>$2') }} />
           {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */}
         </span>
@@ -394,7 +394,6 @@ export default class extends Page {
   renderAnswer() {
     const { question = { content: {} }, showAnswer, userQuestion = {} } = this.state;
     const { questions = [], type, typeset = 'one' } = question.content;
-    console.log(userQuestion);
     return <div className="block block-answer">
       {typeset === 'two' ? <Switch checked={showAnswer} onChange={(value) => {
         this.setState({ showAnswer: value });

+ 10 - 0
front/project/www/routes/question/detail/index.js

@@ -0,0 +1,10 @@
+export default {
+  path: '/paper/question/:id',
+  key: 'paper-question',
+  title: '查看结果',
+  needLogin: true,
+  hideHeader: true,
+  component() {
+    return import('./page');
+  },
+};

+ 472 - 0
front/project/www/routes/question/detail/index.less

@@ -0,0 +1,472 @@
+@charset "utf-8";
+
+#paper-question {
+  height: 100%;
+
+  .base {
+    height: 100%;
+
+    .layout {
+      background: #fff;
+      height: 100%;
+      display: flex;
+      flex-direction: row;
+
+      .layout-header {
+        height: 60px;
+        line-height: 60px;
+        margin: 0 50px;
+        position: fixed;
+        top: 0;
+        left: 0;
+        right: 0;
+        box-shadow: 0px 4px 14px 0px rgba(189, 199, 215, 0.16);
+        text-align: center;
+        z-index: 80;
+
+        .left {
+          position: absolute;
+
+          .no {
+            font-size: 20px;
+            display: inline-block;
+            color: #303036;
+            font-size: 20px;
+            margin-right: 25px;
+          }
+
+          .title {
+            color: #A7A7B7;
+            display: inline-block;
+            font-size: 20px;
+
+            img {
+              margin-top: -3px;
+              margin-right: 5px;
+            }
+          }
+        }
+
+
+        .menu-wrap {
+          position: absolute;
+          right: 0;
+          text-align: left;
+          padding: 0 10px;
+          white-space: nowrap;
+
+          .menu-content {
+            position: absolute;
+            background: #fff;
+            text-align: left;
+            top: 50px;
+            right: 10px;
+            border: 1px solid #EAEDF2;
+            padding: 10px 20px;
+            min-width: 150px;
+
+            p {
+              line-height: 30px;
+              height: 30px;
+              text-align: left;
+              margin: 0;
+            }
+          }
+        }
+
+        .center {
+          position: absolute;
+          right: 50%;
+          transform: translateX(100%);
+
+          .icon {
+            margin-left: 20px;
+          }
+        }
+
+        .right {
+          position: absolute;
+          right: 0;
+
+          .b {
+            margin-left: 30px;
+
+            .s {
+              color: #4299FF;
+            }
+          }
+
+          .icon {
+            margin-left: 10px;
+          }
+        }
+      }
+    }
+
+    .layout-footer {
+      position: fixed;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      height: 60px;
+      line-height: 60px;
+      box-shadow: 0px -4px 14px 0px rgba(189, 199, 215, 0.16);
+
+      .left {
+        width: 30%;
+        display: inline-block;
+        padding-left: 50px;
+      }
+
+      .right {
+        width: 30%;
+        display: inline-block;
+        text-align: right;
+        padding-right: 50px;
+
+        .icon {
+          margin-left: 10px;
+        }
+      }
+
+      .center {
+        width: 40%;
+        display: inline-block;
+        text-align: center;
+
+        .item {
+          margin: 0 10px;
+        }
+      }
+    }
+
+    .layout-body {
+      background: #fff;
+      flex: 1;
+      overflow: hidden;
+      margin: 60px 0;
+
+      .layout-content {
+        height: 100%;
+        position: relative;
+
+        .one {
+          flex: 1;
+          display: flex;
+          flex-direction: column;
+        }
+
+        .two {
+          flex: 1;
+          display: flex;
+          flex-direction: row;
+          overflow: hidden;
+          height: 100%;
+        }
+
+        .block {
+          flex: 1;
+        }
+
+        .block-content,
+        .block-answer,
+        .block-awa {
+          padding: 30px 60px;
+          color: #303036;
+          height: 100%;
+          overflow: hidden;
+          overflow-y: auto;
+        }
+
+        .block-content {
+          h2 {
+            padding: 65px 0 20px 0px;
+            font-size: 20px;
+            color: #303036;
+          }
+        }
+
+        .block-awa {
+          background: #EFF3F7;
+
+          h2 {
+            font-size: 20px;
+            color: #303036;
+            margin-top: 37px;
+            margin-bottom: 23px;
+          }
+
+          .detail {
+            .info {
+              font-weight: bold;
+              font-size: 18px;
+              color: #303036;
+
+              span.b {
+                margin-right: 80px;
+
+                .s {
+                  color: #4299FF;
+                }
+              }
+            }
+          }
+
+          .content-awa {
+            padding-top: 50px;
+            color: #686872;
+            font-size: 16px;
+          }
+
+          .show-awa {
+            font-size: 12px;
+            width: 100%;
+            height: 100%;
+            margin: 50% 0;
+            text-align: center;
+            line-height: 20px;
+            color: #A7A7B7;
+          }
+        }
+
+        .block-analysis {
+          background: #EFF3F7;
+          padding: 25px 25px 0 20px;
+          display: flex;
+          flex-direction: column;
+
+          .block-answer {
+            padding: 38px 50px;
+          }
+
+          .block {
+            background: #fff;
+          }
+
+          .detail {
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+
+            .detail-block {
+              margin-top: 5px;
+              flex: 1;
+              padding: 30px 50px;
+              overflow: hidden;
+              overflow-y: auto;
+              font-size: 16px;
+              color: #686872;
+            }
+
+            .answer-block {
+              margin-bottom: 5px;
+            }
+          }
+
+          .other {
+            flex: 1;
+            background: #fff;
+            padding: 30px 50px;
+            overflow: hidden;
+            overflow-y: auto;
+            font-size: 16px;
+            color: #686872;
+
+            .other-answer {
+              margin-bottom: 30px;
+            }
+          }
+        }
+
+        .two-analysis {
+          position: absolute;
+          height: 100%;
+          top: 0;
+          left: 0;
+          width: 50%;
+          transition: all 0.3s;
+          transform: translateX(200%);
+        }
+
+        .two-analysis.show {
+          transform: translateX(100%);
+        }
+
+        .fixed-analysis {
+          height: 110px;
+          line-height: 20px;
+          position: absolute;
+          width: 35px;
+          padding: 5px;
+          right: 0;
+          top: 50%;
+          transform: translateY(-50%);
+          border: 1px solid #E7E7E7;
+          background: #fff;
+          z-index: 9;
+          color: #787883;
+          cursor: pointer;
+          text-align: center;
+        }
+      }
+    }
+
+    .modal {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+
+      >.mask {
+        background: #000;
+        opacity: .2;
+        width: 100%;
+        height: 100%;
+      }
+
+      .body {
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        background: #fff;
+        width: 630px;
+        color: #686872;
+        padding: 20px 30px;
+
+        .title {
+          color: #303036;
+          font-size: 20px;
+          font-weight: 600;
+        }
+
+        .desc {
+          color: #686872;
+          font-size: 16px;
+          padding: 20px 0;
+
+          .select-inline {
+            margin-bottom: 15px;
+
+            .select {
+              display: inline-block;
+            }
+          }
+
+          .label {
+            margin-bottom: 5px;
+          }
+        }
+
+        .textarea {
+          width: 570px;
+          height: 80px;
+          background: rgba(247, 247, 247, 1);
+          margin-bottom: 15px;
+          border: none;
+          padding: 5px 10px;
+        }
+
+        .textarea::placeholder {
+          color: #A7A7B7;
+        }
+
+        .bottom {
+          border-top: 1px solid #E1E1E1;
+          padding-top: 10px;
+          text-align: right;
+        }
+      }
+    }
+
+    .modal.ask-ok,
+    .modal.error-ok {
+      .body {
+        .content {
+          width: 100%;
+          padding-top: 20px;
+          padding-bottom: 40px;
+          color: #686872;
+          overflow: hidden;
+
+          .left {
+            float: left;
+            width: 360px;
+            font-size: 18px;
+
+            a {
+              padding-top: 30px;
+              display: inline-block;
+              font-size: 14px;
+            }
+          }
+
+          .right {
+            float: right;
+            text-align: right;
+            font-size: 12px;
+          }
+        }
+
+        .confirm {
+          text-align: center;
+          padding-bottom: 10px;
+
+          .answer-button.lager {
+            font-size: 16px;
+          }
+        }
+      }
+    }
+
+    .modal.note {
+      .body {
+        width: 720px;
+
+        .content {
+          padding-top: 20px;
+
+          .tabs {
+            display: inline-block;
+            width: 170px;
+            vertical-align: top;
+            margin-left: -30px;
+            margin-right: 30px;
+
+            .tab {
+              padding: 5px 0px 5px 40px;
+              line-height: 20px;
+              color: #686872;
+              margin-bottom: 30px;
+              cursor: pointer;
+              transition: all 0.3s;
+              border-top-right-radius: 25px;
+              border-bottom-right-radius: 25px;
+
+              .date {
+                font-size: 12px;
+              }
+            }
+
+            .tab.active,
+            .tab:hover {
+              color: #fff;
+              background: #4299FF;
+            }
+          }
+
+          .input {
+            display: inline-block;
+
+            .textarea {
+              width: 490px;
+              height: 350px;
+              margin-bottom: 20px;
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 648 - 0
front/project/www/routes/question/detail/page.js

@@ -0,0 +1,648 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Carousel, Tooltip } from 'antd';
+import { Link } from 'react-router-dom';
+import Fullscreen from 'react-fullscreen-crossbrowser';
+import './index.less';
+import Page from '@src/containers/Page';
+import { formatSeconds, formatPercent, formatDate, sortListWithOrder } from '@src/services/Tools';
+import Assets from '@src/components/Assets';
+import Navigation from '../../../components/Navigation';
+import Tabs from '../../../components/Tabs';
+import Icon from '../../../components/Icon';
+import Switch from '../../../components/Switch';
+import Select from '../../../components/Select';
+import AnswerSelect from '../../../components/AnswerSelect';
+import AnswerList from '../../../components/AnswerList';
+import AnswerButton from '../../../components/AnswerButton';
+import AnswerTable from '../../../components/AnswerTable';
+import OtherAnswer from '../../../components/OtherAnswer';
+import { AskTarget } from '../../../../Constant';
+import { Question } from '../../../stores/question';
+import { My } from '../../../stores/my';
+import Sentence from '../../paper/process/sentence';
+
+export default class extends Page {
+  initState() {
+    return {
+      step: 0,
+      hideAnalysis: true,
+      analysisTab: 'official',
+      showAnswer: false,
+      noteField: AskTarget[0].key,
+      showIds: false,
+
+      // question: {
+      //   content: {
+      //     typeset: 'one',
+      //   },
+      //   // questionType: 'awa',
+      //   answer: {
+      //     subject: [[{ text: 'like', uuid: 'hKyz' }]],
+      //     options: ['parallel'],
+      //   },
+      //   stem: "<p><span uuid='kBJe'>I</span> <span uuid='hKyz'>like</span> <span uuid='fQXh'>book</span></p>",
+      // },
+      // userQuestion: {
+      //   userAnswer: {
+      //     subject: [{ text: 'I', uuid: 'kBJe' }],
+      //     options: ['compare'],
+      //   },
+      //   no: 2,
+      // },
+      // paper: {
+      //   title: '长难句练习',
+      //   questionNumber: 20,
+      // },
+      // report: {
+      //   paperModule: 'sentence',
+      // },
+
+    };
+  }
+
+  initData() {
+    const { id } = this.params;
+    Question.getDetailById(id).then(userQuestion => {
+      const { question, questionNos, paper, note, report, setting } = userQuestion;
+      let { questionNo } = userQuestion;
+      if (!questionNo) ([questionNo] = questionNos);
+      if (!question.answer) question.answer = { questions: [] };
+      if (!question.answerDistributed) question.answerDistributed = { questions: [] };
+      if (!userQuestion.userAnswer) userQuestion.userAnswer = { questions: [] };
+      if ((report.setting || {}).disorder) {
+        const { content } = question;
+        // 还原做题顺序
+        content.questions.forEach((q, i) => {
+          q.select = sortListWithOrder(question.select, setting.questions[i]);
+        });
+        question.answer.questions.forEach((q, i) => {
+          Object.keys(q).forEach((k) => {
+            if (q[k]) q[k] = sortListWithOrder(q[k], setting.questions[i]);
+          });
+        });
+        question.answerDistributed.questions.forEach((q, i) => {
+          Object.keys(q).forEach((k) => {
+            if (q[k]) q[k] = sortListWithOrder(q[k], setting.questions[i]);
+          });
+        });
+        userQuestion.userAnswer.questions.forEach((q, i) => {
+          Object.keys(q).forEach((k) => {
+            if (q[k]) q[k] = sortListWithOrder(q[k], setting.questions[i]);
+          });
+        });
+      }
+      this.setState({ userQuestion, question, questionNo, note, paper, questionNos });
+    });
+  }
+
+  prevQuestion() {
+    const { userQuestion } = this.state;
+    if (userQuestion.no === 1) return;
+    Question.getDetailByNo(userQuestion.reportId, userQuestion.no - 1).then((r) => {
+      linkTo(`/paper/question/${r.id}`);
+    });
+  }
+
+  nextQuestion() {
+    const { userQuestion } = this.state;
+    if (userQuestion.questionNumber === userQuestion.no) return;
+    Question.getDetailByNo(userQuestion.reportId, userQuestion.no + 1).then((r) => {
+      linkTo(`/paper/question/${r.id}`);
+    });
+  }
+
+  submitAsk() {
+    const { question = {}, questionNo = {}, paper = {}, ask = {} } = this.state;
+    if (ask.originContent === '' || ask.content === '' || ask.target === '') return;
+    My.addQuestionAsk(paper.id, ask.target, question.questionModule, questionNo.id, ask.originContent, ask.content).then(() => {
+      this.setState({ askModal: false, askOkModal: true });
+    }).catch(err => {
+      this.setState({ askError: err.message });
+    });
+  }
+
+  submitFeedbackError() {
+    const { feedback = {}, question = {}, questionNo = {} } = this.state;
+    if (feedback.originContent === '' || feedback.content === '' || feedback.target === '') return;
+    My.addFeedbackErrorQuestion(question.questionModule, questionNo.id, questionNo.title, feedback.target, feedback.originContent, feedback.content).then(() => {
+      this.setState({ feedbackModal: false, feedbackOkModal: true });
+    }).catch(err => {
+      this.setState({ feedbackError: err.message });
+    });
+  }
+
+  submitNote(close) {
+    const { question = {}, questionNo = {}, note = {} } = this.state;
+    My.updateQuestionNote(question.questionModule, questionNo.id, note).then(() => {
+      if (close) this.setState({ noteModal: false });
+    }).catch(err => {
+      this.setState({ noteError: err.message });
+    });
+  }
+
+  toggleFullscreen() {
+    const { isFullscreenEnabled } = this.state;
+    this.setState({ isFullscreenEnabled: !isFullscreenEnabled });
+  }
+
+  toggleCollect() {
+    const { userQuestion = {}, question = {}, questionNo = {} } = this.state;
+    if (!userQuestion.collect) {
+      My.addQuestionCollect(question.questionModule, questionNo.id).then(() => {
+        userQuestion.collect = true;
+        this.setState({ userQuestion });
+      });
+    } else {
+      My.delQuestionCollect(question.questionModule, questionNo.id).then(() => {
+        userQuestion.collect = false;
+        this.setState({ userQuestion });
+      });
+    }
+  }
+
+  formatStem(text) {
+    if (!text) return '';
+    const { showAnswer, question = { content: {} }, userQuestion } = this.state;
+    const { table = {}, questions = [] } = question.content;
+    text = text.replace(/#select#/g, "<span class='#select#' />");
+    text = text.replace(/#table#/g, "<span class='#table#' />");
+    setTimeout(() => {
+      const selectList = document.getElementsByClassName('#select#');
+      const tableList = document.getElementsByClassName('#table#');
+      for (let i = 0; i < selectList.length; i += 1) {
+        if (!questions[i]) break;
+        ReactDOM.render(
+          <AnswerSelect
+            list={questions[i].select}
+            type={'single'}
+            selected={(userQuestion.userAnswer || { questions: [] }).questions[i]}
+            answer={(question.answer || { questions: [] }).questions[i]}
+            fix
+            show={showAnswer} />,
+          selectList[i],
+        );
+      }
+      if (table.row && table.col && table.header) {
+        const columns = table.header.map((title, index) => {
+          return { title, key: index };
+        });
+        for (let i = 0; i < tableList.length; i += 1) {
+          ReactDOM.render(<AnswerTable list={columns} columns={columns} data={table.data} />, tableList[i]);
+        }
+      }
+    }, 1);
+    return text;
+  }
+
+  renderView() {
+    return (
+      <Fullscreen
+        enabled={this.state.isFullscreenEnabled}
+        onChange={isFullscreenEnabled => this.setState({ isFullscreenEnabled })}
+      >
+        {this.renderDetail()}
+      </Fullscreen>
+    );
+  }
+
+  renderDetail() {
+    const { report = {} } = this.state;
+    switch (report.paperModule) {
+      case 'sentence':
+        return <Sentence {...this.state} flow={this} scene='answer' mode='question' />;
+      default:
+        return <div className='base'>{this.renderBase()}</div>;
+    }
+  }
+
+  renderHeader() {
+    const { userQuestion = {}, questionNo = {}, paper = {}, showIds, questionNos = [], question = {} } = this.state;
+    return <div className="layout-header">
+      <div className="left">
+        <div className="no">No.{userQuestion.stageNo || userQuestion.no}</div>
+        <div className="title"><Assets name='book' />{paper.title}</div>
+      </div>
+      <div className="center">
+        <div className="menu-wrap">
+          ID:{questionNo.title}
+          {questionNos && questionNos.length > 0 && <Icon name="more" onClick={() => {
+            this.setState({ showIds: true });
+          }} />}
+          {showIds && <div className='menu-content'>
+            <p>题源汇总</p>
+            {(questionNos || []).map((row) => <p>ID:{row.title}</p>)}
+          </div>}
+        </div>
+      </div>
+      <div className="right" hidden={question.questionType === 'awa'}>
+        <span className="b" hidden={!userQuestion.id}>
+          用时:<span dangerouslySetInnerHTML={{ __html: formatSeconds(userQuestion.userTime).replace(/([0-9]+)([msh])/g, '<span class="s">$1</span>$2') }} />
+          {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */}
+        </span>
+        <span className="b">
+          全站:<span dangerouslySetInnerHTML={{ __html: formatSeconds(questionNo.totalTime / questionNo.totalNumber).replace(/([0-9]+)([msh])/g, '<span class="s">$1</span>$2') }} />
+          {/* 全站:<span className="s">1</span>m<span className="s">39</span>s */}
+        </span>
+        <span className="b">
+          <span className="s">{formatPercent(questionNo.totalCorrect, questionNo.totalNumber)}</span>%
+        </span>
+        <Icon name="question" />
+        <Icon name="star" active={userQuestion.collect} onClick={() => this.toggleCollect()} />
+      </div>
+    </div>;
+  }
+
+  renderBase() {
+    const { questionStatus, userQuestion = {}, showIds } = this.state;
+    return <div className="layout" onClick={() => {
+      if (showIds) this.setState({ showIds: false });
+    }}>
+      {this.renderHeader()}
+      <div className="layout-body">{this.renderBody()}</div>
+      <div className="layout-footer">
+        <div className="left">
+          <Tooltip overlayClassName='gray' placement='top' title='全屏'>
+            <a>
+              <Icon name={this.state.isFullscreenEnabled ? 'sceen-restore' : 'sceen-full'} onClick={() => this.toggleFullscreen()} />
+            </a>
+          </Tooltip>
+        </div>
+        <div className="center">
+          <AnswerButton className="item" onClick={() => this.setState({ noteModal: true })}>笔记</AnswerButton>
+          {questionStatus >= 0 && <AnswerButton className="item" onClick={() => {
+            if (questionStatus > 0) {
+              this.setState({ askModal: true });
+            } else {
+              this.setState({ askFailModal: true });
+            }
+          }}>提问</AnswerButton>}
+          <AnswerButton className="item" onClick={() => this.setState({ feedbackModal: true })}>纠错</AnswerButton>
+        </div>
+        <div className="right">
+          {userQuestion.no !== 1 && <Icon name="prev" onClick={() => this.prevQuestion()} />}
+          {userQuestion.questionNumber !== userQuestion.no && <Icon name="next" onClick={() => this.nextQuestion()} />}
+        </div>
+      </div>
+      {this.state.askModal && this.renderAsk()}
+      {this.state.askOkModal && this.renderAskOk()}
+      {this.state.askFailModal && this.renderAskFail()}
+      {this.state.feedbackModal && this.renderFeedbackError()}
+      {this.state.feedbackOkModal && this.renderFeedbackErrorOk()}
+      {this.state.noteModal && this.renderNote()}
+    </div>;
+  }
+
+  renderBody() {
+    const { question = { content: {} } } = this.state;
+    const { typeset = 'one' } = question.content;
+    const { hideAnalysis } = this.state;
+    const show = typeset === 'one' ? true : !hideAnalysis;
+    return (
+      <div className="layout-content">
+        <div className='two'>
+          {this.renderContent()}
+          {question.questionType !== 'awa' && this.renderAnswer()}
+          {question.questionType === 'awa' && this.renderAWA()}
+        </div>
+        {question.questionType !== 'awa' && this.renderAnalysis()}
+        {typeset === 'two' && question.questionType !== 'awa' && (
+          <div className="fixed-analysis" onClick={() => this.setState({ hideAnalysis: !hideAnalysis })}>
+            {show ? '收起解析 >' : '查看解析 <'}
+          </div>
+        )}
+      </div>
+    );
+  }
+
+  renderAnalysis() {
+    const { question = { content: {} }, analysisTab } = this.state;
+    const { typeset = 'one' } = question.content;
+    const { hideAnalysis } = this.state;
+    const show = typeset === 'one' ? true : !hideAnalysis;
+    return (
+      <div className={`block block-analysis two-analysis ${show ? 'show' : ''}`}>
+        <Tabs
+          type="division"
+          active={analysisTab}
+          space={2}
+          tabs={[
+            { key: 'official', name: '官方解析' },
+            { key: 'qx', name: '千行解析' },
+            { key: 'association', name: '题源联想' },
+            { key: 'qa', name: '相关回答' },
+          ]}
+          onChange={(key) => {
+            this.setState({ analysisTab: key });
+          }}
+        />
+        <div className="detail">
+          {typeset === 'two' && this.renderAnswer()}
+          {this.renderText()}
+        </div>
+      </div>
+    );
+  }
+
+  renderText() {
+    const { analysisTab, question = {}, userQuestion = {} } = this.state;
+    const { asks = [], associations = [] } = userQuestion;
+    let content;
+    switch (analysisTab) {
+      case 'official':
+        content = <div className="detail-block text-block" dangerouslySetInnerHTML={{ __html: question.officialContent }} />;
+        break;
+      case 'qx':
+        content = <div className="detail-block text-block" dangerouslySetInnerHTML={{ __html: question.qxContent }} />;
+        break;
+      case 'association':
+        content = <div className="detail-block">
+          <Carousel>
+            {associations.map(association => {
+              return <div className="text-block" dangerouslySetInnerHTML={{ __html: association.stem }} />;
+            })}
+          </Carousel>
+        </div>;
+        break;
+      case 'qa':
+        content = <div className="detail-block answer-block">
+          {asks.map((ask, index) => {
+            return <OtherAnswer key={index} data={ask} />;
+          })}
+        </div>;
+        break;
+      default:
+        break;
+    }
+    return content;
+  }
+
+  renderAnswer() {
+    const { question = { content: {} }, showAnswer, userQuestion = {} } = this.state;
+    const { questions = [], type, typeset = 'one' } = question.content;
+    return <div className="block block-answer">
+      {typeset === 'two' ? <Switch checked={showAnswer} onChange={(value) => {
+        this.setState({ showAnswer: value });
+      }}>{showAnswer ? '显示答案' : '关闭答案'}</Switch> : ''}
+      {questions.map((item, index) => {
+        return (
+          <div>
+            <div className="text m-b-2">{item.description}</div>
+            <AnswerList
+              show={showAnswer}
+              selected={(userQuestion.userAnswer || { questions: [] }).questions[index]}
+              answer={(question.answer || { questions: [] }).questions[index]}
+              distributed={(question.answerDistributed || { questions: [] }).questions[index]}
+              list={item.select}
+              type={type}
+              first={item.first}
+              second={item.second}
+              direction={item.direction}
+            />
+          </div>
+        );
+      })}
+    </div>;
+  }
+
+  renderContent() {
+    const { question = { content: {} }, showAnswer, step } = this.state;
+    const { typeset = 'one' } = question.content;
+    const { steps = [] } = question.content;
+    return (
+      <div className="block block-content">
+        {typeset === 'one' && question.questionType !== 'awa' ? <Switch checked={showAnswer} onChange={(value) => {
+          this.setState({ showAnswer: value });
+        }}>{showAnswer ? '显示答案' : '关闭答案'}</Switch> : ''}
+        {question.questionType === 'awa' && <h2>Analytical Writing Assessment</h2>}
+        {steps.length > 0 && <Navigation theme='detail' list={question.content.steps} active={step} onChange={(v) => this.setState({ step: v })} />}
+        <div className="text" style={{ height: 2000 }} dangerouslySetInnerHTML={{ __html: this.formatStem(steps.length > 0 ? steps[step].stem : question.stem) }} />
+      </div>
+    );
+  }
+
+  renderAWA() {
+    const { showAnswer, userQuestion = { detail: {}, userAnswer: {} } } = this.state;
+    return <div className="block block-awa">
+      <Switch checked={showAnswer} onChange={(value) => {
+        this.setState({ showAnswer: value });
+      }}>{showAnswer ? '显示答案' : '关闭答案'}</Switch>
+      <div className="body">
+        <h2>Your Response</h2>
+        {showAnswer && <div className='detail'>
+          <div className='info'>
+            <span className="b">
+              用时:<span dangerouslySetInnerHTML={{ __html: formatSeconds(userQuestion.userTime).replace(/([0-9]+)([msh])/g, '<span class="s">$1</span>$2') }} />
+              {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */}
+            </span>
+            <span className="b">
+              单词数:<span className="s">{Number((userQuestion.detail || {}).words || 0)}</span>词
+            </span>
+          </div>
+          <div className='content-awa' dangerouslySetInnerHTML={{ __html: userQuestion.userAnswer.awa || '' }} />
+        </div>}
+        {!showAnswer && <div className='show-awa'>选择「显示答案」查看自己的作文</div>}
+      </div>
+    </div>;
+  }
+
+  renderAsk() {
+    const { ask = {} } = this.state;
+    return (
+      <div className="modal ask">
+        <div className="mask" />
+        <div className="body">
+          <div className="title">提问</div>
+          <div className="desc">
+            <div className="select-inline">我想对<Select excludeSelf size="small" theme="white" value={ask.target} list={AskTarget} onChange={(item) => {
+              ask.target = item.value;
+              this.setState({ ask });
+            }} />进行提问</div>
+            <div className="label">有疑问的具体内容是:</div>
+            <textarea className="textarea" value={ask.originContent} placeholder="请复制粘贴有疑问的内容。" onChange={(e) => {
+              ask.originContent = e.target.value;
+              this.setState({ ask });
+            }} />
+            <div className="label">针对以上内容的问题是:</div>
+            <textarea className="textarea" value={ask.content} placeholder="提问频率高的问题会被优先回答哦。" onChange={(e) => {
+              ask.content = e.target.value;
+              this.setState({ ask });
+            }} />
+          </div>
+          <div className="bottom">
+            <AnswerButton theme="cancel" size="lager" onClick={() => this.setState({ askModal: false })}>
+              取消
+            </AnswerButton>
+            <AnswerButton size="lager" onClick={() => this.submitAsk()}>提交</AnswerButton>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderAskOk() {
+    return (
+      <div className="modal ask-ok">
+        <div className="mask" />
+        <div className="body">
+          <div className="title">提问</div>
+          <div className="content">
+            <div className="left">
+              <div className="text">已提交成功!</div>
+              <div className="text">关注公众号,老师回答后会立即收到通知。</div>
+              <div className="text">我们也会通过站内信的方式通知你。</div>
+              <div className="small">成为学员享受极速答疑特权。<Link>了解更多</Link></div>
+            </div>
+            <div className="right">
+              <div className="text">扫码关注公众号</div>
+              <div className="text">千行GMAT</div>
+            </div>
+          </div>
+          <div className="confirm">
+            <AnswerButton size="lager" theme="confirm" onClick={() => {
+              this.setState({ askOkModal: false });
+            }}>
+              好的,知道了
+            </AnswerButton>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderAskFail() {
+    return (
+      <div className="modal ask-ok">
+        <div className="mask" />
+        <div className="body">
+          <div className="title">提问</div>
+          <div className="content">
+            <div className="left">
+              <div className="text">提问功能正在维护中。</div>
+              <div className="text">可先查阅“相关问答” 或 成为学员享受极速 答疑特权。</div>
+              <Link to="/">了解更多></Link>
+            </div>
+            <div className="right">
+              <div className="text">扫码关注公众号</div>
+              <div className="text">千行GMAT</div>
+            </div>
+          </div>
+          <div className="confirm">
+            <AnswerButton size="lager" theme="confirm" onClick={() => {
+              this.setState({ askFailModal: false });
+            }}>
+              好的,知道了
+            </AnswerButton>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderFeedbackError() {
+    const { feedback = {} } = this.state;
+    return (
+      <div className="modal error">
+        <div className="mask" />
+        <div className="body">
+          <div className="title">纠错</div>
+          <div className="desc">
+            <div className="select-inline">我想对<Select excludeSelf size="small" theme="white" value={feedback.target} list={AskTarget} onChange={(item) => {
+              feedback.target = item.value;
+              this.setState({ feedback });
+            }} />进行提问</div>
+            <div className="label">错误内容是:</div>
+            <textarea className="textarea" value={feedback.originContent} placeholder="你可以适当扩大复制范围以使我们准确定位,感谢。" />
+            <div className="label">应该改为:</div>
+            <textarea className="textarea" placeholder="只需提供正确内容即可" />
+          </div>
+          <div className="bottom">
+            <AnswerButton theme="cancel" size="lager" onClick={() => {
+              this.setState({ feedbackModal: false });
+            }}>
+              取消
+            </AnswerButton>
+            <AnswerButton size="lager" onClick={() => {
+              this.submitFeedbackError();
+            }}>提交</AnswerButton>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderFeedbackErrorOk() {
+    return (
+      <div className="modal error-ok">
+        <div className="mask" />
+        <div className="body">
+          <div className="title">纠错</div>
+          <div className="content">
+            <div className="left">
+              <div className="text"><Assets name='right' svg />已提交成功!</div>
+              <div className="text">感谢您的耐心反馈,我们会尽快核实并以站内信的方式告知结果。</div>
+              <div className="text">您也可以关注公众号及时获取结果。</div>
+            </div>
+            <div className="right">
+              <div className="text">扫码关注公众号</div>
+              <div className="text">千行GMAT</div>
+            </div>
+          </div>
+          <div className="confirm">
+            <AnswerButton size="lager" theme="confirm" onClick={() => {
+              this.setState({ feedbackOkModal: false });
+            }}>
+              好的,知道了
+            </AnswerButton>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderNote() {
+    const { noteField, note = {} } = this.state;
+    return (
+      <div className="modal note">
+        <div className="mask" />
+        <div className="body">
+          <div className="title">笔记</div>
+          <div className="content">
+            <div className="tabs">
+              {AskTarget.map(item => {
+                return (
+                  <div className={`tab ${noteField === item.key ? 'active' : ''}`} onClick={() => {
+                    this.setState({ noteField: item.key });
+                  }}>
+                    <div className="text">{item.label}</div>
+                    <div className="date">{note[`${item.key}Time`] ? formatDate(note[`${item.key}Time`]) : ''}</div>
+                  </div>
+                );
+              })}
+            </div>
+            <div className="input">
+              <textarea className="textarea" value={note[`${noteField}Content`] || ''} placeholder="记下笔记,方便以后复习" onChange={(e) => {
+                note[`${noteField}Time`] = new Date();
+                note[`${noteField}Content`] = e.target.value;
+                this.setState({ note });
+              }} />
+              <div className="bottom">
+                <AnswerButton theme="cancel" size="lager" onClick={() => {
+                  this.setState({ noteModal: false });
+                }}>
+                  取消
+                </AnswerButton>
+                <AnswerButton size="lager" onClick={() => {
+                  this.submitNote();
+                }}>编辑</AnswerButton>
+                <AnswerButton size="lager" onClick={() => {
+                  this.submitNote(true);
+                }}>保存</AnswerButton>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+}

+ 3 - 0
front/project/www/routes/question/index.js

@@ -0,0 +1,3 @@
+import detail from './detail';
+
+export default [detail];

+ 2 - 2
front/project/www/routes/textbook/list/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/textbook/list/:id',
-  key: 'textbook',
+  path: '/textbook/list',
+  key: 'textbook-list',
   title: '数学机经',
   needLogin: false,
   tab: 'examination',

+ 16 - 0
front/project/www/routes/textbook/list/index.less

@@ -1,6 +1,22 @@
 @charset "utf-8";
 
 #textbook-list {
+  .ant-breadcrumb {
+    margin-top: 20px;
+    margin-bottom: 20px;
+  }
+
+  .textbook-head {
+    position: relative;
+    overflow: hidden;
+
+    .textbook-time {
+      position: absolute;
+      right: 0;
+      top: 20px;
+    }
+  }
+
   .code-module {
     padding: 80px 250px;
     text-align: center;

+ 170 - 227
front/project/www/routes/textbook/list/page.js

@@ -1,246 +1,161 @@
 import React from 'react';
+import { Breadcrumb, Switch } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
-import { asyncConfirm } from '@src/services/AsyncTools';
+import { asyncConfirm, asyncSMessage } from '@src/services/AsyncTools';
 import { formatPercent, formatSeconds, formatDate } from '@src/services/Tools';
-import Tabs from '../../../components/Tabs';
-import Module from '../../../components/Module';
 import ListTable from '../../../components/ListTable';
 import ProgressText from '../../../components/ProgressText';
 import IconButton from '../../../components/IconButton';
-import { Main } from '../../../stores/main';
+import Button from '../../../components/Button';
 import { Question } from '../../../stores/question';
-import { QuestionDifficult } from '../../../../Constant';
+import { Textbook } from '../../../stores/textbook';
+import Select from '../../../components/Select';
 
-const LOGIC_NO = 'no';
-const LOGIC_PLACE = 'place';
-const LOGIC_DIFFICULT = 'difficult';
-const LOGIC_ERROR = 'error';
+const TextbookMinYear = 2019;
 
 export default class extends Page {
   initState() {
-    this.columns = [
-      {
-        title: '练习册',
-        width: 250,
-        align: 'left',
-        render: (record) => {
-          let progress = 0;
-          if (record.report) {
-            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16">{record.title}</div>
-              <div>
-                <ProgressText progress={progress} size="small" />
-              </div>
+    this.columns = [{
+      title: '练习册',
+      width: 250,
+      align: 'left',
+      render: (record) => {
+        let progress = 0;
+        if (record.report) {
+          progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16">{record.title}</div>
+            <div>
+              <ProgressText progress={progress} times={record.paper ? record.paper.times : 0} size="small" />
             </div>
-          );
-        },
+          </div>
+        );
       },
-      {
-        title: '正确率',
-        width: 150,
-        align: 'left',
-        render: (record) => {
-          let correct = '--';
-          if (record.report) {
-            correct = formatPercent(record.report.userCorrect, record.report.userNumber, false);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16 f-w-b">{correct}</div>
-              <div className="f-s-12">全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}</div>
-            </div>
-          );
-        },
+    }, {
+      title: '正确率',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        let correct = '--';
+        if (record.report) {
+          correct = formatPercent(record.report.userCorrect, record.report.userNumber, false);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16 f-w-b">{correct}</div>
+            <div className="f-s-12">全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}</div>
+          </div>
+        );
       },
-      {
-        title: '全站用时',
-        width: 150,
-        align: 'left',
-        render: (record) => {
-          let time = '--';
-          if (record.paper) {
-            time = formatSeconds(record.paper.report.userTime / record.paper.report.userNumber);
-          }
-          return (
-            <div className="table-row">
-              <div className="night f-s-16 f-w-b">{time}</div>
-              <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
-            </div>
-          );
-        },
+    }, {
+      title: '全站用时',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        let time = '--';
+        if (record.paper) {
+          time = formatSeconds(record.paper.report.userTime / record.paper.report.userNumber);
+        }
+        return (
+          <div className="table-row">
+            <div className="night f-s-16 f-w-b">{time}</div>
+            <div className="f-s-12">全站{formatSeconds(record.stat.totalTime / record.stat.totalNumber)}</div>
+          </div>
+        );
       },
-      {
-        title: '最近做题',
-        width: 150,
-        align: 'left',
-        render: (record) => {
-          if (!record.report) return null;
-          return (
-            <div className="table-row">
-              <div>{formatDate(record.report.updateTime, 'YYYY-MM-DD')}</div>
-              <div>{formatDate(record.report.updateTime, 'HH:mm')}</div>
-            </div>
-          );
-        },
+    }, {
+      title: '最近做题',
+      width: 150,
+      align: 'left',
+      render: (record) => {
+        const time = record.report ? record.report.updateTime : record.paper ? record.paper.latestTime : null;
+        return (
+          <div className="table-row">
+            <div>{time && formatDate(time, 'YYYY-MM-DD')}</div>
+            <div>{time && formatDate(time, 'HH:mm')}</div>
+          </div>
+        );
       },
-      {
-        title: '操作',
-        width: 180,
-        align: 'left',
-        render: (record) => {
-          return (
-            <div className="table-row p-t-1">
-              {!record.report && <IconButton type="start" tip="Start" onClick={() => {
-                Question.startLink('exercise', record);
-              }} />}
-              {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
-                Question.continueLink('exercise', record);
-              }} />}
-              <IconButton type="restart" tip="Restart" onClick={() => {
-                this.restart(record);
-              }} />
-            </div>
-          );
-        },
+    }, {
+      title: '操作',
+      width: 180,
+      align: 'left',
+      render: (record) => {
+        return (
+          <div className="table-row p-t-1">
+            {!record.report && <IconButton type="start" tip="Start" onClick={() => {
+              Question.startLink('exercise', record);
+            }} />}
+            {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
+              Question.continueLink('exercise', record);
+            }} />}
+            <IconButton type="restart" tip="Restart" onClick={() => {
+              this.restart(record);
+            }} />
+          </div>
+        );
       },
-      {
-        title: '报告',
-        width: 30,
-        align: 'right',
-        render: (record) => {
-          if (!record.report || !record.report.isFinish) return null;
-          return (
-            <div className="table-row p-t-1">
-              <IconButton type="report" tip="Report" onClick={() => {
-                Question.reportLink(record);
-              }} />
-            </div>
-          );
-        },
+    }, {
+      title: '报告',
+      width: 30,
+      align: 'right',
+      render: (record) => {
+        return (
+          <div className="table-row p-t-1">
+            {record.report && record.report.isFinish && <IconButton type="report" tip="Report" onClick={() => {
+              Question.reportLink(record);
+            }} />}
+          </div>
+        );
       },
-    ];
-    this.placeList = [];
+    }];
     this.inited = false;
+    const year = [];
+    const nowYear = new Date().getFullYear();
+    for (let i = TextbookMinYear; i <= nowYear; i += 1) {
+      year.push({
+        title: i.toString(),
+        key: i.toString(),
+      });
+    }
     return {
-      logic: LOGIC_NO,
-      logicExtend: '',
-      logics: [{
-        key: LOGIC_NO,
-        title: '按顺序练习',
-      }, {
-        key: LOGIC_PLACE,
-        title: '按考点练习',
-      }, {
-        key: LOGIC_DIFFICULT,
-        title: '按难度练习',
-      }, {
-        key: LOGIC_ERROR,
-        title: '按易错度练习',
-      }],
+      yearList: year,
     };
   }
 
-  init() {
-    const { id } = this.params;
-    Main.getExerciseParent(id).then(result => {
-      const navs = result;
-      this.inited = true;
-      this.setState({ navs });
-    });
-  }
-
   initData() {
+    const { search } = this.state;
+    const info = {};
+    info.latest = search.latest ? Number(search.latest) : 0;
+    info.logic = search.logic ? search.logic : 'ds';
+    info.year = search.year ? search.year : new Date().getFullYear().toString();
     const data = Object.assign(this.state, this.state.search);
+    data.info = info;
     this.setState(data);
     this.refreshData();
   }
 
-  refreshData(newLogic) {
-    const { logic } = this.state;
-    let handler = null;
-    switch (newLogic || logic) {
-      case LOGIC_PLACE:
-        handler = this.refreshPlace();
-        break;
-      case LOGIC_DIFFICULT:
-        handler = this.refreshDifficult();
-        break;
-      default:
-        handler = Promise.resolve();
-    }
-    handler.then(() => {
-      this.refreshExercise();
+  refreshData() {
+    Textbook.getInfo().then((result) => {
+      this.setState({ textbook: result });
     });
+    this.refreshTextbook();
   }
 
-  refreshPlace() {
-    const { id } = this.params;
-    let handler;
-    if (this.placeList.length > 0) {
-      this.setState({ logicExtends: this.placeList });
-      handler = Promise.resolve();
-    } else {
-      handler = Question.getExercisePlace(id).then(result => {
-        this.placeList = result.map(row => {
-          return {
-            name: row,
-            key: row,
-          };
-        });
-        this.setState({ logicExtends: this.placeList });
-      });
-    }
-    return handler.then(() => {
-      let { logicExtend } = this.state;
-      if (logicExtend === '') {
-        logicExtend = this.placeList[0].key;
-        this.setState({ logicExtend });
-      }
-    });
-  }
-
-  refreshDifficult() {
-    let { logicExtend } = this.state;
-    this.setState({
-      logicExtends: QuestionDifficult.map(difficult => {
-        difficult.name = difficult.label;
-        difficult.key = difficult.value;
-        return difficult;
-      }),
-    });
-    return Promise.resolve().then(() => {
-      if (logicExtend === '') {
-        logicExtend = QuestionDifficult[0].key;
-        this.setState({ logicExtend });
-      }
-    });
-  }
-
-  refreshExercise() {
-    const { logic, logicExtend } = this.state;
-    Question.getExerciseList(Object.assign({ structId: this.params.id, logic, logicExtend }, this.state.search))
+  refreshTextbook() {
+    Textbook.listPaper(Object.assign({}, this.state.search, this.state.info))
       .then((result) => {
         this.setState({ list: result.list, total: result.total });
+      })
+      .catch(err => {
+        asyncSMessage(err.message, 'warn');
+        linkTo('/examination');
       });
   }
 
-  onChangeTab(key, value) {
-    const { logic } = this.state;
-    const data = {};
-    if (key === 'logicExtend') {
-      data.logic = logic;
-      data.logicExtend = value;
-    } else {
-      data.logic = value;
-    }
-    // this.refreshData(tab);
-    this.refreshQuery(data);
-  }
-
   restart(item) {
     asyncConfirm('提示', '是否重置', () => {
       Question.restart(item.paper.id).then(() => {
@@ -249,33 +164,61 @@ export default class extends Page {
     });
   }
 
+  subscribe(value) {
+    Textbook.subscribe(value)
+      .then(() => {
+        this.refresh();
+      })
+      .catch(err => {
+        asyncSMessage(err.message, 'warn');
+      });
+  }
+
   renderView() {
-    const { logic, logicExtend, logics = [], logicExtends = [], list } = this.state;
+    const { list, search, info = {}, textbook = {} } = this.state;
+    const { finish } = search;
     return (
       <div>
         <div className="content">
-          <Module className="m-t-2">
-            <Tabs
-              active={logic}
-              border
-              width="180px"
-              space="0"
-              tabs={logics}
-              onChange={(key) => {
-                this.onChangeTab('logic', key);
-              }}
-            />
-            {logicExtends.length > 0 && <Tabs
-              active={logicExtend}
-              type="text"
-              tabs={logicExtends}
-              onChange={(key) => {
-                this.onChangeTab('logicExtend', key);
-              }}
-            />}
-          </Module>
-
+          <div className="textbook-head">
+            <Breadcrumb separator=">">
+              <Breadcrumb.Item href="/examination">模考</Breadcrumb.Item>
+              <Breadcrumb.Item href="/examination?tab1=textbook">数学机经</Breadcrumb.Item>
+              <Breadcrumb.Item>{info.latest ? '最新' : '往期'}</Breadcrumb.Item>
+              <Breadcrumb.Item>{info.logic ? info.logic.toUpperCase() : ''}</Breadcrumb.Item>
+            </Breadcrumb>
+            {!!info.latest && !!textbook.latest && <div className='textbook-time'>最近更新:{formatDate(textbook.latest.updateTime, 'YYYY-MM-DD')}</div>}
+          </div>
           <ListTable
+            rightAction={<div>
+              {!!info.latest && <span>邮箱订阅<Switch checked={textbook.subscribe} onChange={() => {
+                this.subscribe(!textbook.subscribe);
+              }} /></span>}
+              {!!info.latest && <Button radius onClick={() => {
+                this.download();
+              }}>下载</Button>}
+              {!info.latest && <Select value={info.year} theme="default" list={this.state.yearList || []} onChange={(item) => {
+                this.search({ year: item.key });
+              }} />}
+            </div>}
+            filters={[
+              {
+                type: 'radio',
+                checked: finish,
+                list: [{ key: '0', title: '未完成' }, { key: '1', title: '已完成' }],
+                onChange: item => {
+                  if (item.key === finish) {
+                    this.search({ finish: null });
+                  } else if (item.key === '0') {
+                    this.search({ finish: 0 });
+                  } else if (item.key === '1') {
+                    this.search({ finish: 1 });
+                  } else {
+                    this.search({ finish: null });
+                  }
+                },
+              },
+            ]}
             data={list}
             columns={this.columns}
           />

+ 3 - 2
front/project/www/static/login.html

@@ -203,15 +203,16 @@
   }
   var code = getQuery('code');
   if (code) {
-    window.parent.postMessage('code:' + code, '*');
+    window.top.postMessage('code:' + code, '*');
   } else {
     document.getElementById('loading').style.display = 'none';
   }
   new WxLogin({
     id: 'root',
+    self_redirect: true,
     appid: getQuery('appid'),
     scope: 'snsapi_login',
-    redirect_uri: getQuery('redirectUri'),
+    redirect_uri: getQuery('redirectUri') + '/login.html',
   });
 </script>
 

+ 4 - 0
front/project/www/stores/course.js

@@ -32,6 +32,10 @@ export default class CourseStore extends BaseStore {
     return this.apiGet('/course/data/detail', { dataId });
   }
 
+  historyData(dataId) {
+    return this.apiGet('/course/data/history', { dataId });
+  }
+
   listExperience({ page, size, perpareStatus, experienceDay, experienceScore, experiencePercent, order, direction }) {
     return this.apiGet('/course/experience/list', { page, size, perpareStatus, experienceDay, experienceScore, experiencePercent, order, direction });
   }

+ 23 - 0
front/project/www/stores/main.js

@@ -44,6 +44,25 @@ export default class MainStore extends BaseStore {
     return this.apiGet('/base/score', { total, quant });
   }
 
+  courseStruct() {
+    return this.getExercise().then((result) => {
+      return result.filter(row => row.isCourse);
+    });
+  }
+
+  dataStruct() {
+    return this.getExercise().then((result) => {
+      return result.filter(row => row.isData);
+    });
+  }
+
+  /**
+   * 单个科目下的范围
+   */
+  rangeExercise() {
+    return this.apiGet('/base/exercise/range');
+  }
+
   /**
    * 所有练习头2
    */
@@ -112,6 +131,10 @@ export default class MainStore extends BaseStore {
   listComment(page, size, channel, position) {
     return this.apiGet('/base/comment/list', { page, size, channel, position });
   }
+
+  listArticle(page, size, channel, position) {
+    return this.apiGet('/base/article/list', { page, size, channel, position });
+  }
 }
 
 export const Main = new MainStore({ key: 'main' });

+ 95 - 22
front/project/www/stores/my.js

@@ -99,7 +99,38 @@ export default class MyStore extends BaseStore {
   }
 
   /**
-   * 添加收藏
+   * 添加心经收藏
+   * @param {*} experienceId
+   */
+  addExperienceCollect(experienceId) {
+    return this.apiPut('/my/collect/experience/add', { experienceId });
+  }
+
+  /**
+   * 删除收藏
+   * @param {*} experienceId
+   */
+  delExperienceCollect(experienceId) {
+    return this.apiDel('/my/collect/experience/delete', { experienceId });
+  }
+
+  /**
+   * 获取收藏心经列表
+   * @param {*} questionModule
+   * @param {*} questionType
+   * @param {*} page
+   * @param {*} size
+   * @param {*} startTime
+   * @param {*} endTime
+   * @param {*} order
+   * @param {*} direction
+   */
+  listExperienceCollect({ page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/collect/question/list', { page, size, startTime, endTime, order, direction });
+  }
+
+  /**
+   * 添加题目收藏
    * @param {*} questionModule
    * @param {*} questionNoId
    */
@@ -108,7 +139,7 @@ export default class MyStore extends BaseStore {
   }
 
   /**
-   * 删除收藏
+   * 删除题目收藏
    * @param {*} questionModule
    * @param {*} questionNoId
    */
@@ -128,8 +159,6 @@ export default class MyStore extends BaseStore {
 
   /**
    * 获取收藏题目列表
-   * @param {*} questionModule
-   * @param {*} questionType
    * @param {*} page
    * @param {*} size
    * @param {*} startTime
@@ -137,8 +166,8 @@ export default class MyStore extends BaseStore {
    * @param {*} order
    * @param {*} direction
    */
-  listQuestionCollect(questionModule, questionType, page, size, startTime, endTime, order, direction) {
-    return this.apiGet('/my/collect/question/list', { questionModule, questionType, page, size, startTime, endTime, order, direction });
+  listQuestionCollect({ module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/collect/question/list', { module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
   }
 
   /**
@@ -147,8 +176,8 @@ export default class MyStore extends BaseStore {
    * @param {*} page
    * @param {*} size
    */
-  listError(questionModule, page, size) {
-    return this.apiGet('/my/error/list', { questionModule, page, size });
+  listError({ module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/error/list', { module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
   }
 
   /**
@@ -178,6 +207,18 @@ export default class MyStore extends BaseStore {
   }
 
   /**
+   * 获取学习数据
+   * @param {*} module
+   * @param {*} subject
+   * @param {*} structIds
+   * @param {*} startTime
+   * @param {*} endTime
+   */
+  getData(module, subject, structIds, startTime, endTime) {
+    return this.apiGet('/my/data', { module, subject, structIds, startTime, endTime });
+  }
+
+  /**
    * 更新题目笔记
    * @param {*} questionModule
    * @param {*} questionNoId
@@ -203,8 +244,6 @@ export default class MyStore extends BaseStore {
 
   /**
    * 获取笔记列表
-   * @param {*} questionModule
-   * @param {*} questionType
    * @param {*} page
    * @param {*} size
    * @param {*} startTime
@@ -212,8 +251,8 @@ export default class MyStore extends BaseStore {
    * @param {*} order
    * @param {*} direction
    */
-  questionNoteList(questionModule, questionType, page, size, startTime, endTime, order, direction) {
-    return this.apiGet('/my/note/question/list', { questionModule, questionType, page, size, startTime, endTime, order, direction });
+  questionNoteList({ module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/note/question/list', { module, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
   }
 
   /**
@@ -227,19 +266,32 @@ export default class MyStore extends BaseStore {
    * @param {*} order
    * @param {*} direction
    */
-  reportList(origin, structId, page, size, startTime, endTime, order, direction) {
-    return this.apiGet('/my/report/list', { origin, structId, page, size, startTime, endTime, order, direction });
+  reportList({ module, origin, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/report/list', { module, origin, questionTypes, structIds, latest, year, page, size, startTime, endTime, order, direction });
   }
 
   /**
    * 添加题目提问
+   * @param {*} userPaperId : 用于获取预习作业,判断权限
    * @param {*} target
    * @param {*} questionModule
    * @param {*} questionNoId
    * @param {*} content
    */
-  addQuestionAsk(userQuestionId, target, questionModule, questionNoId, originContent, content) {
-    return this.apiPost('/my/ask/question', { userQuestionId, target, questionModule, questionNoId, originContent, content });
+  addQuestionAsk(userPaperId, target, questionModule, questionNoId, originContent, content) {
+    return this.apiPost('/my/ask/question', { userPaperId, target, questionModule, questionNoId, originContent, content });
+  }
+
+  /**
+   * 删除题目提问
+   * @param {*} id
+   */
+  delQuestionAsk(id) {
+    return this.apiDel('/my/ask/question/delete', { id });
+  }
+
+  listQuestionAsk({ module, questionTypes, structIds, latest, year, askStatus, page, size, startTime, endTime, order, direction }) {
+    return this.apiGet('/my/ask/question/list', { module, questionTypes, structIds, latest, year, askStatus, page, size, startTime, endTime, order, direction });
   }
 
   /**
@@ -255,26 +307,27 @@ export default class MyStore extends BaseStore {
 
   /**
    * 添加题目勘误
-   * @param {*} moduleId
+   * @param {*} questionModule
+   * @param {*} questionNoId
    * @param {*} title
    * @param {*} position
    * @param {*} originContent
    * @param {*} content
    */
-  addFeedbackErrorQuestion(moduleId, title, position, originContent, content) {
-    return this.apiPost('/my/feedback/error/question', { moduleId, title, position, originContent, content });
+  addFeedbackErrorQuestion(questionModule, questionNoId, title, position, originContent, content) {
+    return this.apiPost('/my/feedback/error/question', { questionModule, questionNoId, title, position, originContent, content });
   }
 
   /**
    * 添加数据勘误
-   * @param {*} moduleId
+   * @param {*} dataId
    * @param {*} title
    * @param {*} position
    * @param {*} originContent
    * @param {*} content
    */
-  addErrorData(moduleId, title, position, originContent, content) {
-    return this.apiPost('/my/feedback/error/question', { moduleId, title, position, originContent, content });
+  addErrorData(dataId, title, position, originContent, content) {
+    return this.apiPost('/my/feedback/error/question', { dataId, title, position, originContent, content });
   }
 
   /**
@@ -286,6 +339,26 @@ export default class MyStore extends BaseStore {
   addTextbookFeedback(topicId, target, content) {
     return this.apiPost('/my/feedback/textbook', { topicId, target, content });
   }
+
+  /**
+   * 添加Faq
+   * @param {*} channel
+   * @param {*} position
+   * @param {*} content
+   */
+  addFaq(channel, position, content) {
+    return this.apiPost('/my/faq', { channel, position, content });
+  }
+
+  /**
+   * 添加评论
+   * @param {*} channel
+   * @param {*} position
+   * @param {*} content
+   */
+  addComment(channel, position, content) {
+    return this.apiPost('/my/comment', { channel, position, content });
+  }
 }
 
 export const My = new MyStore({ key: 'my' });

+ 22 - 4
front/project/www/stores/question.js

@@ -17,6 +17,18 @@ export default class QuestionStore extends BaseStore {
     openLink(`/paper/report/${item.report.id}`);
   }
 
+  reportPrevLink(item) {
+    openLink(`/paper/report/${item.prevReport.id}`);
+  }
+
+  /**
+   * 通过题目Id获取详情
+   * @param {*} questionId
+   */
+  getInfoById(questionId) {
+    return this.apiGet('/question/info', { questionId });
+  }
+
   /**
    * 练习进度
    * @param {*} structId
@@ -34,6 +46,13 @@ export default class QuestionStore extends BaseStore {
   }
 
   /**
+   * 获取模考信息:cat
+   */
+  getExaminationInfo() {
+    return this.apiGet('/question/examination/info');
+  }
+
+  /**
    * 模考进度
    * @param {*} structId
    */
@@ -59,7 +78,7 @@ export default class QuestionStore extends BaseStore {
    * @param {*} page
    * @param {*} size
    */
-  getExaminationList(page, size, structId, finish) {
+  getExaminationList({ page, size, structId, finish }) {
     return this.apiGet('/question/examination/list', { page, size, structId, times: finish });
   }
 
@@ -209,10 +228,9 @@ export default class QuestionStore extends BaseStore {
 
   /**
    * 重置整套模考卷
-   * @param {*} structId
    */
-  restartExamination(structId) {
-    return this.apiPost('/question/restart/examination', { structId });
+  resetCat() {
+    return this.apiPost('/question/reset/cat');
   }
 }
 

+ 6 - 2
front/project/www/stores/textbook.js

@@ -18,8 +18,8 @@ export default class TextbookStore extends BaseStore {
   /**
    * 机经组卷列表
    */
-  listPaper(page, size, latest, logic, finish) {
-    return this.apiGet('/textbook/paper', { page, size, latest: !!latest, logic, times: finish });
+  listPaper({ page, size, latest, logic, finish }) {
+    return this.apiGet('/textbook/paper/list', { page, size, latest: !!latest, logic, times: finish });
   }
 
   listYear(year) {
@@ -33,6 +33,10 @@ export default class TextbookStore extends BaseStore {
   listTopic(page, size, latest, qualitys, isOld, order, direction) {
     return this.apiGet('/textbook/topic/list', { page, size, latest, qualitys, isOld, order, direction });
   }
+
+  subscribe(subscribe) {
+    return this.apiPost('/textbook/subscribe', { subscribe });
+  }
 }
 
 export const Textbook = new TextbookStore({ key: 'textbook' });

+ 3 - 1
front/src/layouts/FormLayout/index.js

@@ -1,6 +1,6 @@
 import React, { Component } from 'react';
 import moment from 'moment';
-import { Form, Button, Switch, DatePicker, Input, InputNumber, Modal, Alert } from 'antd';
+import { Form, Button, Switch, DatePicker, Input, InputNumber, Modal, Alert, Cascader } from 'antd';
 import Select from '../../components/Select';
 import Multiple from '../../components/Multiple';
 import Radio from '../../components/Radio';
@@ -82,6 +82,8 @@ class FormLayout extends Component {
             disabled={!!item.disabled}
           />
         );
+      case 'cascader':
+        return <Cascader {...item} className={item.class} options={item.select} placeholder={item.placeholder} />;
       case 'render':
         return item.render(item, this.props.data, this.props.form);
       case 'textarea':

+ 1 - 1
server/data/src/main/java/com/qxgmat/data/constants/enums/logic/TextbookLogic.java

@@ -11,7 +11,7 @@ public enum TextbookLogic {
 
     public static TextbookLogic ValueOf(String name){
         if (name == null) return null;
-        return TextbookLogic.valueOf(name.toUpperCase());
+        return TextbookLogic.valueOf(name.replace("+","_").toUpperCase());
     }
 
     public static TextbookLogic[] all(){

+ 37 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/module/AskModule.java

@@ -0,0 +1,37 @@
+package com.qxgmat.data.constants.enums.module;
+
+// 组卷模块
+public enum AskModule {
+    EXERCISE("exercise"),
+    EXAMINATION("examination"),
+    ;
+    public String key;
+    private AskModule(String key){
+        this.key = key;
+    }
+
+    public static AskModule ValueOf(String name){
+        if (name == null) return null;
+        return AskModule.valueOf(name.toUpperCase());
+    }
+
+    /**
+     * 根据origin类型判断组卷类型
+     * @param module
+     * @return
+     */
+    public static AskModule WithPaper(PaperModule module){
+        switch(module){
+            case SENTENCE:
+                return EXERCISE;
+            case TEXTBOOK:
+                return EXAMINATION;
+            case EXERCISE:
+                return EXERCISE;
+            case EXAMINATION:
+                return EXAMINATION;
+            default:
+                return null;
+        }
+    }
+}

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

@@ -1,18 +0,0 @@
-package com.qxgmat.data.constants.enums.module;
-
-// Faq模块
-public enum FaqModule {
-    CONSULT("consult"),
-    SYSTEM("system"),
-
-    ;
-    public String key;
-    private FaqModule(String key){
-        this.key = key;
-    }
-
-    public static FaqModule ValueOf(String name){
-        if (name == null) return null;
-        return FaqModule.valueOf(name.toUpperCase());
-    }
-}

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

@@ -0,0 +1,18 @@
+package com.qxgmat.data.constants.enums.module;
+
+// Faq模块
+public enum InsideModule {
+    CONSULT("consult"), // 用户提问
+    SYSTEM("system"), // 系统添加
+
+    ;
+    public String key;
+    private InsideModule(String key){
+        this.key = key;
+    }
+
+    public static InsideModule ValueOf(String name){
+        if (name == null) return null;
+        return InsideModule.valueOf(name.toUpperCase());
+    }
+}

+ 175 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/ExaminationPaper.java

@@ -70,6 +70,36 @@ public class ExaminationPaper implements Serializable {
     @Column(name = "`ir_score`")
     private Integer irScore;
 
+    /**
+     * 第二次总分
+     */
+    @Column(name = "`second_total_score`")
+    private Integer secondTotalScore;
+
+    /**
+     * 第二次总做卷次数
+     */
+    @Column(name = "`second_total_times`")
+    private Integer secondTotalTimes;
+
+    /**
+     * 第二次总Q分
+     */
+    @Column(name = "`second_quant_score`")
+    private Integer secondQuantScore;
+
+    /**
+     * 第二次总V分
+     */
+    @Column(name = "`second_verbal_score`")
+    private Integer secondVerbalScore;
+
+    /**
+     * 第二次总ir分
+     */
+    @Column(name = "`second_ir_score`")
+    private Integer secondIrScore;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -266,6 +296,96 @@ public class ExaminationPaper implements Serializable {
         this.irScore = irScore;
     }
 
+    /**
+     * 获取第二次总分
+     *
+     * @return second_total_score - 第二次总分
+     */
+    public Integer getSecondTotalScore() {
+        return secondTotalScore;
+    }
+
+    /**
+     * 设置第二次总分
+     *
+     * @param secondTotalScore 第二次总分
+     */
+    public void setSecondTotalScore(Integer secondTotalScore) {
+        this.secondTotalScore = secondTotalScore;
+    }
+
+    /**
+     * 获取第二次总做卷次数
+     *
+     * @return second_total_times - 第二次总做卷次数
+     */
+    public Integer getSecondTotalTimes() {
+        return secondTotalTimes;
+    }
+
+    /**
+     * 设置第二次总做卷次数
+     *
+     * @param secondTotalTimes 第二次总做卷次数
+     */
+    public void setSecondTotalTimes(Integer secondTotalTimes) {
+        this.secondTotalTimes = secondTotalTimes;
+    }
+
+    /**
+     * 获取第二次总Q分
+     *
+     * @return second_quant_score - 第二次总Q分
+     */
+    public Integer getSecondQuantScore() {
+        return secondQuantScore;
+    }
+
+    /**
+     * 设置第二次总Q分
+     *
+     * @param secondQuantScore 第二次总Q分
+     */
+    public void setSecondQuantScore(Integer secondQuantScore) {
+        this.secondQuantScore = secondQuantScore;
+    }
+
+    /**
+     * 获取第二次总V分
+     *
+     * @return second_verbal_score - 第二次总V分
+     */
+    public Integer getSecondVerbalScore() {
+        return secondVerbalScore;
+    }
+
+    /**
+     * 设置第二次总V分
+     *
+     * @param secondVerbalScore 第二次总V分
+     */
+    public void setSecondVerbalScore(Integer secondVerbalScore) {
+        this.secondVerbalScore = secondVerbalScore;
+    }
+
+    /**
+     * 获取第二次总ir分
+     *
+     * @return second_ir_score - 第二次总ir分
+     */
+    public Integer getSecondIrScore() {
+        return secondIrScore;
+    }
+
+    /**
+     * 设置第二次总ir分
+     *
+     * @param secondIrScore 第二次总ir分
+     */
+    public void setSecondIrScore(Integer secondIrScore) {
+        this.secondIrScore = secondIrScore;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -283,6 +403,11 @@ public class ExaminationPaper implements Serializable {
         sb.append(", quantScore=").append(quantScore);
         sb.append(", verbalScore=").append(verbalScore);
         sb.append(", irScore=").append(irScore);
+        sb.append(", secondTotalScore=").append(secondTotalScore);
+        sb.append(", secondTotalTimes=").append(secondTotalTimes);
+        sb.append(", secondQuantScore=").append(secondQuantScore);
+        sb.append(", secondVerbalScore=").append(secondVerbalScore);
+        sb.append(", secondIrScore=").append(secondIrScore);
         sb.append("]");
         return sb.toString();
     }
@@ -406,6 +531,56 @@ public class ExaminationPaper implements Serializable {
             return this;
         }
 
+        /**
+         * 设置第二次总分
+         *
+         * @param secondTotalScore 第二次总分
+         */
+        public Builder secondTotalScore(Integer secondTotalScore) {
+            obj.setSecondTotalScore(secondTotalScore);
+            return this;
+        }
+
+        /**
+         * 设置第二次总做卷次数
+         *
+         * @param secondTotalTimes 第二次总做卷次数
+         */
+        public Builder secondTotalTimes(Integer secondTotalTimes) {
+            obj.setSecondTotalTimes(secondTotalTimes);
+            return this;
+        }
+
+        /**
+         * 设置第二次总Q分
+         *
+         * @param secondQuantScore 第二次总Q分
+         */
+        public Builder secondQuantScore(Integer secondQuantScore) {
+            obj.setSecondQuantScore(secondQuantScore);
+            return this;
+        }
+
+        /**
+         * 设置第二次总V分
+         *
+         * @param secondVerbalScore 第二次总V分
+         */
+        public Builder secondVerbalScore(Integer secondVerbalScore) {
+            obj.setSecondVerbalScore(secondVerbalScore);
+            return this;
+        }
+
+        /**
+         * 设置第二次总ir分
+         *
+         * @param secondIrScore 第二次总ir分
+         */
+        public Builder secondIrScore(Integer secondIrScore) {
+            obj.setSecondIrScore(secondIrScore);
+            return this;
+        }
+
         public ExaminationPaper build() {
             return this.obj;
         }

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

@@ -12,12 +12,6 @@ public class Faq implements Serializable {
     private Integer id;
 
     /**
-     * faq模块
-     */
-    @Column(name = "`faq_module`")
-    private String faqModule;
-
-    /**
      * 用户id
      */
     @Column(name = "`user_id`")
@@ -30,12 +24,6 @@ public class Faq implements Serializable {
     private String email;
 
     /**
-     * 提问者电话
-     */
-    @Column(name = "`phone`")
-    private String phone;
-
-    /**
      * 回复站内信
      */
     @Column(name = "`message`")
@@ -121,24 +109,6 @@ public class Faq implements Serializable {
     }
 
     /**
-     * 获取faq模块
-     *
-     * @return faq_module - faq模块
-     */
-    public String getFaqModule() {
-        return faqModule;
-    }
-
-    /**
-     * 设置faq模块
-     *
-     * @param faqModule faq模块
-     */
-    public void setFaqModule(String faqModule) {
-        this.faqModule = faqModule;
-    }
-
-    /**
      * 获取用户id
      *
      * @return user_id - 用户id
@@ -175,24 +145,6 @@ public class Faq implements Serializable {
     }
 
     /**
-     * 获取提问者电话
-     *
-     * @return phone - 提问者电话
-     */
-    public String getPhone() {
-        return phone;
-    }
-
-    /**
-     * 设置提问者电话
-     *
-     * @param phone 提问者电话
-     */
-    public void setPhone(String phone) {
-        this.phone = phone;
-    }
-
-    /**
      * 获取回复站内信
      *
      * @return message - 回复站内信
@@ -411,10 +363,8 @@ public class Faq implements Serializable {
         sb.append(" [");
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
-        sb.append(", faqModule=").append(faqModule);
         sb.append(", userId=").append(userId);
         sb.append(", email=").append(email);
-        sb.append(", phone=").append(phone);
         sb.append(", message=").append(message);
         sb.append(", channel=").append(channel);
         sb.append(", position=").append(position);
@@ -451,16 +401,6 @@ public class Faq implements Serializable {
         }
 
         /**
-         * 设置faq模块
-         *
-         * @param faqModule faq模块
-         */
-        public Builder faqModule(String faqModule) {
-            obj.setFaqModule(faqModule);
-            return this;
-        }
-
-        /**
          * 设置用户id
          *
          * @param userId 用户id
@@ -481,16 +421,6 @@ public class Faq implements Serializable {
         }
 
         /**
-         * 设置提问者电话
-         *
-         * @param phone 提问者电话
-         */
-        public Builder phone(String phone) {
-            obj.setPhone(phone);
-            return this;
-        }
-
-        /**
          * 设置回复站内信
          *
          * @param message 回复站内信

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

@@ -36,6 +36,18 @@ public class PreviewPaper implements Serializable {
     private Integer courseNo;
 
     /**
+     * 作业学科
+     */
+    @Column(name = "`question_subject`")
+    private String questionSubject;
+
+    /**
+     * 作业题型
+     */
+    @Column(name = "`question_type`")
+    private String questionType;
+
+    /**
      * 题目编号id:json
      */
     @Column(name = "`question_no_ids`")
@@ -142,6 +154,42 @@ public class PreviewPaper implements Serializable {
     }
 
     /**
+     * 获取作业学科
+     *
+     * @return question_subject - 作业学科
+     */
+    public String getQuestionSubject() {
+        return questionSubject;
+    }
+
+    /**
+     * 设置作业学科
+     *
+     * @param questionSubject 作业学科
+     */
+    public void setQuestionSubject(String questionSubject) {
+        this.questionSubject = questionSubject;
+    }
+
+    /**
+     * 获取作业题型
+     *
+     * @return question_type - 作业题型
+     */
+    public String getQuestionType() {
+        return questionType;
+    }
+
+    /**
+     * 设置作业题型
+     *
+     * @param questionType 作业题型
+     */
+    public void setQuestionType(String questionType) {
+        this.questionType = questionType;
+    }
+
+    /**
      * 获取题目编号id:json
      *
      * @return question_no_ids - 题目编号id:json
@@ -216,6 +264,8 @@ public class PreviewPaper implements Serializable {
         sb.append(", courseId=").append(courseId);
         sb.append(", courseModule=").append(courseModule);
         sb.append(", courseNo=").append(courseNo);
+        sb.append(", questionSubject=").append(questionSubject);
+        sb.append(", questionType=").append(questionType);
         sb.append(", questionNoIds=").append(questionNoIds);
         sb.append(", paperModule=").append(paperModule);
         sb.append(", createTime=").append(createTime);
@@ -284,6 +334,26 @@ public class PreviewPaper implements Serializable {
         }
 
         /**
+         * 设置作业学科
+         *
+         * @param questionSubject 作业学科
+         */
+        public Builder questionSubject(String questionSubject) {
+            obj.setQuestionSubject(questionSubject);
+            return this;
+        }
+
+        /**
+         * 设置作业题型
+         *
+         * @param questionType 作业题型
+         */
+        public Builder questionType(String questionType) {
+            obj.setQuestionType(questionType);
+            return this;
+        }
+
+        /**
          * 设置题目编号id:json
          *
          * @param questionNoIds 题目编号id:json

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

@@ -18,6 +18,9 @@ public class Question implements Serializable {
     @Column(name = "`keyword`")
     private String[] keyword;
 
+    @Column(name = "`question_module`")
+    private String questionModule;
+
     /**
      * 题型
      */
@@ -96,6 +99,12 @@ public class Question implements Serializable {
     @Column(name = "`total_correct`")
     private Integer totalCorrect;
 
+    /**
+     * 收藏数
+     */
+    @Column(name = "`collect_number`")
+    private Integer collectNumber;
+
     @Column(name = "`create_time`")
     private Date createTime;
 
@@ -155,6 +164,20 @@ public class Question implements Serializable {
     }
 
     /**
+     * @return question_module
+     */
+    public String getQuestionModule() {
+        return questionModule;
+    }
+
+    /**
+     * @param questionModule
+     */
+    public void setQuestionModule(String questionModule) {
+        this.questionModule = questionModule;
+    }
+
+    /**
      * 获取题型
      *
      * @return question_type - 题型
@@ -409,6 +432,24 @@ public class Question implements Serializable {
     }
 
     /**
+     * 获取收藏数
+     *
+     * @return collect_number - 收藏数
+     */
+    public Integer getCollectNumber() {
+        return collectNumber;
+    }
+
+    /**
+     * 设置收藏数
+     *
+     * @param collectNumber 收藏数
+     */
+    public void setCollectNumber(Integer collectNumber) {
+        this.collectNumber = collectNumber;
+    }
+
+    /**
      * @return create_time
      */
     public Date getCreateTime() {
@@ -508,6 +549,7 @@ public class Question implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", keyword=").append(keyword);
+        sb.append(", questionModule=").append(questionModule);
         sb.append(", questionType=").append(questionType);
         sb.append(", place=").append(place);
         sb.append(", difficult=").append(difficult);
@@ -523,6 +565,7 @@ public class Question implements Serializable {
         sb.append(", totalTime=").append(totalTime);
         sb.append(", totalNumber=").append(totalNumber);
         sb.append(", totalCorrect=").append(totalCorrect);
+        sb.append(", collectNumber=").append(collectNumber);
         sb.append(", createTime=").append(createTime);
         sb.append(", updateTime=").append(updateTime);
         sb.append(", answerDistributed=").append(answerDistributed);
@@ -563,6 +606,14 @@ public class Question implements Serializable {
         }
 
         /**
+         * @param questionModule
+         */
+        public Builder questionModule(String questionModule) {
+            obj.setQuestionModule(questionModule);
+            return this;
+        }
+
+        /**
          * 设置题型
          *
          * @param questionType 题型
@@ -715,6 +766,16 @@ public class Question implements Serializable {
         }
 
         /**
+         * 设置收藏数
+         *
+         * @param collectNumber 收藏数
+         */
+        public Builder collectNumber(Integer collectNumber) {
+            obj.setCollectNumber(collectNumber);
+            return this;
+        }
+
+        /**
          * @param createTime
          */
         public Builder createTime(Date createTime) {

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

@@ -59,8 +59,8 @@ public class TextbookLibraryHistory implements Serializable {
     /**
      * 数学更新日志
      */
-    @Column(name = "`quant_contennt`")
-    private String quantContennt;
+    @Column(name = "`quant_content`")
+    private String quantContent;
 
     /**
      * 阅读更新日志
@@ -233,19 +233,19 @@ public class TextbookLibraryHistory implements Serializable {
     /**
      * 获取数学更新日志
      *
-     * @return quant_contennt - 数学更新日志
+     * @return quant_content - 数学更新日志
      */
-    public String getQuantContennt() {
-        return quantContennt;
+    public String getQuantContent() {
+        return quantContent;
     }
 
     /**
      * 设置数学更新日志
      *
-     * @param quantContennt 数学更新日志
+     * @param quantContent 数学更新日志
      */
-    public void setQuantContennt(String quantContennt) {
-        this.quantContennt = quantContennt;
+    public void setQuantContent(String quantContent) {
+        this.quantContent = quantContent;
     }
 
     /**
@@ -299,7 +299,7 @@ public class TextbookLibraryHistory implements Serializable {
         sb.append(", ir=").append(ir);
         sb.append(", irVersion=").append(irVersion);
         sb.append(", createTime=").append(createTime);
-        sb.append(", quantContennt=").append(quantContennt);
+        sb.append(", quantContent=").append(quantContent);
         sb.append(", rcContent=").append(rcContent);
         sb.append(", irContent=").append(irContent);
         sb.append("]");
@@ -348,10 +348,10 @@ public class TextbookLibraryHistory implements Serializable {
         /**
          * 设置数学更新日志
          *
-         * @param quantContennt 数学更新日志
+         * @param quantContent 数学更新日志
          */
-        public Builder quantContennt(String quantContennt) {
-            obj.setQuantContennt(quantContennt);
+        public Builder quantContent(String quantContent) {
+            obj.setQuantContent(quantContent);
             return this;
         }
 

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

@@ -42,6 +42,12 @@ public class UserAskCourse implements Serializable {
     private String position;
 
     /**
+     * 提问优先回答时间
+     */
+    @Column(name = "`ask_time`")
+    private Integer askTime;
+
+    /**
      * 回答状态: 0未回答,1回答,2忽略
      */
     @Column(name = "`answer_status`")
@@ -196,6 +202,24 @@ public class UserAskCourse implements Serializable {
     }
 
     /**
+     * 获取提问优先回答时间
+     *
+     * @return ask_time - 提问优先回答时间
+     */
+    public Integer getAskTime() {
+        return askTime;
+    }
+
+    /**
+     * 设置提问优先回答时间
+     *
+     * @param askTime 提问优先回答时间
+     */
+    public void setAskTime(Integer askTime) {
+        this.askTime = askTime;
+    }
+
+    /**
      * 获取回答状态: 0未回答,1回答,2忽略
      *
      * @return answer_status - 回答状态: 0未回答,1回答,2忽略
@@ -361,6 +385,7 @@ public class UserAskCourse implements Serializable {
         sb.append(", courseNoId=").append(courseNoId);
         sb.append(", recordId=").append(recordId);
         sb.append(", position=").append(position);
+        sb.append(", askTime=").append(askTime);
         sb.append(", answerStatus=").append(answerStatus);
         sb.append(", managerId=").append(managerId);
         sb.append(", showStatus=").append(showStatus);
@@ -444,6 +469,16 @@ public class UserAskCourse implements Serializable {
         }
 
         /**
+         * 设置提问优先回答时间
+         *
+         * @param askTime 提问优先回答时间
+         */
+        public Builder askTime(Integer askTime) {
+            obj.setAskTime(askTime);
+            return this;
+        }
+
+        /**
          * 设置回答
          *
          * @param answer 回答

+ 86 - 16
server/data/src/main/java/com/qxgmat/data/dao/entity/UserAskQuestion.java

@@ -24,6 +24,12 @@ public class UserAskQuestion implements Serializable {
     private Integer userQuestionId;
 
     /**
+     * 题目板块
+     */
+    @Column(name = "`ask_module`")
+    private String askModule;
+
+    /**
      * 题目模块
      */
     @Column(name = "`question_module`")
@@ -42,16 +48,22 @@ public class UserAskQuestion implements Serializable {
     private Integer questionNoId;
 
     /**
+     * 课程对应recordid
+     */
+    @Column(name = "`record_id`")
+    private Integer recordId;
+
+    /**
      * 问题对象:question,official,qx,association
      */
     @Column(name = "`target`")
     private String target;
 
     /**
-     * 课程对应recordid
+     * 提问优先回答时间
      */
-    @Column(name = "`record_id`")
-    private Integer recordId;
+    @Column(name = "`ask_time`")
+    private Integer askTime;
 
     /**
      * 回答状态: 0未回答,1回答,2忽略
@@ -160,6 +172,24 @@ public class UserAskQuestion implements Serializable {
     }
 
     /**
+     * 获取题目板块
+     *
+     * @return ask_module - 题目板块
+     */
+    public String getAskModule() {
+        return askModule;
+    }
+
+    /**
+     * 设置题目板块
+     *
+     * @param askModule 题目板块
+     */
+    public void setAskModule(String askModule) {
+        this.askModule = askModule;
+    }
+
+    /**
      * 获取题目模块
      *
      * @return question_module - 题目模块
@@ -214,6 +244,24 @@ public class UserAskQuestion implements Serializable {
     }
 
     /**
+     * 获取课程对应recordid
+     *
+     * @return record_id - 课程对应recordid
+     */
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    /**
+     * 设置课程对应recordid
+     *
+     * @param recordId 课程对应recordid
+     */
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+
+    /**
      * 获取问题对象:question,official,qx,association
      *
      * @return target - 问题对象:question,official,qx,association
@@ -232,21 +280,21 @@ public class UserAskQuestion implements Serializable {
     }
 
     /**
-     * 获取课程对应recordid
+     * 获取提问优先回答时间
      *
-     * @return record_id - 课程对应recordid
+     * @return ask_time - 提问优先回答时间
      */
-    public Integer getRecordId() {
-        return recordId;
+    public Integer getAskTime() {
+        return askTime;
     }
 
     /**
-     * 设置课程对应recordid
+     * 设置提问优先回答时间
      *
-     * @param recordId 课程对应recordid
+     * @param askTime 提问优先回答时间
      */
-    public void setRecordId(Integer recordId) {
-        this.recordId = recordId;
+    public void setAskTime(Integer askTime) {
+        this.askTime = askTime;
     }
 
     /**
@@ -430,11 +478,13 @@ public class UserAskQuestion implements Serializable {
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
         sb.append(", userQuestionId=").append(userQuestionId);
+        sb.append(", askModule=").append(askModule);
         sb.append(", questionModule=").append(questionModule);
         sb.append(", questionId=").append(questionId);
         sb.append(", questionNoId=").append(questionNoId);
-        sb.append(", target=").append(target);
         sb.append(", recordId=").append(recordId);
+        sb.append(", target=").append(target);
+        sb.append(", askTime=").append(askTime);
         sb.append(", answerStatus=").append(answerStatus);
         sb.append(", managerId=").append(managerId);
         sb.append(", showStatus=").append(showStatus);
@@ -489,6 +539,16 @@ public class UserAskQuestion implements Serializable {
         }
 
         /**
+         * 设置题目板块
+         *
+         * @param askModule 题目板块
+         */
+        public Builder askModule(String askModule) {
+            obj.setAskModule(askModule);
+            return this;
+        }
+
+        /**
          * 设置题目模块
          *
          * @param questionModule 题目模块
@@ -519,6 +579,16 @@ public class UserAskQuestion implements Serializable {
         }
 
         /**
+         * 设置课程对应recordid
+         *
+         * @param recordId 课程对应recordid
+         */
+        public Builder recordId(Integer recordId) {
+            obj.setRecordId(recordId);
+            return this;
+        }
+
+        /**
          * 设置问题对象:question,official,qx,association
          *
          * @param target 问题对象:question,official,qx,association
@@ -529,12 +599,12 @@ public class UserAskQuestion implements Serializable {
         }
 
         /**
-         * 设置课程对应recordid
+         * 设置提问优先回答时间
          *
-         * @param recordId 课程对应recordid
+         * @param askTime 提问优先回答时间
          */
-        public Builder recordId(Integer recordId) {
-            obj.setRecordId(recordId);
+        public Builder askTime(Integer askTime) {
+            obj.setAskTime(askTime);
             return this;
         }
 

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

@@ -61,12 +61,6 @@ public class UserCourseAppointment implements Serializable {
     private Date endTime;
 
     /**
-     * 用户作业id
-     */
-    @Column(name = "`paper_id`")
-    private Integer paperId;
-
-    /**
      * 是否完成
      */
     @Column(name = "`is_finish`")
@@ -248,24 +242,6 @@ public class UserCourseAppointment implements Serializable {
     }
 
     /**
-     * 获取用户作业id
-     *
-     * @return paper_id - 用户作业id
-     */
-    public Integer getPaperId() {
-        return paperId;
-    }
-
-    /**
-     * 设置用户作业id
-     *
-     * @param paperId 用户作业id
-     */
-    public void setPaperId(Integer paperId) {
-        this.paperId = paperId;
-    }
-
-    /**
      * 获取是否完成
      *
      * @return is_finish - 是否完成
@@ -348,7 +324,6 @@ public class UserCourseAppointment implements Serializable {
         sb.append(", cctalkChannel=").append(cctalkChannel);
         sb.append(", startTime=").append(startTime);
         sb.append(", endTime=").append(endTime);
-        sb.append(", paperId=").append(paperId);
         sb.append(", isFinish=").append(isFinish);
         sb.append(", supplyList=").append(supplyList);
         sb.append(", noteList=").append(noteList);
@@ -467,16 +442,6 @@ public class UserCourseAppointment implements Serializable {
         }
 
         /**
-         * 设置用户作业id
-         *
-         * @param paperId 用户作业id
-         */
-        public Builder paperId(Integer paperId) {
-            obj.setPaperId(paperId);
-            return this;
-        }
-
-        /**
          * 设置是否完成
          *
          * @param isFinish 是否完成

+ 105 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserFeedbackError.java

@@ -30,6 +30,24 @@ public class UserFeedbackError implements Serializable {
     private Integer moduleId;
 
     /**
+     * 题目类型
+     */
+    @Column(name = "`question_type`")
+    private String questionType;
+
+    /**
+     * 题目模块
+     */
+    @Column(name = "`question_module`")
+    private String questionModule;
+
+    /**
+     * 题目编号id
+     */
+    @Column(name = "`question_no_id`")
+    private Integer questionNoId;
+
+    /**
      * 处理人id
      */
     @Column(name = "`manager_id`")
@@ -145,6 +163,60 @@ public class UserFeedbackError implements Serializable {
     }
 
     /**
+     * 获取题目类型
+     *
+     * @return question_type - 题目类型
+     */
+    public String getQuestionType() {
+        return questionType;
+    }
+
+    /**
+     * 设置题目类型
+     *
+     * @param questionType 题目类型
+     */
+    public void setQuestionType(String questionType) {
+        this.questionType = questionType;
+    }
+
+    /**
+     * 获取题目模块
+     *
+     * @return question_module - 题目模块
+     */
+    public String getQuestionModule() {
+        return questionModule;
+    }
+
+    /**
+     * 设置题目模块
+     *
+     * @param questionModule 题目模块
+     */
+    public void setQuestionModule(String questionModule) {
+        this.questionModule = questionModule;
+    }
+
+    /**
+     * 获取题目编号id
+     *
+     * @return question_no_id - 题目编号id
+     */
+    public Integer getQuestionNoId() {
+        return questionNoId;
+    }
+
+    /**
+     * 设置题目编号id
+     *
+     * @param questionNoId 题目编号id
+     */
+    public void setQuestionNoId(Integer questionNoId) {
+        this.questionNoId = questionNoId;
+    }
+
+    /**
      * 获取处理人id
      *
      * @return manager_id - 处理人id
@@ -294,6 +366,9 @@ public class UserFeedbackError implements Serializable {
         sb.append(", userId=").append(userId);
         sb.append(", module=").append(module);
         sb.append(", moduleId=").append(moduleId);
+        sb.append(", questionType=").append(questionType);
+        sb.append(", questionModule=").append(questionModule);
+        sb.append(", questionNoId=").append(questionNoId);
         sb.append(", managerId=").append(managerId);
         sb.append(", title=").append(title);
         sb.append(", position=").append(position);
@@ -356,6 +431,36 @@ public class UserFeedbackError implements Serializable {
         }
 
         /**
+         * 设置题目类型
+         *
+         * @param questionType 题目类型
+         */
+        public Builder questionType(String questionType) {
+            obj.setQuestionType(questionType);
+            return this;
+        }
+
+        /**
+         * 设置题目模块
+         *
+         * @param questionModule 题目模块
+         */
+        public Builder questionModule(String questionModule) {
+            obj.setQuestionModule(questionModule);
+            return this;
+        }
+
+        /**
+         * 设置题目编号id
+         *
+         * @param questionNoId 题目编号id
+         */
+        public Builder questionNoId(Integer questionNoId) {
+            obj.setQuestionNoId(questionNoId);
+            return this;
+        }
+
+        /**
          * 设置处理人id
          *
          * @param managerId 处理人id

+ 75 - 49
server/data/src/main/java/com/qxgmat/data/dao/entity/UserNoteQuestion.java

@@ -38,6 +38,12 @@ public class UserNoteQuestion implements Serializable {
     @Column(name = "`question_time`")
     private Date questionTime;
 
+    @Column(name = "`create_time`")
+    private Date createTime;
+
+    @Column(name = "`update_time`")
+    private Date updateTime;
+
     @Column(name = "`official_time`")
     private Date officialTime;
 
@@ -50,17 +56,14 @@ public class UserNoteQuestion implements Serializable {
     @Column(name = "`qa_time`")
     private Date qaTime;
 
-    @Column(name = "`create_time`")
-    private Date createTime;
-
-    @Column(name = "`update_time`")
-    private Date updateTime;
+    @Column(name = "`question_content`")
+    private String questionContent;
 
     /**
      * 笔记内容
      */
-    @Column(name = "`question_content`")
-    private String questionContent;
+    @Column(name = "`content`")
+    private String content;
 
     @Column(name = "`official_content`")
     private String officialContent;
@@ -177,6 +180,34 @@ public class UserNoteQuestion implements Serializable {
     }
 
     /**
+     * @return create_time
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * @param createTime
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * @return update_time
+     */
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    /**
+     * @param updateTime
+     */
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    /**
      * @return official_time
      */
     public Date getOfficialTime() {
@@ -233,49 +264,35 @@ public class UserNoteQuestion implements Serializable {
     }
 
     /**
-     * @return create_time
-     */
-    public Date getCreateTime() {
-        return createTime;
-    }
-
-    /**
-     * @param createTime
-     */
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
-    }
-
-    /**
-     * @return update_time
+     * @return question_content
      */
-    public Date getUpdateTime() {
-        return updateTime;
+    public String getQuestionContent() {
+        return questionContent;
     }
 
     /**
-     * @param updateTime
+     * @param questionContent
      */
-    public void setUpdateTime(Date updateTime) {
-        this.updateTime = updateTime;
+    public void setQuestionContent(String questionContent) {
+        this.questionContent = questionContent;
     }
 
     /**
      * 获取笔记内容
      *
-     * @return question_content - 笔记内容
+     * @return content - 笔记内容
      */
-    public String getQuestionContent() {
-        return questionContent;
+    public String getContent() {
+        return content;
     }
 
     /**
      * 设置笔记内容
      *
-     * @param questionContent 笔记内容
+     * @param content 笔记内容
      */
-    public void setQuestionContent(String questionContent) {
-        this.questionContent = questionContent;
+    public void setContent(String content) {
+        this.content = content;
     }
 
     /**
@@ -346,13 +363,14 @@ public class UserNoteQuestion implements Serializable {
         sb.append(", questionId=").append(questionId);
         sb.append(", questionNoId=").append(questionNoId);
         sb.append(", questionTime=").append(questionTime);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
         sb.append(", officialTime=").append(officialTime);
         sb.append(", qxTime=").append(qxTime);
         sb.append(", associationTime=").append(associationTime);
         sb.append(", qaTime=").append(qaTime);
-        sb.append(", createTime=").append(createTime);
-        sb.append(", updateTime=").append(updateTime);
         sb.append(", questionContent=").append(questionContent);
+        sb.append(", content=").append(content);
         sb.append(", officialContent=").append(officialContent);
         sb.append(", qxContent=").append(qxContent);
         sb.append(", associationContent=").append(associationContent);
@@ -429,6 +447,22 @@ public class UserNoteQuestion implements Serializable {
         }
 
         /**
+         * @param createTime
+         */
+        public Builder createTime(Date createTime) {
+            obj.setCreateTime(createTime);
+            return this;
+        }
+
+        /**
+         * @param updateTime
+         */
+        public Builder updateTime(Date updateTime) {
+            obj.setUpdateTime(updateTime);
+            return this;
+        }
+
+        /**
          * @param officialTime
          */
         public Builder officialTime(Date officialTime) {
@@ -461,28 +495,20 @@ public class UserNoteQuestion implements Serializable {
         }
 
         /**
-         * @param createTime
-         */
-        public Builder createTime(Date createTime) {
-            obj.setCreateTime(createTime);
-            return this;
-        }
-
-        /**
-         * @param updateTime
+         * @param questionContent
          */
-        public Builder updateTime(Date updateTime) {
-            obj.setUpdateTime(updateTime);
+        public Builder questionContent(String questionContent) {
+            obj.setQuestionContent(questionContent);
             return this;
         }
 
         /**
          * 设置笔记内容
          *
-         * @param questionContent 笔记内容
+         * @param content 笔记内容
          */
-        public Builder questionContent(String questionContent) {
-            obj.setQuestionContent(questionContent);
+        public Builder content(String content) {
+            obj.setContent(content);
             return this;
         }
 

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

@@ -57,6 +57,12 @@ public class UserOrder implements Serializable {
     private JSONObject promote;
 
     /**
+     * 赠品信息
+     */
+    @Column(name = "`gift`")
+    private JSONArray gift;
+
+    /**
      * 回复时长
      */
     @Column(name = "`ask_time`")
@@ -229,6 +235,24 @@ public class UserOrder implements Serializable {
     }
 
     /**
+     * 获取赠品信息
+     *
+     * @return gift - 赠品信息
+     */
+    public JSONArray getGift() {
+        return gift;
+    }
+
+    /**
+     * 设置赠品信息
+     *
+     * @param gift 赠品信息
+     */
+    public void setGift(JSONArray gift) {
+        this.gift = gift;
+    }
+
+    /**
      * 获取回复时长
      *
      * @return ask_time - 回复时长
@@ -342,6 +366,7 @@ public class UserOrder implements Serializable {
         sb.append(", originMoney=").append(originMoney);
         sb.append(", invoiceMoney=").append(invoiceMoney);
         sb.append(", promote=").append(promote);
+        sb.append(", gift=").append(gift);
         sb.append(", askTime=").append(askTime);
         sb.append(", payId=").append(payId);
         sb.append(", payStatus=").append(payStatus);
@@ -442,6 +467,16 @@ public class UserOrder implements Serializable {
         }
 
         /**
+         * 设置赠品信息
+         *
+         * @param gift 赠品信息
+         */
+        public Builder gift(JSONArray gift) {
+            obj.setGift(gift);
+            return this;
+        }
+
+        /**
          * 设置回复时长
          *
          * @param askTime 回复时长

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

@@ -1,6 +1,7 @@
 package com.qxgmat.data.dao.entity;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.util.Date;
 import javax.persistence.*;
 
@@ -170,6 +171,12 @@ public class UserOrderRecord implements Serializable {
     @Column(name = "`create_time`")
     private Date createTime;
 
+    @Column(name = "`money`")
+    private BigDecimal money;
+
+    @Column(name = "`origin_money`")
+    private BigDecimal originMoney;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -668,6 +675,34 @@ public class UserOrderRecord implements Serializable {
         this.createTime = createTime;
     }
 
+    /**
+     * @return money
+     */
+    public BigDecimal getMoney() {
+        return money;
+    }
+
+    /**
+     * @param money
+     */
+    public void setMoney(BigDecimal money) {
+        this.money = money;
+    }
+
+    /**
+     * @return origin_money
+     */
+    public BigDecimal getOriginMoney() {
+        return originMoney;
+    }
+
+    /**
+     * @param originMoney
+     */
+    public void setOriginMoney(BigDecimal originMoney) {
+        this.originMoney = originMoney;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -702,6 +737,8 @@ public class UserOrderRecord implements Serializable {
         sb.append(", suspendTime=").append(suspendTime);
         sb.append(", restoreTime=").append(restoreTime);
         sb.append(", createTime=").append(createTime);
+        sb.append(", money=").append(money);
+        sb.append(", originMoney=").append(originMoney);
         sb.append("]");
         return sb.toString();
     }
@@ -993,6 +1030,22 @@ public class UserOrderRecord implements Serializable {
             return this;
         }
 
+        /**
+         * @param money
+         */
+        public Builder money(BigDecimal money) {
+            obj.setMoney(money);
+            return this;
+        }
+
+        /**
+         * @param originMoney
+         */
+        public Builder originMoney(BigDecimal originMoney) {
+            obj.setOriginMoney(originMoney);
+            return this;
+        }
+
         public UserOrderRecord build() {
             return this.obj;
         }

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

@@ -25,7 +25,7 @@ public class UserQuestion implements Serializable {
     private Integer reportId;
 
     /**
-     * 题目模块:exercise, examination, sentence, textbook
+     * 题目模块
      */
     @Column(name = "`question_module`")
     private String questionModule;
@@ -152,18 +152,18 @@ public class UserQuestion implements Serializable {
     }
 
     /**
-     * 获取题目模块:exercise, examination, sentence, textbook
+     * 获取题目模块
      *
-     * @return question_module - 题目模块:exercise, examination, sentence, textbook
+     * @return question_module - 题目模块
      */
     public String getQuestionModule() {
         return questionModule;
     }
 
     /**
-     * 设置题目模块:exercise, examination, sentence, textbook
+     * 设置题目模块
      *
-     * @param questionModule 题目模块:exercise, examination, sentence, textbook
+     * @param questionModule 题目模块
      */
     public void setQuestionModule(String questionModule) {
         this.questionModule = questionModule;
@@ -447,9 +447,9 @@ public class UserQuestion implements Serializable {
         }
 
         /**
-         * 设置题目模块:exercise, examination, sentence, textbook
+         * 设置题目模块
          *
-         * @param questionModule 题目模块:exercise, examination, sentence, textbook
+         * @param questionModule 题目模块
          */
         public Builder questionModule(String questionModule) {
             obj.setQuestionModule(questionModule);

+ 7 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/ExaminationPaperMapper.xml

@@ -16,12 +16,18 @@
     <result column="quant_score" jdbcType="INTEGER" property="quantScore" />
     <result column="verbal_score" jdbcType="INTEGER" property="verbalScore" />
     <result column="ir_score" jdbcType="INTEGER" property="irScore" />
+    <result column="second_total_score" jdbcType="INTEGER" property="secondTotalScore" />
+    <result column="second_total_times" jdbcType="INTEGER" property="secondTotalTimes" />
+    <result column="second_quant_score" jdbcType="INTEGER" property="secondQuantScore" />
+    <result column="second_verbal_score" jdbcType="INTEGER" property="secondVerbalScore" />
+    <result column="second_ir_score" jdbcType="INTEGER" property="secondIrScore" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
     `id`, `struct_two`, `struct_three`, `is_adapt`, `title`, `status`, `total_score`, 
-    `total_times`, `quant_score`, `verbal_score`, `ir_score`
+    `total_times`, `quant_score`, `verbal_score`, `ir_score`, `second_total_score`, `second_total_times`, 
+    `second_quant_score`, `second_verbal_score`, `second_ir_score`
   </sql>
 </mapper>

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

@@ -6,10 +6,8 @@
       WARNING - @mbg.generated
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
-    <result column="faq_module" jdbcType="VARCHAR" property="faqModule" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="email" jdbcType="VARCHAR" property="email" />
-    <result column="phone" jdbcType="VARCHAR" property="phone" />
     <result column="message" jdbcType="INTEGER" property="message" />
     <result column="channel" jdbcType="VARCHAR" property="channel" />
     <result column="position" jdbcType="VARCHAR" property="position" />
@@ -32,9 +30,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `faq_module`, `user_id`, `email`, `phone`, `message`, `channel`, `position`, 
-    `manager_id`, `is_show`, `is_special`, `answer_status`, `answer_time`, `is_system`, 
-    `create_time`
+    `id`, `user_id`, `email`, `message`, `channel`, `position`, `manager_id`, `is_show`, 
+    `is_special`, `answer_status`, `answer_time`, `is_system`, `create_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -10,6 +10,8 @@
     <result column="course_id" jdbcType="INTEGER" property="courseId" />
     <result column="course_module" jdbcType="VARCHAR" property="courseModule" />
     <result column="course_no" jdbcType="INTEGER" property="courseNo" />
+    <result column="question_subject" jdbcType="VARCHAR" property="questionSubject" />
+    <result column="question_type" jdbcType="VARCHAR" property="questionType" />
     <result column="question_no_ids" jdbcType="VARCHAR" property="questionNoIds" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler" />
     <result column="paper_module" jdbcType="VARCHAR" property="paperModule" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
@@ -19,7 +21,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title`, `course_id`, `course_module`, `course_no`, `question_no_ids`, `paper_module`, 
-    `create_time`, `update_time`
+    `id`, `title`, `course_id`, `course_module`, `course_no`, `question_subject`, `question_type`, 
+    `question_no_ids`, `paper_module`, `create_time`, `update_time`
   </sql>
 </mapper>

+ 6 - 4
server/data/src/main/java/com/qxgmat/data/dao/mapping/QuestionMapper.xml

@@ -7,6 +7,7 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="keyword" jdbcType="VARCHAR" property="keyword" typeHandler="com.nuliji.tools.mybatis.handler.StringArrayHandler" />
+    <result column="question_module" jdbcType="VARCHAR" property="questionModule" />
     <result column="question_type" jdbcType="VARCHAR" property="questionType" />
     <result column="place" jdbcType="VARCHAR" property="place" />
     <result column="difficult" jdbcType="VARCHAR" property="difficult" />
@@ -22,6 +23,7 @@
     <result column="total_time" jdbcType="INTEGER" property="totalTime" />
     <result column="total_number" jdbcType="INTEGER" property="totalNumber" />
     <result column="total_correct" jdbcType="INTEGER" property="totalCorrect" />
+    <result column="collect_number" jdbcType="INTEGER" property="collectNumber" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
     <result column="answer_distributed" jdbcType="VARCHAR" property="answerDistributed" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler" />
@@ -38,10 +40,10 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `keyword`, `question_type`, `place`, `difficult`, `difficult_score`, `description`, 
-    `content`, `answer`, `question_time`, `qx_time`, `official_time`, `association_time`, 
-    `association_content`, `total_time`, `total_number`, `total_correct`, `create_time`, 
-    `update_time`, `answer_distributed`
+    `id`, `keyword`, `question_module`, `question_type`, `place`, `difficult`, `difficult_score`, 
+    `description`, `content`, `answer`, `question_time`, `qx_time`, `official_time`, 
+    `association_time`, `association_content`, `total_time`, `total_number`, `total_correct`, 
+    `collect_number`, `create_time`, `update_time`, `answer_distributed`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -19,7 +19,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    <result column="quant_contennt" jdbcType="LONGVARCHAR" property="quantContennt" />
+    <result column="quant_content" jdbcType="LONGVARCHAR" property="quantContent" />
     <result column="rc_content" jdbcType="LONGVARCHAR" property="rcContent" />
     <result column="ir_content" jdbcType="LONGVARCHAR" property="irContent" />
   </resultMap>
@@ -34,6 +34,6 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `quant_contennt`, `rc_content`, `ir_content`
+    `quant_content`, `rc_content`, `ir_content`
   </sql>
 </mapper>

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

@@ -11,6 +11,7 @@
     <result column="course_no_id" jdbcType="INTEGER" property="courseNoId" />
     <result column="record_id" jdbcType="INTEGER" property="recordId" />
     <result column="position" jdbcType="VARCHAR" property="position" />
+    <result column="ask_time" jdbcType="INTEGER" property="askTime" />
     <result column="answer_status" jdbcType="INTEGER" property="answerStatus" />
     <result column="manager_id" jdbcType="INTEGER" property="managerId" />
     <result column="show_status" jdbcType="INTEGER" property="showStatus" />
@@ -30,8 +31,9 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `course_id`, `course_no_id`, `record_id`, `position`, `answer_status`, 
-    `manager_id`, `show_status`, `answer_time`, `order`, `create_time`, `update_time`
+    `id`, `user_id`, `course_id`, `course_no_id`, `record_id`, `position`, `ask_time`, 
+    `answer_status`, `manager_id`, `show_status`, `answer_time`, `order`, `create_time`, 
+    `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 6 - 4
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserAskQuestionMapper.xml

@@ -8,11 +8,13 @@
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="user_question_id" jdbcType="INTEGER" property="userQuestionId" />
+    <result column="ask_module" jdbcType="VARCHAR" property="askModule" />
     <result column="question_module" jdbcType="VARCHAR" property="questionModule" />
     <result column="question_id" jdbcType="INTEGER" property="questionId" />
     <result column="question_no_id" jdbcType="INTEGER" property="questionNoId" />
-    <result column="target" jdbcType="VARCHAR" property="target" />
     <result column="record_id" jdbcType="INTEGER" property="recordId" />
+    <result column="target" jdbcType="VARCHAR" property="target" />
+    <result column="ask_time" jdbcType="INTEGER" property="askTime" />
     <result column="answer_status" jdbcType="INTEGER" property="answerStatus" />
     <result column="manager_id" jdbcType="INTEGER" property="managerId" />
     <result column="show_status" jdbcType="INTEGER" property="showStatus" />
@@ -33,9 +35,9 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `user_question_id`, `question_module`, `question_id`, `question_no_id`, 
-    `target`, `record_id`, `answer_status`, `manager_id`, `show_status`, `answer_time`, 
-    `order`, `create_time`, `update_time`
+    `id`, `user_id`, `user_question_id`, `ask_module`, `question_module`, `question_id`, 
+    `question_no_id`, `record_id`, `target`, `ask_time`, `answer_status`, `manager_id`, 
+    `show_status`, `answer_time`, `order`, `create_time`, `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -14,7 +14,6 @@
     <result column="cctalk_channel" jdbcType="VARCHAR" property="cctalkChannel" />
     <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
     <result column="end_time" jdbcType="TIMESTAMP" property="endTime" />
-    <result column="paper_id" jdbcType="INTEGER" property="paperId" />
     <result column="is_finish" jdbcType="INTEGER" property="isFinish" />
     <result column="supply_list" jdbcType="VARCHAR" property="supplyList" typeHandler="com.nuliji.tools.mybatis.handler.JsonArrayHandler" />
     <result column="note_list" jdbcType="VARCHAR" property="noteList" typeHandler="com.nuliji.tools.mybatis.handler.JsonArrayHandler" />
@@ -25,6 +24,6 @@
       WARNING - @mbg.generated
     -->
     `id`, `user_id`, `no`, `title`, `record_id`, `course_id`, `cctalk_channel`, `start_time`, 
-    `end_time`, `paper_id`, `is_finish`, `supply_list`, `note_list`, `create_time`
+    `end_time`, `is_finish`, `supply_list`, `note_list`, `create_time`
   </sql>
 </mapper>

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

@@ -9,6 +9,9 @@
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="module" jdbcType="VARCHAR" property="module" />
     <result column="module_id" jdbcType="INTEGER" property="moduleId" />
+    <result column="question_type" jdbcType="VARCHAR" property="questionType" />
+    <result column="question_module" jdbcType="VARCHAR" property="questionModule" />
+    <result column="question_no_id" jdbcType="INTEGER" property="questionNoId" />
     <result column="manager_id" jdbcType="INTEGER" property="managerId" />
     <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="position" jdbcType="VARCHAR" property="position" />
@@ -27,8 +30,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `module`, `module_id`, `manager_id`, `title`, `position`, `create_time`, 
-    `status`, `handle_time`
+    `id`, `user_id`, `module`, `module_id`, `question_type`, `question_module`, `question_no_id`, 
+    `manager_id`, `title`, `position`, `create_time`, `status`, `handle_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 6 - 4
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserNoteQuestionMapper.xml

@@ -11,18 +11,19 @@
     <result column="question_id" jdbcType="INTEGER" property="questionId" />
     <result column="question_no_id" jdbcType="INTEGER" property="questionNoId" />
     <result column="question_time" jdbcType="TIMESTAMP" property="questionTime" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
     <result column="official_time" jdbcType="TIMESTAMP" property="officialTime" />
     <result column="qx_time" jdbcType="TIMESTAMP" property="qxTime" />
     <result column="association_time" jdbcType="TIMESTAMP" property="associationTime" />
     <result column="qa_time" jdbcType="TIMESTAMP" property="qaTime" />
-    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
-    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.UserNoteQuestion">
     <!--
       WARNING - @mbg.generated
     -->
     <result column="question_content" jdbcType="LONGVARCHAR" property="questionContent" />
+    <result column="content" jdbcType="LONGVARCHAR" property="content" />
     <result column="official_content" jdbcType="LONGVARCHAR" property="officialContent" />
     <result column="qx_content" jdbcType="LONGVARCHAR" property="qxContent" />
     <result column="association_content" jdbcType="LONGVARCHAR" property="associationContent" />
@@ -33,12 +34,13 @@
       WARNING - @mbg.generated
     -->
     `id`, `user_id`, `question_module`, `question_id`, `question_no_id`, `question_time`, 
-    `official_time`, `qx_time`, `association_time`, `qa_time`, `create_time`, `update_time`
+    `create_time`, `update_time`, `official_time`, `qx_time`, `association_time`, `qa_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `question_content`, `official_content`, `qx_content`, `association_content`, `qa_content`
+    `question_content`, `content`, `official_content`, `qx_content`, `association_content`, 
+    `qa_content`
   </sql>
 </mapper>

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

@@ -13,6 +13,7 @@
     <result column="origin_money" jdbcType="DECIMAL" property="originMoney" />
     <result column="invoice_money" jdbcType="DECIMAL" property="invoiceMoney" />
     <result column="promote" jdbcType="VARCHAR" property="promote" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler" />
+    <result column="gift" jdbcType="VARCHAR" property="gift" typeHandler="com.nuliji.tools.mybatis.handler.JsonArrayHandler" />
     <result column="ask_time" jdbcType="INTEGER" property="askTime" />
     <result column="pay_id" jdbcType="BIGINT" property="payId" />
     <result column="pay_status" jdbcType="INTEGER" property="payStatus" />
@@ -25,6 +26,7 @@
       WARNING - @mbg.generated
     -->
     `id`, `user_id`, `product_types`, `pay_method`, `money`, `origin_money`, `invoice_money`, 
-    `promote`, `ask_time`, `pay_id`, `pay_status`, `create_time`, `pay_time`, `transaction_no`
+    `promote`, `gift`, `ask_time`, `pay_id`, `pay_status`, `create_time`, `pay_time`, 
+    `transaction_no`
   </sql>
 </mapper>

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

@@ -33,6 +33,8 @@
     <result column="suspend_time" jdbcType="TIMESTAMP" property="suspendTime" />
     <result column="restore_time" jdbcType="TIMESTAMP" property="restoreTime" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="money" jdbcType="DECIMAL" property="money" />
+    <result column="origin_money" jdbcType="DECIMAL" property="originMoney" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
@@ -42,6 +44,6 @@
     `param`, `source`, `teacher_id`, `number`, `vs_no`, `time_id`, `ask_time`, `cctalk_name`, 
     `is_subscribe`, `start_time`, `end_time`, `use_start_time`, `use_end_time`, `is_used`, 
     `use_time`, `is_stop`, `stop_time`, `is_suspend`, `suspend_time`, `restore_time`, 
-    `create_time`
+    `create_time`, `money`, `origin_money`
   </sql>
 </mapper>

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

@@ -0,0 +1,24 @@
+package com.qxgmat.data.relation;
+
+import com.qxgmat.data.dao.entity.Comment;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * Created by gaojie on 2017/11/9.
+ */
+public interface CommentRelationMapper {
+    List<Comment> listWithUser(
+            @Param("user") Boolean user,
+            @Param("channel") String channel,
+            @Param("position") String position,
+            @Param("userId") Number userId,
+            @Param("isSpecial") Integer isSpecial,
+            @Param("isShow") Integer isShow,
+            @Param("min") Integer min,
+            @Param("max") Integer max,
+            String order,
+            String direction
+    );
+}

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

@@ -1,5 +1,6 @@
 package com.qxgmat.data.relation;
 
+import com.qxgmat.data.dao.entity.CourseExperience;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
@@ -14,4 +15,12 @@ public interface CourseExperienceRelationMapper {
             @Param("view") int view,
             @Param("collect") int collect
     );
+
+    List<CourseExperience> listWithUser(
+            @Param("userId") Number userId,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            String order,
+            String direction
+    );
 }

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

@@ -10,6 +10,15 @@ import java.util.List;
  */
 public interface ExaminationPaperRelationMapper {
 
+    void accumulation(
+            @Param("id") Number paperId,
+            @Param("totalScore") Integer totalScore,
+            @Param("quantScore") Integer quantScore,
+            @Param("verbalScore") Integer verbalScore,
+            @Param("irScore") Integer irScore,
+            @Param("second") Boolean second
+    );
+
     List<ExercisePaper> listWithUser(
             @Param("structId") Number structId,
             @Param("userId") Number userId,

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

@@ -0,0 +1,25 @@
+package com.qxgmat.data.relation;
+
+import com.qxgmat.data.dao.entity.Faq;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * Created by gaojie on 2017/11/9.
+ */
+public interface FaqRelationMapper {
+    List<Faq> listWithUser(
+            @Param("user") Boolean user,
+            @Param("channel") String channel,
+            @Param("position") String position,
+            @Param("userId") Number userId,
+            @Param("answerStatus") Integer answerStatus,
+            @Param("isSpecial") Integer isSpecial,
+            @Param("isShow") Integer isShow,
+            @Param("min") Integer min,
+            @Param("max") Integer max,
+            String order,
+            String direction
+    );
+}

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

@@ -11,6 +11,7 @@ import java.util.List;
 public interface PreviewPaperRelationMapper {
     List<PreviewPaper> listAdmin(
             @Param("courseModule") String courseModule,
+            @Param("questionType") String questionType,
             @Param("structId") Integer structId,
             @Param("order") String order,
             @Param("direction") String direction

+ 5 - 0
server/data/src/main/java/com/qxgmat/data/relation/QuestionRelationMapper.java

@@ -16,4 +16,9 @@ public interface QuestionRelationMapper {
             @Param("time") Integer time,
             @Param("correct") Integer correct
     );
+
+    void accumulationCollect(
+            @Param("id") Number questionId,
+            @Param("collect") int collect
+    );
 }

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

@@ -14,6 +14,7 @@ public interface TextbookPaperRelationMapper {
             @Param("libraryId") Number libraryId,
             @Param("userId") Number userId,
             @Param("logic") String logic,
+            @Param("year") String year,
             @Param("times") Integer times
     );
 }

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

@@ -9,9 +9,33 @@ import java.util.List;
  * Created by gaojie on 2017/11/9.
  */
 public interface UserAskQuestionRelationMapper {
+    List<UserAskQuestion> listExercise(
+            @Param("userId") Number userId,
+            @Param("questionTypes") String[] questionTypes,
+            @Param("structIds") Integer[] structIds,
+            @Param("answerStatus") Integer answerStatus,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            String order,
+            String direction
+    );
+    List<UserAskQuestion> listExamination(
+            @Param("userId") Number userId,
+            @Param("questionTypes") String[] questionTypes,
+            @Param("structIds") Integer[] structIds,
+            @Param("libraryId") Integer libraryId,
+            @Param("year") String year,
+            @Param("answerStatus") Integer answerStatus,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            String order,
+            String direction
+    );
+
     List<UserAskQuestion> listWithUser(
+            @Param("askModule") String askModule,
             @Param("questionType") String questionType,
-            @Param("module") String module,
+            @Param("questionModule") String questionModule,
             @Param("userId") Number userId,
             @Param("questionNoId") Number questionNoId,
             @Param("target") String target,

+ 17 - 5
server/data/src/main/java/com/qxgmat/data/relation/UserCollectQuestionRelationMapper.java

@@ -11,13 +11,25 @@ import java.util.List;
  */
 public interface UserCollectQuestionRelationMapper {
 
-    List<UserCollectQuestion> list(
+    List<UserCollectQuestion> listExercise(
             @Param("userId") Number userId,
-            @Param("module") String module,
-            @Param("questionType") String questionType,
+            @Param("questionTypes") String[] questionTypes,
+            @Param("structIds") Integer[] structIds,
             @Param("startTime") String startTime,
             @Param("endTime") String endTime,
-            @Param("order") String order,
-            @Param("direction") String direction
+            String order,
+            String direction
+    );
+
+    List<UserCollectQuestion> listExamination(
+            @Param("userId") Number userId,
+            @Param("questionTypes") String[] questionTypes,
+            @Param("structIds") Integer[] structIds,
+            @Param("libraryId") Integer libraryId,
+            @Param("year") String year,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            String order,
+            String direction
     );
 }

+ 6 - 0
server/data/src/main/java/com/qxgmat/data/relation/UserCourseRecordRelationMapper.java

@@ -1,16 +1,22 @@
 package com.qxgmat.data.relation;
 
+import com.qxgmat.data.dao.entity.UserCourseRecord;
 import com.qxgmat.data.relation.entity.UserModuleRecordStatRelation;
 import com.qxgmat.data.relation.entity.UserRankStatRelation;
 import com.qxgmat.data.relation.entity.UserRecordStatRelation;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
  * Created by gaojie on 2017/11/9.
  */
 public interface UserCourseRecordRelationMapper {
+    List<UserCourseRecord> listLast(
+            @Param("recordIds") Collection recordIds
+    );
+
     List<UserRecordStatRelation> stat(
             @Param("userId") Integer userId,
             @Param("startTime") String startTime,

+ 3 - 0
server/data/src/main/java/com/qxgmat/data/relation/UserFeedbackErrorRelationMapper.java

@@ -12,6 +12,9 @@ public interface UserFeedbackErrorRelationMapper {
     List<UserAskQuestion> listAdmin(
             @Param("module") String module,
             @Param("status") Integer status,
+            @Param("questionType") String questionType,
+            @Param("target") String target,
+            @Param("moduleId") Integer moduleId,
             @Param("userId") Integer userId,
             @Param("keyword") String keyword,
             @Param("min") Integer min,

+ 16 - 5
server/data/src/main/java/com/qxgmat/data/relation/UserNoteQuestionRelationMapper.java

@@ -9,14 +9,25 @@ import java.util.List;
  * Created by gaojie on 2017/11/9.
  */
 public interface UserNoteQuestionRelationMapper {
+    List<UserNoteQuestion> listExercise(
+            @Param("userId") Number userId,
+            @Param("questionTypes") String[] questionTypes,
+            @Param("structIds") Integer[] structIds,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            String order,
+            String direction
+    );
 
-    List<UserNoteQuestion> list(
+    List<UserNoteQuestion> listExamination(
             @Param("userId") Number userId,
-            @Param("module") String module,
-            @Param("questionType") String questionType,
+            @Param("questionTypes") String[] questionTypes,
+            @Param("structIds") Integer[] structIds,
+            @Param("libraryId") Integer libraryId,
+            @Param("year") String year,
             @Param("startTime") String startTime,
             @Param("endTime") String endTime,
-            @Param("order") String order,
-            @Param("direction") String direction
+            String order,
+            String direction
     );
 }

+ 25 - 0
server/data/src/main/java/com/qxgmat/data/relation/UserOrderRelationMapper.java

@@ -0,0 +1,25 @@
+package com.qxgmat.data.relation;
+
+import com.qxgmat.data.dao.entity.UserReport;
+import com.qxgmat.data.relation.entity.UserRankStatRelation;
+import com.qxgmat.data.relation.entity.UserReportLimitRelation;
+import com.qxgmat.data.relation.entity.UserStudyStatRelation;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Created by gaojie on 2017/11/9.
+ */
+public interface UserOrderRelationMapper {
+
+    List<UserReport> listAdmin(
+            @Param("userId") Integer userId,
+            @Param("productType") String productType,
+            @Param("payMethod") String payMethod,
+            @Param("orderId") Integer orderId,
+            String order,
+            String direction
+    );
+}

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

@@ -19,6 +19,38 @@ public interface UserPaperRelationMapper {
             @Param("time") Integer time,
             @Param("correct") Integer correct,
             @Param("times") Integer times,
-            @Param("latestTime") Date latestTime
+            @Param("latestTime") Date latestTime,
+            @Param("latestReportId") Integer latestReportId
+    );
+
+    List<UserPaper> list(
+            @Param("userId") Number userId,
+            @Param("paperOrigin") String paperOrigin,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            String order,
+            String direction
+    );
+
+    List<UserPaper> listExercise(
+            @Param("userId") Number userId,
+            @Param("questionTypes") String[] questionTypes,
+            @Param("structIds") Integer[] structIds,
+            @Param("courseModules") String[] courseModules,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            String order,
+            String direction
+    );
+
+    List<UserPaper> listExamination(
+            @Param("userId") Number userId,
+            @Param("structIds") Integer[] structIds,
+            @Param("libraryId") Integer libraryId,
+            @Param("year") String year,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            String order,
+            String direction
     );
 }

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

@@ -1,5 +1,6 @@
 package com.qxgmat.data.relation;
 
+import com.qxgmat.data.dao.entity.UserQuestion;
 import com.qxgmat.data.relation.entity.UserRecordStatRelation;
 import org.apache.ibatis.annotations.Param;
 
@@ -9,6 +10,29 @@ import java.util.List;
  * Created by gaojie on 2017/11/9.
  */
 public interface UserQuestionRelationMapper {
+
+    List<UserQuestion> listExerciseError(
+            @Param("userId") Number userId,
+            @Param("questionTypes") String[] questionTypes,
+            @Param("structIds") Integer[] structIds,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            String order,
+            String direction
+    );
+
+    List<UserQuestion> listExaminationError(
+            @Param("userId") Number userId,
+            @Param("questionTypes") String[] questionTypes,
+            @Param("structIds") Integer[] structIds,
+            @Param("libraryId") Integer libraryId,
+            @Param("year") String year,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            String order,
+            String direction
+    );
+
     List<UserRecordStatRelation> stat(
             @Param("userId") Integer userId,
             @Param("startTime") String startTime,

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


Some files were not shown because too many files changed in this diff