Browse Source

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

# Conflicts:
#	front/project/www/components/Login/index.js
#	front/project/www/routes/paper/process/sentence/index.js
#	front/project/www/stores/user.js
KaysonCui 5 years ago
parent
commit
39722f6b6c
100 changed files with 3548 additions and 1052 deletions
  1. 7 1
      front/project/Constant.js
  2. 9 0
      front/project/admin/routes/course/detail/page.js
  3. 1 1
      front/project/admin/routes/course/vsDetail/page.js
  4. 15 0
      front/project/admin/routes/show/deploy/index.js
  5. 3 0
      front/project/admin/routes/show/deploy/index.less
  6. 418 0
      front/project/admin/routes/show/deploy/page.js
  7. 2 1
      front/project/admin/routes/show/index.js
  8. 16 0
      front/project/admin/stores/system.js
  9. 3 2
      front/project/h5/routes/page/identity/page.js
  10. 16 11
      front/project/h5/routes/page/study/page.js
  11. 2 2
      front/project/h5/routes/textbook/main/page.js
  12. 1 1
      front/project/h5/stores/course.js
  13. 7 0
      front/project/h5/stores/main.js
  14. 4 3
      front/project/h5/stores/my.js
  15. 20 6
      front/project/h5/stores/user.js
  16. 282 46
      front/project/www/components/Login/index.js
  17. 7 3
      front/project/www/local.json
  18. 13 0
      front/project/www/routes/examination/main/page.js
  19. 52 42
      front/project/www/routes/exercise/main/page.js
  20. 99 141
      front/project/www/routes/paper/process/base/index.js
  21. 2 2
      front/project/www/routes/paper/process/page.js
  22. 109 34
      front/project/www/routes/paper/process/sentence/index.js
  23. 12 0
      front/project/www/routes/paper/process/sentence/index.less
  24. 56 38
      front/project/www/routes/paper/question/page.js
  25. 1 0
      front/project/www/routes/paper/report/index.js
  26. 46 16
      front/project/www/routes/paper/report/page.js
  27. 10 0
      front/project/www/routes/preview/list/index.js
  28. 59 0
      front/project/www/routes/preview/list/index.less
  29. 286 0
      front/project/www/routes/preview/list/page.js
  30. 0 10
      front/project/www/routes/sentence/process/index.js
  31. 0 0
      front/project/www/routes/sentence/process/page.js
  32. 29 26
      front/project/www/routes/sentence/read/page.js
  33. 10 2
      front/project/www/stores/course.js
  34. 14 0
      front/project/www/stores/main.js
  35. 15 3
      front/project/www/stores/question.js
  36. 25 23
      front/project/www/stores/user.js
  37. 1 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/ServiceKey.java
  38. 2 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/SettingKey.java
  39. 17 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/logic/TextbookLogic.java
  40. 0 7
      server/data/src/main/java/com/qxgmat/data/dao/CourseStudentOnlineMapper.java
  41. 23 23
      server/data/src/main/java/com/qxgmat/data/dao/entity/Course.java
  42. 0 195
      server/data/src/main/java/com/qxgmat/data/dao/entity/CourseStudentOnline.java
  43. 7 7
      server/data/src/main/java/com/qxgmat/data/dao/entity/ExaminationPaper.java
  44. 92 22
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookLibraryHistory.java
  45. 35 35
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookPaper.java
  46. 16 16
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookQuestion.java
  47. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrderRecord.java
  48. 70 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserPaper.java
  49. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserReport.java
  50. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserService.java
  51. 3 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseMapper.xml
  52. 0 20
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseStudentOnlineMapper.xml
  53. 4 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookLibraryHistoryMapper.xml
  54. 3 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookPaperMapper.xml
  55. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookQuestionMapper.xml
  56. 5 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderRecordMapper.xml
  57. 5 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserPaperMapper.xml
  58. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserReportMapper.xml
  59. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserServiceMapper.xml
  60. 36 0
      server/data/src/main/java/com/qxgmat/data/relation/PreviewAssignRelationMapper.java
  61. 19 0
      server/data/src/main/java/com/qxgmat/data/relation/TextbookPaperRelationMapper.java
  62. 14 6
      server/data/src/main/java/com/qxgmat/data/relation/UserCourseRecordRelationMapper.java
  63. 36 0
      server/data/src/main/java/com/qxgmat/data/relation/UserOrderRecordRelationMapper.java
  64. 0 5
      server/data/src/main/java/com/qxgmat/data/relation/UserPaperRelationMapper.java
  65. 22 0
      server/data/src/main/java/com/qxgmat/data/relation/UserQuestionRelationMapper.java
  66. 7 2
      server/data/src/main/java/com/qxgmat/data/relation/UserSentenceRecordRelationMapper.java
  67. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/entity/UserCourseStatRelation.java
  68. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/entity/UserSentenceStatRelation.java
  69. 2 2
      server/data/src/main/java/com/qxgmat/data/relation/mapping/ExaminationPaperRelationMapper.xml
  70. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/ExercisePaperRelationMapper.xml
  71. 97 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/PreviewAssignRelationMapper.xml
  72. 40 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/TextbookPaperRelationMapper.xml
  73. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/TextbookQuestionRelationMapper.xml
  74. 41 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserCourseRecordRelationMapper.xml
  75. 88 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserOrderRecordRelationMapper.xml
  76. 0 21
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserPaperRelationMapper.xml
  77. 56 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserQuestionRelationMapper.xml
  78. 21 4
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserSentenceRecordRelationMapper.xml
  79. 45 18
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java
  80. 8 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/PreviewController.java
  81. 34 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  82. 11 4
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  83. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/AuthController.java
  84. 21 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java
  85. 101 58
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  86. 28 81
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  87. 72 19
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  88. 2 6
      server/gateway-api/src/main/java/com/qxgmat/controller/api/SentenceController.java
  89. 163 11
      server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java
  90. 0 5
      server/gateway-api/src/main/java/com/qxgmat/controller/api/WechatController.java
  91. 37 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/CourseStudentOnlineDto.java
  92. 30 10
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/TextbookLibraryHistoryDto.java
  93. 15 15
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/CourseStudentOnlineListDto.java
  94. 140 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserCourseAppointmentExtendDto.java
  95. 109 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserServiceRecordExtendDto.java
  96. 73 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserTextbookGroupExtendDto.java
  97. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/MyDto.java
  98. 18 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/PaperBaseDto.java
  99. 102 21
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseDetailDto.java
  100. 0 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseProgressDto.java

+ 7 - 1
front/project/Constant.js

@@ -56,7 +56,11 @@ export const SentenceOption = [{ label: '平行', value: 'parallel' }, { label:
 
 export const ExaminationSubject = [{ short: 'V', value: 'verbal', label: '语文', english: 'Verbal' }, { short: 'Q', value: 'quant', label: '数学', english: 'Quant' }, { short: 'IR', value: 'ir', label: '综合推理', english: 'LR' }, { short: 'AWA', value: 'awa', label: '作文', english: 'AWA' }];
 
-export const ExaminationOrder = [{ list: ['awa', 'ir', 'quant', 'verbal'] }, { list: ['quant', 'verbal', 'ir', 'awa'] }, { list: ['verbal', 'quant', 'ir', 'awa'] }];
+export const ExaminationOrder = [
+  { label: 'ARQV', value: ['awa', 'ir', 'quant', 'verbal'], list: [{ label: 'Analytical Writing Analysis', value: 'awa' }, { label: 'Integrated Reasoning', value: 'ir' }, { label: 'Quantitative', value: 'quant' }, { label: 'Verbal', value: 'verbal' }] },
+  { label: 'VQRA', value: ['verbal', 'quant', 'ir', 'awa'], list: [{ label: 'Verbal', value: 'verbal' }, { label: 'Quantitative', value: 'quant' }, { label: 'Integrated Reasoning', value: 'ir' }, { label: 'Analytical Writing Analysis', value: 'awa' }] },
+  { label: 'QVRA', value: ['quant', 'verbal', 'ir', 'awa'], list: [{ label: 'Quantitative', value: 'quant' }, { label: 'Verbal', value: 'verbal' }, { label: 'Integrated Reasoning', value: 'ir' }, { label: 'Analytical Writing Analysis', value: 'awa' }] },
+];
 
 export const DataType = [{ label: '电子', value: 'electron' }, { label: '纸质', value: 'paper' }];
 
@@ -70,6 +74,8 @@ export const ExperiencePercent = [{ label: '50+', value: '_50' }, { label: '100+
 
 export const CourseModule = [{ label: '视频课程', value: 'video' }, { label: '小班课程', value: 'online' }, { label: '1v1课程', value: 'vs' }];
 
+export const CourseModuleShow = [{ label: '在线课程', value: 'online', courseModules: ['video', 'online'] }, { label: '1V1私教', value: 'vs', courseModules: ['vs'] }];
+
 export const CourseVsType = [{ label: '新手辅导', value: 'novice' }, { label: '诊断辅导', value: 'coach' }, { label: '系统授课', value: 'system' }, { label: '答疑课', value: 'answer' }];
 
 export const CourseVideoType = [{ label: '基础刷题', value: 'base' }, { label: '系统授课', value: 'system' }, { label: '思维提升', value: 'thinking' }];

+ 9 - 0
front/project/admin/routes/course/detail/page.js

@@ -311,6 +311,15 @@ export default class extends Page {
             <Input placeholder='请输入课程名称' />,
           )}
         </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='有效期'>
+          {getFieldDecorator('expireDays', {
+            rules: [
+              { required: true, message: '请输入有效期' },
+            ],
+          })(
+            <InputNumber placeholder='天' />,
+          )}
+        </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='教师'>
           {getFieldDecorator('teacher', {
             rules: [

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

@@ -138,7 +138,7 @@ export default class extends Page {
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='有效期'>
-          {getFieldDecorator('expireDays', {
+          {getFieldDecorator('expirePreDays', {
             rules: [
               { required: true, message: '请输入有效期' },
             ],

+ 15 - 0
front/project/admin/routes/show/deploy/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/show/deploy',
+  key: 'show-deploy',
+  title: '配置文案',
+  needLogin: true,
+  module,
+  group,
+  index: true,
+  component() {
+    return import('./page');
+  },
+};

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

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#show-deploy {}

+ 418 - 0
front/project/admin/routes/show/deploy/page.js

@@ -0,0 +1,418 @@
+import React from 'react';
+import { Tabs, Form, Row, Col, Input, InputNumber, Button, Upload, Icon } from 'antd';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import { flattenObject } 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.state.tab = 'qx_cat';
+    this.vipList = ServiceParamMap.vip;
+  }
+
+  initData() {
+    this.refresh(this.state.tab);
+  }
+
+  refresh(tab) {
+    if (tab === 'qx_cat') {
+      return this.refreshQxCat();
+    }
+    if (tab === 'textbook') {
+      return this.refreshTextbook();
+    }
+    if (tab === 'vip') {
+      return this.refreshVip();
+    }
+    return Promise.reject();
+  }
+
+  refreshQxCat() {
+    return System.getServiceQxCat().then(result => {
+      this.setState({ qx_cat: 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'));
+    });
+  }
+
+  changeMapValue(field, second, index, key, value) {
+    const data = this.state[field] || {};
+    data[second] = data[second] || [];
+    data[second][index] = data[second][index] || {};
+    data[second][index][key] = value;
+    this.setState({ [field]: data });
+  }
+
+  submit(tab) {
+    let handler;
+    if (tab === 'qx_cat') {
+      handler = this.submitQxCat();
+    }
+    if (tab === 'textbook') {
+      handler = this.submitTextbook();
+    }
+    if (tab === 'vip') {
+      handler = this.submitVip();
+    }
+    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>;
+  }
+
+  renderTextbook() {
+    const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
+    const image = getFieldValue('textbook.image') || null;
+    return <Form>
+      <Row>
+        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='商品图片'>
+          {getFieldDecorator('textbook.image', {
+            rules: [
+              { 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);
+            }} style={{ width: '200px' }} />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='服务名称'>
+          {getFieldDecorator('textbook.package[0].title', {
+            rules: [
+              { required: true, message: '输入数学机经名称' },
+            ],
+          })(
+            <Input placeholder='请输入数学机经名称' onChange={(e) => {
+              this.changeMapValue('textbook', 'package', 0, 'title', e.target.value);
+            }} style={{ width: '200px' }} />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='服务简介'>
+          {getFieldDecorator('textbook.package[0].description', {
+            rules: [
+              { required: true, message: '输入数学机经服务简介' },
+            ],
+          })(
+            <Input placeholder='请输入数学机经服务简介' onChange={(e) => {
+              this.changeMapValue('textbook', 'package', 0, 'description', e.target.value);
+            }} style={{ width: '200px' }} />,
+          )}
+        </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>;
+  }
+
+  renderView() {
+    const { tab } = this.state;
+    return <Block><Tabs activeKey={tab} onChange={(value) => {
+      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>
+    </Tabs>
+      <Row type="flex" justify="center">
+        <Col>
+          <Button type="primary" onClick={() => {
+            this.submit(tab);
+          }}>保存</Button>
+        </Col>
+      </Row>
+    </Block>;
+  }
+}

+ 2 - 1
front/project/admin/routes/show/index.js

@@ -6,5 +6,6 @@ import articleDetail from './articleDetail';
 import comment from './comment';
 import ad from './ad';
 import message from './message';
+import deploy from './deploy';
 
-export default [tips, faq, article, articleDetail, comment, ad, message];
+export default [tips, faq, article, articleDetail, comment, ad, message, deploy];

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

@@ -149,6 +149,14 @@ export default class SystemStore extends BaseStore {
     return this.apiPut('/setting/experience_info', params);
   }
 
+  getWechatInfo() {
+    return this.apiGet('/setting/wechat_info');
+  }
+
+  setWechatInfo(params) {
+    return this.apiPut('/setting/wechat_info', params);
+  }
+
   getTips() {
     return this.apiGet('/setting/tips');
   }
@@ -157,6 +165,14 @@ export default class SystemStore extends BaseStore {
     return this.apiPut('/setting/tips', params);
   }
 
+  getSentenceInfo() {
+    return this.apiGet('/setting/sentence_info');
+  }
+
+  setTSentenceInfo(params) {
+    return this.apiPut('/setting/sentence_info', params);
+  }
+
   listRank(params) {
     return this.apiGet('/setting/rank/list', params);
   }

+ 3 - 2
front/project/h5/routes/page/identity/page.js

@@ -6,6 +6,7 @@ import { asyncSMessage } from '@src/services/AsyncTools';
 import { dataURLtoBlob, formatDate } from '@src/services/Tools';
 import Icon from '../../../components/Icon';
 import { Common } from '../../../stores/common';
+// import { Main } from '../../../stores/main';
 import { My } from '../../../stores/my';
 
 export default class extends Page {
@@ -107,7 +108,7 @@ export default class extends Page {
     return (
       <div>
         <div className="text">
-          请扫描您的居民身份证原件,领取6个月VIP权限。
+          请扫描您的居民身份证原件,领取90天VIP权限。
           <br />
           千行将重视和保护您的隐私。
         </div>
@@ -145,7 +146,7 @@ export default class extends Page {
           <Icon type="check-circle" />
         </div>
         <div className="title">认证完成!</div>
-        <div className="desc">180天VIP权限已赠送至您的账户</div>
+        <div className="desc">90天VIP权限已赠送至您的账户</div>
         <div className="desc">生效时间:{formatDate(data.useStartTime, 'YYYY-MM-DD')}</div>
       </div>
     );

+ 16 - 11
front/project/h5/routes/page/study/page.js

@@ -1,7 +1,7 @@
 import React from 'react';
 import './index.less';
 import Page from '@src/containers/Page';
-import { formatDate, formatSeconds } from '@src/services/Tools';
+import { formatDate, formatSeconds, formatPercent } from '@src/services/Tools';
 import Icon from '../../../components/Icon';
 import { My } from '../../../stores/my';
 
@@ -12,13 +12,20 @@ export default class extends Page {
     My.getStudyTotal().then(total => {
       this.setState({ total });
     });
-    My.getStudyWeek().then(week => {
-      this.setState({ week });
+    My.getStudyWeek(0).then(latest => {
+      const diff = latest.time - latest.avgTime;
+      const diffPercent = diff > 0 ? formatPercent(latest.time - latest.avgTime, latest.avgTime, true) : formatPercent(latest.avgTime - latest.time, latest.avgTime, true);
+      this.setState({ latest, diff, diffPercent });
+      My.getStudyWeek(1).then(last => {
+        const diffLast = latest.time - last.time;
+        const diffLastPercent = diffLast > 0 ? formatPercent(latest.time - last.time, last.time, true) : formatPercent(last.time - latest.time, last.time, true);
+        this.setState({ last, diffLast, diffLastPercent });
+      });
     });
   }
 
   renderView() {
-    const { total } = this.state;
+    const { total, latest = {}, diff = 0, diffPercent = 0, diffLast = 0, diffLastPercent = 0 } = this.state;
     return (
       <div>
         <div className="block">
@@ -33,22 +40,20 @@ export default class extends Page {
         <div className="t-c">
           <div className="item">
             <div className="text">学习时间</div>
-            <div className="value">
-              <span>23</span>Hour
-            </div>
+            <div className="value" dangerouslySetInnerHTML={{ __html: formatSeconds(latest.time).replace(/([0-9]+)([msh])/g, '<span>$1</span>$2') }} />
           </div>
           <div className="item">
             <div className="text">同比上周</div>
             <div className="value">
-              <Icon type="caret-up" theme="filled" color="#6EC64B" />
-              <span>15</span>%
+              {diffLast > 0 ? <Icon type="caret-up" theme="filled" color="#6EC64B" /> : <Icon type="caret-down" theme="filled" color="#F36565" />}
+              <span>{diffLastPercent}</span>%
             </div>
           </div>
           <div className="item">
             <div className="text">同比全站</div>
             <div className="value">
-              <Icon type="caret-down" theme="filled" color="#F36565" />
-              <span>15</span>%
+              {diff > 0 ? <Icon type="caret-up" theme="filled" color="#6EC64B" /> : <Icon type="caret-down" theme="filled" color="#F36565" />}
+              <span>{diffPercent}</span>%
             </div>
           </div>
         </div>

+ 2 - 2
front/project/h5/routes/textbook/main/page.js

@@ -79,14 +79,14 @@ export default class extends Page {
 
   renderEmpty() {
     const { unUseRecord } = this.state;
-    if (unUseRecord > 0) {
+    if (unUseRecord) {
       return <div className="empty">
         <div className="text">
           还未开通本月机经。 <br />
           您可至千行网站「我的-工具」查看往期机经。
       </div>
         <Button block width={120} radius onClick={() => {
-          linkTo(`/open/${unUseRecord}`);
+          linkTo(`/open/${unUseRecord.id}`);
         }}>
           去开通
       </Button>

+ 1 - 1
front/project/h5/stores/course.js

@@ -13,7 +13,7 @@ export default class CourseStore extends BaseStore {
   }
 
   get(courseId) {
-    return this.apiGet('/course/detail', { courseId });
+    return this.apiGet('/course/simple', { courseId });
   }
 
   listPackage(params) {

+ 7 - 0
front/project/h5/stores/main.js

@@ -28,6 +28,13 @@ export default class MainStore extends BaseStore {
   }
 
   /**
+   * 获取微信信息
+   */
+  getWechat() {
+    return this.apiGet('/base/wechat');
+  }
+
+  /**
    * 获取对应位置的提示tips
    * @param {*} position
    */

+ 4 - 3
front/project/h5/stores/my.js

@@ -108,10 +108,11 @@ export default class MyStore extends BaseStore {
   }
 
   /**
-   * 获取本周学习记录
+   * 获取每周学习记录
+   * @param {*} week 0本周,1上周
    */
-  getStudyWeek() {
-    return this.apiGet('/my/study/week', {});
+  getStudyWeek(week) {
+    return this.apiGet('/my/study/week', { week });
   }
 
   /**

+ 20 - 6
front/project/h5/stores/user.js

@@ -10,6 +10,11 @@ export default class UserStore extends BaseStore {
     return { login: false };
   }
 
+  infoHandle(result) {
+    this.setToken(result.token);
+    this.setState({ login: true, needLogin: false, info: result, username: result.username });
+  }
+
   originInviteCode(inviteCode) {
     this.setState({
       inviteCode,
@@ -19,8 +24,14 @@ export default class UserStore extends BaseStore {
   /**
    * 验证token
    */
-  token() {
-    return this.apiPost('/auth/token');
+  refreshToken() {
+    return this.apiPost('/auth/token')
+      .then(result => {
+        this.infoHandle(result);
+      })
+      .catch(() => {
+        this.logout(false);
+      });
   }
 
   /**
@@ -34,13 +45,16 @@ export default class UserStore extends BaseStore {
     if (!inviteCode) {
       ({ inviteCode } = this.state);
     }
-    return this.apiPost('/auth/login', { area, mobile, mobileVerifyCode, inviteCode });
+    return this.apiPost('/auth/login', { area, mobile, mobileVerifyCode, inviteCode }).then(result => {
+      this.infoHandle(result);
+      return result;
+    });
   }
 
   loginWechat(code) {
-    return this.apiGet('/auth/wechat', { code }).then((info) => {
-      this.setState({ login: true, info });
-      return info;
+    return this.apiGet('/auth/wechat', { code }).then((result) => {
+      this.infoHandle(result);
+      return result;
     });
   }
 

+ 282 - 46
front/project/www/components/Login/index.js

@@ -2,12 +2,15 @@ import React, { Component } from 'react';
 import './index.less';
 import { Modal, Icon, Button, Tooltip } from 'antd';
 import Assets from '@src/components/Assets';
+import { asyncSMessage } from '@src/services/AsyncTools';
 import { Icon as GIcon } from '../Icon';
 import { Button as GButton } from '../Button';
 import { User } from '../../stores/user';
+import { My } from '../../stores/my';
+import { Common } from '../../stores/common';
+import { MobileArea } from '../../../Constant';
 
 const LOGIN_PHONE = 'LOGIN_PHONE';
-const REGISTER_PHONE = 'REGISTER_PHONE';
 const LOGIN_WX = 'LOGIN_WX';
 const BIND_PHONE = 'BIND_PHONE';
 const BIND_WX = 'BIND_WX';
@@ -33,23 +36,148 @@ export default class Login extends Component {
     User.closeLogin();
   }
 
+  login() {
+    const { data, needEmail, mobileError, validError } = this.state;
+    const { area, mobile, mobileVerifyCode, email } = data;
+    if (mobileError !== '' || validError !== '') return;
+    if (area === '' || mobile === '' || mobileVerifyCode === '') return;
+    if (needEmail && email === '') return;
+    User.login(area, mobile, mobileVerifyCode)
+      .then(() => {
+        let handler = null;
+        if (needEmail) {
+          handler = My.bindEmail(email);
+        } else {
+          handler = Promise.resolve();
+        }
+        handler.then(result => {
+          if (result.bindWechat) {
+            this.close();
+          } else {
+            this.setState({ type: BIND_WX });
+          }
+        });
+      })
+      .catch(err => {
+        if (err.message.indexOf('验证码') >= 0) {
+          this.setState({ validError: err.message });
+        } else {
+          this.setState({ mobileError: err.message });
+        }
+      });
+  }
+
+  bind() {
+    const { data, needEmail, mobileError, validError } = this.state;
+    const { area, mobile, mobileVerifyCode, email } = data;
+    if (mobileError !== '' || validError !== '') return;
+    if (area === '' || mobile === '' || mobileVerifyCode === '') return;
+    if (needEmail && email === '') return;
+    User.bind(area, mobile, mobileVerifyCode)
+      .then(() => {
+        let handler = null;
+        if (needEmail) {
+          handler = My.bindEmail(email);
+        } else {
+          handler = Promise.resolve();
+        }
+        handler.then(() => {
+          this.close();
+        });
+      })
+      .catch(err => {
+        if (err.message.indexOf('验证码') >= 0) {
+          this.setState({ validError: err.message });
+        } else {
+          this.setState({ mobileError: err.message });
+        }
+      });
+  }
+
+  scanLogin() {
+    User.loginWechat('').then(result => {
+      if (result.bindMobile) {
+        this.close();
+      } else {
+        this.setState({ type: BIND_PHONE });
+      }
+    });
+  }
+
+  scanBind() {
+    User.loginWechat('')
+      .then(() => {
+        this.close();
+      })
+      .catch(err => {
+        this.setState({ type: BIND_WX_ERROR, wechatError: err.message });
+      });
+  }
+
+  changeData(field, value) {
+    let { data } = this.state;
+    data = data || {};
+    data[field] = value;
+    this.setState({ data });
+  }
+
+  validMobile() {
+    const { data } = this.state;
+    const { area, mobile } = data;
+    if (area === '' || mobile === '') return;
+    User.validWechat(area, mobile)
+      .then(result => {
+        if (result) {
+          this.setState({ mobileError: '' });
+          return User.validMobile(area, mobile).then(r => {
+            this.setState({ needEmail: r });
+          });
+        }
+        this.setState({ needEmail: false });
+        return Promise.reject(new Error('该手机已绑定其他账号,请更换手机号码'));
+      })
+      .catch(err => {
+        this.setState({ mobileError: err.message });
+      });
+  }
+
+  sendValid() {
+    const { data, mobileError } = this.state;
+    const { area, mobile } = data;
+    if (area === '' || mobile === '' || mobileError !== '') return Promise.reject();
+    return Common.sendSms(area, mobile)
+      .then(result => {
+        if (result) {
+          asyncSMessage('发送成功');
+          this.setState({ mobileError: '', validError: '' });
+        } else {
+          throw new Error('发送失败');
+        }
+      })
+      .catch(err => {
+        this.setState({ mobileError: err.message });
+        throw err;
+      });
+  }
+
   render() {
     const { type } = this.state;
     const { user } = this.props;
     return (
       <Modal wrapClassName={`login-modal ${type}`} visible={user.needLogin} footer={null} closable={false} width={470}>
         {this.renderBody(type)}
-        <GIcon name="close" onClick={() => this.close()} />
+        <GIcon
+          name="close"
+          onClick={() => {
+            this.close();
+          }}
+        />
       </Modal>
     );
   }
 
   renderBody(type) {
     switch (type) {
-      case LOGIN_PHONE:
-        return this.renderLoginPhone();
-      case REGISTER_PHONE:
-        return this.renderRegisterPhone();
       case LOGIN_WX:
         return this.renderLoginWx();
       case BIND_PHONE:
@@ -58,45 +186,69 @@ export default class Login extends Component {
         return this.renderBindWx();
       case BIND_WX_ERROR:
         return this.renderBindWxError();
+      case LOGIN_PHONE:
       default:
-        return this.LOGIN_PHONE();
+        return this.renderLoginPhone();
     }
   }
 
   renderLoginPhone() {
+    const { needEmail } = this.state;
     return (
       <div className="body">
         <div className="title">手机号登录</div>
-        <SelectInput placeholder="请输入手机号" />
-        <VerificationInput placeholder="请输入验证码" />
-        <Button type="primary" size="large" block>
-          登录
-        </Button>
-        <Tooltip overlayClassName="gray" placement="left" title="微信扫码登录">
-          <a className="other">
-            <Assets name="code" />
-          </a>
-        </Tooltip>
-      </div>
-    );
-  }
-
-  renderRegisterPhone() {
-    return (
-      <div className="body">
-        <div className="title">手机号登录</div>
-        <div className="tip">
+        <div className="tip" hidden={!needEmail}>
           <Assets name="notice" />
           该手机号尚未注册,将自动为您注册账户
         </div>
-        <SelectInput placeholder="请输入手机号" />
-        <VerificationInput placeholder="请输入验证码" />
-        <Input placeholder="请输入邮箱" />
-        <Button type="primary" size="large" block>
+        <SelectInput
+          placeholder="请输入手机号"
+          selectValue={this.state.data.area}
+          select={MobileArea}
+          value={this.state.data.mobile}
+          error={this.state.mobileError}
+          onSelect={value => {
+            this.changeData('area', value);
+          }}
+          onChange={e => {
+            this.changeData('mobile', e.target.value);
+            this.validMobile(e.target.value);
+          }}
+        />
+        <VerificationInput
+          placeholder="请输入验证码"
+          value={this.state.data.mobileVerifyCode}
+          error={this.state.validError}
+          onSend={() => {
+            return this.sendValid();
+          }}
+        />
+        {needEmail && (
+          <Input
+            placeholder="请输入邮箱"
+            value={this.state.data.email}
+            onChange={e => {
+              this.changeData('email', e.target.value);
+            }}
+          />
+        )}
+        <Button
+          type="primary"
+          size="large"
+          block
+          onClick={() => {
+            this.login();
+          }}
+        >
           登录
         </Button>
         <Tooltip overlayClassName="gray" placement="left" title="微信扫码登录">
-          <a className="other">
+          <a
+            className="other"
+            onClick={() => {
+              this.setState({ type: LOGIN_WX });
+            }}
+          >
             <Assets name="code" />
           </a>
         </Tooltip>
@@ -113,7 +265,12 @@ export default class Login extends Component {
           <div className="text">请使用微信扫描二维码登录</div>
         </div>
         <Tooltip overlayClassName="gray" placement="left" title="手机号登录">
-          <a className="other">
+          <a
+            className="other"
+            onClick={() => {
+              this.setState({ type: LOGIN_PHONE });
+            }}
+          >
             <Assets name="phone" />
           </a>
         </Tooltip>
@@ -122,6 +279,7 @@ export default class Login extends Component {
   }
 
   renderBindPhone() {
+    const { needEmail } = this.state;
     return (
       <div className="body">
         <div className="title">绑定手机号</div>
@@ -129,10 +287,45 @@ export default class Login extends Component {
           <Assets name="notice" />
           微信登录成功!为更好的使用服务,请您绑定手机号和邮箱。
         </div>
-        <SelectInput placeholder="请输入手机号" />
-        <VerificationInput placeholder="请输入验证码" />
-        <Input placeholder="请输入邮箱" />
-        <Button type="primary" size="large" block>
+        <SelectInput
+          placeholder="请输入手机号"
+          selectValue={this.state.data.area}
+          select={MobileArea}
+          value={this.state.data.mobile}
+          error={this.state.mobileError}
+          onSelect={value => {
+            this.changeData('area', value);
+          }}
+          onChange={e => {
+            this.changeData('mobile', e.target.value);
+            this.validMobile(e.target.value);
+          }}
+        />
+        <VerificationInput
+          placeholder="请输入验证码"
+          value={this.state.data.mobileVerifyCode}
+          error={this.state.validError}
+          onSend={() => {
+            return this.sendValid();
+          }}
+        />
+        {needEmail && (
+          <Input
+            placeholder="请输入邮箱"
+            value={this.state.data.email}
+            onChange={e => {
+              this.changeData('email', e.target.value);
+            }}
+          />
+        )}
+        <Button
+          type="primary"
+          size="large"
+          block
+          onClick={() => {
+            this.bind();
+          }}
+        >
           绑定
         </Button>
       </div>
@@ -150,7 +343,14 @@ export default class Login extends Component {
         <div className="qr-code">
           <Assets name="qrcode" />
           <div className="text">请使用微信扫描二维码登录</div>
-          <div className="jump">跳过</div>
+          <div
+            className="jump"
+            onClick={() => {
+              this.close();
+            }}
+          >
+            跳过
+          </div>
         </div>
       </div>
     );
@@ -162,7 +362,14 @@ export default class Login extends Component {
         <div className="title">绑定失败</div>
         <div className="text">该微信账户已绑定其他手机号,您可直接使用微信登入。</div>
         <div className="btn">
-          <GButton radius>Ok</GButton>
+          <GButton
+            radius
+            onClick={() => {
+              this.close();
+            }}
+          >
+            Ok
+          </GButton>
         </div>
       </div>
     );
@@ -171,12 +378,17 @@ export default class Login extends Component {
 
 class Input extends Component {
   render() {
-    const { className = '', onChange, placeholder, error, left, right } = this.props;
+    const { className = '', onChange, placeholder, value, error, left, right } = this.props;
     return (
       <div className={`g-input-container ${className}`}>
         <div className={`g-input-wrapper ${error ? 'error' : ''}`}>
           {left && <div className="g-input-left">{left}</div>}
-          <input className="g-input" placeholder={placeholder} onChange={data => onChange && onChange(data)} />
+          <input
+            className="g-input"
+            placeholder={placeholder}
+            value={value}
+            onChange={data => onChange && onChange(data)}
+          />
           {right && <div className="g-input-right">{right}</div>}
         </div>
         <div hidden={!error} className="g-input-error">
@@ -188,26 +400,49 @@ class Input extends Component {
 }
 
 class SelectInput extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { showSelect: false };
+  }
+
   render() {
-    const { className = '', onChange, placeholder, value, selectValue, onSelect } = this.props;
+    const { showSelect } = this.state;
+    const { className = '', onChange, placeholder, value, error, selectValue, select, onSelect } = this.props;
     return (
       <Input
         className={className}
         left={
-          <span className="g-input-left-select" onClick={() => onSelect && onSelect()}>
+          <span className="g-input-left-select" onClick={() => this.setState({ showSelect: !showSelect })}>
             {selectValue}
-            <Icon type="down" />
+            <Icon type={showSelect ? 'up' : 'down'} />
+            {showSelect && (
+              <ul className="select-list">
+                {select.map(row => {
+                  return (
+                    <li
+                      onClick={() => {
+                        this.setState({ showSelect: false });
+                        if (onSelect) onSelect(row.value);
+                      }}
+                    >
+                      {row.label}
+                    </li>
+                  );
+                })}
+              </ul>
+            )}
           </span>
         }
         value={value}
         placeholder={placeholder}
         onChange={data => onChange && onChange(data)}
+        error={error}
       />
     );
   }
 }
 
-class VerificationInput extends Component {
+export class VerificationInput extends Component {
   constructor(props) {
     super(props);
     this.timeKey = null;
@@ -221,9 +456,10 @@ class VerificationInput extends Component {
   onSend() {
     const { onSend, time = 60 } = this.props;
     if (onSend) {
-      onSend();
+      onSend().then(() => {
+        this.setTime(time);
+      });
     }
-    this.setTime(time);
   }
 
   setTime(time) {

+ 7 - 3
front/project/www/local.json

@@ -14,9 +14,13 @@
     ]
   },
   "test": {
-    "scripts": ["http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"]
+    "scripts": [
+      "http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"
+    ]
   },
   "production": {
-    "scripts": ["http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"]
+    "scripts": [
+      "http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"
+    ]
   }
-}
+}

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

@@ -1,6 +1,7 @@
 import React from 'react';
 import './index.less';
 import { Link } from 'react-router-dom';
+import { Tooltip } from 'antd';
 import Page from '@src/containers/Page';
 import { asyncConfirm } from '@src/services/AsyncTools';
 import { formatTreeData, getMap } from '@src/services/Tools';
@@ -532,6 +533,18 @@ export default class extends Page {
             },
             { type: 'select', checked: 'all', list: [{ key: 'all', title: '全部' }] },
           ]}
+          rightAction={
+            <div>
+              有效期至:2019-11-13{' '}
+              <Tooltip overlayClassName="gray" placement="top" title="全部模考做完才可重置">
+                <a>
+                  <Button size="small" disabled radius>
+                    Reset
+                  </Button>
+                </a>
+              </Tooltip>
+            </div>
+          }
           data={previews}
           columns={this.columns}
         />

+ 52 - 42
front/project/www/routes/exercise/main/page.js

@@ -1,6 +1,6 @@
 import React from 'react';
 import './index.less';
-import { Modal, Tooltip } from 'antd';
+import { Modal } from 'antd';
 import { Link } from 'react-router-dom';
 import Page from '@src/containers/Page';
 import { asyncConfirm } from '@src/services/AsyncTools';
@@ -25,10 +25,11 @@ import { Sentence } from '../../../stores/sentence';
 import { Question } from '../../../stores/question';
 import { Course } from '../../../stores/course';
 import { User } from '../../../stores/user';
+import { CourseModuleShow } from '../../../../Constant';
 
 const SENTENCE = 'sentence';
 const PREVIEW = 'preview';
-const PREVIEW_CLASS = 'PREVIEW_CLASS';
+const PREVIEW_COURSE = 'PREVIEW_COURSE';
 const PREVIEW_LIST = 'PREVIEW_LIST';
 
 const exerciseColumns = [
@@ -263,14 +264,15 @@ export default class extends Page {
     this.code = null;
     this.columns = exerciseColumns;
     this.exerciseProgress = {};
+    this.courseProgress = {};
     this.inited = false;
     return {
       tab1: SENTENCE,
       tab2: '',
-      pt: PREVIEW_CLASS,
+      previewType: PREVIEW_COURSE,
       tabs: [],
-      allClass: [],
-      classProcess: {},
+      allCourse: [],
+      courseProgress: {},
     };
   }
 
@@ -282,11 +284,20 @@ export default class extends Page {
         return row;
       });
       const tabs = formatTreeData(list, 'id', 'title', 'parentId');
+      // 课程顶级分类
+      const courseStructs = result.filter(row => row.isCourse && row.level === 1);
       tabs.push({ key: PREVIEW, name: '预习作业' });
-      this.setState({ tabs });
+      this.setState({ tabs, courseStructs });
       this.inited = true;
       this.refreshData();
     });
+    this.setState({
+      courseTabs: CourseModuleShow.map(row => {
+        row.title = row.label;
+        row.key = row.value;
+        return row;
+      }),
+    });
   }
 
   initData() {
@@ -396,26 +407,36 @@ export default class extends Page {
   }
 
   refreshPreview() {
-    const { pt } = this.state;
-    switch (pt) {
+    const { previewType } = this.state;
+    switch (previewType) {
       case PREVIEW_LIST:
         this.refreshListPreview();
         break;
-      case PREVIEW_CLASS:
+      case PREVIEW_COURSE:
       default:
-        this.refreshClassProcess();
+        this.refreshCourseProcess();
         break;
     }
   }
 
-  refreshClassProcess() {
-    Course.classProcess().then(result => {
-      const classProcess = {};
+  refreshCourseProcess() {
+    const { courseTabs, structId } = this.state;
+    let { tab2 } = this.state;
+    let tab;
+    if (tab2 === '') {
+      tab2 = courseTabs[0].key;
+      this.setState({ tab2 });
+      ([tab] = courseTabs);
+    } else {
+      ([tab] = courseTabs.filter(row => row.key === tab2));
+    }
+    Course.progress(tab.courseModules, structId).then(result => {
+      const courseProgress = {};
       for (let i = 0; i < result.length; i += 1) {
         const item = result[i];
-        classProcess[item.category].push(item);
+        courseProgress[item.category].push(item);
       }
-      this.setState({ classProcess });
+      this.setState({ courseProgress });
     });
   }
 
@@ -479,7 +500,7 @@ export default class extends Page {
   }
 
   onChangePreviewType(type) {
-    this.setState({ pt: type });
+    this.setState({ previewType: type });
     this.refreshPreview();
   }
 
@@ -568,9 +589,9 @@ export default class extends Page {
   }
 
   renderView() {
-    const { tab1, tab2, tabs, latest, sentenceModel } = this.state;
+    const { tab1, tab2, tabs, latest, sentenceModel, previewType, courseTabs = [] } = this.state;
     const [subject] = tabs.filter(row => row.key === tab1);
-    const children = subject ? subject.children : [];
+    const children = subject ? subject.children : (tab1 === 'preview' && previewType === 'PREVIEW_COURSE' ? courseTabs : []);
     return (
       <div>
         {latest && (
@@ -603,6 +624,7 @@ export default class extends Page {
             {children && children.length > 1 && (
               <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />
             )}
+            {}
           </Module>
           {tab1 !== SENTENCE && tab1 !== PREVIEW && this.renderExercise()}
           {tab1 === SENTENCE && this.renderSentence()}
@@ -616,8 +638,8 @@ export default class extends Page {
   renderPreview() {
     const { previewType } = this.state;
     switch (previewType) {
-      case PREVIEW_CLASS:
-        return this.renderPreviewClass();
+      case PREVIEW_COURSE:
+        return this.renderPreviewCourse();
       case PREVIEW_LIST:
         return this.renderPreviewList();
       default:
@@ -625,8 +647,8 @@ export default class extends Page {
     }
   }
 
-  renderPreviewClass() {
-    const { allClass, classProcess } = this.state;
+  renderPreviewCourse() {
+    const { allCourse, courseProgress } = this.state;
     return (
       <div className="work-body">
         <div className="work-nav">
@@ -636,8 +658,8 @@ export default class extends Page {
           </div>
         </div>
         <Division col="3">
-          {allClass.map(item => {
-            return <Card data={item} process={classProcess[item.id]} previewAction={this.previewAction} />;
+          {allCourse.map(item => {
+            return <Card data={item} process={courseProgress[item.id]} previewAction={this.previewAction} />;
           })}
         </Division>
       </div>
@@ -650,7 +672,7 @@ export default class extends Page {
       <div className="work-body">
         <div className="work-nav">
           <div className="left">全部作业</div>
-          <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_CLASS)}>
+          <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_COURSE)}>
             我的课程 >
           </div>
         </div>
@@ -666,20 +688,7 @@ export default class extends Page {
               checked: 'unfinish',
               list: [{ key: 'unfinish', title: '未完成' }, { key: 'finish', title: '已完成' }],
             },
-            { type: 'select', checked: 'all', list: [{ key: 'all', title: '全部' }] },
           ]}
-          rightAction={
-            <div>
-              有效期至:2019-11-13{' '}
-              <Tooltip overlayClassName="gray" placement="top" title="全部模考做完才可重置">
-                <a>
-                  <Button size="small" disabled radius>
-                    Reset
-                  </Button>
-                </a>
-              </Tooltip>
-            </div>
-          }
           data={previews}
           columns={this.columns}
         />
@@ -709,6 +718,7 @@ export default class extends Page {
       paperFilterList = [],
       paperList = [],
       paperChecked,
+      sentenceInfo = {},
     } = this.state;
     const { sentenceTrail } = this.props.user;
     let maxStep = 0;
@@ -729,7 +739,7 @@ export default class extends Page {
         {sentence.code && <div className="sentence-code">CODE: {sentence.code}</div>}
         {sentenceTrail && (
           <div className="sentence-code">
-            CODE: <Link to="">去获取</Link>
+            CODE: <a href={sentenceInfo.link} target="_blank">去获取</a>
             <a
               onClick={() => {
                 this.setState({ sentenceModel: true });
@@ -818,7 +828,7 @@ export default class extends Page {
   }
 
   renderInputCode() {
-    const { sentenceError } = this.state;
+    const { sentenceError, sentenceInfo = {} } = this.state;
     return (
       <Module className="code-module">
         <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
@@ -845,9 +855,9 @@ export default class extends Page {
             什么是CODE?
           </Link>
           <span>没有 CODE?</span>
-          <Link to="/" className="link">
+          <a href={sentenceInfo.link} target="_blank" className="link">
             去获取 >>
-          </Link>
+          </a>
           <a
             onClick={() => {
               this.trailSentence();

+ 99 - 141
front/project/www/routes/paper/process/base/index.js

@@ -12,7 +12,7 @@ import Calculator from '../../../../components/Calculator';
 import AnswerSelect from '../../../../components/AnswerSelect';
 import AnswerTable from '../../../../components/AnswerTable';
 import Editor from '../../../../components/Editor';
-import { QuestionType } from '../../../../../Constant';
+import { QuestionType, ExaminationOrder } from '../../../../../Constant';
 
 const QuestionTypeMap = getMap(QuestionType, 'value');
 
@@ -262,51 +262,15 @@ export default class extends Component {
   }
 
   renderExaminationStart() {
-    const { disorder } = this.state;
+    // const { paper, userQuestion, singleTime, stageTime, flow } = this.props;
+    // const { showTime, showNo } = this.state;
     const { paper, flow } = this.props;
     return (
-      <div className="start">
-        <div className="bg" />
-        <div className="fixed-content">
-          <div className="title">{paper.title}</div>
-          <div className="desc">
-            <div className="block">
-              <div className="desc-title">
-                <Assets name="subject_icon" />
-                题目总数
-              </div>
-              <div className="desc-info">{paper.questionNumer}</div>
-            </div>
-            <div className="block">
-              <div className="desc-title">
-                <Assets name="time_icon" />
-                建议用时
-              </div>
-              <div className="desc-info">{formatSeconds(paper.time)}</div>
-            </div>
-          </div>
-          <div className="tip">
-            <Checkbox className="m-r-1" checked={disorder} onChange={() => this.setState({ disorder: !disorder })} />
-            题目选项乱序显示
-          </div>
-          <div className="submit">
-            <Button size="lager" radius onClick={() => flow.start({ disorder })}>
-              开始练习
-            </Button>
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-  renderExerciseStart() {
-    const { paper, userQuestion, singleTime, stageTime, flow } = this.props;
-    const { showTime, showNo } = this.state;
-    return (
       <div className="layout">
+        <div className="fixed" />
         <div className="layout-header">
           <div className="title">{paper.title}</div>
-          <div className="right">
+          {/* <div className="right">
             <div
               className="block"
               onClick={() => {
@@ -326,9 +290,9 @@ export default class extends Component {
               <Assets name="subjectnumber_icon" />
               {showNo && `${userQuestion.no} of ${paper.questionNumber}`}
             </div>
-          </div>
+          </div> */}
         </div>
-        <div className={'layout-body'}>{this.renderExerciseStartCAT()}</div>
+        <div className={'layout-body'}>{paper.isAdapt > 1 ? this.renderExaminationStartCAT() : this.renderExaminationStartDefault()}</div>
         <div className="layout-footer">
           <div className="help">
             <Assets name="help_icon" />
@@ -346,7 +310,47 @@ export default class extends Component {
     );
   }
 
-  renderExerciseStartDefault() {
+  renderExerciseStart() {
+    const { disorder } = this.state;
+    const { paper, flow } = this.props;
+    return (
+      <div className="start">
+        <div className="bg" />
+        <div className="fixed-content">
+          <div className="title">{paper.title}</div>
+          <div className="desc">
+            <div className="block">
+              <div className="desc-title">
+                <Assets name="subject_icon" />
+                题目总数
+              </div>
+              <div className="desc-info">{paper.questionNumer}</div>
+            </div>
+            <div className="block">
+              <div className="desc-title">
+                <Assets name="time_icon" />
+                建议用时
+              </div>
+              <div className="desc-info">{formatSeconds(paper.time)}</div>
+            </div>
+          </div>
+          {paper.times > 0 && <div className="tip">
+            <Checkbox className="m-r-1" checked={!disorder} onChange={() => this.setState({ disorder: !!disorder })} />
+            题目选项乱序显示
+          </div>}
+          <div className="submit">
+            <Button size="lager" radius onClick={() => flow.start({ disorder: paper.times > 0 ? !disorder : false })}>
+              开始练习
+            </Button>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderExaminationStartCAT() {
+    const { disorder, order, orderIndex } = this.state;
+    const { paper, flow } = this.props;
     return (
       <div className="exercise-start default">
         <div className="title">Section Ordering</div>
@@ -361,50 +365,29 @@ export default class extends Component {
           selected, before moving on to the next section. You will NOT be able to return to this screen.
         </div>
         <div className="block-list">
-          <div className="block-item">
-            <div className="block-title">
-              <div className="block-title-border">
-                <AntDIcon type="check" />
-                <span>ARQV</span>
+          {ExaminationOrder.map((row, index) => {
+            return <div className="block-item">
+              <div className="block-title" onClick={() => {
+                this.setState({ order: row.value, orderIndex: index });
+              }}>
+                <div className="block-title-border">
+                  {orderIndex === index && <AntDIcon type="check" />}
+                  <span>{row.label}</span>
+                </div>
               </div>
-            </div>
-            <div className="block-text">1.Analytical Writing Analysis </div>
-            <div className="block-text">2.Integrated Reasoning </div>
-            <div className="block-text">3.Quantitative </div>
-            <div className="block-text">4.Verbal </div>
-          </div>
-          <div className="block-item">
-            <div className="block-title">
-              <div className="block-title-border">
-                <AntDIcon type="check" />
-                <span>VQRA </span>
-              </div>
-            </div>
-            <div className="block-text">1.Verbal </div>
-            <div className="block-text">2.Quantitative </div>
-            <div className="block-text">3.Integrated Reasoning </div>
-            <div className="block-text">4.Analytical Writing Analysis </div>
-          </div>
-          <div className="block-item">
-            <div className="block-title">
-              <div className="block-title-border">
-                <AntDIcon type="check" />
-                <span>QVRA</span>
-              </div>
-            </div>
-            <div className="block-text">1.Quantitative</div>
-            <div className="block-text">2.Verbal </div>
-            <div className="block-text">3.Integrated Reasoning </div>
-            <div className="block-text">4.Analytical Writing Analysis </div>
-          </div>
+              {row.list.map((r, i) => {
+                return <div className="block-text">{i + 1}.{r.label} </div>;
+              })}
+            </div>;
+          })}
         </div>
         <div className="bottom">
-          <div className="text">
-            <Checkbox checked /> 题目选项乱序显示
-          </div>
+          {paper.times > 0 && <div className="text">
+            <Checkbox checked={!disorder} onChange={() => this.setState({ disorder: !!disorder })} /> 题目选项乱序显示
+          </div>}
           <div className="text">
             Click{' '}
-            <div className="next" onClick={() => this.next()}>
+            <div className="next" onClick={() => flow.start({ disorder: paper.times > 0 ? !disorder : false, order })}>
               Next
               <Assets name="next_icon" />
             </div>{' '}
@@ -415,68 +398,44 @@ export default class extends Component {
     );
   }
 
-  renderExerciseStartCAT() {
+  renderExaminationStartDefault() {
+    const { disorder, order, orderIndex } = this.state;
+    const { paper, flow } = this.props;
     return (
       <div className="exercise-start cat">
         <div className="title">Section Ordering</div>
         <div className="block-list">
-          <div className="block-item">
-            <div className="block-item-body active">
-              <AntDIcon type="check" style={{ color: '#fff' }} />
-              <div className="block-text">
-                <Checkbox checked /> Analytical Writing Analysis{' '}
-              </div>
-              <div className="block-text">
-                <Checkbox checked /> Integrated Reasoning{' '}
-              </div>
-              <div className="block-text">
-                <Checkbox checked /> Quantitative{' '}
-              </div>
-              <div className="block-text">
-                <Checkbox checked /> Verbal{' '}
-              </div>
-            </div>
-          </div>
-          <div className="block-item">
-            <div className="block-item-body">
-              <div className="block-text">
-                <Checkbox /> Quantitative
-              </div>
-              <div className="block-text">
-                <Checkbox /> Verbal{' '}
-              </div>
-              <div className="block-text">
-                <Checkbox /> Integrated Reasoning{' '}
-              </div>
-              <div className="block-text">
-                <Checkbox /> Analytical Writing Analysis{' '}
-              </div>
-            </div>
-          </div>
-          <div className="block-item">
-            <div className="block-item-body">
-              <div className="block-text">
-                <Checkbox /> Verbal{' '}
-              </div>
-              <div className="block-text">
-                <Checkbox /> Quantitative{' '}
-              </div>
-              <div className="block-text">
-                <Checkbox /> Integrated Reasoning{' '}
+          {ExaminationOrder.map((row, index) => {
+            return <div className="block-item" onClick={() => {
+              this.setState({ order: row.value, orderIndex: index });
+            }}>
+              <div className={orderIndex === index ? 'block-item-body active' : 'block-item-body'}>
+                {orderIndex === index && <AntDIcon type="check" style={{ color: '#fff' }} />}
+                {row.list.map((r, i) => {
+                  return <div className="block-text" onClick={() => {
+                    if (order.indexOf(r.value) > 0) {
+                      // 取消
+                      order[i] = '';
+                    } else {
+                      // 选中
+                      order[i] = r.value;
+                    }
+                    this.setState({ order });
+                  }}>
+                    <Checkbox checked={orderIndex === index ? order.indexOf(r.value) >= 0 : false} /> {r.label}{' '}
+                  </div>;
+                })}
               </div>
-              <div className="block-text">
-                <Checkbox /> Analytical Writing Analysis{' '}
-              </div>
-            </div>
-          </div>
+            </div>;
+          })}
         </div>
         <div className="bottom">
-          <div className="text">
-            <Checkbox checked /> 题目选项乱序显示
-          </div>
+          {paper.times > 0 && <div className="text">
+            <Checkbox checked={!disorder} onChange={() => this.setState({ disorder: !!disorder })} /> 题目选项乱序显示
+          </div>}
           <div className="text">
             Click{' '}
-            <div className="next" onClick={() => this.next()}>
+            <div className="next" onClick={() => flow.start({ disorder: paper.times > 0 ? !disorder : false, order: order.filter(row => row) })}>
               Next
               <Assets name="next_icon" />
             </div>{' '}
@@ -565,12 +524,11 @@ export default class extends Component {
                 <span className="t-d-l">N</span>o
               </div>
             </div>
-          ) : (
-            <div className="btn-list">
-              <div className="btn" onClick={() => this.hideModal(true)}>
-                <span className="t-d-l">O</span>k
+          ) : (<div className="btn-list">
+            <div className="btn" onClick={() => this.hideModal(true)}>
+              <span className="t-d-l">O</span>k
               </div>
-            </div>
+          </div>
           )}
         </div>
       </div>

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

@@ -291,9 +291,9 @@ export default class extends Page {
     if (!paper.id || !scene) return null;
     switch (paper.paperModule) {
       case 'sentence':
-        return <Sentence key={userQuestion.id} {...this.state} flow={this} />;
+        return <Sentence key={userQuestion.id} {...this.state} flow={this} mode='process' />;
       default:
-        return <Base key={userQuestion.id} {...this.state} flow={this} />;
+        return <Base key={userQuestion.id} {...this.state} flow={this} mode='process' />;
     }
   }
 }

+ 109 - 34
front/project/www/routes/paper/process/sentence/index.js

@@ -1,9 +1,10 @@
 import React, { Component } from 'react';
 import './index.less';
 import Assets from '@src/components/Assets';
-import { formatSecond, formatPercent } from '@src/services/Tools';
+import { formatSecond, formatPercent, formatSeconds } from '@src/services/Tools';
 import Icon from '../../../../components/Icon';
 import Button from '../../../../components/Button';
+import Switch from '../../../../components/Switch';
 import Tabs from '../../../../components/Tabs';
 import Progress from '../../../../components/Progress';
 import HardInput from '../../../../components/HardInput';
@@ -113,23 +114,78 @@ export default class extends Component {
     return stem;
   }
 
+  renderHeader() {
+    const { mode } = this.props;
+    switch (mode) {
+      case 'process':
+        return this.renderProcessHeader();
+      default:
+        return this.renderQuestionHeader();
+    }
+  }
+
+  renderProcessHeader() {
+    const { userQuestion, singleTime, paper, flow } = this.props;
+    return (
+      <div className="layout-header">
+        <div className="left">
+          <div className="title">{paper.title}</div>
+        </div>
+        <div className="right">
+          <div className="text">
+            <Assets name="timecost_icon" />
+            Time cost {formatSecond(userQuestion.userTime || singleTime)}
+          </div>
+          <Icon name="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} />
+        </div>
+      </div>
+    );
+  }
+
+  renderQuestionHeader() {
+    const { userQuestion, questionNo, paper, flow } = this.props;
+    return (
+      <div className="layout-header">
+        <div className="left">
+          <div className="title">{paper.title}</div>
+        </div>
+        <div className="right">
+          <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
+              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="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} />
+        </div>
+      </div>
+    );
+  }
+
   render() {
-    const { flow, paper, userQuestion, singleTime } = this.props;
+    const { flow, paper, userQuestion } = this.props;
     return (
       <div id="paper-process-sentence">
         <div className="layout">
-          <div className="layout-header">
-            <div className="left">
-              <div className="title">{paper.title}</div>
-            </div>
-            <div className="right">
-              <div className="text">
-                <Assets name="timecost_icon" />
-                Time cost {formatSecond(userQuestion.userTime || singleTime)}
-              </div>
-              <Icon name="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} />
-            </div>
-          </div>
+          {this.renderHeader()}
           {this.renderBody()}
           <div className="layout-footer">
             <div className="left">
@@ -245,44 +301,63 @@ export default class extends Component {
   }
 
   renderAnswer() {
-    const { analysisTab, question, userQuestion, stem } = this.state;
+    const { mode } = this.props;
+    const { analysisTab, question, userQuestion, stem, showAnswer } = this.state;
     const { userAnswer = {} } = userQuestion;
     const { answer } = question;
     return (
       <div className="layout-body">
-        <div className="title">
-          <Icon name="question" />
-          请分别找出句子中的主语,谓语和宾语,并做出逻辑关系判断。
-        </div>
+        {mode === 'question' ? (
+          <Switch
+            checked={showAnswer}
+            onChange={value => {
+              this.setState({ showAnswer: value });
+            }}
+          >
+            {showAnswer ? '显示答案' : '关闭答案'}
+          </Switch>
+        ) : (
+          <div className="title">
+            <Icon name="question" />
+            请分别找出句子中的主语,谓语和宾语,并做出逻辑关系判断。
+          </div>
+        )}
+
         <div className="desc" dangerouslySetInnerHTML={{ __html: stem }} />
         <div className="label">主语</div>
         <div className="input">
-          <HardInput show list={userAnswer.subject || []} answer={answer.subject} />
+          <HardInput show={showAnswer} list={userAnswer.subject || []} answer={answer.subject} />
         </div>
         <div className="label">谓语</div>
         <div className="input">
-          <HardInput show list={userAnswer.predicate || []} answer={answer.predicate} />
+          <HardInput show={showAnswer} list={userAnswer.predicate || []} answer={answer.predicate} />
         </div>
         <div className="label">宾语</div>
         <div className="input">
-          <HardInput show list={userAnswer.object || []} answer={answer.object} />
+          <HardInput show={showAnswer} list={userAnswer.object || []} answer={answer.object} />
         </div>
         <div className="select">
           <div className="select-title">本句存在以下哪种逻辑关系?(可多选)</div>
-          <AnswerCheckbox show list={SentenceOption} selected={userAnswer.options} answer={answer.options} />
-        </div>
-        <div className="analysis">
-          <Tabs
-            type="division"
-            active={analysisTab}
-            space={3}
-            tabs={[{ key: 'qx', name: '解析详情' }, { key: 'chinese', name: '中文语意' }]}
-            onChange={key => {
-              this.setState({ analysisTab: key });
-            }}
+          <AnswerCheckbox
+            show={showAnswer}
+            list={SentenceOption}
+            selected={userAnswer.options}
+            answer={answer.options}
           />
-          {this.renderText()}
         </div>
+        {showAnswer && (
+          <div className="analysis">
+            <Tabs
+              type="division"
+              active={analysisTab}
+              tabs={[{ key: 'qx', name: '解析详情' }, { key: 'chinese', name: '中文语意' }]}
+              onChange={key => {
+                this.setState({ analysisTab: key });
+              }}
+            />
+            {this.renderText()}
+          </div>
+        )}
       </div>
     );
   }

+ 12 - 0
front/project/www/routes/paper/process/sentence/index.less

@@ -41,6 +41,18 @@
             margin-right: 5px;
           }
         }
+
+        .b {
+          margin-left: 30px;
+
+          .s {
+            color: #4299FF;
+          }
+        }
+
+        .icon {
+          margin-left: 10px;
+        }
       }
     }
   }

+ 56 - 38
front/project/www/routes/paper/question/page.js

@@ -210,51 +210,69 @@ export default class extends Page {
     const { report = {} } = this.state;
     switch (report.paperModule) {
       case 'sentence':
-        return <Sentence {...this.state} flow={this} scene='answer' />;
+        return <Sentence {...this.state} flow={this} scene='answer' mode='question' />;
       default:
         return <div className='base'>{this.renderBase()}</div>;
     }
   }
 
-  renderBase() {
-    const { questionStatus, userQuestion = {}, questionNo = {}, paper = {}, showIds, questionNos = [] } = this.state;
+  renderHeader() {
+    const { report = {} } = this.state;
+    switch (report.paperModule) {
+      case 'examination':
+        return this.renderExaminationHeader();
+      default:
+        return this.renderExerciseHeader();
+    }
+  }
+
+  renderExaminationHeader() {
 
+  }
+
+  renderExerciseHeader() {
+    const { userQuestion = {}, questionNo = {}, paper = {}, showIds, questionNos = [] } = 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">
+        <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 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 });
     }}>
-      <div className="layout-header">
-        <div className="left">
-          <div className="no">No.{userQuestion.no}</div>
-          <div className="title"><Assets name='book' />{paper.title}13</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">
-          <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 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>
+      {this.renderHeader()}
       <div className="layout-body">{this.renderBody()}</div>
       <div className="layout-footer">
         <div className="left">
@@ -276,8 +294,8 @@ export default class extends Page {
           <AnswerButton className="item" onClick={() => this.setState({ feedbackModal: true })}>纠错</AnswerButton>
         </div>
         <div className="right">
-          <Icon name="prev" onClick={() => this.prevQuestion()} />
-          <Icon name="next" onClick={() => this.nextQuestion()} />
+          {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()}

+ 1 - 0
front/project/www/routes/paper/report/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'paper-report',
   title: '报告',
   needLogin: true,
+  hideHeader: true,
   component() {
     return import('./page');
   },

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

@@ -210,8 +210,13 @@ const pieOption = {
 };
 
 export default class extends Page {
+  initState() {
+    return { report: { paperModule: 'exercise' } };
+  }
+
   initData() {
     const { id } = this.params;
+    const { page = '' } = this.state.search;
     Question.detailReport(id).then(result => {
       switch (result.paperModule) {
         case 'sentence':
@@ -230,22 +235,49 @@ export default class extends Page {
       }
       this.setState({ report: result });
     });
+    switch (page) {
+      case 'question':
+        // 题目回顾列表
+        Question.questionReport(id).then(result => {
+          this.setState({ list: result });
+        });
+        break;
+      default:
+        break;
+    }
   }
 
   refreshSentence() {
-
+    const { page = '' } = this.state.search;
+    switch (page) {
+      case 'question':
+        break;
+      default:
+    }
   }
 
   refreshTextbook() {
-
+    this.refreshExercise();
   }
 
   refreshExamination() {
-
+    const { page = '' } = this.state.search;
+    switch (page) {
+      case 'total':
+        break;
+      case 'question':
+        break;
+      default:
+    }
   }
 
   refreshExercise() {
-
+    const { page = '' } = this.state.search;
+    switch (page) {
+      case 'question':
+        break;
+      default:
+    }
   }
 
   renderView() {
@@ -269,22 +301,20 @@ export default class extends Page {
   }
 
   renderTextbook() {
-    return <div />;
+    return this.renderExercise();
   }
 
   renderExercise() {
-    return (
-      <div>
-        <div className="content">
-          <LineChart option={lineOption} />
-          <BarChart option={bar1Option} />
-          <BarChart option={bar2Option} />
-          <BarChart option={bar3Option} />
-          <BarChart option={barOption} />
-          <PieChart option={pieOption} />
-        </div>
+    return <div>
+      <div className="content">
+        <LineChart option={lineOption} />
+        <BarChart option={bar1Option} />
+        <BarChart option={bar2Option} />
+        <BarChart option={bar3Option} />
+        <BarChart option={barOption} />
+        <PieChart option={pieOption} />
       </div>
-    );
+    </div>;
   }
 
   renderExamination() {

+ 10 - 0
front/project/www/routes/preview/list/index.js

@@ -0,0 +1,10 @@
+export default {
+  path: '/preview/list/:id',
+  key: 'preview',
+  title: '预习作业列表',
+  needLogin: false,
+  tab: 'exercise',
+  component() {
+    return import('./page');
+  },
+};

+ 59 - 0
front/project/www/routes/preview/list/index.less

@@ -0,0 +1,59 @@
+@charset "utf-8";
+
+#exercise-list {
+  .code-module {
+    padding: 80px 250px;
+    text-align: center;
+
+    .title {
+      font-size: 18px;
+      margin-bottom: 24px;
+    }
+
+    .input-block {
+      margin-bottom: 24px;
+
+      .input {
+        width: 350px;
+
+        input {
+          border-top-left-radius: 22px;
+          border-bottom-left-radius: 22px;
+        }
+      }
+
+      .button {
+        width: 150px;
+        border-top-right-radius: 22px;
+        border-bottom-right-radius: 22px;
+      }
+    }
+
+    .tip {
+      .left {
+        float: left;
+      }
+
+      .right {
+        float: right;
+      }
+    }
+  }
+
+  .work-body {
+    .work-nav {
+      margin-bottom: 20px;
+
+      .left {
+        display: inline-block;
+        padding-left: 5px;
+        font-size: 16px;
+        font-weight: 600;
+      }
+
+      .right {
+        float: right;
+      }
+    }
+  }
+}

+ 286 - 0
front/project/www/routes/preview/list/page.js

@@ -0,0 +1,286 @@
+import React from 'react';
+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 ListTable from '../../../components/ListTable';
+import ProgressText from '../../../components/ProgressText';
+import IconButton from '../../../components/IconButton';
+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>
+            </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) => {
+          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('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>
+          );
+        },
+      },
+    ];
+    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 });
+    });
+  }
+
+  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();
+    });
+  }
+
+  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))
+      .then((result) => {
+        this.setState({ list: result.list, total: result.total });
+      });
+  }
+
+  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(() => {
+        this.refresh();
+      });
+    });
+  }
+
+  renderView() {
+    const { logic, logicExtend, logics = [], logicExtends = [], list } = this.state;
+    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>
+
+          <ListTable
+            data={list}
+            columns={this.columns}
+          />
+        </div>
+      </div>
+    );
+  }
+}

+ 0 - 10
front/project/www/routes/sentence/process/index.js

@@ -1,10 +0,0 @@
-export default {
-  path: '/sentence/process/:id',
-  key: 'sentence-process',
-  title: '长难句',
-  needLogin: true,
-  hideHeader: true,
-  component() {
-    return import('./page');
-  },
-};

+ 0 - 0
front/project/www/routes/sentence/process/page.js


+ 29 - 26
front/project/www/routes/sentence/read/page.js

@@ -6,6 +6,8 @@ import Icon from '../../../components/Icon';
 import Progress from '../../../components/Progress';
 import Assets from '../../../../../src/components/Assets';
 import { Sentence } from '../../../stores/sentence';
+import { Main } from '../../../stores/main';
+import { formatMoney } from '../../../../../src/services/Tools';
 
 export default class extends Page {
   constructor(props) {
@@ -40,6 +42,9 @@ export default class extends Page {
       }
       this.jumpPage(page);
     });
+    Main.getSentence().then(result => {
+      this.setState({ info: result });
+    });
   }
 
   refreshSentence() {
@@ -204,7 +209,7 @@ export default class extends Page {
   }
 
   renderBody() {
-    const { showMenu, article, index, chapterMap = {} } = this.state;
+    const { showMenu, article, index, chapterMap = {}, info = {} } = this.state;
     return article ? (
       <div className="layout-body">
         <div className="crumb">千行长难句解析 >> {(chapterMap[article.chapter] || {}).title}</div>
@@ -214,20 +219,20 @@ export default class extends Page {
         </div>
         {showMenu && this.renderMenu()}
       </div>
-    ) : (
-      <div className="layout-body">
-        <div className="free-over">
-          <div className="free-over-title">试读已结束,购买后可继续阅读。</div>
-          <div className="free-over-btn">¥ 20.00 | 立即购买</div>
-          <div className="free-over-desc">
-            <div className="free-over-desc-title">张小爱笑笑笑 2019-07-13</div>
-            <div className="free-over-desc-content">
-              韩瑞祥/文 移民文学(Migrations
-              literatur)成为当今德国文坛上一个备受关注的文学现象,一批又一批脱颖而出的移民文学作家为当今德国文学的发展不断地
-            </div>
+    ) : (<div className="layout-body">
+      <div className="free-over">
+        <div className="free-over-title">试读已结束,购买后可继续阅读。</div>
+        <div className="free-over-btn" onClick={() => {
+          window.location.href = info.link;
+        }}>{formatMoney(info.price)} | 立即购买</div>
+        <div className="free-over-desc">
+          <div className="free-over-desc-title">{info.title}</div>
+          <div className="free-over-desc-content">
+            {info.description}
           </div>
         </div>
       </div>
+    </div>
     );
   }
 
@@ -264,13 +269,12 @@ export default class extends Page {
                 {chapter.title}
                 <div className="page">{chapter.startPage}</div>
               </div>
-            ) : (
-              <Tooltip title={message}>
-                <div className={'chapter-item trail'}>
-                  {chapter.title}
-                  <div className="page">{chapter.startPage}</div>
-                </div>
-              </Tooltip>
+            ) : (<Tooltip title={message}>
+              <div className={'chapter-item trail'}>
+                {chapter.title}
+                <div className="page">{chapter.startPage}</div>
+              </div>
+            </Tooltip>
             );
             const list = [_item];
             if (chapter.value) {
@@ -287,13 +291,12 @@ export default class extends Page {
                     {article.title}
                     <div className="page">{article.startPage}</div>
                   </div>
-                ) : (
-                  <Tooltip title={message}>
-                    <div className={'part-item trail'}>
-                      {article.title}
-                      <div className="page">{article.startPage}</div>
-                    </div>
-                  </Tooltip>
+                ) : (<Tooltip title={message}>
+                  <div className={'part-item trail'}>
+                    {article.title}
+                    <div className="page">{article.startPage}</div>
+                  </div>
+                </Tooltip>
                 );
                 list.push(item);
               });

+ 10 - 2
front/project/www/stores/course.js

@@ -4,8 +4,16 @@ export default class CourseStore extends BaseStore {
   /**
    * 获取课程进度
    */
-  classProgress() {
-    return this.apiGet('/course/progress');
+  progress(courseModules, structId) {
+    return this.apiGet('/course/progress', { courseModules, structId });
+  }
+
+  /**
+   * 获取预习作业列表
+   * @param {*} param0
+   */
+  listPreview({ page, size, isFinish, endTime }) {
+    return this.apiGet('/course/preview', { page, size, isFinish, endTime });
   }
 }
 

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

@@ -24,6 +24,20 @@ export default class MainStore extends BaseStore {
   }
 
   /**
+   * 获取长难句信息
+   */
+  getSentence() {
+    return this.apiGet('/base/sentence');
+  }
+
+  /**
+   * 获取心经信息
+   */
+  getExperience() {
+    return this.apiGet('/base/experience');
+  }
+
+  /**
    * 获取考分排行信息
    */
   getScore(total, quant) {

+ 15 - 3
front/project/www/stores/question.js

@@ -2,15 +2,19 @@ import BaseStore from '@src/stores/base';
 
 export default class QuestionStore extends BaseStore {
   startLink(type, item) {
-    linkTo(`/paper/process/${type}/${item.id}`);
+    openLink(`/paper/process/${type}/${item.id}`);
   }
 
   continueLink(type, item) {
-    linkTo(`/paper/process/${type}/${item.id}?r=${item.report.id}`);
+    // const w = window.open('about:blank');
+    // w.location.href = `/paper/process/${type}/${item.id}?r=${item.report.id}`;
+    openLink(`/paper/process/${type}/${item.id}?r=${item.report.id}`);
   }
 
   reportLink(item) {
-    linkTo(`/paper/report/${item.report.id}`);
+    // const w = window.open('about:blank');
+    // w.location.href = `/paper/report/${item.report.id}`;
+    openLink(`/paper/report/${item.report.id}`);
   }
 
   /**
@@ -135,6 +139,14 @@ export default class QuestionStore extends BaseStore {
   }
 
   /**
+   * 获取做题题目记录
+   * @param {*} userReportId
+   */
+  questionReport(userReportId) {
+    return this.apiGet('/question/report/detail', { userReportId });
+  }
+
+  /**
    * 开始考试
    * @param {*} type
    * @param {*} paperId

+ 25 - 23
front/project/www/stores/user.js

@@ -27,20 +27,28 @@ export default class UserStore extends BaseStore {
   }
 
   needLogin() {
-    // if (this.state.login) {
-    //   return Promise.resolve();
-    // }
-    return new Promise(resolve => {
-      this.loginCB = resolve;
+    if (this.state.login) {
+      return Promise.resolve();
+    }
+    return new Promise((resolve, reject) => {
+      this.successCB = resolve;
+      this.failCB = reject;
       this.setState({ needLogin: true });
     });
   }
 
-  closeLogin() {
-    this.setState({ needLogin: false });
+  closeLogin(err) {
+    this.setState({ needLogin: !!err });
+    if (err) {
+      if (this.failCB) this.failCB();
+    } else if (this.loginCB) this.loginCB();
     this.loginCB = null;
+    this.failCB = null;
   }
 
+  /**
+   * 验证token
+   */
   refreshToken() {
     return this.apiPost('/auth/token')
       .then(result => {
@@ -54,8 +62,6 @@ export default class UserStore extends BaseStore {
   infoHandle(result) {
     if (result.token) this.setToken(result.token);
     this.setState({ login: true, needLogin: false, info: result, username: result.username });
-    if (this.loginCB) this.loginCB();
-    this.loginCB = null;
   }
 
   originInviteCode(inviteCode) {
@@ -79,35 +85,31 @@ export default class UserStore extends BaseStore {
   }
 
   /**
-   * 验证token
-   */
-  token() {
-    return this.apiPost('/auth/token');
-  }
-
-  /**
    * 登陆
    * @param {*} mobile 手机号
    * @param {*} mobileVerifyCode 手机验证码
    * @param {*} inviteCode 邀请人手机/邀请码
    */
-  login(mobile, mobileVerifyCode, inviteCode) {
+  login(area, mobile, mobileVerifyCode, inviteCode) {
     if (!inviteCode) {
       ({ inviteCode } = this.state);
     }
-    return this.apiPost('/auth/login', { mobile, mobileVerifyCode, inviteCode });
+    return this.apiPost('/auth/login', { area, mobile, mobileVerifyCode, inviteCode }).then(result => {
+      this.infoHandle(result);
+      return result;
+    });
   }
 
   loginWechat(code) {
-    return this.apiGet('/auth/wechat_pc', { code }).then(() => {
-      this.setState({ login: true });
+    return this.apiGet('/auth/wechat_pc', { code }).then(result => {
+      this.infoHandle(result);
+      return result;
     });
   }
 
   /**
    * 登出
    */
-
   logout(login = true) {
     return Promise.resolve()
       .then(() => {
@@ -130,8 +132,8 @@ export default class UserStore extends BaseStore {
    * @param {*} mobileVerifyCode 手机验证码
    * @param {*} inviteCode 邀请人手机/邀请码
    */
-  bind(mobile, mobileVerifyCode, inviteCode) {
-    return this.apiPost('/auth/bind', { mobile, mobileVerifyCode, inviteCode });
+  bind(area, mobile, mobileVerifyCode, inviteCode) {
+    return this.apiPost('/auth/bind', { area, mobile, mobileVerifyCode, inviteCode });
   }
 
   /**

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

@@ -3,7 +3,7 @@ package com.qxgmat.data.constants.enums;
 public enum ServiceKey {
     VIP("vip", 0, 0), // 收藏和错题处的组卷、导出;笔记导出功能;部分解析只有VIP可以看;下载模考报告; 解锁完整版模考报告;“提问开放”期间有提问权限
     TEXTBOOK("textbook", 180, 30),
-    QX_CAT("qx_cat", 180, 0), // 可以考2次
+    QX_CAT("qx_cat", 180, 180), // 可以考2次
 
     ;
     public String key;

+ 2 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/SettingKey.java

@@ -28,6 +28,8 @@ public enum SettingKey {
     COURSE_INDEX("course_index"), // 课程首页设置
     PROMOTE("course_promote"), // 促销
     EXPERIENCE_INFO("experience_info"), // 心经信息
+    SENTENCE_INFO("sentence_info"), // 长难句信息
+    WECHAT_INFO("wechat_info"), // 微信公众号信息
 
     TIPS("tips"); // 页面提示信息
 

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

@@ -1,5 +1,7 @@
 package com.qxgmat.data.constants.enums.logic;
 
+import com.qxgmat.data.constants.enums.QuestionType;
+
 public enum TextbookLogic {
     DS("ds"), PS("ps"), DS_PS("ds+ps");
     public String key;
@@ -11,4 +13,19 @@ public enum TextbookLogic {
         if (name == null) return null;
         return TextbookLogic.valueOf(name.toUpperCase());
     }
+
+    public static TextbookLogic[] all(){
+        return new TextbookLogic[]{DS, PS, DS_PS};
+    }
+
+    public boolean contain(QuestionType questionType){
+        switch(this){
+            case DS:
+                return questionType == QuestionType.DS;
+            case PS:
+                return questionType == QuestionType.PS;
+            default:
+                return questionType == QuestionType.DS || questionType == QuestionType.PS;
+        }
+    }
 }

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

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

+ 23 - 23
server/data/src/main/java/com/qxgmat/data/dao/entity/Course.java

@@ -105,14 +105,14 @@ public class Course implements Serializable {
     /**
      * 1v1课时有效天数
      */
-    @Column(name = "`expire_days`")
-    private Integer expireDays;
+    @Column(name = "`expire_pre_days`")
+    private Integer expirePreDays;
 
     /**
      * 视频课程开通有效时长
      */
-    @Column(name = "`expire_time`")
-    private Integer expireTime;
+    @Column(name = "`expire_days`")
+    private Integer expireDays;
 
     /**
      * 使用有效时长
@@ -505,37 +505,37 @@ public class Course implements Serializable {
     /**
      * 获取1v1课时有效天数
      *
-     * @return expire_days - 1v1课时有效天数
+     * @return expire_pre_days - 1v1课时有效天数
      */
-    public Integer getExpireDays() {
-        return expireDays;
+    public Integer getExpirePreDays() {
+        return expirePreDays;
     }
 
     /**
      * 设置1v1课时有效天数
      *
-     * @param expireDays 1v1课时有效天数
+     * @param expirePreDays 1v1课时有效天数
      */
-    public void setExpireDays(Integer expireDays) {
-        this.expireDays = expireDays;
+    public void setExpirePreDays(Integer expirePreDays) {
+        this.expirePreDays = expirePreDays;
     }
 
     /**
      * 获取视频课程开通有效时长
      *
-     * @return expire_time - 视频课程开通有效时长
+     * @return expire_days - 视频课程开通有效时长
      */
-    public Integer getExpireTime() {
-        return expireTime;
+    public Integer getExpireDays() {
+        return expireDays;
     }
 
     /**
      * 设置视频课程开通有效时长
      *
-     * @param expireTime 视频课程开通有效时长
+     * @param expireDays 视频课程开通有效时长
      */
-    public void setExpireTime(Integer expireTime) {
-        this.expireTime = expireTime;
+    public void setExpireDays(Integer expireDays) {
+        this.expireDays = expireDays;
     }
 
     /**
@@ -876,8 +876,8 @@ public class Course implements Serializable {
         sb.append(", cover=").append(cover);
         sb.append(", minNumber=").append(minNumber);
         sb.append(", maxNumber=").append(maxNumber);
+        sb.append(", expirePreDays=").append(expirePreDays);
         sb.append(", expireDays=").append(expireDays);
-        sb.append(", expireTime=").append(expireTime);
         sb.append(", useExpireTime=").append(useExpireTime);
         sb.append(", wechatAvatar=").append(wechatAvatar);
         sb.append(", trailNumber=").append(trailNumber);
@@ -1092,20 +1092,20 @@ public class Course implements Serializable {
         /**
          * 设置1v1课时有效天数
          *
-         * @param expireDays 1v1课时有效天数
+         * @param expirePreDays 1v1课时有效天数
          */
-        public Builder expireDays(Integer expireDays) {
-            obj.setExpireDays(expireDays);
+        public Builder expirePreDays(Integer expirePreDays) {
+            obj.setExpirePreDays(expirePreDays);
             return this;
         }
 
         /**
          * 设置视频课程开通有效时长
          *
-         * @param expireTime 视频课程开通有效时长
+         * @param expireDays 视频课程开通有效时长
          */
-        public Builder expireTime(Integer expireTime) {
-            obj.setExpireTime(expireTime);
+        public Builder expireDays(Integer expireDays) {
+            obj.setExpireDays(expireDays);
             return this;
         }
 

+ 0 - 195
server/data/src/main/java/com/qxgmat/data/dao/entity/CourseStudentOnline.java

@@ -1,195 +0,0 @@
-package com.qxgmat.data.dao.entity;
-
-import java.io.Serializable;
-import java.util.Date;
-import javax.persistence.*;
-
-@Table(name = "course_student_online")
-public class CourseStudentOnline implements Serializable {
-    @Id
-    @Column(name = "`id`")
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    private Integer id;
-
-    /**
-     * 课程id
-     */
-    @Column(name = "`course_id`")
-    private Integer courseId;
-
-    /**
-     * 时间段
-     */
-    @Column(name = "`time_id`")
-    private Integer timeId;
-
-    /**
-     * 用户id
-     */
-    @Column(name = "`user_id`")
-    private Integer userId;
-
-    @Column(name = "`create_time`")
-    private Date createTime;
-
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * @return id
-     */
-    public Integer getId() {
-        return id;
-    }
-
-    /**
-     * @param id
-     */
-    public void setId(Integer id) {
-        this.id = id;
-    }
-
-    /**
-     * 获取课程id
-     *
-     * @return course_id - 课程id
-     */
-    public Integer getCourseId() {
-        return courseId;
-    }
-
-    /**
-     * 设置课程id
-     *
-     * @param courseId 课程id
-     */
-    public void setCourseId(Integer courseId) {
-        this.courseId = courseId;
-    }
-
-    /**
-     * 获取时间段
-     *
-     * @return time_id - 时间段
-     */
-    public Integer getTimeId() {
-        return timeId;
-    }
-
-    /**
-     * 设置时间段
-     *
-     * @param timeId 时间段
-     */
-    public void setTimeId(Integer timeId) {
-        this.timeId = timeId;
-    }
-
-    /**
-     * 获取用户id
-     *
-     * @return user_id - 用户id
-     */
-    public Integer getUserId() {
-        return userId;
-    }
-
-    /**
-     * 设置用户id
-     *
-     * @param userId 用户id
-     */
-    public void setUserId(Integer userId) {
-        this.userId = userId;
-    }
-
-    /**
-     * @return create_time
-     */
-    public Date getCreateTime() {
-        return createTime;
-    }
-
-    /**
-     * @param createTime
-     */
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(getClass().getSimpleName());
-        sb.append(" [");
-        sb.append("Hash = ").append(hashCode());
-        sb.append(", id=").append(id);
-        sb.append(", courseId=").append(courseId);
-        sb.append(", timeId=").append(timeId);
-        sb.append(", userId=").append(userId);
-        sb.append(", createTime=").append(createTime);
-        sb.append("]");
-        return sb.toString();
-    }
-
-    public static CourseStudentOnline.Builder builder() {
-        return new CourseStudentOnline.Builder();
-    }
-
-    public static class Builder {
-        private CourseStudentOnline obj;
-
-        public Builder() {
-            this.obj = new CourseStudentOnline();
-        }
-
-        /**
-         * @param id
-         */
-        public Builder id(Integer id) {
-            obj.setId(id);
-            return this;
-        }
-
-        /**
-         * 设置课程id
-         *
-         * @param courseId 课程id
-         */
-        public Builder courseId(Integer courseId) {
-            obj.setCourseId(courseId);
-            return this;
-        }
-
-        /**
-         * 设置时间段
-         *
-         * @param timeId 时间段
-         */
-        public Builder timeId(Integer timeId) {
-            obj.setTimeId(timeId);
-            return this;
-        }
-
-        /**
-         * 设置用户id
-         *
-         * @param userId 用户id
-         */
-        public Builder userId(Integer userId) {
-            obj.setUserId(userId);
-            return this;
-        }
-
-        /**
-         * @param createTime
-         */
-        public Builder createTime(Date createTime) {
-            obj.setCreateTime(createTime);
-            return this;
-        }
-
-        public CourseStudentOnline build() {
-            return this.obj;
-        }
-    }
-}

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

@@ -23,7 +23,7 @@ public class ExaminationPaper implements Serializable {
     private Integer structThree;
 
     /**
-     * 是否适应难度
+     * 是否适应难度: 0非适应性,1适应性,2千行
      */
     @Column(name = "`is_adapt`")
     private Integer isAdapt;
@@ -123,18 +123,18 @@ public class ExaminationPaper implements Serializable {
     }
 
     /**
-     * 获取是否适应难度
+     * 获取是否适应难度: 0非适应性,1适应性,2千行
      *
-     * @return is_adapt - 是否适应难度
+     * @return is_adapt - 是否适应难度: 0非适应性,1适应性,2千行
      */
     public Integer getIsAdapt() {
         return isAdapt;
     }
 
     /**
-     * 设置是否适应难度
+     * 设置是否适应难度: 0非适应性,1适应性,2千行
      *
-     * @param isAdapt 是否适应难度
+     * @param isAdapt 是否适应难度: 0非适应性,1适应性,2千行
      */
     public void setIsAdapt(Integer isAdapt) {
         this.isAdapt = isAdapt;
@@ -327,9 +327,9 @@ public class ExaminationPaper implements Serializable {
         }
 
         /**
-         * 设置是否适应难度
+         * 设置是否适应难度: 0非适应性,1适应性,2千行
          *
-         * @param isAdapt 是否适应难度
+         * @param isAdapt 是否适应难度: 0非适应性,1适应性,2千行
          */
         public Builder isAdapt(Integer isAdapt) {
             obj.setIsAdapt(isAdapt);

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

@@ -57,10 +57,22 @@ public class TextbookLibraryHistory implements Serializable {
     private Date createTime;
 
     /**
-     * 更新日志
+     * 数学更新日志
      */
-    @Column(name = "`content`")
-    private String content;
+    @Column(name = "`quant_contennt`")
+    private String quantContennt;
+
+    /**
+     * 阅读更新日志
+     */
+    @Column(name = "`rc_content`")
+    private String rcContent;
+
+    /**
+     * 综合推理更新日志
+     */
+    @Column(name = "`ir_content`")
+    private String irContent;
 
     private static final long serialVersionUID = 1L;
 
@@ -219,21 +231,57 @@ public class TextbookLibraryHistory implements Serializable {
     }
 
     /**
-     * 获取更新日志
+     * 获取数学更新日志
+     *
+     * @return quant_contennt - 数学更新日志
+     */
+    public String getQuantContennt() {
+        return quantContennt;
+    }
+
+    /**
+     * 设置数学更新日志
+     *
+     * @param quantContennt 数学更新日志
+     */
+    public void setQuantContennt(String quantContennt) {
+        this.quantContennt = quantContennt;
+    }
+
+    /**
+     * 获取阅读更新日志
+     *
+     * @return rc_content - 阅读更新日志
+     */
+    public String getRcContent() {
+        return rcContent;
+    }
+
+    /**
+     * 设置阅读更新日志
+     *
+     * @param rcContent 阅读更新日志
+     */
+    public void setRcContent(String rcContent) {
+        this.rcContent = rcContent;
+    }
+
+    /**
+     * 获取综合推理更新日志
      *
-     * @return content - 更新日志
+     * @return ir_content - 综合推理更新日志
      */
-    public String getContent() {
-        return content;
+    public String getIrContent() {
+        return irContent;
     }
 
     /**
-     * 设置更新日志
+     * 设置综合推理更新日志
      *
-     * @param content 更新日志
+     * @param irContent 综合推理更新日志
      */
-    public void setContent(String content) {
-        this.content = content;
+    public void setIrContent(String irContent) {
+        this.irContent = irContent;
     }
 
     @Override
@@ -251,7 +299,9 @@ public class TextbookLibraryHistory implements Serializable {
         sb.append(", ir=").append(ir);
         sb.append(", irVersion=").append(irVersion);
         sb.append(", createTime=").append(createTime);
-        sb.append(", content=").append(content);
+        sb.append(", quantContennt=").append(quantContennt);
+        sb.append(", rcContent=").append(rcContent);
+        sb.append(", irContent=").append(irContent);
         sb.append("]");
         return sb.toString();
     }
@@ -296,6 +346,16 @@ public class TextbookLibraryHistory implements Serializable {
         }
 
         /**
+         * 设置数学更新日志
+         *
+         * @param quantContennt 数学更新日志
+         */
+        public Builder quantContennt(String quantContennt) {
+            obj.setQuantContennt(quantContennt);
+            return this;
+        }
+
+        /**
          * 设置数学版本
          *
          * @param quantVersion 数学版本
@@ -316,6 +376,16 @@ public class TextbookLibraryHistory implements Serializable {
         }
 
         /**
+         * 设置阅读更新日志
+         *
+         * @param rcContent 阅读更新日志
+         */
+        public Builder rcContent(String rcContent) {
+            obj.setRcContent(rcContent);
+            return this;
+        }
+
+        /**
          * 设置阅读版本
          *
          * @param rcVersion 阅读版本
@@ -336,6 +406,16 @@ public class TextbookLibraryHistory implements Serializable {
         }
 
         /**
+         * 设置综合推理更新日志
+         *
+         * @param irContent 综合推理更新日志
+         */
+        public Builder irContent(String irContent) {
+            obj.setIrContent(irContent);
+            return this;
+        }
+
+        /**
          * 设置综合推理版本
          *
          * @param irVersion 综合推理版本
@@ -353,16 +433,6 @@ public class TextbookLibraryHistory implements Serializable {
             return this;
         }
 
-        /**
-         * 设置更新日志
-         *
-         * @param content 更新日志
-         */
-        public Builder content(String content) {
-            obj.setContent(content);
-            return this;
-        }
-
         public TextbookLibraryHistory build() {
             return this.obj;
         }

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

@@ -12,12 +12,6 @@ public class TextbookPaper implements Serializable {
     private Integer id;
 
     /**
-     * 所属时间
-     */
-    @Column(name = "`time`")
-    private String time;
-
-    /**
      * 标题
      */
     @Column(name = "`title`")
@@ -59,6 +53,12 @@ public class TextbookPaper implements Serializable {
     @Column(name = "`status`")
     private Integer status;
 
+    /**
+     * 所属年份
+     */
+    @Column(name = "`year`")
+    private String year;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -76,24 +76,6 @@ public class TextbookPaper implements Serializable {
     }
 
     /**
-     * 获取所属时间
-     *
-     * @return time - 所属时间
-     */
-    public String getTime() {
-        return time;
-    }
-
-    /**
-     * 设置所属时间
-     *
-     * @param time 所属时间
-     */
-    public void setTime(String time) {
-        this.time = time;
-    }
-
-    /**
      * 获取标题
      *
      * @return title - 标题
@@ -229,6 +211,24 @@ public class TextbookPaper implements Serializable {
         this.status = status;
     }
 
+    /**
+     * 获取所属年份
+     *
+     * @return year - 所属年份
+     */
+    public String getYear() {
+        return year;
+    }
+
+    /**
+     * 设置所属年份
+     *
+     * @param year 所属年份
+     */
+    public void setYear(String year) {
+        this.year = year;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -236,7 +236,6 @@ public class TextbookPaper implements Serializable {
         sb.append(" [");
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
-        sb.append(", time=").append(time);
         sb.append(", title=").append(title);
         sb.append(", logic=").append(logic);
         sb.append(", no=").append(no);
@@ -245,6 +244,7 @@ public class TextbookPaper implements Serializable {
         sb.append(", questionNumber=").append(questionNumber);
         sb.append(", createTime=").append(createTime);
         sb.append(", status=").append(status);
+        sb.append(", year=").append(year);
         sb.append("]");
         return sb.toString();
     }
@@ -269,16 +269,6 @@ public class TextbookPaper implements Serializable {
         }
 
         /**
-         * 设置所属时间
-         *
-         * @param time 所属时间
-         */
-        public Builder time(String time) {
-            obj.setTime(time);
-            return this;
-        }
-
-        /**
          * 设置标题
          *
          * @param title 标题
@@ -354,6 +344,16 @@ public class TextbookPaper implements Serializable {
             return this;
         }
 
+        /**
+         * 设置所属年份
+         *
+         * @param year 所属年份
+         */
+        public Builder year(String year) {
+            obj.setYear(year);
+            return this;
+        }
+
         public TextbookPaper build() {
             return this.obj;
         }

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

@@ -53,10 +53,10 @@ public class TextbookQuestion implements Serializable {
     private Integer totalCorrect;
 
     /**
-     * 所属时间
+     * 所属年份
      */
-    @Column(name = "`time`")
-    private String time;
+    @Column(name = "`year`")
+    private String year;
 
     private static final long serialVersionUID = 1L;
 
@@ -201,21 +201,21 @@ public class TextbookQuestion implements Serializable {
     }
 
     /**
-     * 获取所属时间
+     * 获取所属年份
      *
-     * @return time - 所属时间
+     * @return year - 所属年份
      */
-    public String getTime() {
-        return time;
+    public String getYear() {
+        return year;
     }
 
     /**
-     * 设置所属时间
+     * 设置所属年份
      *
-     * @param time 所属时间
+     * @param year 所属年份
      */
-    public void setTime(String time) {
-        this.time = time;
+    public void setYear(String year) {
+        this.year = year;
     }
 
     @Override
@@ -232,7 +232,7 @@ public class TextbookQuestion implements Serializable {
         sb.append(", totalTime=").append(totalTime);
         sb.append(", totalNumber=").append(totalNumber);
         sb.append(", totalCorrect=").append(totalCorrect);
-        sb.append(", time=").append(time);
+        sb.append(", year=").append(year);
         sb.append("]");
         return sb.toString();
     }
@@ -327,12 +327,12 @@ public class TextbookQuestion implements Serializable {
         }
 
         /**
-         * 设置所属时间
+         * 设置所属年份
          *
-         * @param time 所属时间
+         * @param year 所属年份
          */
-        public Builder time(String time) {
-            obj.setTime(time);
+        public Builder year(String year) {
+            obj.setYear(year);
             return this;
         }
 

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

@@ -78,6 +78,12 @@ public class UserOrderRecord implements Serializable {
     private Integer vsNo;
 
     /**
+     * 小班课时间段
+     */
+    @Column(name = "`time_id`")
+    private Integer timeId;
+
+    /**
      * 回复时长
      */
     @Column(name = "`ask_time`")
@@ -379,6 +385,24 @@ public class UserOrderRecord implements Serializable {
     }
 
     /**
+     * 获取小班课时间段
+     *
+     * @return time_id - 小班课时间段
+     */
+    public Integer getTimeId() {
+        return timeId;
+    }
+
+    /**
+     * 设置小班课时间段
+     *
+     * @param timeId 小班课时间段
+     */
+    public void setTimeId(Integer timeId) {
+        this.timeId = timeId;
+    }
+
+    /**
      * 获取回复时长
      *
      * @return ask_time - 回复时长
@@ -662,6 +686,7 @@ public class UserOrderRecord implements Serializable {
         sb.append(", teacherId=").append(teacherId);
         sb.append(", number=").append(number);
         sb.append(", vsNo=").append(vsNo);
+        sb.append(", timeId=").append(timeId);
         sb.append(", askTime=").append(askTime);
         sb.append(", cctalkName=").append(cctalkName);
         sb.append(", isSubscribe=").append(isSubscribe);
@@ -811,6 +836,16 @@ public class UserOrderRecord implements Serializable {
         }
 
         /**
+         * 设置小班课时间段
+         *
+         * @param timeId 小班课时间段
+         */
+        public Builder timeId(Integer timeId) {
+            obj.setTimeId(timeId);
+            return this;
+        }
+
+        /**
          * 设置回复时长
          *
          * @param askTime 回复时长

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

@@ -36,12 +36,24 @@ public class UserPaper implements Serializable {
     private String paperOrigin;
 
     /**
+     * 模考:是否适应难度: 0非适应性,1适应性,2千行
+     */
+    @Column(name = "`is_adapt`")
+    private Integer isAdapt;
+
+    /**
      * 对应来源id
      */
     @Column(name = "`origin_id`")
     private Integer originId;
 
     /**
+     * 关联记录id
+     */
+    @Column(name = "`record_id`")
+    private Integer recordId;
+
+    /**
      * 题目编号id列表:json
      */
     @Column(name = "`question_no_ids`")
@@ -190,6 +202,24 @@ public class UserPaper implements Serializable {
     }
 
     /**
+     * 获取模考:是否适应难度: 0非适应性,1适应性,2千行
+     *
+     * @return is_adapt - 模考:是否适应难度: 0非适应性,1适应性,2千行
+     */
+    public Integer getIsAdapt() {
+        return isAdapt;
+    }
+
+    /**
+     * 设置模考:是否适应难度: 0非适应性,1适应性,2千行
+     *
+     * @param isAdapt 模考:是否适应难度: 0非适应性,1适应性,2千行
+     */
+    public void setIsAdapt(Integer isAdapt) {
+        this.isAdapt = isAdapt;
+    }
+
+    /**
      * 获取对应来源id
      *
      * @return origin_id - 对应来源id
@@ -208,6 +238,24 @@ public class UserPaper implements Serializable {
     }
 
     /**
+     * 获取关联记录id
+     *
+     * @return record_id - 关联记录id
+     */
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    /**
+     * 设置关联记录id
+     *
+     * @param recordId 关联记录id
+     */
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+
+    /**
      * 获取题目编号id列表:json
      *
      * @return question_no_ids - 题目编号id列表:json
@@ -398,7 +446,9 @@ public class UserPaper implements Serializable {
         sb.append(", title=").append(title);
         sb.append(", paperModule=").append(paperModule);
         sb.append(", paperOrigin=").append(paperOrigin);
+        sb.append(", isAdapt=").append(isAdapt);
         sb.append(", originId=").append(originId);
+        sb.append(", recordId=").append(recordId);
         sb.append(", questionNoIds=").append(questionNoIds);
         sb.append(", questionNumber=").append(questionNumber);
         sb.append(", times=").append(times);
@@ -473,6 +523,16 @@ public class UserPaper implements Serializable {
         }
 
         /**
+         * 设置模考:是否适应难度: 0非适应性,1适应性,2千行
+         *
+         * @param isAdapt 模考:是否适应难度: 0非适应性,1适应性,2千行
+         */
+        public Builder isAdapt(Integer isAdapt) {
+            obj.setIsAdapt(isAdapt);
+            return this;
+        }
+
+        /**
          * 设置对应来源id
          *
          * @param originId 对应来源id
@@ -483,6 +543,16 @@ public class UserPaper implements Serializable {
         }
 
         /**
+         * 设置关联记录id
+         *
+         * @param recordId 关联记录id
+         */
+        public Builder recordId(Integer recordId) {
+            obj.setRecordId(recordId);
+            return this;
+        }
+
+        /**
          * 设置题目编号id列表:json
          *
          * @param questionNoIds 题目编号id列表:json

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

@@ -105,6 +105,12 @@ public class UserReport implements Serializable {
     @Column(name = "`is_finish`")
     private Integer isFinish;
 
+    /**
+     * 是否完成报告
+     */
+    @Column(name = "`is_stat`")
+    private Integer isStat;
+
     @Column(name = "`create_time`")
     private Date createTime;
 
@@ -412,6 +418,24 @@ public class UserReport implements Serializable {
     }
 
     /**
+     * 获取是否完成报告
+     *
+     * @return is_stat - 是否完成报告
+     */
+    public Integer getIsStat() {
+        return isStat;
+    }
+
+    /**
+     * 设置是否完成报告
+     *
+     * @param isStat 是否完成报告
+     */
+    public void setIsStat(Integer isStat) {
+        this.isStat = isStat;
+    }
+
+    /**
      * @return create_time
      */
     public Date getCreateTime() {
@@ -462,6 +486,7 @@ public class UserReport implements Serializable {
         sb.append(", score=").append(score);
         sb.append(", detail=").append(detail);
         sb.append(", isFinish=").append(isFinish);
+        sb.append(", isStat=").append(isStat);
         sb.append(", createTime=").append(createTime);
         sb.append(", updateTime=").append(updateTime);
         sb.append("]");
@@ -646,6 +671,16 @@ public class UserReport implements Serializable {
         }
 
         /**
+         * 设置是否完成报告
+         *
+         * @param isStat 是否完成报告
+         */
+        public Builder isStat(Integer isStat) {
+            obj.setIsStat(isStat);
+            return this;
+        }
+
+        /**
          * @param createTime
          */
         public Builder createTime(Date createTime) {

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

@@ -29,6 +29,12 @@ public class UserService implements Serializable {
     @Column(name = "`is_subscribe`")
     private Integer isSubscribe;
 
+    /**
+     * 模考是否重置
+     */
+    @Column(name = "`is_reset`")
+    private Integer isReset;
+
     @Column(name = "`start_time`")
     private Date startTime;
 
@@ -106,6 +112,24 @@ public class UserService implements Serializable {
     }
 
     /**
+     * 获取模考是否重置
+     *
+     * @return is_reset - 模考是否重置
+     */
+    public Integer getIsReset() {
+        return isReset;
+    }
+
+    /**
+     * 设置模考是否重置
+     *
+     * @param isReset 模考是否重置
+     */
+    public void setIsReset(Integer isReset) {
+        this.isReset = isReset;
+    }
+
+    /**
      * @return start_time
      */
     public Date getStartTime() {
@@ -143,6 +167,7 @@ public class UserService implements Serializable {
         sb.append(", userId=").append(userId);
         sb.append(", service=").append(service);
         sb.append(", isSubscribe=").append(isSubscribe);
+        sb.append(", isReset=").append(isReset);
         sb.append(", startTime=").append(startTime);
         sb.append(", expireTime=").append(expireTime);
         sb.append("]");
@@ -199,6 +224,16 @@ public class UserService implements Serializable {
         }
 
         /**
+         * 设置模考是否重置
+         *
+         * @param isReset 模考是否重置
+         */
+        public Builder isReset(Integer isReset) {
+            obj.setIsReset(isReset);
+            return this;
+        }
+
+        /**
          * @param startTime
          */
         public Builder startTime(Date startTime) {

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

@@ -21,8 +21,8 @@
     <result column="cover" jdbcType="VARCHAR" property="cover" />
     <result column="min_number" jdbcType="INTEGER" property="minNumber" />
     <result column="max_number" jdbcType="INTEGER" property="maxNumber" />
+    <result column="expire_pre_days" jdbcType="INTEGER" property="expirePreDays" />
     <result column="expire_days" jdbcType="INTEGER" property="expireDays" />
-    <result column="expire_time" jdbcType="INTEGER" property="expireTime" />
     <result column="use_expire_time" jdbcType="INTEGER" property="useExpireTime" />
     <result column="wechat_avatar" jdbcType="VARCHAR" property="wechatAvatar" />
     <result column="trail_number" jdbcType="INTEGER" property="trailNumber" />
@@ -53,8 +53,8 @@
     -->
     `id`, `struct_id`, `parent_struct_id`, `course_module`, `no_number`, `vs_type`, `video_type`, 
     `extend`, `title`, `comment`, `crowd`, `price`, `teacher`, `cover`, `min_number`, 
-    `max_number`, `expire_days`, `expire_time`, `use_expire_time`, `wechat_avatar`, `trail_number`, 
-    `sale_number`, `package_sale_number`, `create_time`, `update_time`
+    `max_number`, `expire_pre_days`, `expire_days`, `use_expire_time`, `wechat_avatar`, 
+    `trail_number`, `sale_number`, `package_sale_number`, `create_time`, `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

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

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

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

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

@@ -6,7 +6,6 @@
       WARNING - @mbg.generated
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
-    <result column="time" jdbcType="VARCHAR" property="time" />
     <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="logic" jdbcType="VARCHAR" property="logic" />
     <result column="no" jdbcType="INTEGER" property="no" />
@@ -15,12 +14,13 @@
     <result column="question_number" jdbcType="INTEGER" property="questionNumber" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="status" jdbcType="INTEGER" property="status" />
+    <result column="year" jdbcType="VARCHAR" property="year" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `time`, `title`, `logic`, `no`, `library_id`, `question_no_ids`, `question_number`, 
-    `create_time`, `status`
+    `id`, `title`, `logic`, `no`, `library_id`, `question_no_ids`, `question_number`, 
+    `create_time`, `status`, `year`
   </sql>
 </mapper>

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

@@ -13,13 +13,13 @@
     <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="time" jdbcType="VARCHAR" property="time" />
+    <result column="year" jdbcType="VARCHAR" property="year" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
     `id`, `title`, `no`, `library_id`, `question_id`, `total_time`, `total_number`, `total_correct`, 
-    `time`
+    `year`
   </sql>
 </mapper>

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

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

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

@@ -10,7 +10,9 @@
     <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="paper_module" jdbcType="VARCHAR" property="paperModule" />
     <result column="paper_origin" jdbcType="VARCHAR" property="paperOrigin" />
+    <result column="is_adapt" jdbcType="INTEGER" property="isAdapt" />
     <result column="origin_id" jdbcType="INTEGER" property="originId" />
+    <result column="record_id" jdbcType="INTEGER" property="recordId" />
     <result column="question_no_ids" jdbcType="VARCHAR" property="questionNoIds" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler" />
     <result column="question_number" jdbcType="INTEGER" property="questionNumber" />
     <result column="times" jdbcType="INTEGER" property="times" />
@@ -26,8 +28,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `title`, `paper_module`, `paper_origin`, `origin_id`, `question_no_ids`, 
-    `question_number`, `times`, `time`, `latest_time`, `total_time`, `total_number`, 
-    `total_correct`, `delete_time`, `is_reset`
+    `id`, `user_id`, `title`, `paper_module`, `paper_origin`, `is_adapt`, `origin_id`, 
+    `record_id`, `question_no_ids`, `question_number`, `times`, `time`, `latest_time`, 
+    `total_time`, `total_number`, `total_correct`, `delete_time`, `is_reset`
   </sql>
 </mapper>

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

@@ -22,6 +22,7 @@
     <result column="score" jdbcType="VARCHAR" property="score" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler" />
     <result column="detail" jdbcType="VARCHAR" property="detail" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler" />
     <result column="is_finish" jdbcType="INTEGER" property="isFinish" />
+    <result column="is_stat" jdbcType="INTEGER" property="isStat" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
   </resultMap>
@@ -31,6 +32,6 @@
     -->
     `id`, `user_id`, `paper_id`, `paper_module`, `paper_origin`, `origin_id`, `question_no_ids`, 
     `question_number`, `time`, `user_number`, `user_time`, `user_correct`, `finish_time`, 
-    `setting`, `score`, `detail`, `is_finish`, `create_time`, `update_time`
+    `setting`, `score`, `detail`, `is_finish`, `is_stat`, `create_time`, `update_time`
   </sql>
 </mapper>

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

@@ -9,6 +9,7 @@
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="service" jdbcType="VARCHAR" property="service" />
     <result column="is_subscribe" jdbcType="INTEGER" property="isSubscribe" />
+    <result column="is_reset" jdbcType="INTEGER" property="isReset" />
     <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
     <result column="expire_time" jdbcType="TIMESTAMP" property="expireTime" />
   </resultMap>
@@ -16,6 +17,6 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `service`, `is_subscribe`, `start_time`, `expire_time`
+    `id`, `user_id`, `service`, `is_subscribe`, `is_reset`, `start_time`, `expire_time`
   </sql>
 </mapper>

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

@@ -0,0 +1,36 @@
+package com.qxgmat.data.relation;
+
+import com.qxgmat.data.dao.entity.ExercisePaper;
+import com.qxgmat.data.dao.entity.PreviewAssign;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Created by gaojie on 2017/11/9.
+ */
+public interface PreviewAssignRelationMapper {
+
+    List<PreviewAssign> listByCourse(
+            @Param("courseId") Number courseId,
+            @Param("userId") Number userId,
+            @Param("times") Integer times
+    );
+
+    List<PreviewAssign> listByAppointment(
+            @Param("courseId") Number courseId,
+            @Param("appointmentIds") Collection appointmentIds,
+            @Param("userId") Number userId,
+            @Param("endTime") String endTime,
+            @Param("times") Integer times
+    );
+
+    List<PreviewAssign> listByTime(
+            @Param("courseId") Number courseId,
+            @Param("timeId") Number timeId,
+            @Param("userId") Number userId,
+            @Param("endTime") String endTime,
+            @Param("times") Integer times
+    );
+}

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

@@ -0,0 +1,19 @@
+package com.qxgmat.data.relation;
+
+import com.qxgmat.data.dao.entity.ExercisePaper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * Created by gaojie on 2017/11/9.
+ */
+public interface TextbookPaperRelationMapper {
+
+    List<ExercisePaper> listWithUser(
+            @Param("libraryId") Number libraryId,
+            @Param("userId") Number userId,
+            @Param("logic") String logic,
+            @Param("times") Integer times
+    );
+}

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

@@ -1,20 +1,28 @@
 package com.qxgmat.data.relation;
 
-import com.qxgmat.data.dao.entity.UserReport;
-import com.qxgmat.data.relation.entity.UserCourseStatRelation;
+import com.qxgmat.data.relation.entity.UserModuleRecordStatRelation;
 import com.qxgmat.data.relation.entity.UserRankStatRelation;
-import com.qxgmat.data.relation.entity.UserReportLimitRelation;
-import com.qxgmat.data.relation.entity.UserStudyStatRelation;
+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<UserCourseStatRelation> statGroupType(
+    List<UserRecordStatRelation> stat(
+            @Param("userId") Integer userId,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime
+    );
+
+    List<UserRecordStatRelation> statAvg(
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime
+    );
+
+    List<UserModuleRecordStatRelation> statGroupType(
             @Param("userId") Integer userId,
             @Param("startTime") String startTime,
             @Param("endTime") String endTime

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

@@ -0,0 +1,36 @@
+package com.qxgmat.data.relation;
+
+import com.qxgmat.data.dao.entity.UserCollectQuestion;
+import com.qxgmat.data.dao.entity.UserOrderRecord;
+import com.qxgmat.data.relation.entity.CourseStudentNumberRelation;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Created by gaojie on 2017/11/9.
+ */
+public interface UserOrderRecordRelationMapper {
+
+    List<UserOrderRecord> listWithStudyAdmin(
+            @Param("courseModules") String[] courseModules,
+            @Param("structId") Integer structId,
+            @Param("courseId") Integer courseId,
+            @Param("userId") Integer userId,
+            @Param("order") String order,
+            @Param("direction") String direction
+    );
+
+    List<UserOrderRecord> listWithVs(
+            @Param("vsType") String vsType,
+            @Param("courseId") Integer courseId,
+            @Param("userId") Integer userId,
+            @Param("order") String order,
+            @Param("direction") String direction
+    );
+
+    List<CourseStudentNumberRelation> groupByTime(
+            @Param("ids") Collection ids
+    );
+}

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

@@ -13,11 +13,6 @@ import java.util.List;
  */
 public interface UserPaperRelationMapper {
 
-    List<UserPaper> listPreviewGroupTop(
-            @Param("userId") Number userId,
-            @Param("top") Number top
-    );
-
     void accumulation(
             @Param("id") Number userPaperId,
             @Param("number") Integer number,

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

@@ -0,0 +1,22 @@
+package com.qxgmat.data.relation;
+
+import com.qxgmat.data.relation.entity.UserRecordStatRelation;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * Created by gaojie on 2017/11/9.
+ */
+public interface UserQuestionRelationMapper {
+    List<UserRecordStatRelation> stat(
+            @Param("userId") Integer userId,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime
+    );
+
+    List<UserRecordStatRelation> statAvg(
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime
+    );
+}

+ 7 - 2
server/data/src/main/java/com/qxgmat/data/relation/UserSentenceRecordRelationMapper.java

@@ -1,6 +1,6 @@
 package com.qxgmat.data.relation;
 
-import com.qxgmat.data.relation.entity.UserSentenceStatRelation;
+import com.qxgmat.data.relation.entity.UserRecordStatRelation;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
@@ -9,9 +9,14 @@ import java.util.List;
  * Created by gaojie on 2017/11/9.
  */
 public interface UserSentenceRecordRelationMapper {
-    List<UserSentenceStatRelation> stat(
+    List<UserRecordStatRelation> stat(
             @Param("userId") Integer userId,
             @Param("startTime") String startTime,
             @Param("endTime") String endTime
     );
+
+    List<UserRecordStatRelation> statAvg(
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime
+    );
 }

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

@@ -2,7 +2,7 @@ package com.qxgmat.data.relation.entity;
 
 import javax.persistence.Column;
 
-public class UserCourseStatRelation {
+public class UserModuleRecordStatRelation {
     /**
      * 对应模块或题型
      */

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

@@ -2,7 +2,7 @@ package com.qxgmat.data.relation.entity;
 
 import javax.persistence.Column;
 
-public class UserSentenceStatRelation {
+public class UserRecordStatRelation {
     /**
      * 用户做题时间
      */

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

@@ -31,10 +31,10 @@
     </if>
     where 1
     <if test="structId != null">
-      and ep.`struct_three` = #{structId,jdbcType=VARCHAR} or ep.`struct_four` = #{structId,jdbcType=VARCHAR}
+      and (ep.`struct_three` = #{structId,jdbcType=VARCHAR} or ep.`struct_four` = #{structId,jdbcType=VARCHAR})
     </if>
     <if test="userId != null">
-      and up.`id` != null
+      and up.`id` &gt; 0
     </if>
   </select>
 </mapper>

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

@@ -43,7 +43,7 @@
     </if>
     where 1
     <if test="structId != null">
-      and ep.`struct_three` = #{structId,jdbcType=VARCHAR} or ep.`struct_four` = #{structId,jdbcType=VARCHAR}
+      and (ep.`struct_three` = #{structId,jdbcType=VARCHAR} or ep.`struct_four` = #{structId,jdbcType=VARCHAR})
     </if>
     <if test="userId != null">
       and up.`id` &gt; 0

+ 97 - 0
server/data/src/main/java/com/qxgmat/data/relation/mapping/PreviewAssignRelationMapper.xml

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qxgmat.data.relation.PreviewAssignRelationMapper">
+  <resultMap id="IdMap" type="com.qxgmat.data.dao.entity.PreviewAssign">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+  </resultMap>
+  <sql id="Id_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    ep.`id`
+  </sql>
+
+  <!--
+   视频课程作业列表
+  -->
+  <select id="listByCourse" resultMap="IdMap">
+    select
+    <include refid="Id_Column_List" />
+    from `preview_assign` pa
+    <if test="userId != null">
+    left join `user_paper` up on ep.`id` = up.`origin_id`
+      and up.`paper_origin` = 'preview'
+      and up.`user_id` = #{userId,jdbcType=VARCHAR}
+      <if test="times != null">
+        and up.`times` >= #{times,jdbcType=VARCHAR}
+      </if>
+    </if>
+    where 1
+    <if test="courseId != null">
+      and pa.`course_id` = #{courseId,jdbcType=VARCHAR}
+    </if>
+    <if test="userId != null">
+      and up.`id` &gt; 0
+    </if>
+  </select>
+
+  <!--
+   1v1课程作业列表
+  -->
+  <select id="listByAppointment" resultMap="IdMap">
+    select
+    <include refid="Id_Column_List" />
+    from `preview_assign` pa
+    <if test="userId != null">
+      left join `user_paper` up on ep.`id` = up.`origin_id`
+      and up.`paper_origin` = 'preview'
+      and up.`user_id` = #{userId,jdbcType=VARCHAR}
+      <if test="times != null">
+        and up.`times` >= #{times,jdbcType=VARCHAR}
+      </if>
+    </if>
+    where 1
+    <if test="courseId != null">
+      and pa.`course_id` = #{courseId,jdbcType=VARCHAR}
+    </if>
+    <if test="appointmentIds != null">
+      pa.`course_appointment` IN
+      <foreach collection="appointmentIds" item="item" index="index" open="(" close=")" separator=",">
+        #{item}
+      </foreach>
+    </if>
+    <if test="userId != null">
+      and up.`id` &gt; 0
+    </if>
+  </select>
+
+  <!--
+   小班课程作业列表
+  -->
+  <select id="listByAppointment" resultMap="IdMap">
+    select
+    <include refid="Id_Column_List" />
+    from `preview_assign` pa
+    <if test="userId != null">
+      left join `user_paper` up on ep.`id` = up.`origin_id`
+      and up.`paper_origin` = 'preview'
+      and up.`user_id` = #{userId,jdbcType=VARCHAR}
+      <if test="times != null">
+        and up.`times` >= #{times,jdbcType=VARCHAR}
+      </if>
+    </if>
+    where 1
+    <if test="courseId != null">
+      and pa.`course_id` = #{courseId,jdbcType=VARCHAR}
+    </if>
+    <if test="timeId != null">
+      and pa.`course_time` = #{timeId,jdbcType=VARCHAR}
+    </if>
+    <if test="userId != null">
+      and up.`id` &gt; 0
+    </if>
+  </select>
+</mapper>

+ 40 - 0
server/data/src/main/java/com/qxgmat/data/relation/mapping/TextbookPaperRelationMapper.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qxgmat.data.relation.TextbookPaperRelationMapper">
+  <resultMap id="IdMap" type="com.qxgmat.data.dao.entity.TextbookPaper">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+  </resultMap>
+  <sql id="Id_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    ep.`id`
+  </sql>
+
+  <!--
+    用户机经-练习册列表: 用户端
+  -->
+  <select id="listWithUser" resultMap="IdMap">
+    select
+    <include refid="Id_Column_List" />
+    from `textbook_paper` tp
+    <if test="userId != null">
+    left join `user_paper` up on ep.`id` = up.`origin_id`
+      and up.`paper_origin` = 'textbook'
+      and up.`user_id` = #{userId,jdbcType=VARCHAR}
+      <if test="times != null">
+        and up.`times` >= #{times,jdbcType=VARCHAR}
+      </if>
+    </if>
+    where 1
+    <if test="libraryId != null">
+      and tp.`library_id` = #{libraryId,jdbcType=VARCHAR}
+    </if>
+    <if test="userId != null">
+      and up.`id` &gt; 0
+    </if>
+  </select>
+</mapper>

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

@@ -35,7 +35,7 @@
     </if>
     where 1
     <if test="paperId != null">
-      and tp.`id` != null
+      and tp.`id` &gt; 0
     </if>
     <if test="questionNoId != null">
       and tq.`id` =#{questionNoId,jdbcType=VARCHAR}

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

@@ -7,7 +7,13 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
   </resultMap>
-  <resultMap id="studyMap" type="com.qxgmat.data.relation.entity.UserCourseStatRelation">
+  <resultMap id="studyMap" type="com.qxgmat.data.relation.entity.UserRecordStatRelation">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="user_time" jdbcType="INTEGER" property="user_time" />
+  </resultMap>
+  <resultMap id="studyModuleMap" type="com.qxgmat.data.relation.entity.UserModuleRecordStatRelation">
     <!--
       WARNING - @mbg.generated
     -->
@@ -29,6 +35,40 @@
   </sql>
 
   <!--
+    用户听课记录统计
+  -->
+  <select id="stat" resultMap="studyMap">
+    select
+    sum(ucr.`user_time`) as `user_time`
+    from `user_course_record` ucr
+    where
+    ucr.`user_id` = #{userId,jdbcType=VARCHAR}
+    <if test="startTime != null">
+      and ucr.`create_time` &gt; #{startTime,jdbcType=VARCHAR}
+    </if>
+    <if test="endTime != null">
+      and ucr.`create_time` &lt; #{endTime,jdbcType=VARCHAR}
+    </if>
+  </select>
+
+  <!--
+    全站听课记录统计
+  -->
+  <select id="statAvg" resultMap="studyMap">
+    select
+    sum(ucr.`user_time`) / count(distance(ucr.`user_id`)) as `user_time`
+    from `user_course_record` ucr
+    where
+    1
+    <if test="startTime != null">
+      and ucr.`create_time` &gt; #{startTime,jdbcType=VARCHAR}
+    </if>
+    <if test="endTime != null">
+      and ucr.`create_time` &lt; #{endTime,jdbcType=VARCHAR}
+    </if>
+  </select>
+
+  <!--
     用户听课记录统计,分题型
   -->
   <select id="statGroupType" resultMap="studyMap">

+ 88 - 0
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserOrderRecordRelationMapper.xml

@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qxgmat.data.relation.UserOrderRecordRelationMapper">
+  <resultMap id="IdMap" type="com.qxgmat.data.dao.entity.UserNoteQuestion">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+  </resultMap>
+  <resultMap id="NumberMap" type="com.qxgmat.data.relation.entity.CourseStudentNumberRelation">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+    <id column="number" jdbcType="INTEGER" property="number" />
+  </resultMap>
+  <sql id="Id_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    up.`id`
+  </sql>
+
+  <!--
+    统计学生人数
+  -->
+  <select id="groupByTime" resultMap="NumberMap">
+    select
+    count(cs.`id`) as `number`, cs.`no` as `id`
+    from `user_order_record` uor
+    where
+    uor.`no` IN
+    <foreach collection="ids" item="item" index="index" open="(" close=")" separator=",">
+      #{item}
+    </foreach>
+  </select>
+
+  <!--
+    获取用户学习记录
+  -->
+  <select id="listWithStudyAdmin" resultMap="IdMap">
+    select
+    <include refid="Id_Column_List" />
+    from `user_order_record` uor
+    left join `course` c on c.`id` = uor.`product_id` and uor.`product_type` = 'course'
+    <if test="courseModules != null">
+      c.`course_module` IN
+      <foreach collection="courseModules" item="item" index="index" open="(" close=")" separator=",">
+        #{item, jdbcType=VARCHAR}
+      </foreach>
+    </if>
+    <if test="structId != null">
+      and (c.`struct_id` = #{structId,jdbcType=VARCHAR} or c.`parent_struct_id` = #{structId,jdbcType=VARCHAR})
+    </if>
+    <if test="courseId != null">
+      and c.`id` = #{courseId,jdbcType=VARCHAR}
+    </if>
+    where
+    c.`id` &gt; 0
+    <if test="userId != null">
+      and uor.`user_id` = #{userId,jdbcType=VARCHAR}
+    </if>
+    order by ${order} ${direction}
+  </select>
+
+  <!--
+    获取用户VS学习记录
+  -->
+  <select id="listWithVs" resultMap="IdMap">
+    select
+    <include refid="Id_Column_List" />
+    from `user_order_record` uor
+    left join `course` c on c.`id` = uor.`product_id` and uor.`product_type` = 'course'
+    <if test="vsType != null">
+      and c.`vs_type` = #{vsType,jdbcType=VARCHAR}
+    </if>
+    <if test="courseId != null">
+      and c.`id` = #{courseId,jdbcType=VARCHAR}
+    </if>
+    where
+    c.`id` &gt; 0
+    <if test="userId != null">
+      and uor.`user_id` = #{userId,jdbcType=VARCHAR}
+    </if>
+    order by ${order} ${direction}
+  </select>
+
+</mapper>

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

@@ -27,25 +27,4 @@
     WHERE `id` = #{id, jdbcType=VARCHAR}
   </update>
 
-
-  <!--
-    用户预习作业Top列表: 用户端
-  -->
-  <select id="listPreviewGroupTop" resultMap="IdMap">
-    select
-    <include refid="Id_Column_List" />
-    from `user_paper` up
-    where
-    hp.`id` &gt; 0
-    and up.`module` = "preview"
-    <if test="userId != null">
-      and up.`user_id` = #{userId,jdbcType=VARCHAR}
-    </if>
-    and ( select count(up1.id)
-        from `user_paper` up1
-        where
-        up.`module_id` = up1.`module_id`
-        and up.`module` = "preview"
-        and up1.create_time &gt; up.create_time) &lt; ${top}
-  </select>
 </mapper>

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

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qxgmat.data.relation.UserQuestionRelationMapper">
+  <resultMap id="IdMap" type="com.qxgmat.data.dao.entity.UserQuestion">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+  </resultMap>
+  <resultMap id="studyMap" type="com.qxgmat.data.relation.entity.UserRecordStatRelation">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="user_time" jdbcType="INTEGER" property="user_time" />
+  </resultMap>
+  <sql id="Id_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    ur.`id`
+  </sql>
+
+  <!--
+    用户做题记录统计
+  -->
+  <select id="stat" resultMap="studyMap">
+    select
+    sum(uq.`user_time`) as `user_time`
+    from `user_question` uq
+    where
+    uq.`user_id` = #{userId,jdbcType=VARCHAR}
+    <if test="startTime != null">
+      and uq.`create_time` &gt; #{startTime,jdbcType=VARCHAR}
+    </if>
+    <if test="endTime != null">
+      and uq.`create_time` &lt; #{endTime,jdbcType=VARCHAR}
+    </if>
+  </select>
+
+  <!--
+    全站做题记录统计
+  -->
+  <select id="statAvg" resultMap="studyMap">
+    select
+    sum(uq.`user_time`) /count(distance(uq.`user_id`)) as `user_time`
+    from `user_question` uq
+    where
+    1
+    <if test="startTime != null">
+      and uq.`create_time` &gt; #{startTime,jdbcType=VARCHAR}
+    </if>
+    <if test="endTime != null">
+      and uq.`create_time` &lt; #{endTime,jdbcType=VARCHAR}
+    </if>
+  </select>
+</mapper>

+ 21 - 4
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserSentenceRecordRelationMapper.xml

@@ -7,7 +7,7 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
   </resultMap>
-  <resultMap id="studyMap" type="com.qxgmat.data.relation.entity.UserSentenceStatRelation">
+  <resultMap id="studyMap" type="com.qxgmat.data.relation.entity.UserRecordStatRelation">
     <!--
       WARNING - @mbg.generated
     -->
@@ -28,12 +28,29 @@
     sum(usr.`user_time`) as `user_time`
     from `user_sentence_record` usr
     where
-    ucr.`user_id` = #{userId,jdbcType=VARCHAR}
+    usr.`user_id` = #{userId,jdbcType=VARCHAR}
     <if test="startTime != null">
-      and ur.`update_time` &gt; #{startTime,jdbcType=VARCHAR}
+      and usr.`create_time` &gt; #{startTime,jdbcType=VARCHAR}
     </if>
     <if test="endTime != null">
-      and ur.`update_time` &lt; #{endTime,jdbcType=VARCHAR}
+      and usr.`create_time` &lt; #{endTime,jdbcType=VARCHAR}
+    </if>
+  </select>
+
+  <!--
+    全站阅读记录统计
+  -->
+  <select id="statAvg" resultMap="studyMap">
+    select
+    sum(usr.`user_time`) / count(distance(usr.`user_id`)) as `user_time`
+    from `user_sentence_record` usr
+    where
+    1
+    <if test="startTime != null">
+      and usr.`create_time` &gt; #{startTime,jdbcType=VARCHAR}
+    </if>
+    <if test="endTime != null">
+      and usr.`create_time` &lt; #{endTime,jdbcType=VARCHAR}
     </if>
   </select>
 </mapper>

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

@@ -2,12 +2,14 @@ package com.qxgmat.controller.admin;
 
 import com.github.pagehelper.Page;
 import com.nuliji.tools.*;
+import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.ExperienceDayRange;
 import com.qxgmat.data.constants.enums.ExperienceScoreRange;
 import com.qxgmat.data.constants.enums.module.CourseModule;
 import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.status.AskStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
+import com.qxgmat.data.constants.enums.trade.RecordSource;
 import com.qxgmat.data.constants.enums.user.DataType;
 import com.qxgmat.data.constants.enums.user.MoneyRange;
 import com.qxgmat.data.dao.entity.*;
@@ -65,7 +67,10 @@ public class CourseController {
     private CourseTimeService courseTimeService;
 
     @Autowired
-    private CourseStudentOnlineService courseStudentOnlineService;
+    private PreviewPaperService previewPaperService;
+
+    @Autowired
+    private PreviewAssignService previewAssignService;
 
     @Autowired
     private UserAskCourseService userAskCourseService;
@@ -415,6 +420,9 @@ public class CourseController {
         // 统计课时数
         Course course = courseService.get(in.getCourseId());
         courseService.edit(Course.builder().id(course.getId()).noNumber(course.getNoNumber() - 1).build());
+        // 删除对应预习作业关系
+        previewPaperService.removeCourseNo(course.getId(), in.getId());
+        previewAssignService.removeCourseNo(course.getId(), in.getId());
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -449,6 +457,8 @@ public class CourseController {
     public Response<Boolean> editTime(@RequestBody @Validated CourseTimeDto dto, HttpServletRequest request) {
         CourseTime entity = Transform.dtoToEntity(dto);
         entity = courseTimeService.edit(entity);
+        // 修改所有学员记录
+        userOrderRecordService.changeStudentTime(entity.getCourseId(), dto.getId(), entity.getStartTime(), entity.getEndTime());
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -456,7 +466,11 @@ public class CourseController {
     @RequestMapping(value = "/time/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "删除课时", httpMethod = "DELETE")
     public Response<Boolean> deleteTime(@RequestParam int id, HttpServletRequest request) {
+        CourseTime in = courseTimeService.get(id);
         courseTimeService.delete(id);
+        // 删除对应预习作业关系
+        previewAssignService.removeCourseTime(in.getCourseId(), in.getId());
+        // todo 删除对应学生关系
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -490,7 +504,7 @@ public class CourseController {
 
         // 绑定学生数量
         Collection timeIds = Transform.getIds(p, CourseTime.class, "id");
-        List<CourseStudentNumberRelation> relations = courseStudentOnlineService.groupByCourse(timeIds);
+        List<CourseStudentNumberRelation> relations = userOrderRecordService.groupByCourse(timeIds);
         Map map = Transform.getMap(relations, CourseStudentNumberRelation.class, "id", "number");
         Transform.combine(pr, map, CourseTimeInfoDto.class, "id", "studentNumber");
 
@@ -499,18 +513,31 @@ public class CourseController {
 
     @RequestMapping(value = "/student/online/add", method = RequestMethod.POST)
     @ApiOperation(value = "添加学员", httpMethod = "POST")
-    public Response<CourseStudentOnline> addStudentVideo(@RequestBody @Validated CourseStudentOnline entity, HttpServletRequest request) {
-//        CourseStudentOnline entity = Transform.dtoToEntity(dto);
-        entity = courseStudentOnlineService.addStudent(entity);
+    public Response<UserOrderRecord> addStudentOnline(@RequestBody @Validated CourseStudentOnlineDto dto, HttpServletRequest request) {
+        UserOrderRecord entity = Transform.dtoToEntity(dto);
+
+        CourseTime courseTime = courseTimeService.get(dto.getTimeId());
+        if (courseTime == null){
+            throw new ParameterException("时间段错误");
+        }
+        entity.setSource(RecordSource.BACKEND.key);
+        entity.setProductType(ProductType.COURSE.key);
+        entity.setTimeId(dto.getTimeId());
+        entity.setProductId(dto.getCourseId());
+        entity.setIsUsed(1);
+        entity.setUseTime(new Date());
+        entity.setStartTime(courseTime.getStartTime());
+        entity.setEndTime(courseTime.getEndTime());
+        entity = userOrderRecordService.addStudent(entity);
         managerLogService.log(request);
-        return ResponseHelp.success(Transform.convert(entity, CourseStudentOnline.class));
+        return ResponseHelp.success(Transform.convert(entity, UserOrderRecord.class));
     }
 
     @RequestMapping(value = "/student/online/edit", method = RequestMethod.PUT)
     @ApiOperation(value = "编辑学员", httpMethod = "PUT")
-    public Response<Boolean> editStudentVideo(@RequestBody @Validated CourseStudentOnline entity, HttpServletRequest request) {
-//        CourseStudentOnline entity = Transform.dtoToEntity(dto);
-        entity = courseStudentOnlineService.edit(entity);
+    public Response<Boolean> editStudentVideo(@RequestBody @Validated CourseStudentOnlineDto dto, HttpServletRequest request) {
+        UserOrderRecord entity = Transform.dtoToEntity(dto);
+        entity = userOrderRecordService.edit(entity);
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -518,16 +545,16 @@ public class CourseController {
     @RequestMapping(value = "/student/online/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "删除学员", httpMethod = "DELETE")
     public Response<Boolean> deleteStudentOnline(@RequestParam int id, HttpServletRequest request) {
-        courseTimeService.delete(id);
+        userOrderRecordService.delete(id);
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
 
     @RequestMapping(value = "/student/online/detail", method = RequestMethod.GET)
     @ApiOperation(value = "获取学员", httpMethod = "GET")
-    public Response<CourseStudentOnline> detailStudentVideo(@RequestParam int id,HttpSession session) {
-        CourseStudentOnline entity = courseStudentOnlineService.get(id);
-        return ResponseHelp.success(Transform.convert(entity, CourseStudentOnline.class));
+    public Response<UserOrderRecord> detailStudentVideo(@RequestParam int id,HttpSession session) {
+        UserOrderRecord entity = userOrderRecordService.get(id);
+        return ResponseHelp.success(Transform.convert(entity, UserOrderRecord.class));
     }
 
     @RequestMapping(value = "/student/online/list", method = RequestMethod.GET)
@@ -540,18 +567,18 @@ public class CourseController {
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<CourseStudentOnline> p = courseStudentOnlineService.listAdmin(page, size, courseId, timeId);
+        Page<UserOrderRecord> p = userOrderRecordService.listAdminByOnline(page, size, courseId, timeId);
         List<CourseStudentOnlineListDto> pr = Transform.convert(p, CourseStudentOnlineListDto.class);
 
         // 绑定用户
-        Collection userIds = Transform.getIds(p, CourseStudentOnline.class, "userId");
+        Collection userIds = Transform.getIds(p, UserOrderRecord.class, "userId");
         List<User> userList = usersService.select(userIds);
         Transform.combine(pr, userList, CourseStudentOnlineListDto.class, "userId", "user", User.class, "id", UserExtendDto.class);
 
         // 绑定时间段
-        Collection timeIds = Transform.getIds(p, CourseStudentOnline.class, "timeId");
+        Collection timeIds = Transform.getIds(p, UserOrderRecord.class, "no");
         List<CourseTime> timeList = courseTimeService.select(timeIds);
-        Transform.combine(pr, timeList, CourseStudentOnlineListDto.class, "timeId", "time", CourseTime.class, "id", CourseTimeExtendDto.class);
+        Transform.combine(pr, timeList, CourseStudentOnlineListDto.class, "no", "time", CourseTime.class, "id", CourseTimeExtendDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
@@ -637,7 +664,7 @@ public class CourseController {
         if (ids != null && ids.length > 0){
             p = userOrderRecordService.select(ids);
         }else{
-            p = userOrderRecordService.listWithStudyAdmin(page, size, CourseModule.ValueOf(courseModule), structId, courseId, userId, teacher);
+            p = userOrderRecordService.listWithStudyAdmin(page, size, new String[]{courseModule}, structId, courseId, userId, teacher, order, DirectionStatus.ValueOf(direction));
         }
         List<UserCourseStudyRecordInfoDto> pr = Transform.convert(p, UserCourseStudyRecordInfoDto.class);
 

+ 8 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/PreviewController.java

@@ -5,6 +5,7 @@ import com.nuliji.tools.PageMessage;
 import com.nuliji.tools.Response;
 import com.nuliji.tools.ResponseHelp;
 import com.nuliji.tools.Transform;
+import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.module.CourseModule;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.entity.Course;
@@ -60,6 +61,12 @@ public class PreviewController {
     @ApiOperation(value = "添加预习作业", httpMethod = "POST")
     public Response<PreviewPaper> add(@RequestBody @Validated PreviewPaperDto dto, HttpServletRequest request) {
         PreviewPaper entity = Transform.dtoToEntity(dto);
+        if (dto.getCourseNo() != null){
+            PreviewAssign assign = previewAssignService.getByCourseNo(dto.getCourseId(), dto.getCourseNo());
+            if (assign != null){
+                throw new ParameterException("该课时已经创建");
+            }
+        }
         previewPaperService.add(entity);
         managerLogService.log(request);
         return ResponseHelp.success(entity);
@@ -70,6 +77,7 @@ public class PreviewController {
     public Response<Boolean> edit(@RequestBody @Validated PreviewPaperDto dto, HttpServletRequest request) {
         PreviewPaper entity = Transform.dtoToEntity(dto);
         previewPaperService.edit(entity);
+        // 不允许编辑课时,如果允许,该处进行处理
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }

+ 34 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java

@@ -348,6 +348,40 @@ public class SettingController {
         return ResponseHelp.success(entity.getValue());
     }
 
+    @RequestMapping(value = "/sentence_info", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改长难句信息", httpMethod = "PUT")
+    private Response<Boolean> editSentenceInfo(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.SENTENCE_INFO);
+        entity.setValue(dto);
+        settingService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/sentence_info", method = RequestMethod.GET)
+    @ApiOperation(value = "获取长难句信息", httpMethod = "GET")
+    private Response<JSONObject> getSentenceInfo(){
+        Setting entity = settingService.getByKey(SettingKey.SENTENCE_INFO);
+
+        return ResponseHelp.success(entity.getValue());
+    }
+
+    @RequestMapping(value = "/wechat_info", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改长难句信息", httpMethod = "PUT")
+    private Response<Boolean> editWechatInfo(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.WECHAT_INFO);
+        entity.setValue(dto);
+        settingService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/wechat_info", method = RequestMethod.GET)
+    @ApiOperation(value = "获取长难句信息", httpMethod = "GET")
+    private Response<JSONObject> getWechatInfo(){
+        Setting entity = settingService.getByKey(SettingKey.WECHAT_INFO);
+
+        return ResponseHelp.success(entity.getValue());
+    }
+
     @RequestMapping(value = "/tips", method = RequestMethod.PUT)
     @ApiOperation(value = "修改结构说明", httpMethod = "PUT")
     private Response<Boolean> editTips(@RequestBody @Validated JSONObject dto){

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

@@ -92,6 +92,9 @@ public class UserController {
     private PreviewPaperService previewPaperService;
 
     @Autowired
+    private PreviewAssignService previewAssignService;
+
+    @Autowired
     private TextbookLibraryService textbookLibraryService;
 
     @Autowired
@@ -155,9 +158,9 @@ public class UserController {
         UserDetailDto dto = Transform.convert(entity, UserDetailDto.class);
         Integer time = 0;
 
-        time += courseExtendService.studyTime(id);
-        time += sentenceService.studyTime(id);
-        time += questionFlowService.studyTime(id);
+        time += courseExtendService.studyTime(id, null, null);
+        time += sentenceService.studyTime(id, null, null);
+        time += questionFlowService.studyTime(id, null, null);
         dto.setTotalTime(time);
 
         return ResponseHelp.success(dto);
@@ -555,7 +558,11 @@ public class UserController {
     @RequestMapping(value = "/course/appointment/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "删除课程预约", httpMethod = "DELETE")
     public Response<Boolean> deleteCourseAppointment(@RequestParam int id, HttpServletRequest request) {
-        userCourseAppointmentService.delete(id);
+        UserCourseAppointment in = userCourseAppointmentService.get(id);
+        // 调整课时序号
+        userCourseAppointmentService.deleteAppointment(id);
+        // 删除对应预习作业关系
+        previewAssignService.removeCourseAppointment(in.getCourseId(), in.getId());
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }

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

@@ -73,7 +73,7 @@ public class AuthController {
         }else{
             user = usersService.getUserByToken(token);
             // 用该token登录
-            shiroHelp.getSession().login(shiroHelp.user(user.getMobile(), ""));
+            shiroHelp.getSession().login(shiroHelp.user(user.getArea()+":"+user.getMobile(), ""));
         }
 
         User entity = usersService.get(user.getId());

+ 21 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java

@@ -87,6 +87,27 @@ public class BaseController {
         return ResponseHelp.success(map);
     }
 
+    @RequestMapping(value = "/sentence", method = RequestMethod.GET)
+    @ApiOperation(value = "获取长难句信息", notes = "获取长难句信息", httpMethod = "GET")
+    public Response<JSONObject> sentence()  {
+        Setting entity = settingService.getByKey(SettingKey.SENTENCE_INFO);
+        return ResponseHelp.success(entity.getValue());
+    }
+
+    @RequestMapping(value = "/experience", method = RequestMethod.GET)
+    @ApiOperation(value = "获取心经信息", notes = "获取心经信息", httpMethod = "GET")
+    public Response<JSONObject> experience()  {
+        Setting entity = settingService.getByKey(SettingKey.EXPERIENCE_INFO);
+        return ResponseHelp.success(entity.getValue());
+    }
+
+    @RequestMapping(value = "/wechat", method = RequestMethod.GET)
+    @ApiOperation(value = "获取心经信息", notes = "获取心经信息", httpMethod = "GET")
+    public Response<JSONObject> wechat()  {
+        Setting entity = settingService.getByKey(SettingKey.WECHAT_INFO);
+        return ResponseHelp.success(entity.getValue());
+    }
+
     @RequestMapping(value = "/score", method = RequestMethod.GET)
     @ApiOperation(value = "考分计算", notes = "获取考分排行信息", httpMethod = "GET")
     public Response<Rank> score(

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

@@ -3,13 +3,17 @@ package com.qxgmat.controller.api;
 
 import com.github.pagehelper.Page;
 import com.nuliji.tools.*;
+import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.ExperienceDayRange;
 import com.qxgmat.data.constants.enums.ExperienceScoreRange;
 import com.qxgmat.data.constants.enums.module.CourseModule;
+import com.qxgmat.data.constants.enums.module.VsCourseType;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.user.DataType;
 import com.qxgmat.data.dao.entity.*;
+import com.qxgmat.data.relation.entity.UserPreviewPaperRelation;
 import com.qxgmat.dto.extend.CourseExtendDto;
+import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
 import com.qxgmat.dto.response.*;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.extend.PreviewService;
@@ -20,9 +24,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpSession;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.stream.Collectors;
 
 @RestController
 @RequestMapping("/api/course")
@@ -102,6 +105,18 @@ public class CourseController {
         return ResponseHelp.success(course);
     }
 
+    @RequestMapping(value = "/simple", method = RequestMethod.GET)
+    @ApiOperation(value = "课程基本信息", httpMethod = "GET")
+    public Response<Course> simple(
+            @RequestParam(required = true) Integer courseId
+    ) {
+        User user = (User) shiroHelp.getLoginUser();
+
+        Course course = courseService.get(courseId);
+
+        return ResponseHelp.success(course);
+    }
+
     @RequestMapping(value = "/package/list", method = RequestMethod.GET)
     @ApiOperation(value = "套餐列表", httpMethod = "GET")
     public Response<PageMessage<CoursePackageListDto>> listPackage(
@@ -203,59 +218,87 @@ public class CourseController {
         return ResponseHelp.success(p, page, size, p.getTotal());
     }
 
-//    @RequestMapping(value = "/progress", method = RequestMethod.GET)
-//    @ApiOperation(value = "获取课程进度", notes = "获取所有课程及状态进度", httpMethod = "GET")
-//    public Response<List<UserCourseDetailDto>> progress()  {
-//        User user = (User) shiroHelp.getLoginUser();
-//        List<UserCourse> userCourseList = userOrderRecordService.getByUser(user.getId());
-//        List<UserCourseDetailDto> dtos = Transform.convert(userCourseList, UserCourseDetailDto.class);
-//
-//        // 获取每个科目的最后2次作业
-//        Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByCategory(user.getId(), 3);
-//        Transform.combine(dtos, previewMap, UserCourseDetailDto.class, "courseId", "previews", UserPreviewPaperExtendDto.class);
-//
-//        // 获取课程状态:已购买未开通
-//        List<UserOrderRecord> records = userOrderRecordService.listUnUse(user.getId(), ProductType.COURSE);
-//        Collection ids = Transform.getIds(userCourseList, UserCourse.class, "courseId");
-//        for(UserOrderRecord record : records){
-//            Integer courseId = record.getProductId();
-//            if (!ids.contains(courseId)){
-//                UserCourseDetailDto dto = new UserCourseDetailDto();
-//                dto.setCourseId(courseId);
-//                dto.setPayed(true);
-//                dtos.add(dto);
-//            }
-//        }
-//        // todo 区分完成状态
-//
-//        return ResponseHelp.success(dtos);
-//    }
-
-//    @RequestMapping(value = "/preview/list", method = RequestMethod.GET)
-//    @ApiOperation(value = "获取预习作业列表", notes = "获取预习作业列表", httpMethod = "GET")
-//    public Response<PageMessage<UserPreviewPaperExtendDto>> listPreview(
-//            @RequestParam(required = false, defaultValue = "1") int page,
-//            @RequestParam(required = false, defaultValue = "100") int size,
-//            @RequestParam(required = false) Number category,
-//            @RequestParam(required = false) String endTime,
-//            @RequestParam(required = false) Boolean finish
-//    )  {
-//        User user = (User) shiroHelp.getLoginUser();
-//        PageResult<UserPreviewPaperRelation> p = previewService.list(page, size, category, user.getId(), endTime, finish);
-//
-//        List<UserPreviewPaperExtendDto> pr = Transform.convert(p, UserPreviewPaperExtendDto.class);
-//
-//        // 获取试卷统计信息
-//        Map map = Transform.getMap(p, UserPreviewPaperRelation.class, "id", "paper");
-//        Map<Integer, Integer[]> questionNoIdsMap = new HashMap<>();
-//        for(Object value : map.keySet()){
-//            Integer key = (Integer) value;
-//            PreviewPaper preview = (PreviewPaper) map.get(key);
-//            questionNoIdsMap.put(key, preview.getQuestionNoIds());
-//        }
-//        Map statMap = questionNoService.statPaperMap(questionNoIdsMap);
-//        Transform.combine(pr, statMap, UserPreviewPaperExtendDto.class, "id", "stat");
-//
-//        return ResponseHelp.success(pr, page, size, p.getTotal());
-//    }
+    @RequestMapping(value = "/record", method = RequestMethod.GET)
+    @ApiOperation(value = "获取课程记录信息", notes = "获取所有课程及状态进度", httpMethod = "GET")
+    public Response<UserCourseDetailDto> record(
+            @RequestParam(required = true) Integer recordId
+    )  {
+        User user = (User) shiroHelp.getLoginUser();
+        UserOrderRecord userOrderRecord = userOrderRecordService.get(recordId);
+        if (userOrderRecord == null){
+            throw new ParameterException("记录不存在");
+        }
+        if (!userOrderRecord.getUserId().equals(user.getId())){
+            throw new ParameterException("记录不存在");
+        }
+        UserCourseDetailDto dto = Transform.convert(userOrderRecord, UserCourseDetailDto.class);
+        Course course = courseService.get(userOrderRecord.getProductId());
+        dto.setCourse(Transform.convert(course, CourseExtendDto.class));
+        return ResponseHelp.success(dto);
+    }
+
+    @RequestMapping(value = "/progress", method = RequestMethod.GET)
+    @ApiOperation(value = "获取课程进度", notes = "获取所有课程及状态进度", httpMethod = "GET")
+    public Response<List<UserCourseProgressDto>> progress(
+            @RequestParam(required = false) String courseModule,
+            @RequestParam(required = false) Integer structId,
+            @RequestParam(required = false) Integer courseId
+    )  {
+        User user = (User) shiroHelp.getLoginUser();
+        List<UserOrderRecord> userOrderRecordList;
+        CourseModule module = CourseModule.ValueOf(courseModule);
+        if (module == CourseModule.VIDEO){
+            // 视频课程包含:小班课程
+            userOrderRecordList = userOrderRecordService.listWithStudyAdmin(1, 1000, new String[]{CourseModule.VIDEO.key, CourseModule.ONLINE.key}, structId, courseId, user.getId(), null,null, null);
+        } else if (module == CourseModule.VS){
+            // 1v1课程:只有系统授课有作业
+            userOrderRecordList = userOrderRecordService.listWithVs(1, 1000, VsCourseType.COACH, courseId, user.getId(), null, null);
+        }else{
+            throw new ParameterException("课程类型错误");
+        }
+        List<UserCourseProgressDto> dtos = Transform.convert(userOrderRecordList, UserCourseProgressDto.class);
+
+        // 绑定课程
+        Collection courseIds = Transform.getIds(userOrderRecordList, UserOrderRecord.class, "productId");
+        List<Course> courseList = courseService.select(courseIds);
+        Transform.combine(dtos, courseList, UserCourseProgressDto.class, "productId", "course", Course.class, "id", CourseExtendDto.class);
+
+        // 获取每个科目的最后2次作业: 正在进行中的
+        Date now = new Date();
+        List<UserOrderRecord> processList = userOrderRecordList.stream().filter(row-> row.getIsUsed() > 0 && row.getUseEndTime().after(now)).collect(Collectors.toList());
+        Collection processIds = Transform.getIds(processList, UserOrderRecord.class, "id");
+        Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByCourseId(user.getId(), processIds, 2);
+        Transform.combine(dtos, previewMap, UserCourseProgressDto.class, "productId", "previews", UserPaperBaseExtendDto.class);
+
+        return ResponseHelp.success(dtos);
+    }
+
+    @RequestMapping(value = "/preview/list", method = RequestMethod.GET)
+    @ApiOperation(value = "获取预习作业列表", notes = "获取预习作业列表", httpMethod = "GET")
+    public Response<List<UserExercisePaperDto>> listPreview(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) Integer recordId,
+            @RequestParam(required = false) String endTime,
+            @RequestParam(required = false) Integer times
+    )  {
+        User user = (User) shiroHelp.getLoginUser();
+
+        List<UserPreviewPaperRelation> p = previewService.list(page, size, recordId, user.getId(), endTime, times);
+        List<UserExercisePaperDto> pr = Transform.convert(p, UserExercisePaperDto.class);
+
+        // 获取试卷统计信息
+        Map map = Transform.getMap(p, UserPreviewPaperRelation.class, "id", "paper");
+        Map<Integer, Integer[]> questionNoIdsMap = new HashMap<>();
+        for(Object value : map.keySet()){
+            Integer key = (Integer) value;
+            PreviewPaper preview = (PreviewPaper) map.get(key);
+            questionNoIdsMap.put(key, preview.getQuestionNoIds());
+        }
+        Map statMap = questionNoService.statPaperMap(questionNoIdsMap);
+        Transform.combine(pr, statMap, UserExercisePaperDto.class, "id", "stat");
+
+        return ResponseHelp.success(pr);
+    }
 }
+

+ 28 - 81
server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java

@@ -25,8 +25,10 @@ import com.qxgmat.help.AiHelp;
 import com.qxgmat.help.MailHelp;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.*;
+import com.qxgmat.service.extend.CourseExtendService;
 import com.qxgmat.service.extend.OrderFlowService;
 import com.qxgmat.service.extend.QuestionFlowService;
+import com.qxgmat.service.extend.SentenceService;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -147,6 +149,12 @@ public class MyController {
     private QuestionFlowService questionFlowService;
 
     @Autowired
+    private SentenceService sentenceService;
+
+    @Autowired
+    private CourseExtendService courseExtendService;
+
+    @Autowired
     private OrderFlowService orderFlowService;
 
     @RequestMapping(value = "/email", method = RequestMethod.POST)
@@ -459,92 +467,31 @@ public class MyController {
 
     @RequestMapping(value = "/study/week", method = RequestMethod.GET)
     @ApiOperation(value = "获取本周记录", notes = "获取本周学习记录", httpMethod = "GET")
-    public Response<UserStudyDetailDto> studyWeekTime()  {
+    public Response<UserStudyDetailDto> studyWeekTime(
+            @RequestParam(required = false) Integer week
+    )  {
         User user = (User) shiroHelp.getLoginUser();
         UserStudyDetailDto dto = new UserStudyDetailDto();
         dto.setCreateTime(user.getCreateTime());
         dto.setDays((int)((user.getCreateTime().getTime() - new Date().getTime()) / (1000*3600*24)));
-        Integer totalTime = 0;
-        Map<String, Integer> categoryMap = new HashMap<>();
-        // 按模块来源分组查询: module=> sentence, examination, collect+error, 忽略exercise,preview
-        List<UserStudyStatRelation> moduleList = userReportService.statGroupModule(user.getId());
-        for(UserStudyStatRelation module:moduleList){
-            // 练习时间过滤
-            if (module.getModule().equals(PaperModule.EXERCISE.key)){
-                continue;
-            }
-            Integer time = module.getUserTime();
-            String key = module.getModule();
-            totalTime += time;
-            // 收藏及错误组卷合并
-            if (module.getModule().equals(PaperOrigin.COLLECT.key)
-                    || module.getModule().equals(PaperOrigin.ERROR.key)){
-                key = "freedom";
-                time += categoryMap.getOrDefault(key, 0);
-            }else if (module.getModule().equals(PaperOrigin.PREVIEW.key)){
-                key = PaperOrigin.EXERCISE.key;
-            }
-            categoryMap.put(key, time);
-        }
-        // 按题型统计练习
-        List<UserStudyStatRelation> exerciseList = userReportService.statGroupExerciseType(user.getId(), null, null);
-        for(UserStudyStatRelation type:exerciseList){
-            totalTime += type.getUserTime();
-            categoryMap.put(type.getModule(), type.getUserTime());
-        }
-        // 按题型统计预习作业
-        List<UserStudyStatRelation> previewList = userReportService.statGroupExerciseType(user.getId(), null, null);
-        for(UserStudyStatRelation type:previewList){
-            totalTime += type.getUserTime();
-            categoryMap.put(type.getModule(), type.getUserTime());
-        }
-        // 按题型统计课程
-        List<UserCourseStatRelation> recordList = userCourseRecordService.statGroupType(user.getId(), null, null);
-        for (UserCourseStatRelation record : recordList){
-            totalTime += record.getUserTime();
-            // 累加同类型时间
-            Integer time = categoryMap.getOrDefault(record.getModule(), 0);
-            categoryMap.put(record.getModule(), time);
-        }
-        // 获取长难句阅读统计
-        UserSentenceStatRelation sentenceStatRelation = userSentenceRecordService.stat(user.getId(), null, null);
-        if (sentenceStatRelation != null){
-            Integer sentenceTime = categoryMap.getOrDefault(PaperModule.SENTENCE.key, 0);
-            categoryMap.put(PaperModule.SENTENCE.key, sentenceTime + sentenceStatRelation.getUserTime());
-        }
 
-        List<ExerciseStruct> p = exerciseStructService.main();
-        Map<String, String> m = new HashMap<>();
-        for (ExerciseStruct struct : p){
-            if (struct.getExtend() == null || struct.getExtend().isEmpty()) continue;
-            m.put(struct.getExtend(), struct.getTitleZh() + (struct.getTitleEn().isEmpty() ? "":" "+struct.getTitleEn()));
-        }
+        Date now = Tools.today();
+        int day = Tools.getDayOfWeek(now);
+        Date start = Tools.addDate(now, -1 * (day + week * 7));
+        Date end = Tools.addDate(start, 7);
 
-        // 组装数据
-        List<UserStudyExtendDto> categorys = new ArrayList<>();
-        if (categoryMap.containsKey(PaperModule.SENTENCE.key)) categorys.add(new UserStudyExtendDto(m.get(PaperModule.SENTENCE.key), categoryMap.get(PaperModule.SENTENCE.key)));
-        if (categoryMap.containsKey(QuestionType.SC.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.SC.key), categoryMap.get(QuestionType.SC.key)));
-        if (categoryMap.containsKey(QuestionType.RC.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.RC.key), categoryMap.get(QuestionType.RC.key)));
-        if (categoryMap.containsKey(QuestionType.CR.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.CR.key), categoryMap.get(QuestionType.CR.key)));
-        if (categoryMap.containsKey(QuestionType.PS.key)){
-            // 累加数学
-            Integer time = categoryMap.getOrDefault(QuestionSubject.QUANT.key, 0);
-            categoryMap.put(QuestionSubject.QUANT.key, time + categoryMap.get(QuestionType.PS.key));
-        }
-        if (categoryMap.containsKey(QuestionType.DS.key)){
-            // 累加数学
-            Integer time = categoryMap.getOrDefault(QuestionSubject.QUANT.key, 0);
-            categoryMap.put(QuestionSubject.QUANT.key, time + categoryMap.get(QuestionType.DS.key));
-        }
-        if (categoryMap.containsKey(QuestionSubject.QUANT.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionSubject.QUANT.key), categoryMap.get(QuestionSubject.QUANT.key)));
+        Integer time = 0;
+        time += courseExtendService.studyTime(user.getId(), start, end);
+        time += sentenceService.studyTime(user.getId(), start, end);
+        time += questionFlowService.studyTime(user.getId(), start, end);
+        dto.setTime(time);
 
-        if (categoryMap.containsKey(QuestionType.IR.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.IR.key), categoryMap.get(QuestionType.IR.key)));
-        if (categoryMap.containsKey(QuestionType.AWA.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.AWA.key), categoryMap.get(QuestionType.AWA.key)));
-        if (categoryMap.containsKey(PaperModule.EXAMINATION.key)) categorys.add(new UserStudyExtendDto("模考", categoryMap.get(PaperModule.EXAMINATION.key)));
-        if (categoryMap.containsKey("freedom")) categorys.add(new UserStudyExtendDto("自由组卷", categoryMap.get("freedom")));
+        Integer avgTime = 0;
+        avgTime += courseExtendService.studyAvgTime(start, end);
+        avgTime += sentenceService.studyAvgTime(start, end);
+        avgTime += questionFlowService.studyAvgTime(start, end);
+        dto.setAvgTime(avgTime);
 
-        dto.setTime(totalTime);
-        dto.setCategorys(categorys);
         return ResponseHelp.success(dto);
     }
 
@@ -590,15 +537,15 @@ public class MyController {
             categoryMap.put(type.getModule(), type.getUserTime());
         }
         // 按题型统计课程
-        List<UserCourseStatRelation> recordList = userCourseRecordService.statGroupType(user.getId(), null, null);
-        for (UserCourseStatRelation record : recordList){
+        List<UserModuleRecordStatRelation> recordList = userCourseRecordService.statGroupType(user.getId(), null, null);
+        for (UserModuleRecordStatRelation record : recordList){
             totalTime += record.getUserTime();
             // 累加同类型时间
             Integer time = categoryMap.getOrDefault(record.getModule(), 0);
             categoryMap.put(record.getModule(), time);
         }
         // 获取长难句阅读统计
-        UserSentenceStatRelation sentenceStatRelation = userSentenceRecordService.stat(user.getId(), null, null);
+        UserRecordStatRelation sentenceStatRelation = userSentenceRecordService.stat(user.getId(), null, null);
         if (sentenceStatRelation != null){
             Integer sentenceTime = categoryMap.getOrDefault(PaperModule.SENTENCE.key, 0);
             categoryMap.put(PaperModule.SENTENCE.key, sentenceTime + sentenceStatRelation.getUserTime());

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

@@ -2,9 +2,9 @@ package com.qxgmat.controller.api;
 
 
 import com.alibaba.fastjson.JSONObject;
-import com.github.pagehelper.Page;
 import com.nuliji.tools.*;
 import com.nuliji.tools.exception.ParameterException;
+import com.qxgmat.data.constants.enums.ServiceKey;
 import com.qxgmat.data.constants.enums.logic.ExerciseLogic;
 import com.qxgmat.data.constants.enums.module.PaperOrigin;
 import com.qxgmat.data.constants.enums.module.QuestionModule;
@@ -57,6 +57,9 @@ public class QuestionController {
     private ExaminationPaperService examinationPaperService;
 
     @Autowired
+    private ExaminationStructService examinationStructService;
+
+    @Autowired
     private ExaminationService examinationService;
 
     @Autowired
@@ -71,7 +74,6 @@ public class QuestionController {
     @Autowired
     private TextbookQuestionService textbookQuestionService;
 
-
     @Autowired
     private SentencePaperService sentencePaperService;
 
@@ -97,12 +99,18 @@ public class QuestionController {
     private UserOrderService userOrderService;
 
     @Autowired
+    private UserOrderRecordService userOrderRecordService;
+
+    @Autowired
     private UserReportService userReportService;
 
     @Autowired
     private UserPaperService userPaperService;
 
     @Autowired
+    private UserServiceService userServiceService;
+
+    @Autowired
     private QuestionFlowService questionFlowService;
 
 
@@ -142,6 +150,7 @@ public class QuestionController {
                     for(UserQuestionStat stat : userQuestionStatMap.values()){
                         if(stat.getUserNumber() > minTimes) userNumber += 1;
                     }
+                    dto.setMinTimes(minTimes);
                     dto.setUserNumber(userNumber);
                 }
             }
@@ -183,16 +192,16 @@ public class QuestionController {
                     childrenDtos.add(extendDto);
                 }
 
-                Collection ids = Transform.getIds(p, ExercisePaper.class, "id");
-                List<UserPaper> userPaperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.EXERCISE, ids);
+                Collection ids = Transform.getIds(paperList, ExercisePaper.class, "id");
+                List<UserPaper> userPaperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.EXERCISE, ids, null);
                 // 绑定userPaperId,用于关联report
                 Map userPaperMap = Transform.getMap(userPaperList, UserPaper.class, "originId", "id");
-                Transform.combine(childrenDtos, userPaperMap, UserSentencePaperDto.class, "id", "userPaperId");
+                Transform.combine(childrenDtos, userPaperMap, UserExerciseGroupExtendDto.class, "id", "userPaperId");
 
                 // 获取最后一次作业结果
-                Collection paperIds = Transform.getIds(paperList, ExercisePaper.class, "id");
+                Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
                 List<UserReport> reportList = userReportService.listWithLater(paperIds);
-                Transform.combine(childrenDtos, reportList, UserSentencePaperDto.class, "userPaperId", "report", UserReport.class, "paperId", UserReportExtendDto.class);
+                Transform.combine(childrenDtos, reportList, UserExerciseGroupExtendDto.class, "userPaperId", "report", UserReport.class, "paperId", UserReportExtendDto.class);
 
                 dto.setChildren(childrenDtos);
             }else{
@@ -270,7 +279,7 @@ public class QuestionController {
         if (user != null){
             // 获取做题记录
             Collection ids = Transform.getIds(p, ExercisePaper.class, "id");
-            List<UserPaper> paperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.EXERCISE, ids);
+            List<UserPaper> paperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.EXERCISE, ids, null);
             Transform.combine(pr, paperList, UserExercisePaperDto.class, "id", "paper", UserPaper.class, "originId", UserPaperBaseExtendDto.class);
             // 绑定userPaperId,用于关联report
             Map userPaperMap = Transform.getMap(paperList, UserPaper.class, "originId", "id");
@@ -288,15 +297,52 @@ public class QuestionController {
 
     @RequestMapping(value = "/examination/progress", method = RequestMethod.GET)
     @ApiOperation(value = "模考进度", httpMethod = "GET")
-    public Response<PageMessage<ExercisePaper>> examinationProgress(
-            @RequestParam(required = false, defaultValue = "1") int page,
-            @RequestParam(required = false, defaultValue = "100") int size,
+    public Response<List<UserExaminationGroupDto>> examinationProgress(
+            @RequestParam(required = true) Integer structId, // 第一层,查询第三层,以及第二层汇总
             HttpSession session) {
-        Page<ExercisePaper> p = null;
+        User user = (User) shiroHelp.getLoginUser();
+
+        List<ExaminationStruct> two = examinationStructService.children(structId, 0);
+        List<UserExaminationGroupDto> p = new ArrayList<>(two.size());
+        for(ExaminationStruct struct : two){
+            UserExaminationGroupDto dto = Transform.convert(struct, UserExaminationGroupDto.class);
+            ServiceKey serviceKey = ServiceKey.ValueOf(struct.getExtend());
+            dto.setHasService(true);
+            // 获取第三层节点
+            // 以下属的paper作为children
+            List<ExaminationPaper> paperList = examinationPaperService.listByTwo(struct.getId());
+            dto.setPaperNumber(paperList.size());
+            dto.setMinTimes(0);
+
+            if(user != null){
+                dto.setHasService(userServiceService.hasService(user.getId(), serviceKey));
+                if (serviceKey != null){
+                    // 服务, 判断对应服务状态
+                    UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), serviceKey);
+                    dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
+                }
+                Collection ids = Transform.getIds(paperList, ExaminationPaper.class, "id");
+                List<UserPaper> userPaperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.EXAMINATION, ids, null);
 
-        // todo
+                if (paperList.size() > userPaperList.size()){
+                    dto.setUserNumber(userPaperList.size());
+                    dto.setMinTimes(0);
+                }else{
+                    int minTimes = 0;
+                    // 统计最小轮的已做题数
+                    for(UserPaper userPaper : userPaperList){
+                        if(userPaper.getTimes() < minTimes || minTimes == 0) minTimes = userPaper.getTimes();
+                    }
+                    int userNumber = 0;
+                    for(UserPaper userPaper : userPaperList){
+                        if(userPaper.getTimes() > minTimes) userNumber += 1;
+                    }
+                    dto.setUserNumber(userNumber);
+                }
+            }
+        }
 
-        return ResponseHelp.success(p, page, size, p.getTotal());
+        return ResponseHelp.success(p);
     }
 
     @RequestMapping(value = "/examination/list", method = RequestMethod.GET)
@@ -315,7 +361,7 @@ public class QuestionController {
         if (user != null){
             // 获取做题记录
             Collection ids = Transform.getIds(p, ExaminationPaper.class, "id");
-            List<UserPaper> paperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.EXAMINATION, ids);
+            List<UserPaper> paperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.EXAMINATION, ids, null);
             Transform.combine(pr, paperList, UserExaminationPaperDto.class, "id", "paper", UserPaper.class, "originId", UserPaperBaseExtendDto.class);
             // 绑定userPaperId,用于关联report
             Map userPaperMap = Transform.getMap(paperList, UserPaper.class, "originId", "id");
@@ -701,14 +747,21 @@ public class QuestionController {
         return ResponseHelp.success(true);
     }
 
-    @RequestMapping(value = "/restart/examination", method = RequestMethod.POST)
+    @RequestMapping(value = "/reset/cat", method = RequestMethod.POST)
     @ApiOperation(value = "重置整套模拟卷", notes = "重置考试", httpMethod = "POST")
-    public Response<Boolean> restartExamination(@RequestBody @Validated ExaminationRestartDto dto)  {
+    public Response<Boolean> resetCat()  {
         User user = (User) shiroHelp.getLoginUser();
 
-        // todo 判断是否已经重置达到上限
+        UserService userService = userServiceService.getService(user.getId(), ServiceKey.QX_CAT);
+        if (userService == null){
+            throw new ParameterException("无重置权限");
+        }
+        if (userService.getIsReset() > 0){
+            throw new ParameterException("已重置,请再次购买服务");
+        }
         // reset当前考卷的所有状态
-        questionFlowService.restart(dto.getStructId(), user.getId());
+        examinationService.resetCat(user.getId(), false);
+        userServiceService.edit(UserService.builder().id(userService.getId()).isReset(1).build());
         return ResponseHelp.success(true);
     }
 }

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

@@ -114,11 +114,7 @@ public class SentenceController
     public Response<Boolean> active(@RequestBody @Validated UserSentenceCodeDto dto) {
         User user = (User) shiroHelp.getLoginUser();
         if (user == null) throw new AuthException("需要登录");
-
-        if (sentenceCodeService.isActive(user.getId()) == null){
-            sentenceCodeService.active(user.getId(), dto.getCode());
-        }
-
+        sentenceService.active(user.getId(), dto.getCode());
         return ResponseHelp.success(true);
     }
 
@@ -220,7 +216,7 @@ public class SentenceController
         if (user != null){
             // 获取做题记录
             Collection ids = Transform.getIds(p, SentencePaper.class, "id");
-            List<UserPaper> userPaperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.SENTENCE, ids);
+            List<UserPaper> userPaperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.SENTENCE, ids, null);
             Transform.combine(pr, userPaperList, UserSentencePaperDto.class, "id", "paper", UserPaper.class, "originId", UserPaperBaseExtendDto.class);
             // 绑定userPaperId,用于关联report
             Map userPaperMap = Transform.getMap(userPaperList, UserPaper.class, "originId", "id");

+ 163 - 11
server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java

@@ -1,21 +1,28 @@
 package com.qxgmat.controller.api;
 
 import com.github.pagehelper.Page;
-import com.nuliji.tools.PageMessage;
-import com.nuliji.tools.Response;
-import com.nuliji.tools.ResponseHelp;
-import com.nuliji.tools.Tools;
+import com.nuliji.tools.*;
 import com.nuliji.tools.exception.AuthException;
 import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.QuestionSubject;
+import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.ServiceKey;
+import com.qxgmat.data.constants.enums.logic.TextbookLogic;
+import com.qxgmat.data.constants.enums.module.PaperOrigin;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
-import com.qxgmat.data.dao.entity.TextbookLibrary;
-import com.qxgmat.data.dao.entity.TextbookLibraryHistory;
-import com.qxgmat.data.dao.entity.TextbookTopic;
-import com.qxgmat.data.dao.entity.User;
+import com.qxgmat.data.dao.entity.*;
+import com.qxgmat.data.inline.UserQuestionStat;
+import com.qxgmat.data.relation.entity.TextbookQuestionRelation;
+import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
+import com.qxgmat.dto.extend.UserReportExtendDto;
+import com.qxgmat.dto.extend.UserServiceRecordExtendDto;
+import com.qxgmat.dto.extend.UserTextbookGroupExtendDto;
+import com.qxgmat.dto.response.UserTextbookGroupDto;
 import com.qxgmat.dto.response.UserTextbookInfoDto;
+import com.qxgmat.dto.response.UserTextbookPaperDto;
 import com.qxgmat.help.ShiroHelp;
+import com.qxgmat.service.UserPaperService;
+import com.qxgmat.service.UserQuestionService;
 import com.qxgmat.service.UserServiceService;
 import com.qxgmat.service.UsersService;
 import com.qxgmat.service.extend.QuestionFlowService;
@@ -28,8 +35,8 @@ import org.springframework.web.bind.annotation.*;
 import javax.servlet.http.HttpSession;
 import java.text.DateFormat;
 import java.text.ParseException;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
 
 @RestController
 @RequestMapping("/api/textbook")
@@ -49,6 +56,15 @@ public class TextbookController
     private UsersService usersService;
 
     @Autowired
+    private UserPaperService userPaperService;
+
+    @Autowired
+    private UserQuestionService userQuestionService;
+
+    @Autowired
+    private UserReportService userReportService;
+
+    @Autowired
     private UserServiceService userServiceService;
 
     @Autowired
@@ -58,6 +74,12 @@ public class TextbookController
     private QuestionFlowService questionFlowService;
 
     @Autowired
+    private TextbookPaperService textbookPaperService;
+
+    @Autowired
+    private TextbookQuestionService textbookQuestionService;
+
+    @Autowired
     private TextbookLibraryService textbookLibraryService;
 
     @Autowired
@@ -66,6 +88,82 @@ public class TextbookController
     @Autowired
     private TextbookTopicService textbookTopicService;
 
+    @RequestMapping(value = "/progress", method = RequestMethod.GET)
+    @ApiOperation(value = "练习进度", httpMethod = "GET")
+    public Response<List<UserTextbookGroupDto>> progress(HttpSession session) {
+        User user = (User) shiroHelp.getLoginUser();
+
+        TextbookLibrary latest = textbookLibraryService.getLatest();
+        TextbookLibrary second = textbookLibraryService.getSecond();
+        List<UserTextbookGroupDto> p = new ArrayList<>(2);
+
+        for(TextbookLibrary library : new ArrayList<TextbookLibrary>(2){{add(latest);add(second);}}){
+            UserTextbookGroupDto dto = Transform.convert(library, UserTextbookGroupDto.class);
+            // 获取第三层所有题目,并获取题目统计
+            List<TextbookQuestion> list = textbookQuestionService.listByLibrary(library.getId());
+            List<TextbookQuestionRelation> relations = textbookQuestionService.relation(list);
+            dto.setStat(textbookQuestionService.statPaper(list));
+            dto.setQuestionNumber(list.size());
+            Map<Object, UserQuestionStat> userQuestionStatMap = null;
+            if(user != null){
+                Collection questionNoIds = Transform.getIds(list, QuestionNo.class, "id");
+                List<UserQuestion> userQuestionList = userQuestionService.listByQuestionNo(user.getId(), questionNoIds);
+                userQuestionStatMap = userQuestionService.statQuestionNoMap(userQuestionList);
+
+                dto.setUserStat(userQuestionService.statQuestion(userQuestionList));
+
+                if (list.size() > userQuestionStatMap.size()){
+                    dto.setUserNumber(userQuestionStatMap.size());
+                    dto.setMinTimes(0);
+                }else{
+                    int minTimes = 0;
+                    // 统计最小轮的已做题数
+                    for(UserQuestionStat stat : userQuestionStatMap.values()){
+                        if(stat.getUserNumber() < minTimes || minTimes == 0) minTimes = stat.getUserNumber();
+                    }
+                    int userNumber = 0;
+                    for(UserQuestionStat stat : userQuestionStatMap.values()){
+                        if(stat.getUserNumber() > minTimes) userNumber += 1;
+                    }
+                    dto.setMinTimes(minTimes);
+                    dto.setUserNumber(userNumber);
+                }
+            }
+
+            List<UserTextbookGroupExtendDto> childrenDtos = new ArrayList<>(TextbookLogic.all().length);
+            for(TextbookLogic logic : TextbookLogic.all()){
+                UserTextbookGroupExtendDto extendDto = new UserTextbookGroupExtendDto();
+                extendDto.setLogic(logic.key);
+                List<TextbookQuestionRelation> childQuestionList = relations.stream().filter((q)-> logic.contain(QuestionType.ValueOf(q.getQuestion().getQuestionType()))).collect(Collectors.toList());
+                extendDto.setQuestionNumber(childQuestionList.size());
+                if (user != null){
+                    int minTimes = 0;
+                    int userQuestionNumber = 0;
+                    boolean flag = true;
+                    for(TextbookQuestion questionNo : childQuestionList){
+                        UserQuestionStat stat = userQuestionStatMap.get(questionNo.getId());
+                        if (stat == null) {
+                            flag = false;
+                            break;
+                        }
+                        if (stat.getUserNumber() < minTimes || minTimes == 0) minTimes = stat.getUserNumber();
+                    }
+                    if (!flag) minTimes = 0;
+                    for(TextbookQuestion questionNo : childQuestionList){
+                        UserQuestionStat stat = userQuestionStatMap.get(questionNo.getId());
+                        if (stat != null && stat.getUserNumber() > minTimes)  userQuestionNumber += 1;
+                    }
+                    extendDto.setUserNumber(userQuestionNumber);
+                    extendDto.setMinTimes(minTimes);
+                }
+                childrenDtos.add(extendDto);
+            }
+            dto.setChildren(childrenDtos);
+            p.add(dto);
+        }
+
+        return ResponseHelp.success(p);
+    }
 
     @RequestMapping(value = "/info", method = RequestMethod.GET)
     @ApiOperation(value = "机经信息", httpMethod = "GET")
@@ -77,7 +175,8 @@ public class TextbookController
         dto.setLatest(latest);
         if (user != null){
             dto.setHasService(userServiceService.hasService(user.getId(), ServiceKey.TEXTBOOK));
-            dto.setUnUseRecord(userOrderRecordService.getUnUseService(user.getId(), ServiceKey.TEXTBOOK));
+            UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), ServiceKey.TEXTBOOK);
+            dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
         }
         TextbookLibrary second = textbookLibraryService.getSecond();
         dto.setSecond(second);
@@ -154,4 +253,57 @@ public class TextbookController
 
         return ResponseHelp.success(p, page, size, p.getTotal());
     }
+
+
+    @RequestMapping(value = "/paper/list", method = RequestMethod.GET)
+    @ApiOperation(value = "机经组卷列表", httpMethod = "GET")
+    public Response<List<UserTextbookPaperDto>> listPaper(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = true) boolean latest,
+            @RequestParam(required = true) String logic,
+            @RequestParam(required = false)  Integer times,
+            HttpSession session) {
+        User user = (User) shiroHelp.getLoginUser();
+        TextbookLibrary library;
+        if (latest){
+            if (user == null){
+                throw new AuthException("请先登录");
+            }
+            if (!userServiceService.hasService(user.getId(), ServiceKey.TEXTBOOK)){
+                throw new ParameterException("没有机经查看权限");
+            }
+            library = textbookLibraryService.getLatest();
+        }else{
+            // 获取往期:倒数第二
+            library = textbookLibraryService.getSecond();
+        }
+        List<TextbookPaper> p = textbookPaperService.list(page, size, library.getId(), user != null ? user.getId():null, TextbookLogic.ValueOf(logic), times);
+        List<UserTextbookPaperDto> pr = Transform.convert(p, UserTextbookPaperDto.class);
+
+        // 获取试卷统计信息
+        Map<Integer, Integer[]> questionNoIdsMap = new HashMap<>();
+        for(TextbookPaper paper : p){
+            questionNoIdsMap.put(paper.getId(), paper.getQuestionNoIds());
+        }
+        Map statMap = textbookQuestionService.statPaperMap(questionNoIdsMap);
+        Transform.combine(pr, statMap, UserTextbookPaperDto.class, "id", "stat");
+
+        if (user != null){
+            // 获取做题记录
+            Collection ids = Transform.getIds(p, TextbookPaper.class, "id");
+            List<UserPaper> userPaperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.TEXTBOOK, ids, null);
+            Transform.combine(pr, userPaperList, UserTextbookPaperDto.class, "id", "paper", UserPaper.class, "originId", UserPaperBaseExtendDto.class);
+            // 绑定userPaperId,用于关联report
+            Map userPaperMap = Transform.getMap(userPaperList, UserPaper.class, "originId", "id");
+            Transform.combine(pr, userPaperMap, UserTextbookPaperDto.class, "id", "userPaperId");
+
+            // 获取最后一次作业结果
+            Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
+            List<UserReport> reportList = userReportService.listWithLater(paperIds);
+            Transform.combine(pr, reportList, UserTextbookPaperDto.class, "userPaperId", "report", UserReport.class, "paperId", UserReportExtendDto.class);
+        }
+
+        return ResponseHelp.success(pr);
+    }
 }

+ 0 - 5
server/gateway-api/src/main/java/com/qxgmat/controller/api/WechatController.java

@@ -1,5 +0,0 @@
-package com.qxgmat.controller.api;
-
-public class WechatController {
-
-}

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

@@ -0,0 +1,37 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserOrderRecord;
+
+@Dto(entity = UserOrderRecord.class)
+public class CourseStudentOnlineDto {
+    private Integer userId;
+
+    private Integer timeId;
+
+    private Integer courseId;
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public Integer getTimeId() {
+        return timeId;
+    }
+
+    public void setTimeId(Integer timeId) {
+        this.timeId = timeId;
+    }
+
+    public Integer getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Integer courseId) {
+        this.courseId = courseId;
+    }
+}

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

@@ -7,14 +7,18 @@ import com.qxgmat.data.dao.entity.TextbookLibraryHistory;
 public class TextbookLibraryHistoryDto {
     private Integer libraryId;
 
-    private String content;
-
     private String quant = "";
 
+    private String quantContent = "";
+
     private String ir = "";
 
+    private String irContent = "";
+
     private String rc = "";
 
+    private String rcContent = "";
+
     public Integer getLibraryId() {
         return libraryId;
     }
@@ -23,14 +27,6 @@ public class TextbookLibraryHistoryDto {
         this.libraryId = libraryId;
     }
 
-    public String getContent() {
-        return content;
-    }
-
-    public void setContent(String content) {
-        this.content = content;
-    }
-
     public String getQuant() {
         return quant;
     }
@@ -54,4 +50,28 @@ public class TextbookLibraryHistoryDto {
     public void setRc(String rc) {
         this.rc = rc;
     }
+
+    public String getQuantContent() {
+        return quantContent;
+    }
+
+    public void setQuantContent(String quantContent) {
+        this.quantContent = quantContent;
+    }
+
+    public String getIrContent() {
+        return irContent;
+    }
+
+    public void setIrContent(String irContent) {
+        this.irContent = irContent;
+    }
+
+    public String getRcContent() {
+        return rcContent;
+    }
+
+    public void setRcContent(String rcContent) {
+        this.rcContent = rcContent;
+    }
 }

+ 15 - 15
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/CourseStudentOnlineListDto.java

@@ -1,12 +1,12 @@
 package com.qxgmat.dto.admin.response;
 
 import com.nuliji.tools.annotation.Dto;
-import com.qxgmat.data.dao.entity.CourseStudentOnline;
+import com.qxgmat.data.dao.entity.UserOrderRecord;
 import com.qxgmat.dto.admin.extend.CourseTimeExtendDto;
 import com.qxgmat.dto.admin.extend.UserExtendDto;
 
 
-@Dto(entity = CourseStudentOnline.class)
+@Dto(entity = UserOrderRecord.class)
 public class CourseStudentOnlineListDto {
     private Integer id;
 
@@ -18,7 +18,7 @@ public class CourseStudentOnlineListDto {
 
     private CourseTimeExtendDto time;
 
-    private Integer courseId;
+    private Integer productId;
 
     public Integer getId() {
         return id;
@@ -44,27 +44,27 @@ public class CourseStudentOnlineListDto {
         this.user = user;
     }
 
-    public Integer getTimeId() {
-        return timeId;
+    public CourseTimeExtendDto getTime() {
+        return time;
     }
 
-    public void setTimeId(Integer timeId) {
-        this.timeId = timeId;
+    public void setTime(CourseTimeExtendDto time) {
+        this.time = time;
     }
 
-    public Integer getCourseId() {
-        return courseId;
+    public Integer getProductId() {
+        return productId;
     }
 
-    public void setCourseId(Integer courseId) {
-        this.courseId = courseId;
+    public void setProductId(Integer productId) {
+        this.productId = productId;
     }
 
-    public CourseTimeExtendDto getTime() {
-        return time;
+    public Integer getTimeId() {
+        return timeId;
     }
 
-    public void setTime(CourseTimeExtendDto time) {
-        this.time = time;
+    public void setTimeId(Integer timeId) {
+        this.timeId = timeId;
     }
 }

+ 140 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserCourseAppointmentExtendDto.java

@@ -0,0 +1,140 @@
+package com.qxgmat.dto.extend;
+
+import com.alibaba.fastjson.JSONArray;
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserCourseAppointment;
+
+import java.util.Date;
+
+@Dto(entity = UserCourseAppointment.class)
+public class UserCourseAppointmentExtendDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private Integer no;
+
+    private String title;
+
+    private Integer recordId;
+
+    private Integer courseId;
+
+    private String cctalkChannel;
+
+    private Date startTime;
+
+    private Date endTime;
+
+    private Integer paperId;
+
+    private Integer isFinish;
+
+    private JSONArray supplyList;
+
+    private JSONArray noteList;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public Integer getNo() {
+        return no;
+    }
+
+    public void setNo(Integer no) {
+        this.no = no;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+
+    public Integer getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Integer courseId) {
+        this.courseId = courseId;
+    }
+
+    public String getCctalkChannel() {
+        return cctalkChannel;
+    }
+
+    public void setCctalkChannel(String cctalkChannel) {
+        this.cctalkChannel = cctalkChannel;
+    }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    public Integer getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(Integer paperId) {
+        this.paperId = paperId;
+    }
+
+    public Integer getIsFinish() {
+        return isFinish;
+    }
+
+    public void setIsFinish(Integer isFinish) {
+        this.isFinish = isFinish;
+    }
+
+    public JSONArray getSupplyList() {
+        return supplyList;
+    }
+
+    public void setSupplyList(JSONArray supplyList) {
+        this.supplyList = supplyList;
+    }
+
+    public JSONArray getNoteList() {
+        return noteList;
+    }
+
+    public void setNoteList(JSONArray noteList) {
+        this.noteList = noteList;
+    }
+}

+ 109 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserServiceRecordExtendDto.java

@@ -0,0 +1,109 @@
+package com.qxgmat.dto.extend;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserOrderRecord;
+
+import java.util.Date;
+
+@Dto(entity = UserOrderRecord.class)
+public class UserServiceRecordExtendDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private Integer isSubscribe;
+
+    private Date startTime;
+
+    private Date endTime;
+
+    private Date useStartTime;
+
+    private Date useEndTime;
+
+    private Integer isUsed;
+
+    private Date useTime;
+
+    private Date createTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public Integer getIsSubscribe() {
+        return isSubscribe;
+    }
+
+    public void setIsSubscribe(Integer isSubscribe) {
+        this.isSubscribe = isSubscribe;
+    }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    public Date getUseStartTime() {
+        return useStartTime;
+    }
+
+    public void setUseStartTime(Date useStartTime) {
+        this.useStartTime = useStartTime;
+    }
+
+    public Date getUseEndTime() {
+        return useEndTime;
+    }
+
+    public void setUseEndTime(Date useEndTime) {
+        this.useEndTime = useEndTime;
+    }
+
+    public Integer getIsUsed() {
+        return isUsed;
+    }
+
+    public void setIsUsed(Integer isUsed) {
+        this.isUsed = isUsed;
+    }
+
+    public Date getUseTime() {
+        return useTime;
+    }
+
+    public void setUseTime(Date useTime) {
+        this.useTime = useTime;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+}

+ 73 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserTextbookGroupExtendDto.java

@@ -0,0 +1,73 @@
+package com.qxgmat.dto.extend;
+
+public class UserTextbookGroupExtendDto {
+    private String logic;
+
+    private String title;
+
+    private Integer questionNumber;
+
+    private Integer userNumber;
+
+    private Integer minTimes;
+
+    private Integer userPaperId;
+
+    private UserReportExtendDto report;
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Integer getQuestionNumber() {
+        return questionNumber;
+    }
+
+    public void setQuestionNumber(Integer questionNumber) {
+        this.questionNumber = questionNumber;
+    }
+
+    public Integer getUserNumber() {
+        return userNumber;
+    }
+
+    public void setUserNumber(Integer userNumber) {
+        this.userNumber = userNumber;
+    }
+
+    public Integer getMinTimes() {
+        return minTimes;
+    }
+
+    public void setMinTimes(Integer minTimes) {
+        this.minTimes = minTimes;
+    }
+
+    public UserReportExtendDto getReport() {
+        return report;
+    }
+
+    public void setReport(UserReportExtendDto report) {
+        this.report = report;
+    }
+
+    public Integer getUserPaperId() {
+        return userPaperId;
+    }
+
+    public void setUserPaperId(Integer userPaperId) {
+        this.userPaperId = userPaperId;
+    }
+
+    public String getLogic() {
+        return logic;
+    }
+
+    public void setLogic(String logic) {
+        this.logic = logic;
+    }
+}

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

@@ -7,6 +7,7 @@ import io.swagger.annotations.ApiModelProperty;
  * Created by GaoJie on 2017/11/1.
  */
 public class MyDto extends UserDto {
+    private Integer id;
 
     private String nickname;
 
@@ -130,4 +131,13 @@ public class MyDto extends UserDto {
     public void setVip(Boolean vip) {
         this.vip = vip;
     }
+
+    @Override
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
 }

+ 18 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/PaperBaseDto.java

@@ -11,6 +11,8 @@ public class PaperBaseDto {
     private String title;
     private Integer time;
     private Integer questionNumber;
+    private Integer times;
+    private Integer isAdapt;
 
     public Integer getQuestionNumber() {
         return questionNumber;
@@ -59,4 +61,20 @@ public class PaperBaseDto {
     public void setTime(Integer time) {
         this.time = time;
     }
+
+    public Integer getIsAdapt() {
+        return isAdapt;
+    }
+
+    public void setIsAdapt(Integer isAdapt) {
+        this.isAdapt = isAdapt;
+    }
+
+    public Integer getTimes() {
+        return times;
+    }
+
+    public void setTimes(Integer times) {
+        this.times = times;
+    }
 }

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

@@ -1,21 +1,38 @@
 package com.qxgmat.dto.response;
 
 import com.nuliji.tools.annotation.Dto;
-import com.qxgmat.data.dao.entity.UserCourse;
+import com.qxgmat.data.dao.entity.UserOrderRecord;
+import com.qxgmat.dto.extend.CourseExtendDto;
 import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
 
 import java.util.Date;
 import java.util.List;
 
-@Dto(entity = UserCourse.class)
+@Dto(entity = UserOrderRecord.class)
 public class UserCourseDetailDto {
-    private Integer courseId;
+    private Integer id;
+
+    private Integer productId;
+
+    private CourseExtendDto course;
 
     private Date startTime;
 
-    private Date expireTime;
+    private Date endTime;
+
+    private Date useStartTime;
+
+    private Date useEndTime;
+
+    private Integer isUse;
+
+    private Integer isStop;
 
-    private Boolean payed;
+    private Integer isSuspend;
+
+    private Date suspendTime;
+
+    private Date restoreTime;
 
     private List<UserPaperBaseExtendDto> papers;
 
@@ -27,35 +44,99 @@ public class UserCourseDetailDto {
         this.startTime = startTime;
     }
 
-    public Date getExpireTime() {
-        return expireTime;
+    public List<UserPaperBaseExtendDto> getPapers() {
+        return papers;
+    }
+
+    public void setPapers(List<UserPaperBaseExtendDto> papers) {
+        this.papers = papers;
     }
 
-    public void setExpireTime(Date expireTime) {
-        this.expireTime = expireTime;
+    public Integer getId() {
+        return id;
     }
 
-    public Boolean getPayed() {
-        return payed;
+    public void setId(Integer id) {
+        this.id = id;
     }
 
-    public void setPayed(Boolean payed) {
-        this.payed = payed;
+    public Integer getProductId() {
+        return productId;
     }
 
-    public List<UserPaperBaseExtendDto> getPapers() {
-        return papers;
+    public void setProductId(Integer productId) {
+        this.productId = productId;
     }
 
-    public void setPapers(List<UserPaperBaseExtendDto> papers) {
-        this.papers = papers;
+    public CourseExtendDto getCourse() {
+        return course;
+    }
+
+    public void setCourse(CourseExtendDto course) {
+        this.course = course;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    public Date getUseStartTime() {
+        return useStartTime;
+    }
+
+    public void setUseStartTime(Date useStartTime) {
+        this.useStartTime = useStartTime;
+    }
+
+    public Date getUseEndTime() {
+        return useEndTime;
+    }
+
+    public void setUseEndTime(Date useEndTime) {
+        this.useEndTime = useEndTime;
+    }
+
+    public Integer getIsUse() {
+        return isUse;
+    }
+
+    public void setIsUse(Integer isUse) {
+        this.isUse = isUse;
+    }
+
+    public Integer getIsStop() {
+        return isStop;
+    }
+
+    public void setIsStop(Integer isStop) {
+        this.isStop = isStop;
+    }
+
+    public Integer getIsSuspend() {
+        return isSuspend;
+    }
+
+    public void setIsSuspend(Integer isSuspend) {
+        this.isSuspend = isSuspend;
+    }
+
+    public Date getSuspendTime() {
+        return suspendTime;
+    }
+
+    public void setSuspendTime(Date suspendTime) {
+        this.suspendTime = suspendTime;
     }
 
-    public Integer getCourseId() {
-        return courseId;
+    public Date getRestoreTime() {
+        return restoreTime;
     }
 
-    public void setCourseId(Integer courseId) {
-        this.courseId = courseId;
+    public void setRestoreTime(Date restoreTime) {
+        this.restoreTime = restoreTime;
     }
 }

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


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