Browse Source

feat(front): 个人中心-工具

Go 5 years ago
parent
commit
280137bcb1
38 changed files with 1102 additions and 330 deletions
  1. 5 0
      front/project/Constant.js
  2. 66 0
      front/project/admin/routes/show/deploy/page.js
  3. 8 0
      front/project/admin/stores/system.js
  4. 0 1
      front/project/h5/routes/textbook/main/page.js
  5. 6 10
      front/project/www/components/Examination/index.js
  6. 2 2
      front/project/www/local.json
  7. 99 75
      front/project/www/routes/my/data/page.js
  8. 10 7
      front/project/www/routes/my/main/page.js
  9. 52 17
      front/project/www/routes/my/message/page.js
  10. 377 113
      front/project/www/routes/my/tools/page.js
  11. 28 4
      front/project/www/stores/my.js
  12. 3 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/QuestionSubject.java
  13. 2 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/SettingKey.java
  14. 26 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/TextbookSubject.java
  15. 26 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/CourseData.java
  16. 105 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookLibrary.java
  17. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseDataMapper.xml
  18. 6 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookLibraryMapper.xml
  19. 3 3
      server/data/src/main/java/com/qxgmat/data/relation/mapping/CourseDataHistoryRelationMapper.xml
  20. 3 3
      server/data/src/main/java/com/qxgmat/data/relation/mapping/CourseDataRelationMapper.xml
  21. 7 2
      server/data/src/main/resources/db/migration/V1__init_table.sql
  22. 17 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  23. 7 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/TextbookController.java
  24. 48 8
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  25. 1 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  26. 3 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java
  27. 13 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/DataSubscribeDto.java
  28. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/MyDto.java
  29. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserDataDto.java
  30. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserExaminationInfoDto.java
  31. 13 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserPrepareDetailDto.java
  32. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserTextbookInfoDto.java
  33. 47 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserVipInfoDto.java
  34. 0 2
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExaminationService.java
  35. 11 70
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  36. 45 5
      server/gateway-api/src/main/java/com/qxgmat/service/extend/TextbookService.java
  37. 10 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserTextbookEnrollService.java
  38. 1 0
      server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java

+ 5 - 0
front/project/Constant.js

@@ -143,6 +143,11 @@ export const MessageCustomStatus = [
   { label: '发送完', value: 2 },
   { label: '发送完', value: 2 },
 ];
 ];
 
 
+export const MessageType = [
+  { label: '系统消息', value: 'system' },
+  { label: '动态消息', value: 'feed' },
+];
+
 export const FaqChannel = [
 export const FaqChannel = [
   { label: 'GetReady', value: 'getready' },
   { label: 'GetReady', value: 'getready' },
   { label: '模考', value: 'examination' },
   { label: '模考', value: 'examination' },

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

@@ -22,6 +22,9 @@ export default class extends Page {
     if (tab === 'sentence') {
     if (tab === 'sentence') {
       return this.refreshSentence();
       return this.refreshSentence();
     }
     }
+    if (tab === 'prepare') {
+      return this.refreshPrepare();
+    }
     return Promise.reject();
     return Promise.reject();
   }
   }
 
 
@@ -33,6 +36,14 @@ export default class extends Page {
     });
     });
   }
   }
 
 
+  refreshPrepare() {
+    return System.getPrepareInfo().then(result => {
+      this.setState({ prepare: result || {} });
+      const { form } = this.props;
+      form.setFieldsValue(flattenObject(result, 'prepare'));
+    });
+  }
+
   changeMapValue(field, second, index, key, value) {
   changeMapValue(field, second, index, key, value) {
     const data = this.state[field] || {};
     const data = this.state[field] || {};
     data[second] = data[second] || [];
     data[second] = data[second] || [];
@@ -53,6 +64,9 @@ export default class extends Page {
     if (tab === 'sentence') {
     if (tab === 'sentence') {
       handler = this.submitSentence();
       handler = this.submitSentence();
     }
     }
+    if (tab === 'prepare') {
+      handler = this.submitPrepare();
+    }
     handler.then(() => {
     handler.then(() => {
       asyncSMessage('保存成功');
       asyncSMessage('保存成功');
     });
     });
@@ -77,6 +91,25 @@ export default class extends Page {
     });
     });
   }
   }
 
 
+  submitPrepare() {
+    const { form } = this.props;
+    return new Promise((resolve, reject) => {
+      form.validateFields(['prepare'], (err, values) => {
+        if (err) {
+          reject(err);
+        }
+        const data = values.prepare;
+        return System.setPrepareInfo(data)
+          .then(() => {
+            resolve();
+          }).catch((e) => {
+            form.setFields(formatFormError(data, e.result));
+            reject(e);
+          });
+      });
+    });
+  }
+
   renderSentence() {
   renderSentence() {
     const { getFieldDecorator } = this.props.form;
     const { getFieldDecorator } = this.props.form;
     return <Form>
     return <Form>
@@ -113,6 +146,36 @@ export default class extends Page {
     </Form>;
     </Form>;
   }
   }
 
 
+  renderPrepare() {
+    const { getFieldDecorator } = this.props.form;
+    return <Form>
+      <Row>
+        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='访问链接'>
+          {getFieldDecorator('prepare.link', {
+            rules: [
+              { required: true, message: '输入访问链接' },
+            ],
+          })(
+            <Input placeholder='请输入访问链接' onChange={(e) => {
+              this.changeValue('prepare', 'link', e.target.value);
+            }} style={{ width: '200px' }} />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='访问标题'>
+          {getFieldDecorator('prepare.title', {
+            rules: [
+              { required: true, message: '输入访问标题' },
+            ],
+          })(
+            <Input placeholder='请输入访问标题' onChange={(value) => {
+              this.changeValue('prepare', 'title', value);
+            }} style={{ width: '200px' }} />,
+          )}
+        </Form.Item>
+      </Row>
+    </Form>;
+  }
+
   renderView() {
   renderView() {
     const { tab } = this.state;
     const { tab } = this.state;
     return <Block><Tabs activeKey={tab} onChange={(value) => {
     return <Block><Tabs activeKey={tab} onChange={(value) => {
@@ -122,6 +185,9 @@ export default class extends Page {
       <Tabs.TabPane tab="长难句购买" key="sentence">
       <Tabs.TabPane tab="长难句购买" key="sentence">
         {this.renderSentence()}
         {this.renderSentence()}
       </Tabs.TabPane>
       </Tabs.TabPane>
+      <Tabs.TabPane tab="备考时间" key="prepare">
+        {this.renderPrepare()}
+      </Tabs.TabPane>
     </Tabs>
     </Tabs>
       <Row type="flex" justify="center">
       <Row type="flex" justify="center">
         <Col>
         <Col>

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

@@ -165,6 +165,14 @@ export default class SystemStore extends BaseStore {
     return this.apiPut('/setting/tips', params);
     return this.apiPut('/setting/tips', params);
   }
   }
 
 
+  getPrepareInfo() {
+    return this.apiGet('/setting/prepare_info');
+  }
+
+  setPrepareInfo(params) {
+    return this.apiPut('/setting/prepare_info', params);
+  }
+
   getSentenceInfo() {
   getSentenceInfo() {
     return this.apiGet('/setting/sentence_info');
     return this.apiGet('/setting/sentence_info');
   }
   }

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

@@ -25,7 +25,6 @@ export default class extends Page {
     Textbook.getInfo()
     Textbook.getInfo()
       .then(result => {
       .then(result => {
         result.day = parseInt((new Date().getTime() - new Date(result.latest.startDate).getTime()) / 86400000, 10);
         result.day = parseInt((new Date().getTime() - new Date(result.latest.startDate).getTime()) / 86400000, 10);
-        result.hasService = true;
         this.setState(result);
         this.setState(result);
       });
       });
     this.refreshTab(this.state.tab);
     this.refreshTab(this.state.tab);

+ 6 - 10
front/project/www/components/Examination/index.js

@@ -10,7 +10,6 @@ import TotalSort from '../TotalSort';
 import Date from '../Date';
 import Date from '../Date';
 import Ratio from '../Ratio';
 import Ratio from '../Ratio';
 import { My } from '../../stores/my';
 import { My } from '../../stores/my';
-import { Main } from '../../stores/main';
 import { PrepareStatus, PrepareExaminationTime, PrepareScoreTime } from '../../../Constant';
 import { PrepareStatus, PrepareExaminationTime, PrepareScoreTime } from '../../../Constant';
 
 
 const PrepareStatusMap = getMap(PrepareStatus, 'value', 'short');
 const PrepareStatusMap = getMap(PrepareStatus, 'value', 'short');
@@ -84,11 +83,8 @@ export default class extends Component {
         };
         };
         result.prepareGoal = result.prepareGoal || 650;
         result.prepareGoal = result.prepareGoal || 650;
         result.prepareScoreTime = result.prepareScoreTime ? moment(result.prepareScoreTime) : null;
         result.prepareScoreTime = result.prepareScoreTime ? moment(result.prepareScoreTime) : null;
-        this.setState({ data: result, stat, first: !result.prepareStatus, step: !result.prepareStatus ? 0 : 4 });
+        this.setState({ data: result, stat, first: !result.prepareStatus, step: !result.prepareStatus ? 0 : 4, info: result.info });
       });
       });
-    Main.getContract('register').then(() => {
-      this.setState({ link: { url: 'www', title: '了解出分时间信息>' } });
-    });
   }
   }
 
 
   onChange(type, key) {
   onChange(type, key) {
@@ -150,7 +146,7 @@ export default class extends Component {
           onChange={key => this.onChange('prepareStatus', key)}
           onChange={key => this.onChange('prepareStatus', key)}
         />
         />
         <div className="action-layout">
         <div className="action-layout">
-          <div className="next" onClick={() => this.onNext()}>
+          <div className="next" onClick={() => prepareStatus && this.onNext()}>
             下一题
             下一题
             <Icon type="right-circle" theme="filled" />
             <Icon type="right-circle" theme="filled" />
           </div>
           </div>
@@ -176,7 +172,7 @@ export default class extends Component {
             <Icon type="left-circle" theme="filled" />
             <Icon type="left-circle" theme="filled" />
             上一题
             上一题
           </div>
           </div>
-          <div className="next" onClick={() => this.onNext()}>
+          <div className="next" onClick={() => prepareExaminationTime && this.onNext()}>
             下一题
             下一题
             <Icon type="right-circle" theme="filled" />
             <Icon type="right-circle" theme="filled" />
           </div>
           </div>
@@ -201,7 +197,7 @@ export default class extends Component {
             <Icon type="left-circle" theme="filled" />
             <Icon type="left-circle" theme="filled" />
             上一题
             上一题
           </div>
           </div>
-          <div className="next" onClick={() => this.onNext()}>
+          <div className="next" onClick={() => prepareGoal && this.onNext()}>
             下一题
             下一题
             <Icon type="right-circle" theme="filled" />
             <Icon type="right-circle" theme="filled" />
           </div>
           </div>
@@ -211,11 +207,11 @@ export default class extends Component {
   }
   }
 
 
   renderStep3() {
   renderStep3() {
-    const { data, link, step } = this.state;
+    const { data, info, step } = this.state;
     const { prepareScoreTime } = data;
     const { prepareScoreTime } = data;
     return (
     return (
       <div className="step-3-layout">
       <div className="step-3-layout">
-        {link && <a href={link.url} className="a-l" target='_blank'>{link.title}</a>}
+        {info && <a href={info.link} className="a-l" target='_blank'>{info.title}</a>}
         <Date
         <Date
           show={step === 3}
           show={step === 3}
           theme="filled"
           theme="filled"

+ 2 - 2
front/project/www/local.json

@@ -8,7 +8,7 @@
     ],
     ],
     "proxy": [
     "proxy": [
       {
       {
-        "target": "http://qianxing.nuliji.com",
+        "target": "http://127.0.0.1:8888",
         "from": "/api",
         "from": "/api",
         "to": "/api"
         "to": "/api"
       }
       }
@@ -30,4 +30,4 @@
       "http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"
       "http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"
     ]
     ]
   }
   }
-}
+}

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

@@ -11,8 +11,12 @@ import UserAction from '../../../components/UserAction';
 import Select from '../../../components/Select';
 import Select from '../../../components/Select';
 import menu from '../index';
 import menu from '../index';
 import Tabs from '../../../components/Tabs';
 import Tabs from '../../../components/Tabs';
-// import { QuestionDifficult } from '../../../../Constant';
+import { My } from '../../../stores/my';
+import { QuestionDifficult } from '../../../../Constant';
+import { getMap, formatPercent, formatSeconds } from '../../../../../src/services/Tools';
 
 
+const QuestionDifficultMap = getMap(QuestionDifficult, 'value', 'label');
+console.log(QuestionDifficultMap);
 const columns = [
 const columns = [
   {
   {
     key: 'title',
     key: 'title',
@@ -24,11 +28,11 @@ const columns = [
   {
   {
     key: 'progress',
     key: 'progress',
     title: '进度',
     title: '进度',
-    render() {
+    render(text, record) {
       return (
       return (
         <div className="v">
         <div className="v">
-          <div className="t">50%</div>
+          <div className="t">{formatPercent(record.userQuestion, record.questionNumber, false)}</div>
-          <div className="d">已做20</div>
+          <div className="d">已做{record.userQuestion}</div>
         </div>
         </div>
       );
       );
     },
     },
@@ -36,11 +40,11 @@ const columns = [
   {
   {
     key: 'ratio',
     key: 'ratio',
     title: '正确率',
     title: '正确率',
-    render() {
+    render(text, record) {
       return (
       return (
         <div className="v">
         <div className="v">
-          <div className="t">100%</div>
+          <div className="t">{formatPercent(record.userCorrect, record.userNumber, false)}</div>
-          <div className="d">899/899</div>
+          <div className="d">{record.userCorrect}/{record.userNumber}</div>
         </div>
         </div>
       );
       );
     },
     },
@@ -49,6 +53,9 @@ const columns = [
     key: 'time',
     key: 'time',
     title: '平均用时',
     title: '平均用时',
     help: '',
     help: '',
+    render(text, record) {
+      return formatSeconds(record.userTime / record.userNumber);
+    },
   },
   },
 ];
 ];
 
 
@@ -121,7 +128,7 @@ function barOption1(avgTotal, avgCorrect, avgIncorrent) {
           label: {
           label: {
             show: true,
             show: true,
             position: 'top',
             position: 'top',
-            formatter: `{a|${avgTotal}}`,
+            formatter: `{a|${formatSeconds(avgTotal)}}`,
             rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
             rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
           },
           },
         },
         },
@@ -132,7 +139,7 @@ function barOption1(avgTotal, avgCorrect, avgIncorrent) {
           label: {
           label: {
             show: true,
             show: true,
             position: 'top',
             position: 'top',
-            formatter: `{a|${avgCorrect}}`,
+            formatter: `{a|${formatSeconds(avgCorrect)}}`,
             rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
             rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
           },
           },
         },
         },
@@ -143,7 +150,7 @@ function barOption1(avgTotal, avgCorrect, avgIncorrent) {
           label: {
           label: {
             show: true,
             show: true,
             position: 'top',
             position: 'top',
-            formatter: `{a|${avgIncorrent}}`,
+            formatter: `{a|${formatSeconds(avgIncorrent)}}`,
             rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
             rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
           },
           },
         },
         },
@@ -289,39 +296,60 @@ export default class extends Page {
   initState() {
   initState() {
     return {
     return {
       filterMap: {},
       filterMap: {},
-      data: [
-        { title: '语法SC', time: '1min20s' },
-        { title: '逻辑CR', time: '1min20s' },
-        { title: '阅读RC', time: '1min20s' },
-      ],
       selectList: [],
       selectList: [],
-      tab1: '1',
+      tab: 'exercise',
-      tab2: '1',
+      questionType: '',
-      tab3: '1',
+      info: 'base',
     };
     };
   }
   }
 
 
-  onTabChange(tab) {
+  initData() {
-    this.setState({ tab1: tab });
+    const data = Object.assign(this.state, this.state.search);
+    if (data.order) {
+      data.sortMap = { [data.order]: data.direction };
+    }
+    data.filterMap = this.state.search;
+    const startTime = null;
+    const endTime = null;
+    switch (data.timerange) {
+      case 'all':
+        break;
+      case 'week':
+        break;
+      case 'month':
+        break;
+      case 'month3':
+        break;
+      case 'today':
+      default:
+    }
+    My.getData(data.tab, data.subject, '', startTime, endTime).then(result => {
+      console.log(result);
+      this.data = result;
+      this.setState({ list: Object.values(result) });
+      this.onQuestionTypeChange();
+    });
   }
   }
 
 
-  onTabChange1(tab) {
+  onTabChange(tab) {
-    this.setState({ tab2: tab });
+    const data = { tab };
+    this.refreshQuery(data);
   }
   }
 
 
-  onTabChange2(tab) {
+  onQuestionTypeChange(questionType) {
-    this.setState({ tab3: tab });
+    const data = this.data[questionType];
+    this.setState({ questionType, data });
   }
   }
 
 
-  onFilter(value) {
+  onInfoChange(info) {
-    this.setState({ filterMap: value });
+    this.setState({ info });
   }
   }
 
 
-  onDataChange(page) {
+  onFilter(value) {
-    this.setState({ page, allChecked: false, selectList: [] });
+    this.search(value);
   }
   }
 
 
-  onAction() {}
+  onAction() { }
 
 
   onSelect(selectList) {
   onSelect(selectList) {
     this.setState({ selectList });
     this.setState({ selectList });
@@ -333,7 +361,7 @@ export default class extends Page {
   }
   }
 
 
   renderTable() {
   renderTable() {
-    const { tab1, tab2, tab3, filterMap = {}, data } = this.state;
+    const { tab, questionType, info, questionTypeSelect, filterMap = {}, list = [] } = this.state;
     return (
     return (
       <div className="table-layout">
       <div className="table-layout">
         <Tabs
         <Tabs
@@ -343,48 +371,42 @@ export default class extends Page {
           size="small"
           size="small"
           space={2.5}
           space={2.5}
           width={100}
           width={100}
-          active={tab1}
+          active={tab}
-          tabs={[{ key: '1', title: '练习' }, { key: '2', title: '模考' }]}
+          tabs={[{ key: 'exercise', title: '练习' }, { key: 'examination', title: '模考' }]}
           onChange={key => this.onTabChange(key)}
           onChange={key => this.onTabChange(key)}
         />
         />
         <UserAction
         <UserAction
-          search
+          selectList={[{
-          selectList={[
+            children: [
-            {
+              {
-              label: '123',
+                key: 'one',
-              children: [
+                select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
-                {
+              },
-                  key: 'one',
+              {
-                  default: '1',
+                key: 'two',
-                  select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+                be: 'one',
-                },
+                placeholder: '全部',
-                {
+                selectMap: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
-                  key: 'two',
+              },
-                  be: 'one',
+            ],
-                  placeholder: '全部',
+          }, {
-                  selectMap: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+            right: true,
-                },
+            key: 'timerange',
-              ],
+            select: [{ title: '今天', key: 'today' }, { title: '近一周', key: 'week' }, { title: '近一个月', key: 'month' }, { title: '近三个月', key: 'month3' }, { title: '全部', key: 'all' }],
-            },
+          }]}
-            {
-              label: '123',
-              right: true,
-              select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
-            },
-          ]}
           filterMap={filterMap}
           filterMap={filterMap}
           onFilter={value => this.onFilter(value)}
           onFilter={value => this.onFilter(value)}
         />
         />
         <div className="title">整体情况</div>
         <div className="title">整体情况</div>
-        <UserTable size="small" columns={columns} data={data} />
+        <UserTable size="small" columns={columns} data={list} />
         <div className="title">
         <div className="title">
           单项分析
           单项分析
           <Select
           <Select
             size="small"
             size="small"
             theme="default"
             theme="default"
-            value={tab2}
+            value={questionType}
-            list={[{ key: '1', title: '全部' }, { key: '2', title: '语法' }]}
+            list={questionTypeSelect}
-            onChange={({ key }) => this.onTabChange1(key)}
+            onChange={({ key }) => this.onQuestionTypeChange(key)}
           />
           />
         </div>
         </div>
         <Tabs
         <Tabs
@@ -393,69 +415,71 @@ export default class extends Page {
           theme="theme"
           theme="theme"
           size="small"
           size="small"
           width={80}
           width={80}
-          active={tab3}
+          active={info}
-          tabs={[{ key: '1', title: '基本情况' }, { key: '2', title: '难度分析' }, { key: '3', title: '考点分析' }]}
+          tabs={[{ key: 'base', title: '基本情况' }, { key: 'difficult', title: '难度分析' }, { key: 'place', title: '考点分析' }]}
-          onChange={key => this.onTabChange2(key)}
+          onChange={key => this.onInfoChange(key)}
         />
         />
-        {this[`renderTab${tab3}`]()}
+        {this[`renderTab${info}`]()}
       </div>
       </div>
     );
     );
   }
   }
 
 
-  renderTab1() {
+  renderTabbase() {
+    const { data = {} } = this.state;
     return (
     return (
       <div className="tab-1-layout">
       <div className="tab-1-layout">
         <div className="block">
         <div className="block">
           <div className="chart">
           <div className="chart">
-            <PieChart height={110} width={110} option={pieOption1(10, '123', '321')} />
+            <PieChart height={110} width={110} option={pieOption1(formatPercent(data.userQuestion, data.questionNumber), formatPercent(data.userQuestion, data.questionNumber, false), `全站${formatPercent(data.totalCorrect, data.totalNumber, false)}`)} />
           </div>
           </div>
           <div className="value">
           <div className="value">
-            <div className="total">200</div>
+            <div className="total">{data.questionNumber}</div>
             <div className="item">
             <div className="item">
               <div className="t">已做</div>
               <div className="t">已做</div>
               <div className="v">
               <div className="v">
-                <b>265</b>
+                <b>{data.userQuestion}</b>
               </div>
               </div>
             </div>
             </div>
             <div className="item">
             <div className="item">
               <div className="t">剩余</div>
               <div className="t">剩余</div>
               <div className="v">
               <div className="v">
-                <b>8</b>
+                <b>{data.questionNumber - data.userQuestion}</b>
               </div>
               </div>
             </div>
             </div>
             <div className="item">
             <div className="item">
               <div className="t">正确率</div>
               <div className="t">正确率</div>
               <div className="v">
               <div className="v">
-                <b>50%</b>
+                <b>{formatPercent(data.userCorrect, data.userNumber, false)}</b>
               </div>
               </div>
             </div>
             </div>
             <div className="item">
             <div className="item">
               <div className="t">全站</div>
               <div className="t">全站</div>
               <div className="v">
               <div className="v">
-                <b>23%</b>
+                <b>{formatPercent(data.totalCorrect, data.totalNumber, false)}</b>
               </div>
               </div>
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
         <div className="block">
         <div className="block">
-          <BarChart height={300} option={barOption1(10, 20, 30)} />
+          <BarChart height={300} option={barOption1(data.userNumber ? data.userTime / data.userNumber : 0, data.userCorrect ? data.correctTime / data.userCorrect : 0, data.userNumber - data.userCorrect ? data.incorrectTime / (data.userNumber - data.userCorrect) : 0)} />
         </div>
         </div>
       </div>
       </div>
     );
     );
   }
   }
 
 
-  renderTab2() {
+  renderTabdifficult() {
+    const { data = {} } = this.state;
     return (
     return (
       <div className="tab-2-layout">
       <div className="tab-2-layout">
         <BarChart
         <BarChart
           height={350}
           height={350}
-          option={barOption2('平均正确率80%', '正确率', [['easy', 10], ['Medium', 30], ['Hard', 40]])}
+          option={barOption2(`平均正确率${formatPercent(data.userCorrect, data.userNumber, false)}`, '正确率', [['easy', 10], ['Medium', 30], ['Hard', 40]])}
         />
         />
       </div>
       </div>
     );
     );
   }
   }
 
 
-  renderTab3() {
+  renderTabplace() {
     return (
     return (
       <div className="tab-3-layout">
       <div className="tab-3-layout">
         <BarChart
         <BarChart

+ 10 - 7
front/project/www/routes/my/main/page.js

@@ -1,12 +1,13 @@
 import React, { Component } from 'react';
 import React, { Component } from 'react';
 import { Link } from 'react-router-dom';
 import { Link } from 'react-router-dom';
 import './index.less';
 import './index.less';
-import { Tooltip, Icon, Calendar } from 'antd';
+import { Tooltip, Icon } from 'antd';
 import Page from '@src/containers/Page';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
 import Assets from '@src/components/Assets';
 import { formatPercent, formatDate, formatSeconds, getMap } from '@src/services/Tools';
 import { formatPercent, formatDate, formatSeconds, getMap } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
 import { asyncSMessage } from '@src/services/AsyncTools';
 import moment from 'moment';
 import moment from 'moment';
+import Date from '../../../components/Date';
 import UserLayout from '../../../layouts/User';
 import UserLayout from '../../../layouts/User';
 import Tabs from '../../../components/Tabs';
 import Tabs from '../../../components/Tabs';
 import Button from '../../../components/Button';
 import Button from '../../../components/Button';
@@ -335,7 +336,7 @@ export default class extends Page {
           return {
           return {
             type: QuestionTypeMap[row.extend],
             type: QuestionTypeMap[row.extend],
             title: `课时${row.no}: ${row.title}`,
             title: `课时${row.no}: ${row.title}`,
-            time: formatDate(row.createTime, 'YYYY-MM-DD HH:mm:ss'),
+            time: formatDate(row.createTime, 'YYYY-MM-DD\n HH:mm:ss'),
           };
           };
         }),
         }),
       });
       });
@@ -414,7 +415,6 @@ export default class extends Page {
             }}
             }}
           />
           />
           <div className="right">
           <div className="right">
-            {day === 'other' && formatDate(time, 'YYYY-MM-DD')}
             <Assets
             <Assets
               className="right"
               className="right"
               name="calendar"
               name="calendar"
@@ -422,12 +422,15 @@ export default class extends Page {
                 this.setState({ showCal: true });
                 this.setState({ showCal: true });
               }}
               }}
             />
             />
+            {day === 'other' && <span className="right">{formatDate(time, 'YYYY-MM-DD')}</span>}
             {showCal && (
             {showCal && (
-              <Calendar
+              <Date
-                className="cal"
+                show
-                fullscreen={false}
+                hideInput
+                theme="filled"
+                value={moment(time)}
                 disabledDate={date => date.unix() <= moment.unix()}
                 disabledDate={date => date.unix() <= moment.unix()}
-                onSelect={date => {
+                onChange={date => {
                   this.refreshDay(date);
                   this.refreshDay(date);
                 }}
                 }}
               />
               />

+ 52 - 17
front/project/www/routes/my/message/page.js

@@ -1,36 +1,68 @@
 import React from 'react';
 import React from 'react';
 import './index.less';
 import './index.less';
 import Page from '@src/containers/Page';
 import Page from '@src/containers/Page';
+import { asyncSMessage } from '@src/services/AsyncTools';
 import UserLayout from '../../../layouts/User';
 import UserLayout from '../../../layouts/User';
 import menu from '../index';
 import menu from '../index';
 import UserTable from '../../../components/UserTable';
 import UserTable from '../../../components/UserTable';
 import UserAction from '../../../components/UserAction';
 import UserAction from '../../../components/UserAction';
 import Tabs from '../../../components/Tabs';
 import Tabs from '../../../components/Tabs';
+import { My } from '../../../stores/my';
+import { MessageType } from '../../../../Constant';
+import { getMap, formatDate } from '../../../../../src/services/Tools';
+
+const MessageTypeMap = getMap(MessageType, 'value', 'label');
 
 
 const columns = [{ title: '消息', key: 'content' }, { title: '类型', key: 'type' }, { title: '发送时间', key: 'date' }];
 const columns = [{ title: '消息', key: 'content' }, { title: '类型', key: 'type' }, { title: '发送时间', key: 'date' }];
 
 
 export default class extends Page {
 export default class extends Page {
   initState() {
   initState() {
     return {
     return {
-      tab: '1',
+      tab: '',
       filterMap: {},
       filterMap: {},
-      data: [
-        { content: '您的会员即将到期,请及时续费', type: '动态消息', date: '2019-07-12 \n 11:38:51' },
-        {
-          content: '请尽快完成作业 \n 消息详情消息详情消息详情消息详情 ',
-          type: '系统消息',
-          date: '2019-07-12 \n 11:38:51',
-        },
-      ],
     };
     };
   }
   }
 
 
-  onFilter(filterMap) {
+  initData() {
-    this.setState({ filterMap });
+    const data = Object.assign(this.state, this.state.search);
+    if (data.order) {
+      data.sortMap = { [data.order]: data.direction };
+    }
+    data.filterMap = this.state.search;
+    data.messageTypeSelect = MessageType.map(row => {
+      row.title = row.label;
+      row.key = row.value;
+      return row;
+    });
+    data.messageTypeSelect.unshift({
+      title: '消息类型',
+      key: '',
+    });
+    this.setState(data);
+    My.message(Object.assign({ read: data.tab === 'unread' ? 0 : null }, this.state.search)).then(result => {
+      result.list = result.list.map(row => {
+        row.type = MessageTypeMap[row.type];
+        row.date = formatDate(row.createTime, 'YYYY-MM-DD\n HH:mm:ss');
+
+        return row;
+      });
+      this.setState(result);
+    });
+  }
+
+  onFilter(value) {
+    this.search(value);
   }
   }
 
 
   onTabChange(tab) {
   onTabChange(tab) {
-    this.setState({ tab });
+    const data = { tab };
+    this.refreshQuery(data);
+  }
+
+  readAllMessage() {
+    My.readAllMessage().then(() => {
+      asyncSMessage('操作成功');
+    });
   }
   }
 
 
   renderView() {
   renderView() {
@@ -39,7 +71,7 @@ export default class extends Page {
   }
   }
 
 
   renderTable() {
   renderTable() {
-    const { tab, data, filterMap } = this.state;
+    const { tab, list, messageTypeSelect, filterMap } = this.state;
     return (
     return (
       <div className="table-layout">
       <div className="table-layout">
         <UserAction
         <UserAction
@@ -51,20 +83,23 @@ export default class extends Page {
               space={5}
               space={5}
               width={54}
               width={54}
               active={tab}
               active={tab}
-              tabs={[{ key: '1', title: '全部' }, { key: '2', title: '未读' }]}
+              tabs={[{ key: '', title: '全部' }, { key: 'unread', title: '未读' }]}
               onChange={key => this.onTabChange(key)}
               onChange={key => this.onTabChange(key)}
             />
             />
           }
           }
-          right={<span>全部已读</span>}
+          right={<span style={{ cursor: 'pointer' }} onClick={() => {
+            this.readAllMessage();
+          }}>全部已读</span>}
           selectList={[
           selectList={[
             {
             {
-              select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+              select: messageTypeSelect,
+              key: 'messageType',
             },
             },
           ]}
           ]}
           filterMap={filterMap}
           filterMap={filterMap}
           onFilter={value => this.onFilter(value)}
           onFilter={value => this.onFilter(value)}
         />
         />
-        <UserTable size="small" columns={columns} data={data} />
+        <UserTable size="small" columns={columns} data={list} />
       </div>
       </div>
     );
     );
   }
   }

+ 377 - 113
front/project/www/routes/my/tools/page.js

@@ -3,6 +3,8 @@ import './index.less';
 import { Icon } from 'antd';
 import { Icon } from 'antd';
 import Page from '@src/containers/Page';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
 import Assets from '@src/components/Assets';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { formatDate } from '@src/services/Tools';
 import UserLayout from '../../../layouts/User';
 import UserLayout from '../../../layouts/User';
 import UserAction from '../../../components/UserAction';
 import UserAction from '../../../components/UserAction';
 import menu from '../index';
 import menu from '../index';
@@ -13,39 +15,254 @@ import Switch from '../../../components/Switch';
 import TotalSort from '../../../components/TotalSort';
 import TotalSort from '../../../components/TotalSort';
 import Modal from '../../../components/Modal';
 import Modal from '../../../components/Modal';
 import UserTable from '../../../components/UserTable';
 import UserTable from '../../../components/UserTable';
+import { My } from '../../../stores/my';
+import { User } from '../../../stores/user';
+import { Order } from '../../../stores/order';
+import { Textbook } from '../../../stores/textbook';
+import { DataType } from '../../../../Constant';
+import { Main } from '../../../stores/main';
+import { Question } from '../../../stores/question';
 
 
-const updateColumns = [
+const dataHistoryColumns = [
-  { title: '更新时间', key: '1', width: 120 },
+  { title: '更新时间', key: 'time', width: 120 },
-  { title: '位置', key: '2', width: 120 },
+  { title: '位置', key: 'position', width: 120 },
-  { title: '原内容', key: '3', width: 120 },
+  { title: '原内容', key: 'originContent', width: 120 },
-  { title: '更改为', key: '4', width: 120 },
+  { title: '更改为', key: 'content', width: 120 },
-  { title: '更新至', key: '5', width: 90 },
+  { title: '更新至', key: 'version', width: 90 },
+];
+
+const textbookHistoryColumns = [
+  { title: '更新时间', key: 'createTime', width: 120 },
+  { title: '版本', key: 'version', width: 90 },
+  { title: '内容', key: 'content', width: 360 },
+];
+
+const openColumns = [
+  { title: '商品名称', key: 'title', width: 240 },
+  { title: '开通期限', key: 'endTime', width: 240 },
+  { title: '操作', key: 'handler', width: 90 },
 ];
 ];
 
 
 export default class extends Page {
 export default class extends Page {
   initState() {
   initState() {
     return {
     return {
-      tab: '1',
+      tab: 'data',
       sortMap: {},
       sortMap: {},
       filterMap: {},
       filterMap: {},
-      data: [
-        { num: '30', version: '7', title: 'OG16/17/18/19语法千行', date: '2019-08-31  09:26:13' },
-        { num: '30', version: '7', title: 'OG16/17/18/19语法千行', date: '2019-08-31  09:26:13' },
-        { num: '30', version: '7', title: 'OG16/17/18/19语法千行', date: '2019-08-31  09:26:13' },
-      ],
     };
     };
   }
   }
 
 
+  initData() {
+    const data = Object.assign(this.state, this.state.search);
+    if (data.order) {
+      data.sortMap = { [data.order]: data.direction };
+    }
+    data.filterMap = this.state.search;
+    this.setState(data);
+
+    const { tab } = this.state;
+    switch (tab) {
+      case 'textbook':
+        this.refreshTextbook();
+        break;
+      case 'examination':
+        this.refreshExamination();
+        break;
+      case 'vip':
+        this.refreshVip();
+        break;
+      case 'cal':
+        break;
+      case 'data':
+      default:
+        this.refreshData();
+        break;
+    }
+  }
+
+  refreshTextbook() {
+    Main.getService('textbook').then(result => {
+      this.setState({ service: result });
+    });
+    Textbook.getInfo()
+      .then(result => {
+        const { latest } = result;
+        result.day = parseInt((new Date().getTime() - new Date(result.latest.startDate).getTime()) / 86400000, 10);
+
+        result.expireDay = result.expireTime && parseInt((new Date().getTime() - new Date(result.expireTime).getTime()) / 86400000, 10);
+
+        const list = [];
+
+        list.push({ subject: 'quant', number: latest.quantNumber, time: latest.quantTime, version: latest.quantVersion });
+        list.push({ subject: 'rc', number: latest.rcNumber, time: latest.rcTime, version: latest.rcVersion });
+        list.push({ subject: 'ir', number: latest.irNumber, time: latest.irTime, version: latest.irVersion });
+        this.setState({ data: result, list });
+      });
+  }
+
+  textbookHistory({ page, size, subject }) {
+    Textbook.listHistory({ subject })
+      .then(result => {
+        this.setState({
+          updateList: result.map(row => {
+            row.version = row[`${subject}Version`];
+            row.content = row[`${subject}Content`];
+            row.createTime = formatDate(row.createTime, 'YYYY-MM-DD\nHH:mm:ss');
+            return row;
+          }),
+          updateTotal: result.length,
+          updatePage: page,
+          updateData: { page, size, subject, columns: textbookHistoryColumns, type: 'textbook' },
+        });
+      });
+  }
+
+  refreshExamination() {
+    Main.getService('qx_cat').then(result => {
+      this.setState({ service: result });
+    });
+    Question.getExaminationInfo(result => {
+      result.expireDay = result.expireTime && parseInt((new Date().getTime() - new Date(result.expireTime).getTime()) / 86400000, 10);
+      this.setState({ data: result });
+    });
+  }
+
+  refreshVip() {
+    Main.getService('vip').then(result => {
+      this.setState({ service: result });
+    });
+  }
+
+  recordList({ page, size, service, isUse }) {
+    Order.listRecord({ page, size, productType: 'service', service, isUse })
+      .then(result => {
+        this.setState({
+          updateList: result.map(row => {
+            row.handler = <a onClick={() => {
+              this.open(row.id);
+            }}>立即开通</a>;
+            row.endTime = `${formatDate(row.endTime, 'YYYY-MM-DD')} 前`;
+            return row;
+          }),
+          updateTotal: result.length,
+          updatePage: page,
+          updateData: { page, size, service, isUse, columns: isUse ? [] : openColumns, type: 'record' },
+        });
+      });
+  }
+
+  refreshData() {
+    const dataTypeSelect = DataType.map(row => {
+      row.title = row.label;
+      row.key = row.value;
+      return row;
+    });
+    dataTypeSelect.unshift({
+      title: '全部',
+      key: '',
+    });
+    this.setState({ dataTypeSelect });
+    Main.dataStruct().then(result => {
+      const structs = result.filter(row => row.level === 1).map(row => {
+        row.title = `${row.titleZh}${row.titleEn}`;
+        row.key = `${row.id}`;
+        return row;
+      });
+      structs.unshift({
+        title: '全部',
+        key: '',
+      });
+      this.setState({
+        structs,
+      });
+    });
+    My.listData(Object.assign({}, this.state.search))
+      .then(result => {
+        result = {
+          list: [{
+            title: '123123',
+            latestTime: '',
+            id: 1,
+            number: 10,
+            version: '1231',
+          }],
+        };
+        this.setState({
+          list: result.list.map(row => {
+            row.time = formatDate(row.time, 'YYYY-MM-DD HH:mm:ss');
+            return row;
+          }),
+          total: result.total,
+        });
+      });
+  }
+
+  dataHistory({ dataId, page, size }) {
+    My.listDataHistory({ page, size, dataId })
+      .then(result => {
+        result.list = result.list.map(row => {
+          row.time = formatDate(row.time, 'YYYY-MM-DD\nHH:mm:ss');
+          return row;
+        });
+        this.setState({ updateList: result.list, updateTotal: result.total, updatePage: page, updateData: { page, size, dataId, columns: dataHistoryColumns, type: 'data' } });
+      });
+  }
+
   onFilter(value) {
   onFilter(value) {
-    this.setState({ filterMap: value });
+    this.search(value);
   }
   }
 
 
   onSort(value) {
   onSort(value) {
-    this.setState({ sortMap: value });
+    const keys = Object.keys(value);
+    this.search({ order: keys.length ? keys[0] : null, direction: keys.length ? value[keys[0]] : null });
   }
   }
 
 
   onTabChange(tab) {
   onTabChange(tab) {
-    this.setState({ tab });
+    const data = { tab };
+    this.refreshQuery(data);
+  }
+
+  submitComment() {
+    const { comment } = this.state;
+    My.addComment(comment.channel, comment.position, comment.content)
+      .then(() => {
+        this.setState({ showComment: false, showFinish: true, comment: {} });
+      });
+  }
+
+  submitFeedbackError() {
+    const { feedbackError } = this.state;
+    My.addFeedbackErrorData(feedbackError.dataId, feedbackError.title, feedbackError.position, feedbackError.originContent, feedbackError.content)
+      .then(() => {
+        this.setState({ showFinish: true, showFeedbackError: false, feedbackError: {} });
+      });
+  }
+
+  submitFeedback() {
+    const { feedback } = this.state;
+    My.addTextbookFeedback('', feedback.target, feedback.content)
+      .then(() => {
+        this.setState({ showFinish: true, showFeedback: false, feedbackError: {} });
+      });
+  }
+
+  subscribe(value) {
+    My.subscribeData(value)
+      .then(() => {
+        const { info } = this.props.user;
+        info.dataEmailSubscribe = value;
+        User.infoHandle(info);
+      })
+      .catch(err => {
+        asyncSMessage(err.message, 'warn');
+      });
+  }
+
+  open(recordId) {
+    Order.useRecord(recordId).then(() => {
+      asyncSMessage('开通成功');
+      this.refresh();
+    });
   }
   }
 
 
   renderView() {
   renderView() {
@@ -54,7 +271,7 @@ export default class extends Page {
   }
   }
 
 
   renderDetail() {
   renderDetail() {
-    const { tab } = this.state;
+    const { tab, comment = {}, showComment, showFinish, showUpdate, updateList, updateTotal, updateData = {} } = this.state;
     return (
     return (
       <div className="table-layout">
       <div className="table-layout">
         <Tabs
         <Tabs
@@ -66,49 +283,42 @@ export default class extends Page {
           width={100}
           width={100}
           active={tab}
           active={tab}
           tabs={[
           tabs={[
-            { key: '1', title: '资料' },
+            { key: 'data', title: '资料' },
-            { key: '2', title: '机经' },
+            { key: 'textbook', title: '机经' },
-            { key: '3', title: '模考' },
+            { key: 'examination', title: '模考' },
-            { key: '4', title: 'VIP' },
+            { key: 'vip', title: 'VIP' },
-            { key: '5', title: '考分计算器' },
+            { key: 'cal', title: '考分计算器' },
           ]}
           ]}
           onChange={key => this.onTabChange(key)}
           onChange={key => this.onTabChange(key)}
         />
         />
         {this[`renderTab${tab}`]()}
         {this[`renderTab${tab}`]()}
-        <Modal maskClosable close={false} body={false} width={630} onClose={() => {}}>
+        <Modal show={showUpdate} maskClosable close={false} body={false} width={630} onClose={() => this.setState({ showUpdate: false, updateList: [] })} >
           <UserTable
           <UserTable
             size="small"
             size="small"
-            columns={updateColumns}
+            columns={updateData.columns}
-            data={[
+            data={updateList}
-              {
+            current={updateData.page}
-                1: '2019-07-12 11:38:51',
+            onChangePage={(page) => {
-                2: 'P30 第 20 行',
+              updateData.page = page;
-                3: 'the number of wolf population',
+              if (updateData.type === 'data') {
-                4: '删除',
+                this.dataHistory(updateData);
-                5: '版本3',
+              } else if (updateData.type === 'textbook') {
-              },
+                this.textbookHistory(updateData);
-              {
+              } else if (updateData.type === 'record') {
-                1: '2019-07-12 11:38:51',
+                this.recordList(updateData);
-                2: 'P30 第 20 行',
+              }
-                3: 'the number of wolf population',
+            }}
-                4: '删除',
+            total={updateTotal}
-                5: '版本3',
-              },
-              {
-                1: '2019-07-12 11:38:51',
-                2: 'P30 第 20 行',
-                3: 'the number of wolf population',
-                4: '删除',
-                5: '版本3',
-              },
-            ]}
           />
           />
         </Modal>
         </Modal>
-        <Modal title="评价" onConfirm={() => {}} onCancel={() => {}}>
+        <Modal show={showComment} title="评价" onConfirm={() => comment.content && this.submitComment()} onCancel={() => this.setState({ showComment: false, comment: {} })}>
-          <textarea className="b-c-1 w-10 p-10" rows={6} placeholder="您的看法对我们来说很重要!" />
+          <textarea value={comment.content} className="b-c-1 w-10 p-10" rows={6} placeholder="您的看法对我们来说很重要!" onChange={(e) => {
+            comment.content = e.target.value;
+            this.setState({ comment });
+          }} />
           <div className="b-b m-t-2" />
           <div className="b-b m-t-2" />
         </Modal>
         </Modal>
-        <Modal title="提交成功" confirmText="好的,知道了" btnAlign="center" onConfirm={() => {}}>
+        <Modal show={showFinish} title="提交成功" confirmText="好的,知道了" btnAlign="center" onConfirm={() => this.setState({ showFinish: false })} >
           <div className="t-2 t-s-18">
           <div className="t-2 t-s-18">
             <Icon type="check" className="t-5 m-r-5" />
             <Icon type="check" className="t-5 m-r-5" />
             您的每一次反馈都是千行进步的动力。
             您的每一次反馈都是千行进步的动力。
@@ -118,53 +328,71 @@ export default class extends Page {
     );
     );
   }
   }
 
 
-  renderTab1() {
+  renderTabdata() {
-    const { data = [], filterMap = {}, sortMap = {} } = this.state;
+    const { list = [], filterMap = {}, sortMap = {}, structs, dataTypeSelect } = this.state;
+    const { info } = this.props.user;
     return (
     return (
       <div className="tab-1-layout">
       <div className="tab-1-layout">
         <UserAction
         <UserAction
-          selectList={[
+          selectList={[{
-            {
+            label: '学科',
-              label: '学科',
+            key: 'structId',
-              key: '1',
+            select: structs,
-              select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+          },
-            },
+          {
-            {
+            label: '资料形式',
-              label: '资料形式',
+            key: 'dataType',
-              key: '2',
+            select: dataTypeSelect,
-              select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+          }]}
-            },
+          sortList={[{ right: true, label: '销量', key: 'sale_number' }, { right: true, label: '更新时间', key: 'latest_time' }]}
-          ]}
-          sortList={[{ right: true, label: '销量', key: '1' }, { right: true, label: '更新时间', key: '2' }]}
           sortMap={sortMap}
           sortMap={sortMap}
           filterMap={filterMap}
           filterMap={filterMap}
           onFilter={value => this.onFilter(value)}
           onFilter={value => this.onFilter(value)}
           onSort={value => this.onSort(value)}
           onSort={value => this.onSort(value)}
           right={
           right={
             <div className="email">
             <div className="email">
-              邮箱订阅 <Switch />
+              邮箱订阅 <Switch checked={info.dataEmailSubscribe} onChange={() => {
+              this.subscribe(!info.dataEmailSubscribe);
+            }} />
             </div>
             </div>
           }
           }
         />
         />
         <div className="data-layout">
         <div className="data-layout">
-          {data.map(item => {
+          {list.map(item => {
             return (
             return (
               <div className="data-item">
               <div className="data-item">
-                <Assets name="sun_blue" />
+                <Assets name="sun_blue" src={item.cover} />
                 <div className="fixed">
                 <div className="fixed">
                   <div className="btns">
                   <div className="btns">
-                    <Button size="small" radius>
+                    <Button size="small" radius onClick={() => {
+                      openLink(item.resource);
+                    }}>
                       阅读
                       阅读
                     </Button>
                     </Button>
-                    <div className="white">下载</div>
+                    <div className="white" onClick={() => {
+                      openLink(item.resource);
+                    }}>下载</div>
                   </div>
                   </div>
                 </div>
                 </div>
                 <div className="title">
                 <div className="title">
                   <span>版本{item.version}</span>
                   <span>版本{item.version}</span>
                   {item.title}
                   {item.title}
                 </div>
                 </div>
-                <div className="date">{item.date}</div>
+                <div className="date">{formatDate(item.latestTime, 'YYYY-MM-DD HH:mm:ss')}</div>
-                <More menu={[{ label: '纠错', key: '1' }, { label: '评价', key: '2' }, { label: '更新', key: '3' }]} />
+                <More
+                  menu={[{ label: '纠错', key: 'feedback' }, { label: '评价', key: 'comment' }, { label: '更新', key: 'update' }]}
+                  onClick={(value) => {
+                    const { key } = value;
+                    if (key === 'comment') {
+                      this.setState({ showComment: true, comment: { channel: 'course_data', position: item.id } });
+                    } else if (key === 'update') {
+                      this.setState({ showUpdate: true });
+                      this.dataHistory({ dataId: item.id, page: 1, size: 10 });
+                    } else if (key === 'feedback') {
+                      this.setState({ showFeedbackError: true, feedbackError: { dataId: item.id, title: item.title } });
+                    }
+                  }}
+                />
               </div>
               </div>
             );
             );
           })}
           })}
@@ -173,23 +401,27 @@ export default class extends Page {
     );
     );
   }
   }
 
 
-  renderTab2() {
+  renderTabtextbook() {
-    const { data = [] } = this.state;
+    const { data = {}, list = [], service } = this.state;
+    const { latest = {}, day } = data;
     return (
     return (
       <div className="tab-2-layout">
       <div className="tab-2-layout">
         <UserAction
         <UserAction
           left={
           left={
             <div className="total-log">
             <div className="total-log">
               <span>最新换库</span>
               <span>最新换库</span>
-              <span>2019-07-22</span>
+              <span>{latest.startDate ? formatDate(latest.startDate, 'YYYY-MM-DD') : ''}</span>
               <span>
               <span>
-                已换库<b>10</b>天
+                已换库<b>{day}</b>天
               </span>
               </span>
             </div>
             </div>
           }
           }
+          right={!data.hasService && data.unUseRecord && <div className="email" onClick={() => {
+            this.recordList({ page: 1, size: 10, service: 'textbook', isUse: false });
+          }}>待开通</div>}
         />
         />
-        <div className="data-layout">
+        {data.hasService && <div className="data-layout">
-          {data.map(item => {
+          {list.map(item => {
             return (
             return (
               <div className="data-item">
               <div className="data-item">
                 <Assets name="sun_blue" />
                 <Assets name="sun_blue" />
@@ -197,92 +429,124 @@ export default class extends Page {
                   已更新至<b>{item.num}</b>题
                   已更新至<b>{item.num}</b>题
                 </div>
                 </div>
                 <div className="date">{item.date}</div>
                 <div className="date">{item.date}</div>
-                <More menu={[{ label: '更新', key: '1' }, { label: '反馈', key: '2' }, { label: '评价', key: '3' }]} />
+                <More
+                  menu={[{ label: '更新', key: 'update' }, { label: '反馈', key: 'feedback' }, { label: '评价', key: 'comment' }]}
+                  onClick={(value) => {
+                    const { key } = value;
+                    if (key === 'comment') {
+                      this.setState({ showComment: true, comment: { channel: 'library' } });
+                    } else if (key === 'update') {
+                      this.setState({ showUpdate: true });
+                      this.textbookHistory({ page: 1, size: 100, subject: item.subject });
+                    } else if (key === 'feedback') {
+                      this.setState({ showFeedback: true });
+                    }
+                  }}
+                />
               </div>
               </div>
             );
             );
           })}
           })}
-        </div>
+        </div>}
-        <div className="tip-layout">
+        {!data.hasService && !data.unUseRecord && <div className="tip-layout">
           <div className="t1">还未购买本月机经</div>
           <div className="t1">还未购买本月机经</div>
-          <div className="desc">¥ 888 / 月</div>
+          <div className="desc">¥ {service && service.package && service.package[0].price}</div>
           <Button radius size="lager" width={150}>
           <Button radius size="lager" width={150}>
             立即购买
             立即购买
           </Button>
           </Button>
-        </div>
+        </div>}
-        <div className="tip-layout">
+        {data.hasService && <div className="tip-layout">
-          <div className="t2">请于2019-11-20前开通</div>
+          <div className="t1">使用中</div>
-          <Button radius size="lager" width={150}>
+          <div className="t2">距离到期还有 {data.expireDay} 天</div>
+        </div>}
+        {!data.hasService && data.unUseRecord && <div className="tip-layout">
+          <div className="t2">请于{formatDate(data.unUseRecord.endTime, 'YYYY-MM-DD')}前开通</div>
+          <Button radius size="lager" width={150} onClick={() => {
+            this.open(data.unUseRecord.id);
+          }}>
             立即开通
             立即开通
           </Button>
           </Button>
-        </div>
+        </div>}
       </div>
       </div>
     );
     );
   }
   }
 
 
-  renderTab3() {
+  renderTabexamination() {
+    const { data = {}, service } = this.state;
     return (
     return (
       <div className="tab-3-layout">
       <div className="tab-3-layout">
-        <UserAction right={<div className="email">待开通</div>} />
+        <UserAction right={data.unUseRecord && <div className="email" onClick={() => {
-        <div className="tip-layout">
+          this.recordList({ page: 1, size: 10, service: 'qx_cat', isUse: false });
+        }}>待开通</div>} />
+        {!data.hasService && !data.unUseRecord && !data.expireTime && <div className="tip-layout">
           <div className="t1">未购买</div>
           <div className="t1">未购买</div>
-          <div className="desc">¥ 888 / 月</div>
+          <div className="desc">¥ {service && service.package && service.package[0].price}</div>
           <Button radius size="lager" width={150}>
           <Button radius size="lager" width={150}>
             立即购买
             立即购买
           </Button>
           </Button>
-        </div>
+        </div>}
-        <div className="tip-layout">
+        {!data.hasService && data.unUseRecord && <div className="tip-layout">
-          <div className="t2">请于2019-11-20前开通</div>
+          <div className="t2">请于{formatDate(data.unUseRecord.endTime, 'YYYY-MM-DD')}前开通</div>
-          <Button radius size="lager" width={150}>
+          <Button radius size="lager" width={150} onClick={() => {
+            this.open(data.unUseRecord.id);
+          }}>
             立即开通
             立即开通
           </Button>
           </Button>
-        </div>
+        </div>}
-        <div className="tip-layout">
+        {data.hasService && <div className="tip-layout">
           <div className="t1">使用中</div>
           <div className="t1">使用中</div>
-          <div className="t2">距离到期还有 10</div>
+          <div className="t2">距离到期还有 {data.expireDay}</div>
-        </div>
+        </div>}
-        <div className="tip-layout">
+        {!data.hasService && !data.unUseRecord && data.expireTime && <div className="tip-layout">
           <div className="t3">已过期</div>
           <div className="t3">已过期</div>
-          <div className="date">2019-05-11 ~ 2019-09-11</div>
+          <div className="date">{formatDate(data.startTime, 'YYYY-MM-DD')} ~ {formatDate(data.expireTime, 'YYYY-MM-DD')}</div>
-          <div className="desc">¥ 800/3个月</div>
+          <div className="desc">¥ {service && service.package && service.package[0].price}</div>
           <Button radius size="lager" width={150}>
           <Button radius size="lager" width={150}>
             立即购买
             立即购买
           </Button>
           </Button>
-        </div>
+        </div>}
       </div>
       </div>
     );
     );
   }
   }
 
 
-  renderTab4() {
+  renderTabvip() {
+    const { data } = this.state;
     return (
     return (
       <div className="tab-4-layout">
       <div className="tab-4-layout">
-        <div className="tip-layout">
+        {!data.hasService && !data.unUseRecord && !data.expireTime && <div className="tip-layout">
           <div className="t2">未购买</div>
           <div className="t2">未购买</div>
           <Button radius size="lager" width={150}>
           <Button radius size="lager" width={150}>
             立即购买
             立即购买
           </Button>
           </Button>
-        </div>
+        </div>}
-        <div className="tip-layout">
+        {data.hasService && <div className="tip-layout">
           <div className="t1">使用中</div>
           <div className="t1">使用中</div>
-          <div className="desc">2019-05-20 到期</div>
+          <div className="desc">{formatDate(data.expireTime, 'YYYY-MM-DD')} 到期</div>
           <Button radius size="lager" width={150}>
           <Button radius size="lager" width={150}>
             续费
             续费
           </Button>
           </Button>
-        </div>
+        </div>}
-        <div className="tip-layout">
+        {!data.hasService && !data.unUseRecord && data.expireTime && <div className="tip-layout">
           <div className="t1">已过期</div>
           <div className="t1">已过期</div>
-          <div className="desc">2019-05-11 ~ 2019-09-11</div>
+          <div className="desc">{formatDate(data.startTime, 'YYYY-MM-DD')} ~ {formatDate(data.expireTime, 'YYYY-MM-DD')}</div>
           <Button radius size="lager" width={150}>
           <Button radius size="lager" width={150}>
             立即购买
             立即购买
           </Button>
           </Button>
-        </div>
+        </div>}
       </div>
       </div>
     );
     );
   }
   }
 
 
-  renderTab5() {
+  renderTabcal() {
+    const { data = {} } = this.state;
     return (
     return (
       <div className="tab-5-layout">
       <div className="tab-5-layout">
-        <TotalSort />
+        <TotalSort
+          value={data.value || 650}
+          onChange={(value) => {
+            data.value = value;
+            this.setState({ data });
+          }}
+        />
       </div>
       </div>
     );
     );
   }
   }

+ 28 - 4
front/project/www/stores/my.js

@@ -38,11 +38,11 @@ export default class MyStore extends BaseStore {
    * 用户站内信
    * 用户站内信
    * @param {*} page
    * @param {*} page
    * @param {*} size
    * @param {*} size
-   * @param {*} type
+   * @param {*} messageType
    * @param {*} read
    * @param {*} read
    */
    */
-  message({ page, size, type, read }) {
+  message({ page, size, messageType, read }) {
-    return this.apiGet('/my/message', { page, size, type, read });
+    return this.apiGet('/my/message', { page, size, messageType, read });
   }
   }
 
 
   /**
   /**
@@ -343,7 +343,7 @@ export default class MyStore extends BaseStore {
    * @param {*} originContent
    * @param {*} originContent
    * @param {*} content
    * @param {*} content
    */
    */
-  addErrorData(dataId, title, position, originContent, content) {
+  addFeedbackErrorData(dataId, title, position, originContent, content) {
     return this.apiPost('/my/feedback/error/question', { dataId, title, position, originContent, content });
     return this.apiPost('/my/feedback/error/question', { dataId, title, position, originContent, content });
   }
   }
 
 
@@ -378,6 +378,30 @@ export default class MyStore extends BaseStore {
   }
   }
 
 
   /**
   /**
+   * 资料全局订阅开关
+   * @param {*} subscribe
+   */
+  subscribeData(subscribe) {
+    return this.apiPost('/my/data/subscribe', { subscribe });
+  }
+
+  /**
+   * 获取资料更新列表
+   * @param {*}} param0
+   */
+  listDataHistory({ page, size, dataId }) {
+    return this.apiGet('/my/data/history', { page, size, dataId });
+  }
+
+  /**
+   * 获取购买资料列表
+   * @param {*} param0
+   */
+  listData({ page, size, structId, dataType, order, direction }) {
+    return this.apiGet('/my/data/list', { page, size, structId, dataType, order, direction });
+  }
+
+  /**
    * 购买的课程列表
    * 购买的课程列表
    * @param {*} param0
    * @param {*} param0
    */
    */

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

@@ -7,7 +7,9 @@ public enum QuestionSubject {
     VERBAL("verbal", "语文"),
     VERBAL("verbal", "语文"),
     QUANT("quant", "数学"),
     QUANT("quant", "数学"),
     IR("ir", "综合推理"),
     IR("ir", "综合推理"),
-    AWA("awa", "作文");
+    AWA("awa", "作文"),
+
+    ;
 
 
     final static public String message = "考试结构:学科";
     final static public String message = "考试结构:学科";
 
 

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

@@ -14,7 +14,8 @@ public enum SettingKey {
     EXERCISE_PAPER_AUTO("exercise_paper_auto"), // 自动组卷设置
     EXERCISE_PAPER_AUTO("exercise_paper_auto"), // 自动组卷设置
     EXAMINATION_TIME("examination_time"), // 考试时间
     EXAMINATION_TIME("examination_time"), // 考试时间
     FILTER_TIME("filter_time"), // 题目剔除时间
     FILTER_TIME("filter_time"), // 题目剔除时间
-    PREPARE_INFO("prepare_info"), // 备考统计信息
+    PREPARE_INFO("prepare_info"), // 备考信息
+    PREPARE_STAT("prepare_stat"), // 备考统计信息
 
 
     EXERCISE_PAPER_STATUS("exercise_paper_status"), // 练习自动组卷状态:process进度,finish时间
     EXERCISE_PAPER_STATUS("exercise_paper_status"), // 练习自动组卷状态:process进度,finish时间
     SENTENCE_PAPER_STATUS("sentence_paper_status"), // 长难句自动组卷状态:process进度,finish时间
     SENTENCE_PAPER_STATUS("sentence_paper_status"), // 长难句自动组卷状态:process进度,finish时间

+ 26 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/TextbookSubject.java

@@ -0,0 +1,26 @@
+package com.qxgmat.data.constants.enums;
+
+/**
+ * Created by gaojie on 2017/11/19.
+ */
+public enum TextbookSubject {
+    QUANT("quant", "数学"),
+    IR("ir", "综合推理"),
+    RC("rc", "阅读"),
+
+    ;
+
+    final static public String message = "机经学科:学科";
+
+    public String key;
+    public String title;
+    private TextbookSubject(String key, String title){
+        this.key = key;
+        this.title = title;
+    }
+
+    public static TextbookSubject ValueOf(String name){
+        if (name == null) return null;
+        return TextbookSubject.valueOf(name.toUpperCase());
+    }
+}

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

@@ -129,6 +129,9 @@ public class CourseData implements Serializable {
     @Column(name = "`latest_time`")
     @Column(name = "`latest_time`")
     private Date latestTime;
     private Date latestTime;
 
 
+    @Column(name = "`version`")
+    private String version;
+
     /**
     /**
      * 摘要介绍
      * 摘要介绍
      */
      */
@@ -536,6 +539,20 @@ public class CourseData implements Serializable {
     }
     }
 
 
     /**
     /**
+     * @return version
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    /**
+     * @param version
+     */
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    /**
      * 获取摘要介绍
      * 获取摘要介绍
      *
      *
      * @return description - 摘要介绍
      * @return description - 摘要介绍
@@ -635,6 +652,7 @@ public class CourseData implements Serializable {
         sb.append(", createTime=").append(createTime);
         sb.append(", createTime=").append(createTime);
         sb.append(", updateTime=").append(updateTime);
         sb.append(", updateTime=").append(updateTime);
         sb.append(", latestTime=").append(latestTime);
         sb.append(", latestTime=").append(latestTime);
+        sb.append(", version=").append(version);
         sb.append(", description=").append(description);
         sb.append(", description=").append(description);
         sb.append(", content=").append(content);
         sb.append(", content=").append(content);
         sb.append(", authorContent=").append(authorContent);
         sb.append(", authorContent=").append(authorContent);
@@ -867,6 +885,14 @@ public class CourseData implements Serializable {
         }
         }
 
 
         /**
         /**
+         * @param version
+         */
+        public Builder version(String version) {
+            obj.setVersion(version);
+            return this;
+        }
+
+        /**
          * 设置摘要介绍
          * 设置摘要介绍
          *
          *
          * @param description 摘要介绍
          * @param description 摘要介绍

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

@@ -42,6 +42,12 @@ public class TextbookLibrary implements Serializable {
     private Date quantTime;
     private Date quantTime;
 
 
     /**
     /**
+     * 数学题目数
+     */
+    @Column(name = "`quant_number`")
+    private Integer quantNumber;
+
+    /**
      * 综合逻辑
      * 综合逻辑
      */
      */
     @Column(name = "`ir`")
     @Column(name = "`ir`")
@@ -60,6 +66,12 @@ public class TextbookLibrary implements Serializable {
     private Date irTime;
     private Date irTime;
 
 
     /**
     /**
+     * 综合逻辑题目数
+     */
+    @Column(name = "`ir_number`")
+    private Integer irNumber;
+
+    /**
      * 阅读
      * 阅读
      */
      */
     @Column(name = "`rc`")
     @Column(name = "`rc`")
@@ -78,6 +90,12 @@ public class TextbookLibrary implements Serializable {
     private Date rcTime;
     private Date rcTime;
 
 
     /**
     /**
+     * 阅读题目数
+     */
+    @Column(name = "`rc_number`")
+    private Integer rcNumber;
+
+    /**
      * 更新次数
      * 更新次数
      */
      */
     @Column(name = "`history_number`")
     @Column(name = "`history_number`")
@@ -202,6 +220,24 @@ public class TextbookLibrary implements Serializable {
     }
     }
 
 
     /**
     /**
+     * 获取数学题目数
+     *
+     * @return quant_number - 数学题目数
+     */
+    public Integer getQuantNumber() {
+        return quantNumber;
+    }
+
+    /**
+     * 设置数学题目数
+     *
+     * @param quantNumber 数学题目数
+     */
+    public void setQuantNumber(Integer quantNumber) {
+        this.quantNumber = quantNumber;
+    }
+
+    /**
      * 获取综合逻辑
      * 获取综合逻辑
      *
      *
      * @return ir - 综合逻辑
      * @return ir - 综合逻辑
@@ -256,6 +292,24 @@ public class TextbookLibrary implements Serializable {
     }
     }
 
 
     /**
     /**
+     * 获取综合逻辑题目数
+     *
+     * @return ir_number - 综合逻辑题目数
+     */
+    public Integer getIrNumber() {
+        return irNumber;
+    }
+
+    /**
+     * 设置综合逻辑题目数
+     *
+     * @param irNumber 综合逻辑题目数
+     */
+    public void setIrNumber(Integer irNumber) {
+        this.irNumber = irNumber;
+    }
+
+    /**
      * 获取阅读
      * 获取阅读
      *
      *
      * @return rc - 阅读
      * @return rc - 阅读
@@ -310,6 +364,24 @@ public class TextbookLibrary implements Serializable {
     }
     }
 
 
     /**
     /**
+     * 获取阅读题目数
+     *
+     * @return rc_number - 阅读题目数
+     */
+    public Integer getRcNumber() {
+        return rcNumber;
+    }
+
+    /**
+     * 设置阅读题目数
+     *
+     * @param rcNumber 阅读题目数
+     */
+    public void setRcNumber(Integer rcNumber) {
+        this.rcNumber = rcNumber;
+    }
+
+    /**
      * 获取更新次数
      * 获取更新次数
      *
      *
      * @return history_number - 更新次数
      * @return history_number - 更新次数
@@ -385,12 +457,15 @@ public class TextbookLibrary implements Serializable {
         sb.append(", quant=").append(quant);
         sb.append(", quant=").append(quant);
         sb.append(", quantVersion=").append(quantVersion);
         sb.append(", quantVersion=").append(quantVersion);
         sb.append(", quantTime=").append(quantTime);
         sb.append(", quantTime=").append(quantTime);
+        sb.append(", quantNumber=").append(quantNumber);
         sb.append(", ir=").append(ir);
         sb.append(", ir=").append(ir);
         sb.append(", irVersion=").append(irVersion);
         sb.append(", irVersion=").append(irVersion);
         sb.append(", irTime=").append(irTime);
         sb.append(", irTime=").append(irTime);
+        sb.append(", irNumber=").append(irNumber);
         sb.append(", rc=").append(rc);
         sb.append(", rc=").append(rc);
         sb.append(", rcVersion=").append(rcVersion);
         sb.append(", rcVersion=").append(rcVersion);
         sb.append(", rcTime=").append(rcTime);
         sb.append(", rcTime=").append(rcTime);
+        sb.append(", rcNumber=").append(rcNumber);
         sb.append(", historyNumber=").append(historyNumber);
         sb.append(", historyNumber=").append(historyNumber);
         sb.append(", questionStatus=").append(questionStatus);
         sb.append(", questionStatus=").append(questionStatus);
         sb.append(", createTime=").append(createTime);
         sb.append(", createTime=").append(createTime);
@@ -449,6 +524,16 @@ public class TextbookLibrary implements Serializable {
         }
         }
 
 
         /**
         /**
+         * 设置数学题目数
+         *
+         * @param quantNumber 数学题目数
+         */
+        public Builder quantNumber(Integer quantNumber) {
+            obj.setQuantNumber(quantNumber);
+            return this;
+        }
+
+        /**
          * 设置数学时间
          * 设置数学时间
          *
          *
          * @param quantTime 数学时间
          * @param quantTime 数学时间
@@ -479,6 +564,16 @@ public class TextbookLibrary implements Serializable {
         }
         }
 
 
         /**
         /**
+         * 设置综合逻辑题目数
+         *
+         * @param irNumber 综合逻辑题目数
+         */
+        public Builder irNumber(Integer irNumber) {
+            obj.setIrNumber(irNumber);
+            return this;
+        }
+
+        /**
          * 设置综合逻辑时间
          * 设置综合逻辑时间
          *
          *
          * @param irTime 综合逻辑时间
          * @param irTime 综合逻辑时间
@@ -509,6 +604,16 @@ public class TextbookLibrary implements Serializable {
         }
         }
 
 
         /**
         /**
+         * 设置阅读题目数
+         *
+         * @param rcNumber 阅读题目数
+         */
+        public Builder rcNumber(Integer rcNumber) {
+            obj.setRcNumber(rcNumber);
+            return this;
+        }
+
+        /**
          * 设置阅读时间
          * 设置阅读时间
          *
          *
          * @param rcTime 阅读时间
          * @param rcTime 阅读时间

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

@@ -27,6 +27,7 @@
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
     <result column="latest_time" jdbcType="TIMESTAMP" property="latestTime" />
     <result column="latest_time" jdbcType="TIMESTAMP" property="latestTime" />
+    <result column="version" jdbcType="VARCHAR" property="version" />
   </resultMap>
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.CourseData">
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.CourseData">
     <!--
     <!--
@@ -44,7 +45,7 @@
     `id`, `title`, `comment`, `struct_id`, `parent_struct_id`, `is_sentence`, `data_type`, 
     `id`, `title`, `comment`, `struct_id`, `parent_struct_id`, `is_sentence`, `data_type`, 
     `is_novice`, `is_original`, `price`, `pages`, `link`, `cover`, `resource`, `trail_resource`, 
     `is_novice`, `is_original`, `price`, `pages`, `link`, `cover`, `resource`, `trail_resource`, 
     `trail_start`, `trail_end`, `view_number`, `sale_number`, `create_time`, `update_time`, 
     `trail_start`, `trail_end`, `view_number`, `sale_number`, `create_time`, `update_time`, 
-    `latest_time`
+    `latest_time`, `version`
   </sql>
   </sql>
   <sql id="Blob_Column_List">
   <sql id="Blob_Column_List">
     <!--
     <!--

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

@@ -11,12 +11,15 @@
     <result column="quant" jdbcType="VARCHAR" property="quant" />
     <result column="quant" jdbcType="VARCHAR" property="quant" />
     <result column="quant_version" jdbcType="INTEGER" property="quantVersion" />
     <result column="quant_version" jdbcType="INTEGER" property="quantVersion" />
     <result column="quant_time" jdbcType="TIMESTAMP" property="quantTime" />
     <result column="quant_time" jdbcType="TIMESTAMP" property="quantTime" />
+    <result column="quant_number" jdbcType="INTEGER" property="quantNumber" />
     <result column="ir" jdbcType="VARCHAR" property="ir" />
     <result column="ir" jdbcType="VARCHAR" property="ir" />
     <result column="ir_version" jdbcType="INTEGER" property="irVersion" />
     <result column="ir_version" jdbcType="INTEGER" property="irVersion" />
     <result column="ir_time" jdbcType="TIMESTAMP" property="irTime" />
     <result column="ir_time" jdbcType="TIMESTAMP" property="irTime" />
+    <result column="ir_number" jdbcType="INTEGER" property="irNumber" />
     <result column="rc" jdbcType="VARCHAR" property="rc" />
     <result column="rc" jdbcType="VARCHAR" property="rc" />
     <result column="rc_version" jdbcType="INTEGER" property="rcVersion" />
     <result column="rc_version" jdbcType="INTEGER" property="rcVersion" />
     <result column="rc_time" jdbcType="TIMESTAMP" property="rcTime" />
     <result column="rc_time" jdbcType="TIMESTAMP" property="rcTime" />
+    <result column="rc_number" jdbcType="INTEGER" property="rcNumber" />
     <result column="history_number" jdbcType="INTEGER" property="historyNumber" />
     <result column="history_number" jdbcType="INTEGER" property="historyNumber" />
     <result column="question_status" jdbcType="INTEGER" property="questionStatus" />
     <result column="question_status" jdbcType="INTEGER" property="questionStatus" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
@@ -26,8 +29,8 @@
     <!--
     <!--
       WARNING - @mbg.generated
       WARNING - @mbg.generated
     -->
     -->
-    `id`, `start_date`, `end_date`, `quant`, `quant_version`, `quant_time`, `ir`, `ir_version`, 
+    `id`, `start_date`, `end_date`, `quant`, `quant_version`, `quant_time`, `quant_number`, 
-    `ir_time`, `rc`, `rc_version`, `rc_time`, `history_number`, `question_status`, `create_time`, 
+    `ir`, `ir_version`, `ir_time`, `ir_number`, `rc`, `rc_version`, `rc_time`, `rc_number`, 
-    `update_time`
+    `history_number`, `question_status`, `create_time`, `update_time`
   </sql>
   </sql>
 </mapper>
 </mapper>

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

@@ -23,13 +23,13 @@
     <include refid="Id_Column_List" />
     <include refid="Id_Column_List" />
     from `course_data_history` cdh
     from `course_data_history` cdh
     <if test="userId != null">
     <if test="userId != null">
-    left join `user_order_record` uo on `uo`.`product_type`='data' and `uo`.`product_id` = cdh.`data_id`
+    left join `user_order_record` uor on `uor`.`product_type`='data' and `uor`.`product_id` = cdh.`data_id`
-      and `uo`.userId = #{userId,jdbcType=VARCHAR}
+      and `uor`.user_id = #{userId,jdbcType=VARCHAR}
     </if>
     </if>
     <!--and `uo`.create_time &lt; cd.create_time-->
     <!--and `uo`.create_time &lt; cd.create_time-->
     where 1
     where 1
     <if test="userId != null">
     <if test="userId != null">
-    `uo`.`id` > 0
+    `uor`.`id` > 0
     </if>
     </if>
     <if test="dataId != null">
     <if test="dataId != null">
       and `cdh`.dataId = #{dataId,jdbcType=VARCHAR}
       and `cdh`.dataId = #{dataId,jdbcType=VARCHAR}

+ 3 - 3
server/data/src/main/java/com/qxgmat/data/relation/mapping/CourseDataRelationMapper.xml

@@ -32,12 +32,12 @@
     select
     select
     <include refid="Id_Column_List" />
     <include refid="Id_Column_List" />
     from `course_data` cd
     from `course_data` cd
-    left join `user_order_record` uo on `uo`.`product_type`='data' and `uo`.`product_id` = cd.`id`
+    left join `user_order_record` uor on `uor`.`product_type`='data' and `uor`.`product_id` = cd.`id`
     <if test="userId != null">
     <if test="userId != null">
-      and `uo`.userId = #{userId,jdbcType=VARCHAR}
+      and `uor`.user_id = #{userId,jdbcType=VARCHAR}
     </if>
     </if>
     where
     where
-    `uo`.`id` > 0
+    `uor`.`id` > 0
     <if test="structId != null">
     <if test="structId != null">
       and (cd.`struct_id` = #{structId,jdbcType=VARCHAR} or cd.`parent_struct_id` = #{structId,jdbcType=VARCHAR})
       and (cd.`struct_id` = #{structId,jdbcType=VARCHAR} or cd.`parent_struct_id` = #{structId,jdbcType=VARCHAR})
     </if>
     </if>

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

@@ -115,6 +115,7 @@ CREATE TABLE course_data (
   create_time datetime DEFAULT NULL,
   create_time datetime DEFAULT NULL,
   update_time datetime DEFAULT NULL,
   update_time datetime DEFAULT NULL,
   latest_time datetime DEFAULT NULL,
   latest_time datetime DEFAULT NULL,
+  version varchar(20) DEFAULT NULL,
   PRIMARY KEY (id),
   PRIMARY KEY (id),
   KEY data_type (data_type,struct_id),
   KEY data_type (data_type,struct_id),
   KEY struct_id (struct_id)
   KEY struct_id (struct_id)
@@ -702,13 +703,13 @@ INSERT INTO setting (id, `key`, value)
 VALUES
 VALUES
 	(1,'index','{}'),
 	(1,'index','{}'),
 	(2,'sentence','{}'),
 	(2,'sentence','{}'),
-	(3,'message_template','{}'),
+	(3,'prepare_info','{}'),
 	(4,'place','{}'),
 	(4,'place','{}'),
 	(5,'exercise_time','{}'),
 	(5,'exercise_time','{}'),
 	(6,'exercise_paper_auto','{}'),
 	(6,'exercise_paper_auto','{}'),
 	(7,'examination_time','{\"verbal\":{\"number\":\"36\",\"time\":\"3900\"},\"ir\":{\"number\":\"12\",\"time\":\"1800\"},\"awa\":{\"number\":\"1\",\"time\":\"1800\"},\"quant\":{\"number\":\"31\",\"time\":\"3720\"}}'),
 	(7,'examination_time','{\"verbal\":{\"number\":\"36\",\"time\":\"3900\"},\"ir\":{\"number\":\"12\",\"time\":\"1800\"},\"awa\":{\"number\":\"1\",\"time\":\"1800\"},\"quant\":{\"number\":\"31\",\"time\":\"3720\"}}'),
 	(8,'filter_time','{}'),
 	(8,'filter_time','{}'),
-	(9,'prepare_info','{}'),
+	(9,'prepare_stat','{}'),
 	(10,'tips','{}'),
 	(10,'tips','{}'),
 	(11,'exercise_paper_status','{}'),
 	(11,'exercise_paper_status','{}'),
 	(12,'sentence_paper_status','{}'),
 	(12,'sentence_paper_status','{}'),
@@ -732,12 +733,15 @@ CREATE TABLE textbook_library (
   quant varchar(255) NOT NULL DEFAULT '' COMMENT '数学',
   quant varchar(255) NOT NULL DEFAULT '' COMMENT '数学',
   quant_version int(11) unsigned NOT NULL DEFAULT '0' COMMENT '数学版本',
   quant_version int(11) unsigned NOT NULL DEFAULT '0' COMMENT '数学版本',
   quant_time datetime DEFAULT NULL COMMENT '数学时间',
   quant_time datetime DEFAULT NULL COMMENT '数学时间',
+  quant_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '数学题目数',
   ir varchar(255) NOT NULL DEFAULT '' COMMENT '综合逻辑',
   ir varchar(255) NOT NULL DEFAULT '' COMMENT '综合逻辑',
   ir_version int(11) unsigned NOT NULL DEFAULT '0' COMMENT '综合逻辑版本',
   ir_version int(11) unsigned NOT NULL DEFAULT '0' COMMENT '综合逻辑版本',
   ir_time datetime DEFAULT NULL COMMENT '综合逻辑时间',
   ir_time datetime DEFAULT NULL COMMENT '综合逻辑时间',
+  ir_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '综合逻辑题目数',
   rc varchar(255) NOT NULL DEFAULT '' COMMENT '阅读',
   rc varchar(255) NOT NULL DEFAULT '' COMMENT '阅读',
   rc_version int(10) unsigned NOT NULL DEFAULT '0' COMMENT '阅读版本',
   rc_version int(10) unsigned NOT NULL DEFAULT '0' COMMENT '阅读版本',
   rc_time datetime DEFAULT NULL COMMENT '阅读时间',
   rc_time datetime DEFAULT NULL COMMENT '阅读时间',
+  rc_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '阅读题目数',
   history_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新次数',
   history_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新次数',
   question_status tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '提问状态',
   question_status tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '提问状态',
   create_time datetime DEFAULT NULL,
   create_time datetime DEFAULT NULL,
@@ -1370,6 +1374,7 @@ CREATE TABLE user_textbook_feedback (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) unsigned NOT NULL COMMENT '用户id',
   user_id int(11) unsigned NOT NULL COMMENT '用户id',
   topic_id int(11) unsigned NOT NULL COMMENT '机经问题',
   topic_id int(11) unsigned NOT NULL COMMENT '机经问题',
+  question_subject varchar(20) NOT NULL COMMENT '学科',
   library_id int(11) unsigned NOT NULL COMMENT '换库表',
   library_id int(11) unsigned NOT NULL COMMENT '换库表',
   target varchar(20) NOT NULL DEFAULT '' COMMENT '反馈类型',
   target varchar(20) NOT NULL DEFAULT '' COMMENT '反馈类型',
   content text COMMENT '正确内容',
   content text COMMENT '正确内容',

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

@@ -347,6 +347,23 @@ public class SettingController {
         return ResponseHelp.success(entity.getValue());
         return ResponseHelp.success(entity.getValue());
     }
     }
 
 
+    @RequestMapping(value = "/prepare_info", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改备考信息", httpMethod = "PUT")
+    private Response<Boolean> editPrepareInfo(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.PREPARE_INFO);
+        entity.setValue(dto);
+        settingService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/prepare_info", method = RequestMethod.GET)
+    @ApiOperation(value = "获取备考信息", httpMethod = "GET")
+    private Response<JSONObject> getPrepareInfo(){
+        Setting entity = settingService.getByKey(SettingKey.PREPARE_INFO);
+
+        return ResponseHelp.success(entity.getValue());
+    }
+
     @RequestMapping(value = "/experience_info", method = RequestMethod.PUT)
     @RequestMapping(value = "/experience_info", method = RequestMethod.PUT)
     @ApiOperation(value = "修改心经信息", httpMethod = "PUT")
     @ApiOperation(value = "修改心经信息", httpMethod = "PUT")
     private Response<Boolean> editExperienceInfo(@RequestBody @Validated JSONObject dto){
     private Response<Boolean> editExperienceInfo(@RequestBody @Validated JSONObject dto){

+ 7 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/TextbookController.java

@@ -3,6 +3,8 @@ package com.qxgmat.controller.admin;
 
 
 import com.github.pagehelper.Page;
 import com.github.pagehelper.Page;
 import com.nuliji.tools.*;
 import com.nuliji.tools.*;
+import com.qxgmat.data.constants.enums.QuestionSubject;
+import com.qxgmat.data.constants.enums.TextbookSubject;
 import com.qxgmat.data.constants.enums.TopicQuality;
 import com.qxgmat.data.constants.enums.TopicQuality;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.dao.entity.*;
@@ -216,14 +218,17 @@ public class TextbookController {
     public Response<TextbookTopic> addTopic(@RequestBody @Validated TextbookTopic dto, HttpServletRequest request) {
     public Response<TextbookTopic> addTopic(@RequestBody @Validated TextbookTopic dto, HttpServletRequest request) {
         TextbookTopic entity = Transform.convert(dto, TextbookTopic.class);
         TextbookTopic entity = Transform.convert(dto, TextbookTopic.class);
         entity = textbookTopicService.add(entity);
         entity = textbookTopicService.add(entity);
+        textbookService.updateLibraryNo(entity.getLibraryId(), entity.getQuestionSubject());
         managerLogService.log(request);
         managerLogService.log(request);
         return ResponseHelp.success(Transform.convert(entity, TextbookTopic.class));
         return ResponseHelp.success(Transform.convert(entity, TextbookTopic.class));
     }
     }
+
     @RequestMapping(value = "/topic/edit", method = RequestMethod.PUT)
     @RequestMapping(value = "/topic/edit", method = RequestMethod.PUT)
     @ApiOperation(value = "编辑机经题目", httpMethod = "PUT")
     @ApiOperation(value = "编辑机经题目", httpMethod = "PUT")
     public Response<Boolean> editTopic(@RequestBody @Validated TextbookTopic dto, HttpServletRequest request) {
     public Response<Boolean> editTopic(@RequestBody @Validated TextbookTopic dto, HttpServletRequest request) {
         TextbookTopic entity = Transform.convert(dto, TextbookTopic.class);
         TextbookTopic entity = Transform.convert(dto, TextbookTopic.class);
         entity = textbookTopicService.edit(entity);
         entity = textbookTopicService.edit(entity);
+        textbookService.updateLibraryNo(entity.getLibraryId(), entity.getQuestionSubject());
         managerLogService.log(request);
         managerLogService.log(request);
         return ResponseHelp.success(true);
         return ResponseHelp.success(true);
     }
     }
@@ -231,7 +236,9 @@ public class TextbookController {
     @RequestMapping(value = "/topic/delete", method = RequestMethod.DELETE)
     @RequestMapping(value = "/topic/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "删除机经题目", httpMethod = "DELETE")
     @ApiOperation(value = "删除机经题目", httpMethod = "DELETE")
     public Response<Boolean> deleteTopic(@RequestParam int id, HttpServletRequest request) {
     public Response<Boolean> deleteTopic(@RequestParam int id, HttpServletRequest request) {
+        TextbookTopic entity = textbookTopicService.get(id);
         textbookTopicService.delete(id);
         textbookTopicService.delete(id);
+        textbookService.updateLibraryNo(entity.getLibraryId(), entity.getQuestionSubject());
         managerLogService.log(request);
         managerLogService.log(request);
         return ResponseHelp.success(true);
         return ResponseHelp.success(true);
     }
     }

+ 48 - 8
server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java

@@ -4,12 +4,10 @@ import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.JSONObject;
 import com.github.pagehelper.Page;
 import com.github.pagehelper.Page;
 import com.nuliji.tools.*;
 import com.nuliji.tools.*;
+import com.nuliji.tools.exception.AuthException;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.exception.SystemException;
-import com.qxgmat.data.constants.enums.MessageType;
+import com.qxgmat.data.constants.enums.*;
-import com.qxgmat.data.constants.enums.QuestionSubject;
-import com.qxgmat.data.constants.enums.QuestionType;
-import com.qxgmat.data.constants.enums.SettingKey;
 import com.qxgmat.data.constants.enums.module.*;
 import com.qxgmat.data.constants.enums.module.*;
 import com.qxgmat.data.constants.enums.status.AskStatus;
 import com.qxgmat.data.constants.enums.status.AskStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
@@ -358,6 +356,25 @@ public class MyController {
         return ResponseHelp.success(true);
         return ResponseHelp.success(true);
     }
     }
 
 
+    @RequestMapping(value = "/vip/info", method = RequestMethod.GET)
+    @ApiOperation(value = "vip信息", httpMethod = "GET")
+    public Response<UserVipInfoDto> info(HttpSession session) {
+        User user = (User) shiroHelp.getLoginUser();
+        UserVipInfoDto dto = new UserVipInfoDto();
+
+        if (user != null){
+            UserService userService = userServiceService.getService(user.getId(), ServiceKey.VIP);
+            dto.setHasService(userService != null);
+            UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), ServiceKey.VIP);
+            dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
+
+            dto.setStartTime(userService!=null ? userService.getStartTime() : null);
+            dto.setExpireTime(userService != null ? userService.getExpireTime() : null);
+        }
+
+        return ResponseHelp.success(dto);
+    }
+
     @RequestMapping(value = "/message", method = RequestMethod.GET)
     @RequestMapping(value = "/message", method = RequestMethod.GET)
     @ApiOperation(value = "用户站内信", notes = "用户消息列表", httpMethod = "GET")
     @ApiOperation(value = "用户站内信", notes = "用户消息列表", httpMethod = "GET")
     public Response<PageMessage<UserMessage>> message(
     public Response<PageMessage<UserMessage>> message(
@@ -427,9 +444,13 @@ public class MyController {
         User entity = usersService.get(user.getId());
         User entity = usersService.get(user.getId());
         UserPrepareDetailDto dto = Transform.convert(entity, UserPrepareDetailDto.class);
         UserPrepareDetailDto dto = Transform.convert(entity, UserPrepareDetailDto.class);
 
 
-        Setting setting = settingService.getByKey(SettingKey.PREPARE_INFO);
+        Setting settingStat = settingService.getByKey(SettingKey.PREPARE_STAT);
-        JSONObject value = setting.getValue();
+        JSONObject valueStat = settingStat.getValue();
-        dto.setStat(value);
+        dto.setStat(valueStat);
+
+        Setting settingInfo = settingService.getByKey(SettingKey.PREPARE_INFO);
+        JSONObject valueInfo = settingInfo.getValue();
+        dto.setInfo(valueInfo);
         return ResponseHelp.success(dto);
         return ResponseHelp.success(dto);
     }
     }
 
 
@@ -689,6 +710,8 @@ public class MyController {
             Integer incorrectTime = 0;
             Integer incorrectTime = 0;
 
 
             List<QuestionNo> list = relationList.stream().filter((row)->row.getQuestion().getQuestionType().equals(questionType)).collect(Collectors.toList());
             List<QuestionNo> list = relationList.stream().filter((row)->row.getQuestion().getQuestionType().equals(questionType)).collect(Collectors.toList());
+            dto.setQuestionNumber(list.size());
+
             PaperStat stat = questionNoService.statPaper(list);
             PaperStat stat = questionNoService.statPaper(list);
             dto.setTotalCorrect(stat.getTotalCorrect());
             dto.setTotalCorrect(stat.getTotalCorrect());
             dto.setTotalNumber(stat.getTotalNumber());
             dto.setTotalNumber(stat.getTotalNumber());
@@ -696,8 +719,10 @@ public class MyController {
 
 
             Collection questionNoIds = Transform.getIds(list, QuestionNo.class, "id");
             Collection questionNoIds = Transform.getIds(list, QuestionNo.class, "id");
             List<UserQuestion> userQuestionList = userQuestionService.listByQuestionWithTime(user.getId(), QuestionModule.BASE, questionNoIds, startTime, endTime);
             List<UserQuestion> userQuestionList = userQuestionService.listByQuestionWithTime(user.getId(), QuestionModule.BASE, questionNoIds, startTime, endTime);
-            UserQuestionStat userQuestionStat = userQuestionService.statQuestion(userQuestionList);
+            Map userQuestionMap = Transform.getMap(userQuestionList, UserQuestion.class, "questionNoId");
+            dto.setUserQuestion(userQuestionMap.size());
 
 
+            UserQuestionStat userQuestionStat = userQuestionService.statQuestion(userQuestionList);
             dto.setUserCorrect(userQuestionStat.getUserCorrect());
             dto.setUserCorrect(userQuestionStat.getUserCorrect());
             dto.setUserNumber(userQuestionStat.getUserNumber());
             dto.setUserNumber(userQuestionStat.getUserNumber());
             dto.setUserTime(userQuestionStat.getUserTime());
             dto.setUserTime(userQuestionStat.getUserTime());
@@ -1467,6 +1492,21 @@ public class MyController {
         return ResponseHelp.success(true);
         return ResponseHelp.success(true);
     }
     }
 
 
+
+    @RequestMapping(value = "/data/subscribe", method = RequestMethod.POST)
+    @ApiOperation(value = "资料订阅", notes = "资料订阅", httpMethod = "POST")
+    public Response<Boolean> addComment(@RequestBody @Validated DataSubscribeDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        if (user == null){
+            throw new AuthException("请先登录");
+        }
+        usersService.edit(User.builder()
+                .id(user.getId())
+                .dataEmailSubscribe(dto.getSubscribe() ? 1 : 0)
+                .build());
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/data/history", method = RequestMethod.GET)
     @RequestMapping(value = "/data/history", method = RequestMethod.GET)
     @ApiOperation(value = "资料更新记录", httpMethod = "GET")
     @ApiOperation(value = "资料更新记录", httpMethod = "GET")
     public Response<PageMessage<CourseDataHistoryInfoDto>> listDataHistory(
     public Response<PageMessage<CourseDataHistoryInfoDto>> listDataHistory(

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

@@ -426,6 +426,7 @@ public class QuestionController {
             dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
             dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
 
 
             dto.setReset(userService != null && userService.getIsReset() > 0);
             dto.setReset(userService != null && userService.getIsReset() > 0);
+            dto.setStartTime(userService!=null ? userService.getStartTime() : null);
             dto.setExpireTime(userService != null ? userService.getExpireTime() : null);
             dto.setExpireTime(userService != null ? userService.getExpireTime() : null);
 
 
             dto.setCanReset(examinationService.isFinishCat(user.getId()));
             dto.setCanReset(examinationService.isFinishCat(user.getId()));

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

@@ -209,12 +209,13 @@ public class TextbookController
         dto.setLatest(latest);
         dto.setLatest(latest);
         dto.setHasService(false);
         dto.setHasService(false);
         if (user != null){
         if (user != null){
-            UserService userService = userServiceService.getService(user.getId(), ServiceKey.TEXTBOOK);
+            UserService userService = userServiceService.getServiceBase(user.getId(), ServiceKey.TEXTBOOK);
-            dto.setHasService(userService != null);
+            dto.setHasService(userService != null && userService.getExpireTime().after(new Date()));
             UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), ServiceKey.TEXTBOOK);
             UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), ServiceKey.TEXTBOOK);
             dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
             dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
 
 
             dto.setSubscribe(userService != null && userService.getIsSubscribe() > 0);
             dto.setSubscribe(userService != null && userService.getIsSubscribe() > 0);
+            dto.setStartTime(userService != null ? userService.getStartTime() : null);
             dto.setExpireTime(userService != null ? userService.getExpireTime() : null);
             dto.setExpireTime(userService != null ? userService.getExpireTime() : null);
         }
         }
         if (!dto.getHasService()){
         if (!dto.getHasService()){

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

@@ -0,0 +1,13 @@
+package com.qxgmat.dto.request;
+
+public class DataSubscribeDto {
+    private Boolean subscribe;
+
+    public Boolean getSubscribe() {
+        return subscribe;
+    }
+
+    public void setSubscribe(Boolean subscribe) {
+        this.subscribe = subscribe;
+    }
+}

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

@@ -31,6 +31,8 @@ public class MyDto extends UserDto {
 
 
     private Integer latestExercise;
     private Integer latestExercise;
 
 
+    private Integer dataEmailSubscribe;
+
     private Date vip;
     private Date vip;
 
 
     private int messageNum;
     private int messageNum;
@@ -138,4 +140,12 @@ public class MyDto extends UserDto {
     public void setMobile(String mobile) {
     public void setMobile(String mobile) {
         this.mobile = mobile;
         this.mobile = mobile;
     }
     }
+
+    public Integer getDataEmailSubscribe() {
+        return dataEmailSubscribe;
+    }
+
+    public void setDataEmailSubscribe(Integer dataEmailSubscribe) {
+        this.dataEmailSubscribe = dataEmailSubscribe;
+    }
 }
 }

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

@@ -3,12 +3,16 @@ package com.qxgmat.dto.response;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONArray;
 
 
 public class UserDataDto {
 public class UserDataDto {
+    private Integer questionNumber;
+
     private Integer totalNumber;
     private Integer totalNumber;
 
 
     private Integer totalTime;
     private Integer totalTime;
 
 
     private Integer totalCorrect;
     private Integer totalCorrect;
 
 
+    private Integer userQuestion;
+
     private Integer userNumber;
     private Integer userNumber;
 
 
     private Integer userCorrect;
     private Integer userCorrect;
@@ -102,4 +106,20 @@ public class UserDataDto {
     public void setPlace(JSONArray place) {
     public void setPlace(JSONArray place) {
         this.place = place;
         this.place = place;
     }
     }
+
+    public Integer getQuestionNumber() {
+        return questionNumber;
+    }
+
+    public void setQuestionNumber(Integer questionNumber) {
+        this.questionNumber = questionNumber;
+    }
+
+    public Integer getUserQuestion() {
+        return userQuestion;
+    }
+
+    public void setUserQuestion(Integer userQuestion) {
+        this.userQuestion = userQuestion;
+    }
 }
 }

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

@@ -7,6 +7,8 @@ import java.util.Date;
 public class UserExaminationInfoDto {
 public class UserExaminationInfoDto {
     private Date expireTime;
     private Date expireTime;
 
 
+    private Date startTime;
+
     private Boolean hasService;
     private Boolean hasService;
 
 
     private UserServiceRecordExtendDto unUseRecord;
     private UserServiceRecordExtendDto unUseRecord;
@@ -54,4 +56,12 @@ public class UserExaminationInfoDto {
     public void setExpireTime(Date expireTime) {
     public void setExpireTime(Date expireTime) {
         this.expireTime = expireTime;
         this.expireTime = expireTime;
     }
     }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
 }
 }

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

@@ -33,6 +33,11 @@ public class UserPrepareDetailDto {
      */
      */
     private JSONObject stat;
     private JSONObject stat;
 
 
+    /**
+     * 备考信息:{link:{url,title}}
+     */
+    private JSONObject info;
+
 
 
     public String getPrepareStatus() {
     public String getPrepareStatus() {
         return prepareStatus;
         return prepareStatus;
@@ -73,4 +78,12 @@ public class UserPrepareDetailDto {
     public void setStat(JSONObject stat) {
     public void setStat(JSONObject stat) {
         this.stat = stat;
         this.stat = stat;
     }
     }
+
+    public JSONObject getInfo() {
+        return info;
+    }
+
+    public void setInfo(JSONObject info) {
+        this.info = info;
+    }
 }
 }

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

@@ -8,6 +8,8 @@ import java.util.Date;
 public class UserTextbookInfoDto {
 public class UserTextbookInfoDto {
     private Date expireTime;
     private Date expireTime;
 
 
+    private Date startTime;
+
     private TextbookLibrary latest;
     private TextbookLibrary latest;
 
 
     private TextbookLibrary second;
     private TextbookLibrary second;
@@ -65,4 +67,12 @@ public class UserTextbookInfoDto {
     public void setExpireTime(Date expireTime) {
     public void setExpireTime(Date expireTime) {
         this.expireTime = expireTime;
         this.expireTime = expireTime;
     }
     }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
 }
 }

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

@@ -0,0 +1,47 @@
+package com.qxgmat.dto.response;
+
+import com.qxgmat.dto.extend.UserServiceRecordExtendDto;
+
+import java.util.Date;
+
+public class UserVipInfoDto {
+    private Date expireTime;
+
+    private Date startTime;
+
+    private Boolean hasService;
+
+    private UserServiceRecordExtendDto unUseRecord;
+
+    public Boolean getHasService() {
+        return hasService;
+    }
+
+    public void setHasService(Boolean hasService) {
+        this.hasService = hasService;
+    }
+
+    public UserServiceRecordExtendDto getUnUseRecord() {
+        return unUseRecord;
+    }
+
+    public void setUnUseRecord(UserServiceRecordExtendDto unUseRecord) {
+        this.unUseRecord = unUseRecord;
+    }
+
+    public Date getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(Date expireTime) {
+        this.expireTime = expireTime;
+    }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+}

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

@@ -217,8 +217,6 @@ public class ExaminationService extends AbstractService {
         return new PageResult<>(list, p.getTotal());
         return new PageResult<>(list, p.getTotal());
     }
     }
 
 
-
-
     /**
     /**
      * cat模考是否已经完成
      * cat模考是否已经完成
      * @param userId
      * @param userId

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

@@ -475,6 +475,7 @@ public class OrderFlowService {
         initRecordCallback.put(ProductType.DATA, ((order, record)->{
         initRecordCallback.put(ProductType.DATA, ((order, record)->{
             // 资料无需开启,也没有有效期
             // 资料无需开启,也没有有效期
             record.setIsUsed(1);
             record.setIsUsed(1);
+            record.setIsSubscribe(1);
             Date time = new Date();
             Date time = new Date();
             record.setUseTime(time);
             record.setUseTime(time);
 
 
@@ -623,44 +624,15 @@ public class OrderFlowService {
         }));
         }));
 
 
         stopRecordCallback.put(ProductType.COURSE, (record->{
         stopRecordCallback.put(ProductType.COURSE, (record->{
-            record.setIsUsed(1);
+            record.setIsStop(1);
-            Date time = new Date();
+            if (record.getIsUsed() == 0) return record;
-            record.setUseTime(time);
+            if (record.getUseEndTime().before(new Date())) return record;
-
-            Course course = courseService.get(record.getProductId());
-            Integer expireDay = 0;
-            if (course.getCourseModule().equals(CourseModule.VS.key)){
-                // 根据课时数进行计算
-                expireDay = courseExtendService.computeExpire(record.getNumber(), course);
-            }else{
-                // 根据设置进行计算
 
 
-            }
-            Date startTime = time;
-            Date endTime = Tools.addDate(startTime, expireDay);
             UserCourse userCourse = userCourseService.getCourseBase(record.getUserId(), record.getProductId());
             UserCourse userCourse = userCourseService.getCourseBase(record.getUserId(), record.getProductId());
-            if(userCourse == null){
+            if(userCourse != null){
-                userCourse = UserCourse.builder()
+                userCourse.setExpireTime(new Date());
-                        .userId(record.getUserId())
-                        .recordId(record.getId())
-                        .startTime(startTime)
-                        .expireTime(endTime)
-                        .build();
-                userCourse = userCourseService.add(userCourse);
-            }else{
-                if (userCourse.getExpireTime().before(time)){
-                    // 已到期 - 续期
-                    userCourse.setStartTime(startTime);
-                    userCourse.setExpireTime(endTime);
-                    userCourse.setRecordId(record.getId());
-                }else{
-                    // 未到期 - 报错
-                    throw new ParameterException("已开通当前课程");
-                }
                 userCourse = userCourseService.edit(userCourse);
                 userCourse = userCourseService.edit(userCourse);
             }
             }
-            record.setUseStartTime(startTime);
-            record.setUseEndTime(endTime);
             return record;
             return record;
         }));
         }));
         stopRecordCallback.put(ProductType.COURSE_PACKAGE, (record->{
         stopRecordCallback.put(ProductType.COURSE_PACKAGE, (record->{
@@ -673,48 +645,17 @@ public class OrderFlowService {
             return record;
             return record;
         }));
         }));
         stopRecordCallback.put(ProductType.SERVICE, (record->{
         stopRecordCallback.put(ProductType.SERVICE, (record->{
-            record.setIsUsed(1);
+            record.setIsStop(1);
-            Date time = new Date();
+            if (record.getIsUsed() == 0) return record;
-            record.setUseTime(time);
+            if (record.getUseEndTime().before(new Date())) return record;
 
 
             ServiceKey serviceKey = ServiceKey.ValueOf(record.getService());
             ServiceKey serviceKey = ServiceKey.ValueOf(record.getService());
-            Integer expireDay = serviceKey.useExpireDay;
-            if(serviceKey == ServiceKey.VIP){
-                ServiceVipKey vipKey = ServiceVipKey.ValueOf(record.getParam());
-                expireDay = vipKey.useExpireDay;
-            }
 
 
-            Date startTime = time;
-            Date endTime = Tools.addDate(startTime, expireDay);
             UserService userService = userServiceService.getServiceBase(record.getUserId(), serviceKey);
             UserService userService = userServiceService.getServiceBase(record.getUserId(), serviceKey);
-            if (userService == null){
+            if (userService != null){
-                userService = UserService.builder()
+                userService.setExpireTime(new Date());
-                        .userId(record.getUserId())
-                        .isSubscribe(record.getIsSubscribe())
-                        .startTime(startTime)
-                        .expireTime(endTime)
-                        .build();
-                userService = userServiceService.add(userService);
-            }else{
-                if (userService.getExpireTime().before(time)){
-                    // 已到期 - 续期
-                    userService.setStartTime(startTime);
-                    userService.setExpireTime(endTime);
-                }else{
-                    if (serviceKey == ServiceKey.QX_CAT){
-                        // 未到期 - 报错
-                        throw new ParameterException("已开通当前服务");
-                    }
-                    // 未到期 - 延长有效期
-                    startTime = userService.getExpireTime();
-                    endTime = Tools.addDate(userService.getExpireTime(), expireDay);
-                    userService.setExpireTime(endTime);
-                }
-                userService.setIsSubscribe(record.getIsSubscribe());
                 userService = userServiceService.edit(userService);
                 userService = userServiceService.edit(userService);
             }
             }
-            record.setUseStartTime(startTime);
-            record.setUseEndTime(endTime);
             return record;
             return record;
         }));
         }));
     }
     }

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

@@ -1,13 +1,11 @@
 package com.qxgmat.service.extend;
 package com.qxgmat.service.extend;
 
 
 import com.nuliji.tools.Tools;
 import com.nuliji.tools.Tools;
+import com.qxgmat.data.constants.enums.TextbookSubject;
 import com.qxgmat.data.constants.enums.logic.SentenceLogic;
 import com.qxgmat.data.constants.enums.logic.SentenceLogic;
 import com.qxgmat.data.constants.enums.logic.TextbookLogic;
 import com.qxgmat.data.constants.enums.logic.TextbookLogic;
 import com.qxgmat.data.constants.enums.module.QuestionModule;
 import com.qxgmat.data.constants.enums.module.QuestionModule;
-import com.qxgmat.data.dao.entity.Question;
+import com.qxgmat.data.dao.entity.*;
-import com.qxgmat.data.dao.entity.TextbookLibrary;
-import com.qxgmat.data.dao.entity.TextbookPaper;
-import com.qxgmat.data.dao.entity.TextbookQuestion;
 import com.qxgmat.data.relation.entity.TextbookQuestionRelation;
 import com.qxgmat.data.relation.entity.TextbookQuestionRelation;
 import com.qxgmat.service.inline.*;
 import com.qxgmat.service.inline.*;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.annotation.Value;
@@ -45,6 +43,9 @@ public class TextbookService {
     @Resource
     @Resource
     private UserTextbookEnrollService userTextbookEnrollService;
     private UserTextbookEnrollService userTextbookEnrollService;
 
 
+    @Resource
+    private TextbookTopicService textbookTopicService;
+
     /**
     /**
      * 报名
      * 报名
      * @param userId
      * @param userId
@@ -53,7 +54,14 @@ public class TextbookService {
     @Transactional
     @Transactional
     public void enroll(Integer userId, Date date){
     public void enroll(Integer userId, Date date){
         date = Tools.month(date);
         date = Tools.month(date);
-
+        UserTextbookEnroll in = userTextbookEnrollService.get(userId, date.toString());
+        if (in != null){
+            return;
+        }
+        userTextbookEnrollService.add(UserTextbookEnroll.builder()
+                .userId(userId)
+                .month(date)
+                .build());
     }
     }
 
 
     /**
     /**
@@ -128,7 +136,39 @@ public class TextbookService {
         return textbookQuestion;
         return textbookQuestion;
     }
     }
 
 
+    /**
+     * 更新换库表题目数
+     * @param libraryId
+     * @param subject
+     */
+    public void updateLibraryNo(Integer libraryId, String subject){
+        TextbookTopic last = textbookTopicService.lastByLibrary(libraryId,subject);
+        TextbookLibrary library = textbookLibraryService.get(last.getLibraryId());
+        TextbookLibrary tmp = TextbookLibrary.builder()
+                .id(library.getId())
+                .build();
+        switch(TextbookSubject.ValueOf(subject)){
+            case QUANT:
+                if (!last.getNo().equals(library.getQuantNumber())){
+                    tmp.setQuantNumber(last.getNo());
+                    textbookLibraryService.edit(tmp);
+                }
+                break;
+            case IR:
+                if (!last.getNo().equals(library.getIrNumber())){
+                    tmp.setIrNumber(last.getNo());
+                    textbookLibraryService.edit(tmp);
+                }
+                break;
+            case RC:
+                if (!last.getNo().equals(library.getRcNumber())){
+                    tmp.setRcNumber(last.getNo());
+                    textbookLibraryService.edit(tmp);
+                }
+                break;
 
 
+        }
+    }
 
 
     private void addQuestionToPaper(TextbookLibrary library, TextbookQuestion question, TextbookLogic logic){
     private void addQuestionToPaper(TextbookLibrary library, TextbookQuestion question, TextbookLogic logic){
         String prefixTitle = generatePrefixTitle(library);
         String prefixTitle = generatePrefixTitle(library);

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

@@ -46,6 +46,16 @@ public class UserTextbookEnrollService extends AbstractService {
         return userTextbookEnrollRelationMapper.groupByMonth(startTime, endTime);
         return userTextbookEnrollRelationMapper.groupByMonth(startTime, endTime);
     }
     }
 
 
+    public UserTextbookEnroll get(Integer userId, String month){
+        Example example = new Example(UserTextbookEnroll.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("month", month)
+        );
+        return one(userTextbookEnrollMapper, example);
+    }
+
     public UserTextbookEnroll add(UserTextbookEnroll ad){
     public UserTextbookEnroll add(UserTextbookEnroll ad){
         int result = insert(userTextbookEnrollMapper, ad);
         int result = insert(userTextbookEnrollMapper, ad);
         ad = one(userTextbookEnrollMapper, ad.getId());
         ad = one(userTextbookEnrollMapper, ad.getId());

+ 1 - 0
server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java

@@ -283,6 +283,7 @@ public class AsyncTask {
             Collection userIds = Transform.getIds(userOrderRecordList, UserOrderRecord.class, "userId");
             Collection userIds = Transform.getIds(userOrderRecordList, UserOrderRecord.class, "userId");
             List<User> userList = usersService.select(userIds);
             List<User> userList = usersService.select(userIds);
             for(User user : userList){
             for(User user : userList){
+                if (user.getDataEmailSubscribe() == 0) continue;
                 messageExtendService.sendDataUpdate(user, courseData, history);
                 messageExtendService.sendDataUpdate(user, courseData, history);
             }
             }
         }while(userOrderRecordList.size() >= size);
         }while(userOrderRecordList.size() >= size);