Browse Source

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

# Conflicts:
#	front/project/www/components/OtherModal/index.js
KaysonCui 5 years ago
parent
commit
fdbc0065fe
66 changed files with 1366 additions and 475 deletions
  1. 37 0
      front/project/admin/routes/setting/time/page.js
  2. 2 2
      front/project/admin/routes/textbook/topic/page.js
  3. 9 9
      front/project/admin/routes/textbook/topicDetail/page.js
  4. 8 0
      front/project/admin/stores/system.js
  5. 3 0
      front/project/h5/routes/textbook/detail/page.js
  6. 8 6
      front/project/www/components/Date/index.js
  7. 1 1
      front/project/www/components/Date/index.less
  8. 11 10
      front/project/www/components/Item/index.js
  9. 9 2
      front/project/www/components/Login/index.js
  10. 2 0
      front/project/www/components/Modal/index.js
  11. 1 1
      front/project/www/components/Other/index.js
  12. 29 17
      front/project/www/components/OtherModal/index.js
  13. 2 2
      front/project/www/components/PayModal/index.js
  14. 2 2
      front/project/www/components/UserPagination/index.js
  15. 75 47
      front/project/www/components/Video/index.js
  16. 41 18
      front/project/www/components/Video/index.less
  17. 10 9
      front/project/www/routes/course/detail/index.less
  18. 19 15
      front/project/www/routes/course/detail/page.js
  19. 1 1
      front/project/www/routes/course/main/index.less
  20. 4 8
      front/project/www/routes/course/main/page.js
  21. 1 1
      front/project/www/routes/course/online/page.js
  22. 1 1
      front/project/www/routes/my/tools/page.js
  23. 4 4
      front/project/www/routes/page/cart/page.js
  24. 329 30
      front/project/www/routes/textbook/main/page.js
  25. 1 1
      front/project/www/routes/textbook/topic/index.js
  26. 109 14
      front/project/www/routes/textbook/topic/page.js
  27. 1 1
      front/project/www/routes/textbook/topicDetail/index.js
  28. 91 30
      front/project/www/routes/textbook/topicDetail/page.js
  29. 81 7
      front/project/www/routes/textbook/year/page.js
  30. 7 7
      front/project/www/static/login.html
  31. 3 3
      front/project/www/stores/my.js
  32. 10 6
      front/project/www/stores/textbook.js
  33. 5 7
      front/project/www/stores/user.js
  34. 1 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/SettingKey.java
  35. 12 12
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookTopic.java
  36. 16 16
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserTextbookEnroll.java
  37. 12 12
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserTextbookFeedback.java
  38. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookTopicMapper.xml
  39. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserTextbookEnrollMapper.xml
  40. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserTextbookFeedbackMapper.xml
  41. 9 0
      server/data/src/main/java/com/qxgmat/data/relation/UserOrderRecordRelationMapper.java
  42. 2 5
      server/data/src/main/java/com/qxgmat/data/relation/UserTextbookEnrollRelationMapper.java
  43. 9 10
      server/data/src/main/java/com/qxgmat/data/relation/entity/TextbookEnrollNumberRelation.java
  44. 37 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserOrderRecordRelationMapper.xml
  45. 16 12
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserTextbookEnrollRelationMapper.xml
  46. 17 4
      server/data/src/main/resources/db/migration/V1__init_table.sql
  47. 17 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  48. 5 5
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/TextbookController.java
  49. 0 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  50. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  51. 78 50
      server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java
  52. 9 9
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/TextbookTopicInfoDto.java
  53. 36 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/TextbookEnrollTimeExtendDto.java
  54. 5 5
      server/gateway-api/src/main/java/com/qxgmat/dto/request/TextbookEnrollDto.java
  55. 9 9
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserTextbookFeedbackDto.java
  56. 13 20
      server/gateway-api/src/main/java/com/qxgmat/dto/response/TextbookEnrollTimeDto.java
  57. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java
  58. 5 3
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  59. 91 6
      server/gateway-api/src/main/java/com/qxgmat/service/extend/TextbookService.java
  60. 10 0
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ToolsService.java
  61. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryHistoryService.java
  62. 2 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryService.java
  63. 16 12
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookTopicService.java
  64. 5 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java
  65. 6 10
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserTextbookEnrollService.java
  66. 1 1
      server/tools/src/main/java/com/nuliji/tools/Tools.java

+ 37 - 0
front/project/admin/routes/setting/time/page.js

@@ -78,6 +78,9 @@ export default class extends Page {
     if (tab === 'paper') {
       return this.refreshExercisePaperAuto();
     }
+    if (tab === 'textbook') {
+      return this.refreshTextbookConfig();
+    }
     return Promise.reject();
   }
 
@@ -132,6 +135,15 @@ export default class extends Page {
     });
   }
 
+  refreshTextbookConfig() {
+    return System.getTextbookConfig().then((result) => {
+      this.setState({ textbookConfig: result || {} });
+
+      const { form } = this.props;
+      form.setFieldsValue(flattenObject(result, 'textbookConfig'));
+    });
+  }
+
   structExercise() {
     return Exercise.allStruct().then(result => {
       const list = result.map(row => { row.title = `${row.titleZh}/${row.titleEn}`; return row; });
@@ -356,6 +368,28 @@ export default class extends Page {
     </Form>;
   }
 
+  renderTextbookConfig() {
+    const { getFieldDecorator } = this.props.form;
+    return <Form>
+      <Row>
+        <Col span={12}>
+          <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='每月基础人数'>
+            {getFieldDecorator('textbookConfig.use_number', {
+              rules: [
+                { required: true, message: '输入每月基础人数' },
+              ],
+            })(
+              <InputNumber placeholder='请输入每月基础人数' onChange={(value) => {
+                this.changeValue('textbookConfig', 'use_number', value);
+              }} style={{ width: '200px' }} />,
+            )}
+          </Form.Item>
+        </Col>
+      </Row>
+    </Form>;
+  }
+
+
   renderExamination() {
     return <TableLayout
       columns={this.examinationColumns}
@@ -386,6 +420,9 @@ export default class extends Page {
         <Tabs.TabPane tab="组卷" key="paper">
           {this.renderExercisePaperAuto()}
         </Tabs.TabPane>
+        <Tabs.TabPane tab="机经" key="textbook">
+          {this.renderTextbookConfig()}
+        </Tabs.TabPane>
       </Tabs>
       {tab !== 'examination' && <Row type="flex" justify="center">
         <Col>

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

@@ -30,7 +30,7 @@ export default class extends Page {
       },
     }];
     this.filterForm = [{
-      key: 'questionSubject',
+      key: 'textbookSubject',
       type: 'select',
       name: '单项',
       select: TextbookSubject,
@@ -52,7 +52,7 @@ export default class extends Page {
     }];
     this.columns = [{
       title: '单项',
-      dataIndex: 'questionSubject',
+      dataIndex: 'textbookSubject',
       render: (text) => {
         return TextbookSubjectMap[text] || '';
       },

+ 9 - 9
front/project/admin/routes/textbook/topicDetail/page.js

@@ -16,7 +16,7 @@ import { Textbook } from '../../../stores/textbook';
 export default class extends Page {
   init() {
     this.filterForm = [{
-      key: 'questionSubject',
+      key: 'testbookSubject',
       type: 'select',
       name: '单项',
       select: TextbookSubject,
@@ -55,8 +55,8 @@ export default class extends Page {
   }
 
   refreshLibrary() {
-    const { libraryId, questionSubject } = this.state.search;
-    Textbook.getNextTopic({ libraryId, questionSubject }).then(result => {
+    const { libraryId, testbookSubject } = this.state.search;
+    Textbook.getNextTopic({ libraryId, testbookSubject }).then(result => {
       this.setState({ no: result });
       this.addTopic();
     });
@@ -94,8 +94,8 @@ export default class extends Page {
         const data = form.getFieldsValue();
         let handler;
         if (!this.params.id) {
-          const { libraryId, questionSubject } = this.state.search;
-          handler = Promise.all([data.topic.filter(row => row).map((row, index) => Textbook.addTopic(Object.assign({ libraryId, questionSubject }, row, { isOld: row.isOld ? 1 : 0, no: this.state.no + index })))]);
+          const { libraryId, textbookSubject } = this.state.search;
+          handler = Promise.all([data.topic.filter(row => row).map((row, index) => Textbook.addTopic(Object.assign({ libraryId, textbookSubject }, row, { isOld: row.isOld ? 1 : 0, no: this.state.no + index })))]);
         } else {
           handler = Textbook.editTopic(Object.assign({ id: this.params.id }, data.topic[0], { isOld: data.topic[0].isOld }));
         }
@@ -111,7 +111,7 @@ export default class extends Page {
     });
   }
 
-  renderInfo(index, questionSubject, no) {
+  renderInfo(index, textbookSubject, no) {
     const { getFieldDecorator } = this.props.form;
     return <Block flex>
       <Row>
@@ -136,7 +136,7 @@ export default class extends Page {
                   message: '请选择',
                 }],
               })(
-                questionSubject === 'quant' ? <Select select={TextbookType} /> : <Input />,
+                textbookSubject === 'quant' ? <Select select={TextbookType} /> : <Input />,
               )}
             </Form.Item>
           </Col>
@@ -189,7 +189,7 @@ export default class extends Page {
           this.search(data);
         }} />
     </Block>
-      {keys.map((key, index) => this.renderInfo(key, this.state.search.questionSubject, this.state.no + index))}
+      {keys.map((key, index) => this.renderInfo(key, this.state.search.textbookSubject, this.state.no + index))}
       {this.state.no && <Row type="flex" justify="center">
         <Col>
           <Form.Item>
@@ -203,7 +203,7 @@ export default class extends Page {
   }
 
   renderEdit() {
-    return this.renderInfo(0, this.state.data.questionSubject, this.state.data.no);
+    return this.renderInfo(0, this.state.data.textbookSubject, this.state.data.no);
   }
 
   renderView() {

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

@@ -93,6 +93,14 @@ export default class SystemStore extends BaseStore {
     return this.apiPut('/setting/textbook_time', params);
   }
 
+  getTextbookConfig() {
+    return this.apiGet('/setting/textbook_config');
+  }
+
+  setTextbookConfig(params) {
+    return this.apiPut('/setting/textbook_config', params);
+  }
+
   getExaminationTime() {
     return this.apiGet('/setting/examination_time');
   }

+ 3 - 0
front/project/h5/routes/textbook/detail/page.js

@@ -50,6 +50,9 @@ export default class extends Page {
   initData() {
     Textbook.getInfo()
       .then(result => {
+        if (!result.hasService) {
+          linkTo('/textbook');
+        }
         this.setState(result);
       });
     const { subject } = this.params;

+ 8 - 6
front/project/www/components/Date/index.js

@@ -98,7 +98,8 @@ export class TwoDate extends Component {
   }
 
   onLeftSelect(date) {
-    const { onChange } = this.props;
+    const { onChange, startDate } = this.props;
+    if (startDate && date.isBefore(startDate)) return;
     this.setState({
       value: date,
       leftValue: date,
@@ -108,7 +109,8 @@ export class TwoDate extends Component {
   }
 
   onRightSelect(date) {
-    const { onChange } = this.props;
+    const { onChange, endDate } = this.props;
+    if (endDate && date.isAfter(endDate)) return;
     this.setState({
       value: date,
       rightValue: date,
@@ -131,7 +133,7 @@ export class TwoDate extends Component {
           <Icon type="left" onClick={() => this.onLeftSelect(leftValue.clone().subtract(1, 'month'))} />
         </div>
         <span>{leftValue.year()}年 </span>
-        <span>{leftValue.month()}月 </span>
+        <span>{leftValue.month() + 1}月 </span>
         <span className="t-4">{extendInfo && extendInfo(leftValue)}</span>
       </div>
     );
@@ -143,14 +145,14 @@ export class TwoDate extends Component {
     return (
       <div className="t-c">
         <span>{rightValue.year()}年 </span>
-        <span>{rightValue.month()}月 </span>
+        <span>{rightValue.month() + 1}月 </span>
         <span className="t-4">{extendInfo && extendInfo(rightValue)}</span>
         <div style={{ right: 15, top: 0 }} className="p-a">
-          <Icon type="right" onClick={() => this.onRightSelect(rightValue.clone().add(1, 'year'))} />
+          <Icon type="right" onClick={() => this.onRightSelect(rightValue.clone().add(1, 'month'))} />
           <Icon
             className="m-l-5"
             type="double-right"
-            onClick={() => this.onRightSelect(rightValue.clone().add(1, 'month'))}
+            onClick={() => this.onRightSelect(rightValue.clone().add(1, 'year'))}
           />
         </div>
       </div>

+ 1 - 1
front/project/www/components/Date/index.less

@@ -204,7 +204,7 @@
   .ant-fullcalendar-last-month-btn-day,
   .ant-fullcalendar-next-month-btn-day {
     .ant-fullcalendar-value {
-      color: rgba(0, 0, 0, .65) !important;
+      color: rgba(0, 0, 0, .25) !important;
       background: #fff !important;
     }
   }

+ 11 - 10
front/project/www/components/Item/index.js

@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
 import './index.less';
 import Assets from '@src/components/Assets';
-import { getMap, formatSeconds } from '@src/services/Tools';
+import { getMap, formatSeconds, formatDate } from '@src/services/Tools';
 import Button from '../Button';
 import More from '../More';
 import { Order } from '../../stores/order';
@@ -29,10 +29,11 @@ export class SingleItem extends Component {
 
   add() {
     const { data } = this.props;
-    User.needLogin().thenn(() => {
-      Order.addCheckout({ productType: 'course', productId: data.id }).then(() => {
-        this.setState({ add: true });
-      });
+    User.needLogin().then(() => {
+      Order.addCheckout({ productType: 'course', productId: data.id })
+        .then(() => {
+          this.setState({ add: true });
+        });
     });
   }
 
@@ -291,17 +292,17 @@ export class TextbookItem extends Component {
   }
 
   render() {
-    const { data = {} } = this.props;
+    const { data = {}, menu, onClick, onMenuClick } = this.props;
     return (
       <div className="textbook-item">
-        <Assets src={data.src} className="m-b-5" />
+        <Assets name="sun_blue" className="m-b-5" onClick={() => onClick()} />
         <div className="t-6 t-s-14 m-b-5">
-          已更新至 <span className="t-4">30</span> 题
+          已更新至 <span className="t-4">{data.number}</span> 题
         </div>
         <div className="t-6 t-s-12 m-b-1">
-          2019-08-31 09:26:13{' '}
+          {formatDate(data.time, 'YYYY-MM-DD HH:mm:ss')}
           <div className="f-r">
-            <More />
+            <More menu={menu} onClick={onMenuClick} />
           </div>
         </div>
       </div>

+ 9 - 2
front/project/www/components/Login/index.js

@@ -36,7 +36,11 @@ export default class Login extends Component {
       },
       false,
     );
-    if (!props.user.login) {
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (nextProps.user.needLogin && !this.init) {
+      this.init = true;
       Main.getContract('register')
         .then(result => {
           this.setState({ registerContract: result });
@@ -101,7 +105,10 @@ export default class Login extends Component {
       } else {
         this.setState({ type: BIND_PHONE });
       }
-    });
+    })
+      .catch(err => {
+        this.setState({ type: BIND_WX_ERROR, wechatError: err.message });
+      });
   }
 
   scanBind(code) {

+ 2 - 0
front/project/www/components/Modal/index.js

@@ -23,12 +23,14 @@ export default class extends Component {
       btnType,
       center,
       height,
+      getContainer,
     } = this.props;
     return (
       <Modal
         wrapClassName={`g-modal ${className || ''}`}
         visible={show}
         closable={false}
+        getContainer={getContainer}
         maskClosable={maskClosable}
         footer={false}
         width={width}

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

@@ -94,7 +94,7 @@ export class AnswerCarousel extends Component {
               })}
             </Carousel>
           )}
-          <div className="fixed" />
+          {!tabs && <div className="fixed" />}
           <Assets name="footer_next_highlight_1" className="next" onClick={() => this.onNext()} />
           <Assets name="footer_previous_highlight_1" className="prev" onClick={() => this.onPrev()} />
         </div>

+ 29 - 17
front/project/www/components/OtherModal/index.js

@@ -8,7 +8,7 @@ import Assets from '@src/components/Assets';
 import scale from '@src/services/Scale';
 import { asyncSMessage } from '@src/services/AsyncTools';
 import { SelectInput, VerificationInput, Input } from '../Login';
-import { MobileArea, TextbookFeedbackTarget } from '../../../Constant';
+import { MobileArea, TextbookFeedbackTarget, TextbookSubject } from '../../../Constant';
 import Invite from '../Invite';
 import Modal from '../Modal';
 import { Common } from '../../stores/common';
@@ -17,7 +17,7 @@ import { My } from '../../stores/my';
 import Select from '../Select';
 import { formatDate, getMap } from '../../../../src/services/Tools';
 
-const TextbookFeedbackTargetMap = getMap(TextbookFeedbackTarget, 'value', 'label');
+const TextbookFeedbackTargetMap = getMap(TextbookFeedbackTarget, 'value', 'tip');
 
 export class BindPhone extends Component {
   constructor(props) {
@@ -816,7 +816,7 @@ export class AskCourseModal extends Component {
   }
 
   render() {
-    const { show, selectList, courseNo } = this.props;
+    const { show, selectList, courseNo, getContainer } = this.props;
     const { data } = this.state;
     return (
       <Modal
@@ -824,6 +824,7 @@ export class AskCourseModal extends Component {
         title="提问"
         btnType="link"
         width={630}
+        getContainer={getContainer}
         confirmText="提交"
         onConfirm={() => this.onConfirm()}
         onCancel={() => this.onCancel()}
@@ -898,13 +899,14 @@ export class CourseNoteModal extends Component {
   }
 
   render() {
-    const { show, course = {}, courseNos = [], noteMap = {} } = this.props;
+    const { show, course = {}, courseNos = [], noteMap = {}, getContainer } = this.props;
     const { data } = this.state;
     return (
       <Modal
         show={show}
         title="笔记"
         width={630}
+        getContainer={getContainer}
         confirmText="提交"
         onConfirm={() => this.onConfirm()}
         onCancel={() => this.onCancel()}
@@ -948,7 +950,21 @@ export class CourseNoteModal extends Component {
 export class TextbookFeedbackModal extends Component {
   constructor(props) {
     super(props);
-    this.state = { data: {} };
+    this.state = {
+      data: {},
+      targetList: TextbookFeedbackTarget.map(row => {
+        return {
+          title: row.label,
+          key: row.value,
+        };
+      }),
+      textbookSubject: TextbookSubject.map(row => {
+        return {
+          title: row.label,
+          key: row.value,
+        };
+      }),
+    };
   }
 
   componentWillReceiveProps(nextProps) {
@@ -962,7 +978,7 @@ export class TextbookFeedbackModal extends Component {
     const { data } = this.state;
     if (!data.content) return;
     if (data.target !== 'new' && !data.no) return;
-    My.addTextbookFeedback(data.questionSubject, data.target, data.no, data.content).then(() => {
+    My.addTextbookFeedback(data.textbookSubject, data.target, data.no, data.content).then(() => {
       if (onConfirm) onConfirm();
       this.setState({ data: {} });
     });
@@ -976,21 +992,17 @@ export class TextbookFeedbackModal extends Component {
 
   render() {
     const { show } = this.props;
-    const { data } = this.state;
+    const { data, targetList, textbookSubject } = this.state;
     return (
       <Modal show={show} title="反馈" width={630} onConfirm={() => this.onConfirm()} onCancel={() => this.onCancel()}>
         <div className="t-2 t-s-16 m-b-1">
           机经类别:{' '}
           <Select
-            value={data.questionSubject}
+            value={data.textbookSubject}
             theme="white"
-            list={[
-              { title: '数学机经', key: 'quant' },
-              { title: '逻辑机经', key: 'rc' },
-              { title: '阅读机经', key: 'ir' },
-            ]}
+            list={textbookSubject}
             onChange={value => {
-              data.questionSubject = value;
+              data.textbookSubject = value;
               this.setState({ data });
             }}
           />
@@ -998,9 +1010,9 @@ export class TextbookFeedbackModal extends Component {
           <Select
             value={data.target}
             theme="white"
-            list={TextbookFeedbackTarget}
-            onChange={value => {
-              data.target = value;
+            list={targetList}
+            onChange={({ key }) => {
+              data.target = key;
               this.setState({ data });
             }}
           />

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

@@ -164,7 +164,7 @@ export class PayModal extends Component {
         checkout={order.checkouts[0]}
         onConfirm={() => this.close()}
       />,
-      show && (order.checkouts.length > 1 || order.productTypes.indexof('course_package') < 0) && <PayMutilModal
+      show && (order.checkouts.length > 1 || order.productTypes.indexOf('course_package') < 0) && <PayMutilModal
         show
         contract={contract}
         order={order}
@@ -173,7 +173,7 @@ export class PayModal extends Component {
         onClose={() => this.close()}
         onConfirm={() => this.confirm()}
       />,
-      show && order.checkouts.length === 1 && order.productTypes.indexof('course_package') < 0 && <PayMModal
+      show && order.checkouts.length === 1 && order.productTypes.indexOf('course_package') < 0 && <PayMModal
         show
         contract={contract}
         order={order}

+ 2 - 2
front/project/www/components/UserPagination/index.js

@@ -30,8 +30,8 @@ export default class extends Component {
               size="small"
               theme="white"
               value={current}
-              list={[...Array(all)].map((key, index) => ({ title: `第${index + 1}页`, key: index + 1 }))}
-              onChange={key => this.onChangePage(key)}
+              list={all > 0 ? [...Array(all)].map((key, index) => ({ title: `第${index + 1}页`, key: index + 1 })) : []}
+              onChange={({ key }) => this.onChangePage(key)}
             />
           </div>
         )}

+ 75 - 47
front/project/www/components/Video/index.js

@@ -1,4 +1,5 @@
 import React, { Component } from 'react';
+import { Slider } from 'antd';
 import './index.less';
 import videojs from 'video.js';
 import Assets from '@src/components/Assets';
@@ -54,20 +55,22 @@ export default class Video extends Component {
   constructor(props) {
     super(props);
     this.ready = false;
-    this.state = { id: generateUUID(8), playing: false, fulling: false };
+    this.state = { id: generateUUID(8), playing: false, fulling: false, progress: 0 };
   }
 
   componentDidMount() {
     this.player = videojs(
       this.videoNode,
       {
-        controls: false,
+        controls: true,
         sources: [
           {
             src: this.props.src,
             type: 'video/mp4',
           },
         ],
+        width: this.props.width,
+        height: this.props.height,
       },
       () => {
         this.ready = true;
@@ -81,6 +84,13 @@ export default class Video extends Component {
     }
   }
 
+  clearTimeUpdate() {
+    if (this.timeInterval) {
+      clearInterval(this.timeInterval);
+      this.timeInterval = null;
+    }
+  }
+
   refreshTimeUpdate() {
     if (this.timeInterval) {
       clearInterval(this.timeInterval);
@@ -89,7 +99,14 @@ export default class Video extends Component {
     this.timeInterval = setInterval(() => {
       const { onTimeUpdate } = this.props;
       if (onTimeUpdate) onTimeUpdate(this.player.currentTime());
-    }, this.props.duration ? this.props.duration * 1000 : 1000);
+      // this.setState({ progress: this.player.currentTime() * 100 / this.player.duration() });
+    }, 1000);
+  }
+
+  onChangeProgress(value) {
+    if (!this.ready) return;
+    this.player.currentTime(this.player.duration() * value / 100);
+    this.setState({ progress: this.player.currentTime() * 100 / this.player.duration() });
   }
 
   onPlay() {
@@ -98,6 +115,7 @@ export default class Video extends Component {
     this.player.play();
     this.setState({ playing: true });
     if (onPlay) onPlay();
+    this.refreshTimeUpdate();
   }
 
   onPause() {
@@ -106,11 +124,13 @@ export default class Video extends Component {
     this.player.pause();
     this.setState({ playing: false });
     if (onPause) onPause();
+    this.clearTimeUpdate();
   }
 
   onNext() {
     const { onNext } = this.props;
     this.player.pause();
+    this.clearTimeUpdate();
     this.setState({ playing: false });
     if (onNext) onNext();
   }
@@ -134,64 +154,72 @@ export default class Video extends Component {
   }
 
   render() {
-    const { action = true, btnList = [], children, onAction, hideAction } = this.props;
+    const { btnList = [], children, onAction, hideAction } = this.props;
     const { playing, fulling, id } = this.state;
     return (
-      <div id={id} className={`video-item ${action ? 'action' : ''} ${fulling ? 'full' : ''}`}>
+      <div id={id} className={`video-item ${!hideAction ? 'action' : ''} ${fulling ? 'full' : ''}`}>
         <div className="video-wrapper">
           <video
             ref={node => {
               this.videoNode = node;
             }}
+          // vjs-fluid
           />
           {!playing && <Assets className="play" name="play" onClick={() => this.onPlay()} />}
-          {playing && <Assets className="stop" name="stop" onClick={() => this.onPuase()} />}
+          {playing && <Assets className="stop" name="stop" onClick={() => this.onPause()} />}
         </div>
-        {!hideAction && <div className="video-bottom">
+        <div className="video-bottom">
           <div className="progress" />
-          {action && (
-            <div className="action-bar">
-              <div className="d-i-b m-r-1">
-                {!playing && <Assets name="play2" onClick={() => this.onPlay()} />}
-                {playing && <Assets name="stop2" onClick={() => this.onPause()} />}
-              </div>
-              <div className="d-i-b m-r-1">
-                <Assets name="next2" onClick={() => this.onNext()} />
-              </div>
-              <div className="flex-block" />
-              {btnList.map(btn => {
-                if (btn.full && !fulling) return '';
-                if (!btn.show) return '';
-                return (
-                  <div className="d-i-b m-r-1">
-                    {btn.render ? (
-                      <div className="fix-btn d-i-b" onClick={() => {
-                        if (btn.pause) this.onPause();
-                        if (onAction) onAction(btn.key);
-                      }}>
-                        {btn.render(btn.active)}
-                      </div>
-                    ) : (<div
-                      className={`btn ${btn.active ? 'active' : ''}`}
-                      onClick={() => onAction && onAction(btn.key)}
-                    >
-                      {btn.title}
-                    </div>)}
-                  </div>
-                );
-              })}
-              <div className="d-i-b m-r-1">
-                <div className="btn">倍速</div>
-              </div>
-              <div className="d-i-b">
-                {!fulling && <Assets name="full2" onClick={() => this.onFull()} />}
-                {fulling && <Assets name="reduction2" onClick={() => this.onExitFull()} />}
-              </div>
+          {/* {this.renderProgress()} */}
+          {!hideAction && <div className="action-bar">
+            <div className="d-i-b m-r-1">
+              <Assets name={!playing ? 'play2' : 'stop2'} onClick={() => (playing ? this.onPause() : this.onPlay())} />
+              {/* {playing && <Assets name="stop2" onClick={() => this.onPause()} />} */}
+            </div>
+            <div className="d-i-b m-r-1">
+              <Assets name="next2" onClick={() => this.onNext()} />
+            </div>
+            {/* <div className="m-r-1">{this.ready ? (formatMinuteSecond(this.player.currentTime())) : ('00:00')}</div>
+            <div className="m-r-1">/{this.ready ? (formatMinuteSecond(this.player.duration())) : ('00:00')}</div> */}
+            <div className="flex-block" />
+            {btnList.map(btn => {
+              if (btn.full && !fulling) return '';
+              if (!btn.show) return '';
+              return (
+                <div className="d-i-b m-r-1">
+                  {btn.render ? (
+                    <div className="fix-btn-action d-i-b" onClick={() => {
+                      if (btn.pause) this.onPause();
+                      if (onAction) onAction(btn.key);
+                    }}>
+                      {btn.render(btn.active)}
+                    </div>
+                  ) : (<div
+                    className={`btn-action ${btn.active ? 'active' : ''}`}
+                    onClick={() => onAction && onAction(btn.key)}
+                  >
+                    {btn.title}
+                  </div>)}
+                </div>
+              );
+            })}
+            <div className="d-i-b m-r-1">
+              <div className="btn-action">倍速</div>
+            </div>
+            <div className="d-i-b">
+              {!fulling && <Assets name="full2" onClick={() => this.onFull()} />}
+              {fulling && <Assets name="reduction2" onClick={() => this.onExitFull()} />}
             </div>
-          )}
-        </div>}
+          </div>}
+        </div>
         {children}
       </div>
     );
   }
+
+  renderProgress() {
+    const { hideProgress } = this.props;
+    const { progress } = this.state;
+    return !hideProgress && <Slider value={progress || 0} tooltipVisible={false} onChange={(value) => this.onChangeProgress(value)} />;
+  }
 }

+ 41 - 18
front/project/www/components/Video/index.less

@@ -6,24 +6,24 @@
   overflow: hidden;
   background: #3A3A3AFF;
 
-  .video-wrapper {
+  // .video-wrapper {
 
-    .vjs-loading-spinner {
-      display: none;
-    }
+  //   .vjs-loading-spinner {
+  //     display: none;s
+  //   }
 
-    .vjs-big-play-button {
-      display: none;
-    }
+  //   .vjs-big-play-button {
+  //     display: none;
+  //   }
 
-    .vjs-control-bar {
-      display: none;
-    }
+  //   .vjs-control-bar {
+  //     display: none;
+  //   }
 
-    .vjs-modal-dialog {
-      display: none;
-    }
-  }
+  //   .vjs-modal-dialog {
+  //     display: none;
+  //   }
+  // }
 
   .video-bottom {
     position: absolute;
@@ -32,6 +32,29 @@
     right: 0;
   }
 
+  .ant-slider {
+    margin: 0;
+
+    .ant-slider-rail {
+      border-radius: 0;
+      background: #616161FF;
+    }
+
+    .ant-slider-track {
+      background: #4292f0;
+    }
+
+    .ant-slider-handle {
+      border: none;
+      background-color: transparent;
+      box-shadow: none;
+    }
+
+    .ant-slider-handle-click-focused {
+      box-shadow: none;
+    }
+  }
+
   .progress {
     height: 3px;
     background: #616161FF;
@@ -47,11 +70,11 @@
       cursor: pointer;
     }
 
-    .fix-btn {
+    .fix-btn-action {
       transform: translateY(-1px);
     }
 
-    .btn {
+    .btn-action {
       display: inline-block;
       font-size: 12px;
       line-height: 12px;
@@ -67,8 +90,8 @@
     //   background: darken(#696969, 10);
     // }
 
-    .btn.active,
-    .btn.active:hover {
+    .btn-action.active,
+    .btn-action.active:hover {
       background: #4292f0;
     }
   }

+ 10 - 9
front/project/www/routes/course/detail/index.less

@@ -61,15 +61,16 @@
             position: absolute;
 
             .tab-body {
-              padding: 0 15px;
+              padding: 5px 15px;
+
             }
 
             .answer-layout {
-              height: 480px;
+              height: 430px;
             }
 
             .item-layout {
-              height: 530px;
+              height: 480px;
             }
           }
         }
@@ -145,25 +146,25 @@
         }
       }
 
-      .right {
+      .detail-right {
         padding-left: 790px;
 
         .answer-layout {
-          height: 480px;
+          height: 430px;
         }
 
         .item-layout {
-          height: 530px;
+          height: 480px;
         }
       }
 
-      .right.progress {
+      .detail-right.have {
         .answer-layout {
-          height: 500px;
+          height: 480px;
         }
 
         .item-layout {
-          height: 550px;
+          height: 530px;
         }
       }
     }

+ 19 - 15
front/project/www/routes/course/detail/page.js

@@ -198,6 +198,7 @@ export default class extends Page {
     const { id } = this.params;
     Course.detail(id).then(result => {
       result = this.formatRecord(result);
+      result.have = true;
       this.setState({ data: result });
       // 选择课时
       if (this.state.search.no) {
@@ -211,7 +212,7 @@ export default class extends Page {
 
   refreshAsk(position) {
     const { id } = this.params;
-    const { item } = this.state;
+    const { item = {} } = this.state;
     Course.listAsk(Object.assign({ page: 1, size: 1000, courseId: id, courseNoId: item.id, position })).then(result => {
       this.setState({ asks: result.list });
     });
@@ -269,13 +270,13 @@ export default class extends Page {
   }
 
   onTimeUpdate(second) {
-    const { position, item, data } = this.state;
+    const { position, item = {}, data } = this.state;
     if (!data.have) {
       // 如果是试用,则按秒数增加
-      second += item.startTrail * 60;
+      second += (item.startTrail || 0) * 60;
     }
-    const minute = second / 60;
-    const nowPosition = (minute / 5) * 5;
+    const minute = parseInt(second / 60, 10);
+    const nowPosition = parseInt(minute / 5, 10) * 5;
     if (nowPosition !== position) {
       this.refreshAsk(position);
       this.setState({ position: nowPosition });
@@ -306,7 +307,7 @@ export default class extends Page {
   }
 
   onVideoAction(key) {
-    const { rightTab, showTab, showAsk, showNote, item } = this.state;
+    const { rightTab, showTab, showAsk, showNote, item = {} } = this.state;
     switch (key) {
       case 'ask':
         return this.setState({ showAsk: !showAsk });
@@ -366,7 +367,7 @@ export default class extends Page {
   }
 
   renderView() {
-    const { base = {}, data = {}, item = {}, add, progress, rightTab, showTab, showAsk, showNote, dataStructMap = {}, showComment, comment = {}, showFaq, faq = {}, showFinish, note = {}, ask = {}, timelineSelect = [] } = this.state;
+    const { base = {}, data = {}, item = {}, add, rightTab, showTab, showAsk, showNote, dataStructMap = {}, showComment, comment = {}, showFaq, faq = {}, showFinish, note = {}, ask = {}, timelineSelect = [] } = this.state;
     const { courseNos = [] } = data;
     return (
       <div>
@@ -390,7 +391,7 @@ export default class extends Page {
             </div>
           </div>
           <div className="t-2 m-b-1">授课老师:{data.teacher}</div>
-          <div className="detail">
+          <div className={'detail'}>
             <div className="left">
               {data.have && <div hidden={(item.paper && item.paper.times > 0)} className="left-top">
                 <span className="d-i-b m-r-1">预习作业</span>
@@ -404,8 +405,9 @@ export default class extends Page {
               <div className="video-layout">
                 {item && <Video
                   key={item.id}
-                  src={item.resource}
-                  duration={10}
+                  src={item.resource || '/1.mp4'}
+                  width={750}
+                  height={467}
                   ref={ref => this.setVideo(ref)}
                   btnList={[
                     { title: '提问', key: 'ask', show: data.have, active: showAsk, pause: true },
@@ -415,10 +417,11 @@ export default class extends Page {
                         return <Assets name={active ? 'question_on' : 'question_off'} />;
                       },
                       full: true,
+                      show: true,
                       active: showTab && rightTab === '1',
                     },
                     { title: '笔记', key: 'note', show: data.have, active: showNote, pause: true },
-                    { title: '课表', key: 'list', full: true, active: showTab && rightTab === '2' },
+                    { title: '课表', key: 'list', show: true, full: true, active: showTab && rightTab === '2' },
                   ]}
                   onPlay={() => this.playVideo()}
                   onPause={() => this.pauseVideo()}
@@ -441,7 +444,7 @@ export default class extends Page {
                 </Video>}
               </div>
             </div>
-            <div className={`right ${progress > 0 ? 'progress' : ''}  tab-warpper`}>
+            <div className={`detail-right ${data.have && !(item.paper && item.paper.times > 0) ? 'have' : ''}  tab-warpper`}>
               <Tabs
                 type="division"
                 theme="gray"
@@ -460,8 +463,8 @@ export default class extends Page {
         </div>
         <Contact data={base.contact} />
         <Footer />
-        <AskCourseModal show={showAsk} defaultData={ask} course={data} courseNo={item} selectList={timelineSelect} onConfirm={() => this.setState({ showAsk: false })} onCancel={() => this.setState({ showAsk: false })} />
-        <CourseNoteModal show={showNote} defaultData={note} course={data} courseNos={courseNos} noteMap={this.noteMap} onConfirm={() => {
+        <AskCourseModal getContainer={() => document.getElementById(this.video.state.id)} show={showAsk} defaultData={ask} course={data} courseNo={item} selectList={timelineSelect} onConfirm={() => this.setState({ showAsk: false })} onCancel={() => this.setState({ showAsk: false })} />
+        <CourseNoteModal getContainer={() => document.getElementById(this.video.state.id)} show={showNote} defaultData={note} course={data} courseNos={courseNos} noteMap={this.noteMap} onConfirm={() => {
           this.setState({ showNote: false });
           this.refreshNote();
         }} onCancel={() => this.setState({ showNote: false })} />
@@ -474,9 +477,10 @@ export default class extends Page {
         />
         <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false, showFinish: true })} />
         <FinishModal
+          getContainer={() => document.getElementById(this.video.state.id)}
           show={showFinish}
           onConfirm={() => this.setState({ showFinish: false })}
-        />,
+        />
       </div >
     );
   }

+ 1 - 1
front/project/www/routes/course/main/index.less

@@ -47,7 +47,7 @@
     .video-list {
       margin-bottom: 80px;
 
-      .video-item {
+      .video-div {
         display: inline-block;
         width: 580px;
         height: 360px;

+ 4 - 8
front/project/www/routes/course/main/page.js

@@ -61,16 +61,12 @@ export default class extends Page {
         <div className="block-2">
           <div className="main-title">找到你的Style</div>
           <div className="video-list">
-            <div className="video-item">
-              <div style={{ width: 70, height: 70 }}>
-                <Video src={courseIndex.onlineVideo} hideAction />
-              </div>
+            <div className="video-div">
+              <Video src={courseIndex.onlineVideo || '/1.mp4'} width={580} height={360} hideAction />
               <div className="name" onClick={() => linkTo('/course/online')}>在线课程 ></div>
             </div>
-            <div className="video-item">
-              <div style={{ width: 70, height: 70 }}>
-                <Video src={courseIndex.vsVideo} hideAction />
-              </div>
+            <div className="video-div">
+              <Video src={'/1.mp4'} width={580} height={360} hideAction />
               <div className="name" onClick={() => linkTo('/course/vs')}>1v1私教 ></div>
             </div>
           </div>

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

@@ -131,7 +131,7 @@ export default class extends Page {
           <Tabs type="text" active={'online'} tabs={[{ title: '在线课程', key: 'online', path: '/course/online' }, { title: '1v1私教', key: 'vs', path: '/course/vs' }]} />
           <div className="f-r">
             <span className="t-2 m-r-1">{(promote.video || {}).text ? `优惠活动:${promote.video.text}` : ''}</span>
-            <Assets name="cart" onClick={() => linkTo('cart')} />
+            <Assets name="cart" onClick={() => linkTo('/cart')} />
             <span className="t-2">( {number || 0} )</span>
           </div>
         </div>

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

@@ -573,7 +573,7 @@ export default class extends Page {
             {list.map(item => {
               return (
                 <div className="data-item">
-                  <Assets name="sun_blue" />
+                  <Assets name="sun_blue" onClick={() => linkTo(`/textbook/topic/list/${item.subject}`)} />
                   <div className="title">
                     已更新至<b>{item.num}</b>题
                   </div>

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

@@ -30,10 +30,10 @@ export default class extends Page {
   }
 
   initData() {
-    Order.getOrder(34)
-      .then((order) => {
-        User.needPay(order);
-      });
+    // Order.getOrder(34)
+    //   .then((order) => {
+    //     User.needPay(order);
+    //   });
     Order.allCheckout()
       .then(result => {
         User.formatOrder(result);

+ 329 - 30
front/project/www/routes/textbook/main/page.js

@@ -3,42 +3,304 @@ import { Link } from 'react-router-dom';
 import './index.less';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
+import { getMap, formatDate } from '@src/services/Tools';
+import { CommentModal, FaqModal, TextbookFeedbackModal, FinishModal } from '../../../components/OtherModal';
 import { CommentFalls, AnswerCarousel, Consultation, Contact } from '../../../components/Other';
+import Modal from '../../../components/Modal';
 import Footer from '../../../components/Footer';
 import Button from '../../../components/Button';
 import UserTable from '../../../components/UserTable';
 import Tabs from '../../../components/Tabs';
 import { TextbookItem } from '../../../components/Item';
 import { TwoDate } from '../../../components/Date';
+import { Main } from '../../../stores/main';
+import { Textbook } from '../../../stores/textbook';
+import { Order } from '../../../stores/order';
+import { User } from '../../../stores/user';
+import { TextbookFeedbackTarget } from '../../../../Constant';
+
+const textbookHistoryColumns = [
+  {
+    title: '更新时间',
+    key: 'createTime',
+    width: 120,
+    render: (text) => {
+      return <div className="sub">
+        <div className="t-2 t-s-12">{text.split(' ')[0]}</div>
+        <div className="t-6 t-s-12">{text.split(' ')[1]}</div>
+      </div>;
+    },
+  },
+  { title: '版本', key: 'version', width: 120 },
+  { title: '更新内容', key: 'content', width: 330 },
+];
 
 export default class extends Page {
   initState() {
     return {
-      list: [{}, {}, {}],
+      tab: 'baselibrary',
+      list: [],
+      enroll: {},
+      load: 0,
     };
   }
 
+  init() {
+    this.enrollMap = {};
+    this.libraryMap = {};
+    Main.getBase()
+      .then(result => {
+        this.setState({ base: 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(result.expireTime).getTime() - new Date().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 });
+    });
+  }
+
+  initData() {
+    this.refreshFaqs(this.state.tab);
+    this.refreshComments();
+    const start = new Date();
+    start.setMinutes(0, 0, 0);
+    start.setHours(0);
+    start.setDate(1);
+    start.setMonth(start.getMonth() - 2);
+    const end = new Date();
+    end.setMinutes(0, 0, 0);
+    end.setHours(0);
+    end.setDate(1);
+    end.setMonth(end.getMonth() + 4);
+    const startDate = formatDate(start, 'YYYY-MM-DD');
+    const endDate = formatDate(end, 'YYYY-MM-DD');
+    this.refreshEnroll(startDate, endDate);
+    this.setState({ startDate, endDate });
+
+    const nowYear = new Date().getFullYear();
+    this.refreshYear(nowYear);
+    if (nowYear > start.getFullYear()) {
+      this.refreshYear(nowYear - 1);
+    }
+  }
+
+  refreshFaqs(tab) {
+    Main.listFaq({ page: 1, size: 1000, channel: `library-${tab}` })
+      .then((result => {
+        this.setState({ faqs: result.list });
+      }));
+  }
+
+  refreshComments() {
+    Main.listComment({ page: 1, size: 1000, channel: 'library' })
+      .then(result => {
+        this.setState({ comments: result.list });
+      });
+  }
+
+  refreshEnroll(startDate, endDate) {
+    const month = formatDate(new Date(), 'YYYY-MM');
+    Textbook.listEnroll(startDate, endDate)
+      .then(result => {
+        result.times = result.times.map(row => {
+          row.month = formatDate(row.month, 'YYYY-MM');
+          if (row.month === month) {
+            // 本月机经开通人数
+            this.setState({ useNumber: row.useNumber });
+          }
+          return row;
+        });
+        this.enrollMap = getMap(result.times, 'month');
+        if (result.date) {
+          const d = new Date(result.date);
+          result.dateF = formatDate(d, 'YYYY-MM-DD');
+          result.day = parseInt((d.getTime() - new Date().getTime()) / 86400000, 10);
+        }
+        this.setState({ enroll: result, load: this.state.load + 1 });
+      });
+  }
+
+  refreshYear(year) {
+    Textbook.listYear(year)
+      .then(result => {
+        result = result.map(row => {
+          row.day = formatDate(row.startDate, 'YYYY-MM-DD');
+          this.libraryMap[row.day] = row;
+          return row;
+        });
+        this.setState({ library: result, load: this.state.load + 1 });
+      });
+  }
+
+  textbookHistory({ page, size, subject }) {
+    this.setState({ subject });
+    Textbook.allHistory(subject).then(result => {
+      this.setState({
+        showUpdate: true,
+        updateList: result.map(row => {
+          row.version = row[`${subject}Version`];
+          row.content = row[`${subject}Content`];
+          row.createTime = formatDate(row.createTime, 'YYYY-MM-DD HH:mm:ss');
+          return row;
+        }),
+        // 不显示分页
+        updateTotal: 0,
+        maxHeight: 730,
+        updatePage: page,
+        updateData: { page, size, subject, columns: textbookHistoryColumns, type: 'textbook' },
+      });
+    });
+  }
+
+  enroll() {
+    const { date, enroll } = this.state;
+    if (enroll.date) return;
+    if (!date) {
+      this.setState({ showWarn: true, warn: { title: '报名', content: '请先选择报考日期' } });
+      return;
+    }
+    User.needLogin()
+      .then(() => {
+        Textbook.enroll(date.format('YYYY-MM-DD'))
+          .then(() => {
+            enroll.date = new Date(date);
+            enroll.dateF = date.format('YYYY-MM-DD');
+            enroll.day = parseInt((enroll.date.getTime() - new Date().getTime()) / 86400000, 10);
+            this.setState({ showWarn: true, warn: { title: '报名', content: `已报考${formatDate(date, 'YYYY-MM-DD')}` } });
+            this.setState({ enroll });
+          });
+      });
+  }
+
+  unEnroll() {
+    const { enroll } = this.state;
+    if (!enroll.date) return;
+    User.needLogin()
+      .then(() => {
+        Textbook.unEnroll()
+          .then(() => {
+            this.setState({ enroll: {} });
+          });
+      });
+  }
+
+  onTabChange(key) {
+    this.refreshFaqs(key);
+    this.setState({ tab: key });
+  }
+
+  open(recordId) {
+    User.needLogin()
+      .then(() => {
+        Order.useRecord(recordId)
+          .then(() => {
+            this.refresh();
+          });
+      });
+  }
+
+  buy() {
+    User.needLogin()
+      .then(() => {
+        return Order.speedPay({ productType: 'service', service: 'textbook' });
+      })
+      .then((order) => {
+        return User.needPay(order);
+      })
+      .then(() => {
+        this.refresh();
+      });
+  }
+
   renderView() {
+    const { data = {}, base = {}, tab, faqs = [], comments = [], showFaq, faq = {}, showFinish, showComment, comment = {}, showUpdate, updateData = {}, updateList = [], updateTotal, maxHeight, showFeedback, feedback = {}, showWarn, warn = {} } = this.state;
     return (
       <div>
         {this.renderDate()}
-        {this.renderLog()}
-        {this.renderCompare()}
-        {this.renderList()}
+        {!data.hasService && data.unUseRecord && this.renderLog()}
+        {!data.hasService && !data.unUseRecord && this.renderCompare()}
+        {data.hasService && this.renderList()}
         <AnswerCarousel
           hideBtn
-          tabActive={'1'}
-          tabs={[{ title: '换库知识', key: '1' }, { title: '机经知识', key: '2' }, { title: '千行机经', key: '3' }]}
+          tabActive={tab}
+          list={faqs}
+          tabs={[{ title: '换库知识', key: 'baselibrary' }, { title: '机经知识', key: 'basetextbook' }, { title: '千行机经', key: 'qxtextbook' }]}
+          onTabChange={(key) => this.onTabChange(key)}
         />
-        <CommentFalls />
+        <CommentFalls list={comments} />
         <Consultation />
-        <Contact />
+        <Contact data={base.contact} />
         <Footer />
+
+        <Modal show={showWarn} title={warn.title} confirmText="好的,知道了" btnAlign="center" onConfirm={() => this.setState({ showWarn: false })}>
+          <div className="t-2 t-s-18">{warn.content}</div>
+        </Modal>,
+        <Modal
+          show={showUpdate}
+          maskClosable
+          close={false}
+          body={false}
+          width={630}
+          onClose={() => this.setState({ showUpdate: false, updateList: [] })}
+        >
+          <UserTable
+            size="small"
+            theme="top"
+            columns={updateData.columns}
+            data={updateList}
+            current={updateData.page}
+            pageSize={updateData.size}
+            onChange={page => {
+              updateData.page = page;
+              if (updateData.type === 'data') {
+                this.dataHistory(updateData);
+              } else if (updateData.type === 'textbook') {
+                this.textbookHistory(updateData);
+              } else if (updateData.type === 'record') {
+                this.recordList(updateData);
+              }
+            }}
+            total={updateTotal}
+            maxHeight={maxHeight}
+          />
+        </Modal>
+        <TextbookFeedbackModal
+          show={showFeedback}
+          defaultData={feedback}
+          onConfirm={() => this.setState({ showFeedback: false, showFinish: true })}
+          onCancel={() => this.setState({ showFeedback: false })}
+          onClose={() => this.setState({ showFeedback: false })}
+        />
+        <CommentModal
+          show={showComment}
+          defaultData={comment}
+          onConfirm={() => this.setState({ showComment: false, showFinish: true })}
+          onCancel={() => this.setState({ showComment: false })}
+          onClose={() => this.setState({ showComment: false })}
+        />
+        <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false, showFinish: true })} />
+        <FinishModal
+          getContainer={() => document.getElementById(this.video.state.id)}
+          show={showFinish}
+          onConfirm={() => this.setState({ showFinish: false })}
+        />
       </div>
     );
   }
 
   renderDate() {
+    const { data, enroll = {}, useNumber, startDate, endDate, load } = this.state;
+    const { latest = {}, day } = data;
     return (
       <div className="date-layout">
         <div className="content">
@@ -47,31 +309,55 @@ export default class extends Page {
               <span className="today">今日</span>
               <span className="type-1">换库</span>
               <span className="type-2">考试日</span>
-              <Button size="small" radius>
+              {enroll.date && <span>
+                {enroll.day > 0 ? `距离考试还有${enroll.day}天` : `距离考试已过去${enroll.day * -1}天`}
+              </span>}
+              {enroll.date && <Button size="small" radius onClick={() => this.unEnroll()}>
+                取消报考
+              </Button>}
+              {!enroll.date && <Button size="small" radius onClick={() => this.enroll()}>
                 我已报考
-              </Button>
-              <Link to="" className="f-r">
+              </Button>}
+              <Link to="/textbook/year" className="f-r">
                 按年份查看 >
               </Link>
             </div>
             <TwoDate
-              getType={date => (date.date() === 1 ? 'type-1' : 'type-2')}
-              extendInfo={date => `${date.month()}人`}
-              onChange={() => {}}
+              key={load}
+              startDate={startDate}
+              endDate={endDate}
+              getType={date => {
+                const d = date.format('YYYY-MM-DD');
+                if (enroll.date && d === enroll.dateF) {
+                  return 'type-2';
+                }
+                if (this.libraryMap[d]) {
+                  return 'type-1';
+                }
+                return null;
+              }}
+              extendInfo={date => {
+                const d = date.format('YYYY-MM');
+                return `${this.enrollMap[d] ? this.enrollMap[d].enrollNumber : 0}人`;
+              }}
+              onChange={(date) => this.setState({ date })}
             />
           </div>
           <div style={{ width: 275 }} className="b f-r p-20">
             <div className="t-13 t-s-16">最近换库</div>
             <Assets name="" />
-            <div className="t-13 t-s-32 t-c">2019-07-22</div>
+            <div className="t-13 t-s-32 t-c">{latest.startDate ? formatDate(latest.startDate, 'YYYY-MM-DD') : ''}</div>
             <div className="t-13 t-c t-s-16">
-              已换库 <span className="t-4">10</span> 天
+              已换库 <span className="t-4">{day}</span> 天
             </div>
             <div className="m-t-2 t-c">
-              <Button width={100} radius size="lager">
+              <Button width={100} radius size="lager" onClick={() => User.needLogin().then(() => linkTo('/my/tools?tab=textbook'))}>
                 我的机经
               </Button>
             </div>
+            <div className="m-t-2 t-13 t-c t-s-14">
+              本月共{useNumber || 0}人使用机经
+            </div>
           </div>
         </div>
       </div>
@@ -84,7 +370,24 @@ export default class extends Page {
       <div className="list-layout">
         <div className="content">
           {list.map(item => {
-            return <TextbookItem data={item} />;
+            return <TextbookItem
+              data={item}
+              menu={[
+                { label: '更新', key: 'update' },
+                { label: '反馈', key: 'feedback' },
+                { label: '评价', key: 'comment' },
+              ]}
+              onClick={() => linkTo(`/textbook/topic/list/${item.subject}`)}
+              onMenuClick={value => {
+                const { key } = value;
+                if (key === 'comment') {
+                  this.setState({ showComment: true, comment: { channel: 'library' } });
+                } else if (key === 'update') {
+                  this.textbookHistory({ page: 1, size: 100, subject: item.subject });
+                } else if (key === 'feedback') {
+                  this.setState({ showFeedback: true, feedback: { questionSubject: item.subject, target: TextbookFeedbackTarget[0].value } });
+                }
+              }} />;
           })}
         </div>
       </div>
@@ -92,6 +395,7 @@ export default class extends Page {
   }
 
   renderLog() {
+    const { data, subject, updateList } = this.state;
     return (
       <div className="table-layout">
         <div className="content">
@@ -99,8 +403,9 @@ export default class extends Page {
             <span className="d-i-b t-1 t-s-18">更新日志</span>
             <Tabs
               type="text"
-              tabs={[{ title: '数学', key: '1' }, { title: '阅读RC', key: '2' }, { title: '逻辑RC', key: '3' }]}
-              active="1"
+              tabs={[{ title: '数学', key: 'quant' }, { title: '阅读RC', key: 'rc' }, { title: '逻辑IR', key: 'ir' }]}
+              active={subject}
+              onTabChange={(key) => this.textbookHistory({ subject: key })}
             />
           </div>
           <UserTable
@@ -110,15 +415,9 @@ export default class extends Page {
               { title: '版本', key: 'version' },
               { title: '更新内容', key: 'content' },
             ]}
-            data={[
-              {
-                date: '2019-07-12 \n 11:38:51',
-                version: '数学机经-版本 7',
-                content: '新增 7 道数学机经;补充第 23 题条件;\n 更新第 54题解析和答案',
-              },
-            ]}
+            data={updateList}
           />
-          <Assets name="textbook_banner" />
+          <Assets name="textbook_banner" onClick={() => this.open(data.unUseRecord.id)} />
         </div>
       </div>
     );
@@ -129,10 +428,10 @@ export default class extends Page {
       <div className="compare-layout">
         <div className="t-14 t-c t-s-32 m-b-2">让机经帮上忙,而不是帮倒忙!</div>
         <div className="t-c m-b-2">
-          <Button width={100} size="lager" radius className="m-r-2">
+          <Button width={100} size="lager" radius className="m-r-2" onClick={() => this.buy()}>
             立刻购买
           </Button>
-          <Button width={100} size="lager" radius className="m-l-2">
+          <Button width={100} size="lager" radius className="m-l-2" onClick={() => linkTo('/examination?tab1=textbook')}>
             试用往期
           </Button>
         </div>

+ 1 - 1
front/project/www/routes/textbook/topic/index.js

@@ -1,5 +1,5 @@
 export default {
-  path: '/textbook/topic',
+  path: '/textbook/topic/list/:subject',
   key: 'textbook-topic',
   title: '机经目录',
   needLogin: false,

+ 109 - 14
front/project/www/routes/textbook/topic/page.js

@@ -1,46 +1,141 @@
 import React from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
 import Page from '@src/containers/Page';
 import Footer from '../../../components/Footer';
 import { Contact } from '../../../components/Other';
 import Select from '../../../components/Select';
 import UserTable from '../../../components/UserTable';
+import { Textbook } from '../../../stores/textbook';
+import { Main } from '../../../stores/main';
+import { TextbookSubject, TextbookQuality, TextbookType } from '../../../../Constant';
+import { getMap, formatDate } from '../../../../../src/services/Tools';
+
+const TextbookSubjectMap = getMap(TextbookSubject, 'value', 'label');
+const TextbookQualityMap = getMap(TextbookQuality, 'value', 'label');
+const TextbookTypeMap = getMap(TextbookType, 'value', 'label');
 
 export default class extends Page {
+  initState() {
+    return {
+      subject: TextbookSubject[0].value,
+      textbookSubject: TextbookSubject.map(row => {
+        return {
+          title: row.label,
+          key: row.value,
+        };
+      }),
+      textbookQuality: TextbookQuality.map(row => {
+        return {
+          title: row.label,
+          key: row.value,
+        };
+      }),
+      textbookType: TextbookType.map(row => {
+        return {
+          title: row.label,
+          key: row.value,
+        };
+      }),
+    };
+  }
+
+  init() {
+    Main.getBase()
+      .then(result => {
+        this.setState({ base: result });
+      });
+    Textbook.getInfo()
+      .then(result => {
+        if (!result.hasService) {
+          linkTo('/textbook');
+        }
+        this.setState({ data: result });
+      });
+  }
+
+  initData() {
+    const { subject } = this.params;
+    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);
+    Textbook.listTopic(Object.assign({ latest: true, subject, order: 'no', direction: 'desc' }, this.state.search, { isOld: !!this.state.search.month }))
+      .then(result => {
+        if (this.state.search.page === 1) {
+          result.list = result.list.map(row => {
+            row.new = '';
+            return row;
+          });
+        }
+        this.setState({ list: result.list, total: result.total });
+      });
+  }
+
+  filter(data) {
+    this.search(data);
+  }
+
+  changeSubject(subject) {
+    linkTo(`/textbook/topic/list/${subject}`);
+  }
+
   renderView() {
+    const { subject } = this.params;
+    const { base = {}, textbookSubject, textbookQuality, textbookType, keyword, quality, month, data = {}, list = [], page, total } = this.state;
+    const { latest = {} } = data;
     return (
       <div>
         <div className="top content t-8">
-          机经 > 本期机经 > <span className="t-1">逻辑</span>
-          <Select className="f-r m-t-1" size="small" theme="white" value={'1'} list={[{ title: '123', key: '1' }]} />
+          机经 > 本期机经 > <span className="t-1">{TextbookSubjectMap[subject]}</span>
+          <Select className="f-r m-t-1" size="small" theme="white" value={subject} list={textbookSubject} onChange={({ key }) => this.changeSubject(key)} />
         </div>
         <div className="center content">
           <div className="t-1 t-s-18 m-b-1">
-            【逻辑】0515 起逻辑机经整理
+            【{TextbookSubjectMap[subject]}】{latest.startDate && formatDate(latest.startDate, 'MMDD')} 起{TextbookSubjectMap[subject]}机经整理
+            <Select className="f-r m-l-1" size="small" theme="default" value={quality} placeholder={'机经质量'} list={textbookQuality} onChange={({ key }) => this.filter({ quality: key })} />
             <Select
               className="f-r m-l-1"
               size="small"
               theme="default"
-              value={'1'}
-              list={[{ title: '123', key: '1' }]}
+              value={month}
+              list={[{ title: '本月', key: '' }, { title: '考古', key: '1' }]}
+              onChange={({ key }) => this.filter({ month: key })}
             />
-            <Select className="f-r" size="small" theme="default" value={'1'} list={[{ title: '123', key: '1' }]} />
+            {subject === 'quant' && <Select
+              className="f-r m-l-1"
+              size="small"
+              theme="default"
+              value={keyword}
+              list={textbookType}
+              placeholder={'题型'}
+              onChange={({ key }) => this.filter({ keyword: key })}
+            />}
           </div>
           <UserTable
             size="small"
-            data={[{}, {}, {}]}
-            current={1}
-            pageSize={20}
-            total={100}
+            data={list}
+            current={page}
+            pageSize={this.state.search.size}
+            total={total}
             jump
             columns={[
-              { title: '文章编号', key: '1', sort: true },
-              { title: '关键词', key: '2' },
-              { title: '机经质量', key: '3' },
+              {
+                title: '文章编号',
+                key: 'no',
+                sort: true,
+                render: (text) => {
+                  return <Link to={`/textbook/topic/detail/${subject}?no=${text}`}>{text}</Link>;
+                },
+              },
+              { title: '关键词', key: 'keyword', render: (text) => (subject === 'quant' ? TextbookTypeMap[text] : text) },
+              { title: '机经质量', key: 'quality', render: (text) => TextbookQualityMap[text] || '' },
             ]}
           />
         </div>
-        <Contact />
+        <Contact data={base.contact} />
         <Footer />
       </div>
     );

+ 1 - 1
front/project/www/routes/textbook/topicDetail/index.js

@@ -1,5 +1,5 @@
 export default {
-  path: '/textbook/topic/detail',
+  path: '/textbook/topic/detail/:subject',
   key: 'textbook-topic-detail',
   title: '机经内容页',
   needLogin: false,

+ 91 - 30
front/project/www/routes/textbook/topicDetail/page.js

@@ -7,16 +7,68 @@ import { Contact } from '../../../components/Other';
 import Select from '../../../components/Select';
 import Modal from '../../../components/Modal';
 import { Button } from '../../../components/Button';
+import { Textbook } from '../../../stores/textbook';
+import { My } from '../../../stores/my';
+import { Main } from '../../../stores/main';
+import { User } from '../../../stores/user';
+import { TextbookSubject, TextbookQuality, TextbookType } from '../../../../Constant';
+import { getMap, formatDate } from '../../../../../src/services/Tools';
+
+const TextbookSubjectMap = getMap(TextbookSubject, 'value', 'label');
+const TextbookQualityMap = getMap(TextbookQuality, 'value', 'label');
+const TextbookTypeMap = getMap(TextbookType, 'value', 'label');
 
 export default class extends Page {
   constructor(props) {
     super(props);
-    this.state = { open: false, showTip: true };
     this.keyMap = {};
     window.onkeydown = this.onKeydown.bind(this);
     window.onkeyup = this.onKeyup.bind(this);
   }
 
+  initState() {
+    const { info = {} } = this.props.user;
+    return {
+      open: false,
+      showTip: !info.textbookTips,
+      subject: TextbookSubject[0].value,
+      textbookSubject: TextbookSubject.map(row => {
+        return {
+          title: row.label,
+          key: row.value,
+        };
+      }),
+    };
+  }
+
+  init() {
+    Main.getBase()
+      .then(result => {
+        this.setState({ base: result });
+      });
+  }
+
+  initData() {
+    Textbook.getInfo()
+      .then(result => {
+        if (!result.hasService) {
+          linkTo('/textbook');
+        }
+        this.setState({ data: result });
+        console.log(this.state);
+        this.refreshItem(this.state.search.no || 1);
+      });
+  }
+
+  refreshItem(no) {
+    const { subject } = this.params;
+    const { data } = this.state;
+    Textbook.noTopic(data.latest.id, subject, no)
+      .then(result => {
+        this.setState({ item: result });
+      });
+  }
+
   onKeydown(e) {
     let active = false;
     if (this.keyMap[e.keyCode]) return false;
@@ -69,30 +121,54 @@ export default class extends Page {
     this.setState({ open: !this.state.open });
   }
 
-  onNext() {}
+  onNext() {
+    const { subject } = this.params;
+    const { item, data } = this.state;
+    const no = item.no + 1;
+    if (no > data.latest[`${subject}Number`]) return;
+    this.refreshItem(no);
+  }
+
+  onPrev() {
+    const { item } = this.state;
+    const no = item.no - 1;
+    if (no === 0) return;
+    this.refreshItem(no);
+  }
+
+  closeTips() {
+    this.setState({ showTip: false });
+    My.textbookTips()
+      .then(() => {
+        User.infoHandle({ textbookTips: 1 });
+      });
+  }
 
-  onPrev() {}
+  changeSubject(subject) {
+    linkTo(`/textbook/topic/list/${subject}`);
+  }
 
   renderView() {
-    const { showTip } = this.state;
+    const { showTip, base = {}, subject, data = {}, textbookSubject } = this.state;
+    const { latest = {} } = data;
     return (
       <div>
         <div className="top content t-8">
-          机经 > 本期机经 > <span className="t-1">逻辑</span>
-          <Select className="f-r m-t-1" size="small" theme="white" value={'1'} list={[{ title: '123', key: '1' }]} />
+          机经 > 本期机经 > <span className="t-1">{TextbookSubjectMap[subject]}</span>
+          <Select className="f-r m-t-1" size="small" theme="white" value={subject} list={textbookSubject} onChange={({ key }) => this.changeSubject(key)} />
         </div>
         <div className="center content">
-          <div className="t-1 t-s-18 m-b-1">【逻辑】0515 起逻辑机经整理</div>
+          【{TextbookSubjectMap[subject]}】{latest.startDate && formatDate(latest.startDate, 'MMDD')} 起{TextbookSubjectMap[subject]}机经整理
           {this.renderDetail()}
           <Assets className="prev" name="footer_previous_highlight_1" onClick={() => this.onPrev()} />
           <Assets className="next" name="footer_next_highlight_1" onClick={() => this.onNext()} />
         </div>
-        <Contact />
+        <Contact data={base.contact} />
         <Footer />
         <Modal
           show={showTip}
           title="提示"
-          onConfirm={() => this.setState({ showTip: false })}
+          onConfirm={() => this.closeTips()}
           confirmText="好的,知道了"
           btnAlign="center"
         >
@@ -104,36 +180,21 @@ export default class extends Page {
   }
 
   renderDetail() {
-    const { open } = this.state;
+    const { open, subject, item = {} } = this.state;
     return (
       <div className="detail">
         <div className="m-b-1">
-          <span className="t-1 t-s-18 m-r-1">NO.34 题目题目题目</span>
-          <span className="t-3 t-s-12">2019-02-21 19:19:20</span>
+          <span className="t-1 t-s-18 m-r-1">NO.{item.no} {subject === 'quant' ? TextbookTypeMap[item.keyword] : item.keyword}</span>
+          <span className="t-3 t-s-12 m-r-1">{TextbookQualityMap[item.quality]}</span>
+          <span className="t-3 t-s-12 m-r-1">{item.createTime && formatDate(item.createTime, 'YYYY-MM-DD HH:mm:ss')}</span>
           <Button radius className="f-r" onClick={() => this.onOpen()}>
             {open ? '收起' : '展开'}解析
           </Button>
         </div>
-        <div className="t-2 t-s-16 m-b-2">
-          GMAT考试由分析写作、推理、数学和语文四部分组成。分别为: a)分析性写作评价(Analytical Writing
-          Assessment)A:GMAT考试由分析写作、推理、数学和语文四部分组成。分别为: a)分析性写作评价(Analytical Writing
-          Assessment)GMAT考试由分析写作、推理、数学和语文四部分组成。分别为: a)分析性写作评价(Analytical Writing
-          Assessment)A:GMAT考试由分析写作、推理、数学和语文四部分组成。分别为: a)分析性写作评价(Analytical Writing
-          Assessment)A:GMAT考试由分析写作、推理、数学和语文四部分组成。分别为: a)分析性写作评价
-        </div>
+        <div className="t-2 t-s-16 m-b-2" dangerouslySetInnerHTML={{ __html: item.detail }} />
         <div hidden={!open} className="p-20 b-c-3">
           <div className="t t-1 t-s-16 f-w-b m-b-5">官方解析</div>
-          <div className="t-1 t-s-16">
-            A.By whom they were supposedly named is a passive construction that is unnecessarily indirect and wordy,
-            especially immediately following another passive construction; the singular its does not agree with the
-            plural antecedent the Glass House Mountains. B.This version of the sentence loses the causal connection,
-            failing to explain why James Cook gave the mountains their particular name. C.As the object of a preposition
-            and not the subject of the clause, James Cook does not work as the noun that the verbal phrase beginning
-            with naming can describe; the preposition since loses the important causal logic of the sentence. D.Correct.
-            This concise sentence uses active- voice construction in the relative clause and maintains agreement between
-            the pronoun their and its antecedent. E The pronoun it does not agree with the plural Mountains and the
-            following pronoun their.
-          </div>
+          <div className="t-1 t-s-16" dangerouslySetInnerHTML={{ __html: item.content }} />
         </div>
       </div>
     );

+ 81 - 7
front/project/www/routes/textbook/year/page.js

@@ -5,30 +5,104 @@ import Footer from '../../../components/Footer';
 import { Contact } from '../../../components/Other';
 import Select from '../../../components/Select';
 import UserTable from '../../../components/UserTable';
+import { Textbook } from '../../../stores/textbook';
+import { Main } from '../../../stores/main';
+import { TextbookMinYear, TextbookSubject } from '../../../../Constant';
+import { formatDate } from '../../../../../src/services/Tools';
 
 export default class extends Page {
+  initState() {
+    const yearList = [];
+    const nowYear = new Date().getFullYear();
+    for (let i = TextbookMinYear; i <= nowYear; i += 1) {
+      yearList.push({
+        title: i.toString(),
+        key: i.toString(),
+      });
+    }
+    return {
+      subject: TextbookSubject[0].value,
+      year: nowYear,
+      yearList,
+      textbookSubject: TextbookSubject.map(row => {
+        return {
+          title: row.label,
+          key: row.value,
+        };
+      }),
+    };
+  }
+
+  init() {
+    Main.getBase()
+      .then(result => {
+        this.setState({ base: result });
+      });
+  }
+
+  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);
+    Textbook.getInfo()
+      .then(result => {
+        this.setState(result);
+      });
+    console.log(this.state);
+    this.refreshYear(this.state.year);
+  }
+
+  refreshYear(year) {
+    this.setState({ year });
+    Textbook.listYear(year)
+      .then((list) => {
+        const monthMap = {};
+        let lastTime = null;
+        list.forEach((row) => {
+          const d = new Date(row.startDate);
+          const month = d.getMonth() + 1;
+          row.month = month;
+          if (lastTime) {
+            row.period = parseInt((d.getTime() - lastTime.getTime()) / 86400000, 10) - 1;
+          } else {
+            row.period = 0;
+          }
+          lastTime = d;
+          if (!monthMap[month]) {
+            monthMap[month] = [];
+          }
+          monthMap[month].push(d.getDate());
+        });
+        this.setState({ monthMap, list });
+      });
+  }
+
   renderView() {
+    const { base = {}, yearList, year, monthMap, list } = this.state;
     return (
       <div>
         <div className="top content t-8">
-          机经 > 本期机经 > <span className="t-1">逻辑</span>
-          <Select className="f-r m-t-1" size="small" theme="white" value={'1'} list={[{ title: '123', key: '1' }]} />
+          机经 > <span className="t-1">按年份查询</span>
         </div>
         <div className="center content">
           <div className="t-1 t-s-18 m-b-1">
             GMAT历年换库记录
-            <Select className="f-r" size="small" theme="default" value={'1'} list={[{ title: '123', key: '1' }]} />
+            <Select className="f-r" size="small" theme="default" value={year} placeholder={'年份'} list={yearList} onChange={({ key }) => this.refreshYear(key)} />
           </div>
           <UserTable
             size="small"
+            data={list}
             columns={[
-              { title: '更新时间', key: '1' },
-              { title: '间隔天数', key: '2' },
-              { title: '当月换库次数(次)', key: '3' },
+              { title: '更新时间', key: 'startDate', render: (text) => formatDate(text, 'YYYY-MM-DD') },
+              { title: '间隔天数', key: 'period' },
+              { title: '当月换库次数(次)', key: 'month', render: (text) => (monthMap[text] ? monthMap[text].length : 0) },
             ]}
           />
         </div>
-        <Contact />
+        <Contact data={base.contact} />
         <Footer />
       </div>
     );

+ 7 - 7
front/project/www/static/login.html

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

+ 3 - 3
front/project/www/stores/my.js

@@ -381,13 +381,13 @@ export default class MyStore extends BaseStore {
 
   /**
    * 添加机经反馈
-   * @param {*} questionSubject
+   * @param {*} textbookSubject
    * @param {*} target
    * @param {*} no
    * @param {*} content
    */
-  addTextbookFeedback(questionSubject, target, no, content) {
-    return this.apiPost('/my/feedback/textbook', { questionSubject, target, no, content });
+  addTextbookFeedback(textbookSubject, target, no, content) {
+    return this.apiPost('/my/feedback/textbook', { textbookSubject, target, no, content });
   }
 
   /**

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

@@ -34,20 +34,24 @@ export default class TextbookStore extends BaseStore {
     return this.apiGet('/textbook/topic/no', { libraryId, subject, no });
   }
 
-  listTopic(page, size, latest, qualitys, isOld, order, direction) {
-    return this.apiGet('/textbook/topic/list', { page, size, latest, qualitys, isOld, order, direction });
+  listTopic({ page, size, latest, subject, keyword, quality, isOld, order, direction }) {
+    return this.apiGet('/textbook/topic/list', { page, size, latest, subject, keyword, quality, isOld, order, direction });
   }
 
   subscribe(subscribe) {
     return this.apiPost('/textbook/subscribe', { subscribe });
   }
 
-  enroll(month) {
-    return this.apiPost('/textbook/enroll', { month });
+  enroll(date) {
+    return this.apiPost('/textbook/enroll', { date });
   }
 
-  listEnroll(year) {
-    return this.apiGet('/textbook/enroll/list', { year });
+  unEnroll() {
+    return this.apiPost('/textbook/enroll/cancel');
+  }
+
+  listEnroll(startDate, endDate) {
+    return this.apiGet('/textbook/enroll/list', { startDate, endDate });
   }
 }
 

+ 5 - 7
front/project/www/stores/user.js

@@ -76,13 +76,11 @@ export default class UserStore extends BaseStore {
   }
 
   initAfter() {
-    if (this.state.login || this.adminLogin) {
-      this.refreshToken().then(() => {
-        if (this.adminLogin) {
-          window.location.href = window.location.href.replace(`token=${this.adminLogin}`, '').replace('&&', '&');
-        }
-      });
-    }
+    this.refreshToken().then(() => {
+      if (this.adminLogin) {
+        window.location.href = window.location.href.replace(`token=${this.adminLogin}`, '').replace('&&', '&');
+      }
+    });
   }
 
   needPay(order) {

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

@@ -31,6 +31,7 @@ public enum SettingKey {
     SENTENCE_INFO("sentence_info"), // 长难句信息
     WECHAT_INFO("wechat_info"), // 微信公众号信息
     READY_READ("ready_read"), // 推荐阅读设置
+    TEXTBOOK_CONFIG("textbook_config"), // 机经设置信息
 
     BASE("base"), // 基础设置
     TIPS("tips"); // 页面提示信息

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

@@ -20,8 +20,8 @@ public class TextbookTopic implements Serializable {
     /**
      * 学科
      */
-    @Column(name = "`question_subject`")
-    private String questionSubject;
+    @Column(name = "`textbook_subject`")
+    private String textbookSubject;
 
     /**
      * 题目序号
@@ -102,19 +102,19 @@ public class TextbookTopic implements Serializable {
     /**
      * 获取学科
      *
-     * @return question_subject - 学科
+     * @return textbook_subject - 学科
      */
-    public String getQuestionSubject() {
-        return questionSubject;
+    public String getTextbookSubject() {
+        return textbookSubject;
     }
 
     /**
      * 设置学科
      *
-     * @param questionSubject 学科
+     * @param textbookSubject 学科
      */
-    public void setQuestionSubject(String questionSubject) {
-        this.questionSubject = questionSubject;
+    public void setTextbookSubject(String textbookSubject) {
+        this.textbookSubject = textbookSubject;
     }
 
     /**
@@ -261,7 +261,7 @@ public class TextbookTopic implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", libraryId=").append(libraryId);
-        sb.append(", questionSubject=").append(questionSubject);
+        sb.append(", textbookSubject=").append(textbookSubject);
         sb.append(", no=").append(no);
         sb.append(", keyword=").append(keyword);
         sb.append(", quality=").append(quality);
@@ -306,10 +306,10 @@ public class TextbookTopic implements Serializable {
         /**
          * 设置学科
          *
-         * @param questionSubject 学科
+         * @param textbookSubject 学科
          */
-        public Builder questionSubject(String questionSubject) {
-            obj.setQuestionSubject(questionSubject);
+        public Builder textbookSubject(String textbookSubject) {
+            obj.setTextbookSubject(textbookSubject);
             return this;
         }
 

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

@@ -18,10 +18,10 @@ public class UserTextbookEnroll implements Serializable {
     private Integer userId;
 
     /**
-     * 报名月份
+     * 报名日期
      */
-    @Column(name = "`month`")
-    private Date month;
+    @Column(name = "`date`")
+    private Date date;
 
     @Column(name = "`create_time`")
     private Date createTime;
@@ -61,21 +61,21 @@ public class UserTextbookEnroll implements Serializable {
     }
 
     /**
-     * 获取报名月份
+     * 获取报名日期
      *
-     * @return month - 报名月份
+     * @return date - 报名日期
      */
-    public Date getMonth() {
-        return month;
+    public Date getDate() {
+        return date;
     }
 
     /**
-     * 设置报名月份
+     * 设置报名日期
      *
-     * @param month 报名月份
+     * @param date 报名日期
      */
-    public void setMonth(Date month) {
-        this.month = month;
+    public void setDate(Date date) {
+        this.date = date;
     }
 
     /**
@@ -100,7 +100,7 @@ public class UserTextbookEnroll implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
-        sb.append(", month=").append(month);
+        sb.append(", date=").append(date);
         sb.append(", createTime=").append(createTime);
         sb.append("]");
         return sb.toString();
@@ -136,12 +136,12 @@ public class UserTextbookEnroll implements Serializable {
         }
 
         /**
-         * 设置报名月份
+         * 设置报名日期
          *
-         * @param month 报名月份
+         * @param date 报名日期
          */
-        public Builder month(Date month) {
-            obj.setMonth(month);
+        public Builder date(Date date) {
+            obj.setDate(date);
             return this;
         }
 

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

@@ -20,8 +20,8 @@ public class UserTextbookFeedback implements Serializable {
     /**
      * 学科
      */
-    @Column(name = "`question_subject`")
-    private String questionSubject;
+    @Column(name = "`textbook_subject`")
+    private String textbookSubject;
 
     /**
      * 题目序号
@@ -111,19 +111,19 @@ public class UserTextbookFeedback implements Serializable {
     /**
      * 获取学科
      *
-     * @return question_subject - 学科
+     * @return textbook_subject - 学科
      */
-    public String getQuestionSubject() {
-        return questionSubject;
+    public String getTextbookSubject() {
+        return textbookSubject;
     }
 
     /**
      * 设置学科
      *
-     * @param questionSubject 学科
+     * @param textbookSubject 学科
      */
-    public void setQuestionSubject(String questionSubject) {
-        this.questionSubject = questionSubject;
+    public void setTextbookSubject(String textbookSubject) {
+        this.textbookSubject = textbookSubject;
     }
 
     /**
@@ -292,7 +292,7 @@ public class UserTextbookFeedback implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
-        sb.append(", questionSubject=").append(questionSubject);
+        sb.append(", textbookSubject=").append(textbookSubject);
         sb.append(", no=").append(no);
         sb.append(", topicId=").append(topicId);
         sb.append(", libraryId=").append(libraryId);
@@ -338,10 +338,10 @@ public class UserTextbookFeedback implements Serializable {
         /**
          * 设置学科
          *
-         * @param questionSubject 学科
+         * @param textbookSubject 学科
          */
-        public Builder questionSubject(String questionSubject) {
-            obj.setQuestionSubject(questionSubject);
+        public Builder textbookSubject(String textbookSubject) {
+            obj.setTextbookSubject(textbookSubject);
             return this;
         }
 

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

@@ -7,7 +7,7 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="library_id" jdbcType="INTEGER" property="libraryId" />
-    <result column="question_subject" jdbcType="VARCHAR" property="questionSubject" />
+    <result column="textbook_subject" jdbcType="VARCHAR" property="textbookSubject" />
     <result column="no" jdbcType="INTEGER" property="no" />
     <result column="keyword" jdbcType="VARCHAR" property="keyword" />
     <result column="quality" jdbcType="VARCHAR" property="quality" />
@@ -26,7 +26,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `library_id`, `question_subject`, `no`, `keyword`, `quality`, `is_old`, `create_time`, 
+    `id`, `library_id`, `textbook_subject`, `no`, `keyword`, `quality`, `is_old`, `create_time`, 
     `update_time`
   </sql>
   <sql id="Blob_Column_List">

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

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

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

@@ -7,7 +7,7 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
-    <result column="question_subject" jdbcType="VARCHAR" property="questionSubject" />
+    <result column="textbook_subject" jdbcType="VARCHAR" property="textbookSubject" />
     <result column="no" jdbcType="INTEGER" property="no" />
     <result column="topic_id" jdbcType="INTEGER" property="topicId" />
     <result column="library_id" jdbcType="INTEGER" property="libraryId" />
@@ -27,7 +27,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `question_subject`, `no`, `topic_id`, `library_id`, `manager_id`, 
+    `id`, `user_id`, `textbook_subject`, `no`, `topic_id`, `library_id`, `manager_id`, 
     `target`, `create_time`, `status`, `handle_time`
   </sql>
   <sql id="Blob_Column_List">

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

@@ -3,6 +3,7 @@ package com.qxgmat.data.relation;
 import com.qxgmat.data.dao.entity.UserCollectQuestion;
 import com.qxgmat.data.dao.entity.UserOrderRecord;
 import com.qxgmat.data.relation.entity.CourseStudentNumberRelation;
+import com.qxgmat.data.relation.entity.MonthNumberRelation;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.Collection;
@@ -43,4 +44,12 @@ public interface UserOrderRecordRelationMapper {
     List<CourseStudentNumberRelation> groupByTime(
             @Param("ids") Collection ids
     );
+
+    List<MonthNumberRelation> groupByMonth(
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime,
+            @Param("productType") String productType,
+            @Param("productId") Integer productId,
+            @Param("service") String service
+    );
 }

+ 2 - 5
server/data/src/main/java/com/qxgmat/data/relation/UserTextbookEnrollRelationMapper.java

@@ -1,11 +1,8 @@
 package com.qxgmat.data.relation;
 
-import com.qxgmat.data.dao.entity.UserOrderRecord;
-import com.qxgmat.data.relation.entity.CourseStudentNumberRelation;
-import com.qxgmat.data.relation.entity.TextbookEnrollNumberRelation;
+import com.qxgmat.data.relation.entity.MonthNumberRelation;
 import org.apache.ibatis.annotations.Param;
 
-import java.util.Collection;
 import java.util.List;
 
 /**
@@ -13,7 +10,7 @@ import java.util.List;
  */
 public interface UserTextbookEnrollRelationMapper {
 
-    List<TextbookEnrollNumberRelation> groupByMonth(
+    List<MonthNumberRelation> groupByMonth(
             @Param("startTime") String startTime,
             @Param("endTime") String endTime
     );

+ 9 - 10
server/data/src/main/java/com/qxgmat/data/relation/entity/TextbookEnrollNumberRelation.java

@@ -1,24 +1,23 @@
 package com.qxgmat.data.relation.entity;
 
 import javax.persistence.Column;
-import java.util.Date;
 
 /**
  * Created by gaojie on 2017/11/9.
  */
-// 备考统计
-public class TextbookEnrollNumberRelation {
+// 月份统计
+public class MonthNumberRelation {
 
     /**
      * 数字字段
      */
-    @Column(name = "`id`")
-    private Date month;
+    @Column(name = "`month`")
+    private Integer month;
 
     /**
      * 统计值
      */
-    @Column(name = "`user_id`")
+    @Column(name = "`number`")
     private Integer number;
 
     public Integer getNumber() {
@@ -29,11 +28,11 @@ public class TextbookEnrollNumberRelation {
         this.number = number;
     }
 
-    public Date getMonth() {
-        return month;
+    public void setMonth(Integer month) {
+        this.month = month;
     }
 
-    public void setMonth(Date month) {
-        this.month = month;
+    public Integer getMonth() {
+        return month;
     }
 }

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

@@ -14,6 +14,14 @@
     <id column="id" jdbcType="INTEGER" property="id" />
     <id column="number" jdbcType="INTEGER" property="number" />
   </resultMap>
+
+  <resultMap id="MonthMap" type="com.qxgmat.data.relation.entity.MonthNumberRelation">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="month" jdbcType="INTEGER" property="month" />
+    <id column="number" jdbcType="INTEGER" property="number" />
+  </resultMap>
   <sql id="Id_Column_List">
     <!--
       WARNING - @mbg.generated
@@ -36,6 +44,35 @@
   </select>
 
   <!--
+    统计人数
+  -->
+  <select id="groupByMonth" resultMap="MonthMap">
+    select
+    count(`id`) as `number`, `month`
+    from (
+    select uor.`id` as `id`, CONVERT(date_format(uor.`use_time`, '%m'), UNSIGNED) as `month`
+    from `user_order_record` uor
+    where 1
+    <if test="startTime != null">
+      and uor.`use_time` &gt; #{startTime,jdbcType=VARCHAR}
+    </if>
+    <if test="endTime != null">
+      and uor.`use_time` &lt; #{endTime,jdbcType=VARCHAR}
+    </if>
+    <if test="productType != null">
+      and uor.`product_type` = #{productType,jdbcType=VARCHAR}
+    </if>
+    <if test="productId != null">
+      and uor.`product_id` = #{productId,jdbcType=VARCHAR}
+    </if>
+    <if test="service != null">
+      and uor.`service` = #{service,jdbcType=VARCHAR}
+    </if>
+    ) as s
+    group by `month`
+  </select>
+
+  <!--
     获取用户学习记录
   -->
   <select id="listWithStudyAdmin" resultMap="IdMap">

+ 16 - 12
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserTextbookEnrollRelationMapper.xml

@@ -7,11 +7,11 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
   </resultMap>
-  <resultMap id="NumberMap" type="com.qxgmat.data.relation.entity.TextbookEnrollNumberRelation">
+  <resultMap id="MonthMap" type="com.qxgmat.data.relation.entity.MonthNumberRelation">
     <!--
       WARNING - @mbg.generated
     -->
-    <id column="month" jdbcType="TIMESTAMP" property="month" />
+    <id column="month" jdbcType="INTEGER" property="month" />
     <id column="number" jdbcType="INTEGER" property="number" />
   </resultMap>
   <sql id="Id_Column_List">
@@ -24,17 +24,21 @@
   <!--
     统计报名人数
   -->
-  <select id="groupByMonth" resultMap="NumberMap">
+  <select id="groupByMonth" resultMap="MonthMap">
     select
-    count(ute.`id`) as `number`, ute.`month` as `month`
-    from `user_textbook_enrool` ute
-    where 1
-    <if test="startTime != null">
-      and ute.`month` &gt; #{startTime,jdbcType=VARCHAR}
-    </if>
-    <if test="endTime != null">
-      and ute.`month` &lt; #{endTime,jdbcType=VARCHAR}
-    </if>
+    count(`id`) as `number`, `month`
+    from (
+      select ute.`id` as `id`, CONVERT(date_format(ute.`date`, '%m'), UNSIGNED) as `month`
+      from `user_textbook_enroll` ute
+      where 1
+      <if test="startTime != null">
+        and ute.`date` &gt; #{startTime,jdbcType=VARCHAR}
+      </if>
+      <if test="endTime != null">
+        and ute.`date` &lt; #{endTime,jdbcType=VARCHAR}
+      </if>
+    ) as s
+    group by `month`
   </select>
 
 </mapper>

+ 17 - 4
server/data/src/main/resources/db/migration/V1__init_table.sql

@@ -734,7 +734,8 @@ VALUES
 	(22,'sentence_info','{}'),
 	(23,'wechat_info','{}'),
 	(24,'ready_read','{}'),
-	(25,'base','{}');
+	(25,'base','{}'),
+	(26,'textbook_config','{}');
 
 CREATE TABLE textbook_library (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
@@ -895,7 +896,7 @@ CREATE TABLE textbook_question (
 CREATE TABLE textbook_topic (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   library_id int(11) unsigned NOT NULL COMMENT '换库表',
-  question_subject varchar(20) NOT NULL DEFAULT '' COMMENT '学科',
+  textbook_subject varchar(20) NOT NULL DEFAULT '' COMMENT '学科',
   no int(11) unsigned NOT NULL DEFAULT '0' COMMENT '题目序号',
   keyword varchar(255) DEFAULT NULL COMMENT '关键词',
   quality varchar(20) DEFAULT NULL COMMENT '质量',
@@ -905,7 +906,7 @@ CREATE TABLE textbook_topic (
   create_time datetime DEFAULT NULL,
   update_time datetime DEFAULT NULL,
   PRIMARY KEY (id),
-  KEY library_id (library_id,question_subject)
+  KEY library_id (library_id,textbook_subject)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='机经-题目';
 
 CREATE TABLE user (
@@ -1381,6 +1382,18 @@ CREATE TABLE user_question (
   KEY report_id (report_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-做题记录';
 
+CREATE TABLE user_ready_room_feedback (
+  id int(11) unsigned NOT NULL AUTO_INCREMENT,
+  user_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',
+  room_id int(11) unsigned NOT NULL COMMENT '考场id',
+  content text COMMENT '补充内容',
+  manager_id int(11) unsigned NOT NULL DEFAULT '0',
+  status tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '审核状态',
+  create_time datetime DEFAULT NULL,
+  handle_time datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-考场-补充';
+
 CREATE TABLE user_report (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',
@@ -1454,7 +1467,7 @@ CREATE TABLE user_service (
 CREATE TABLE user_textbook_enroll (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) unsigned NOT NULL COMMENT '用户ID',
-  month datetime DEFAULT NULL COMMENT '报名月份',
+  date datetime DEFAULT NULL COMMENT '报名日期',
   create_time datetime DEFAULT NULL,
   PRIMARY KEY (id),
   KEY user_id (user_id,month)

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

@@ -264,6 +264,23 @@ public class SettingController {
         return ResponseHelp.success(entity.getValue());
     }
 
+    @RequestMapping(value = "/textbook_config", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改机经设置", httpMethod = "PUT")
+    private Response<Boolean> editTextbookConfig(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.TEXTBOOK_CONFIG);
+        entity.setValue(dto);
+        settingService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/textbook_config", method = RequestMethod.GET)
+    @ApiOperation(value = "获取机经配置", httpMethod = "GET")
+    private Response<JSONObject> getTextbookConfig(){
+        Setting entity = settingService.getByKey(SettingKey.TEXTBOOK_CONFIG);
+
+        return ResponseHelp.success(entity.getValue());
+    }
+
     @RequestMapping(value = "/score_switch", method = RequestMethod.PUT)
     @ApiOperation(value = "修改分数开关", httpMethod = "PUT")
     private Response<Boolean> editScoreSwitch(@RequestBody @Validated JSONObject dto){

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

@@ -220,7 +220,7 @@ public class TextbookController {
     public Response<TextbookTopic> addTopic(@RequestBody @Validated TextbookTopic dto, HttpServletRequest request) {
         TextbookTopic entity = Transform.convert(dto, TextbookTopic.class);
         entity = textbookTopicService.add(entity);
-        textbookService.updateLibraryNo(entity.getLibraryId(), entity.getQuestionSubject());
+        textbookService.updateLibraryNo(entity.getLibraryId(), entity.getTextbookSubject());
         managerLogService.log(request);
         return ResponseHelp.success(Transform.convert(entity, TextbookTopic.class));
     }
@@ -230,7 +230,7 @@ public class TextbookController {
     public Response<Boolean> editTopic(@RequestBody @Validated TextbookTopic dto, HttpServletRequest request) {
         TextbookTopic entity = Transform.convert(dto, TextbookTopic.class);
         entity = textbookTopicService.edit(entity);
-        textbookService.updateLibraryNo(entity.getLibraryId(), entity.getQuestionSubject());
+        textbookService.updateLibraryNo(entity.getLibraryId(), entity.getTextbookSubject());
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -240,7 +240,7 @@ public class TextbookController {
     public Response<Boolean> deleteTopic(@RequestParam int id, HttpServletRequest request) {
         TextbookTopic entity = textbookTopicService.get(id);
         textbookTopicService.delete(id);
-        textbookService.updateLibraryNo(entity.getLibraryId(), entity.getQuestionSubject());
+        textbookService.updateLibraryNo(entity.getLibraryId(), entity.getTextbookSubject());
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -254,8 +254,8 @@ public class TextbookController {
 
     @RequestMapping(value = "/topic/next", method = RequestMethod.GET)
     @ApiOperation(value = "获取机经题目下一题序号", httpMethod = "GET")
-    public Response<Integer> nextTopic(@RequestParam int libraryId, @RequestParam String questionSubject, HttpSession session) {
-        TextbookTopic entity = textbookTopicService.lastByLibrary(libraryId, questionSubject);
+    public Response<Integer> nextTopic(@RequestParam int libraryId, @RequestParam String textbookSubject, HttpSession session) {
+        TextbookTopic entity = textbookTopicService.lastByLibrary(libraryId, textbookSubject);
         Integer no = 1;
         if (entity != null){
             no += entity.getNo();

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

@@ -330,7 +330,6 @@ public class CourseController {
     @RequestMapping(value = "/no/progress", method = RequestMethod.PUT)
     @ApiOperation(value = "更新课时进度", httpMethod = "PUT")
     public Response<Boolean> noProgress(@RequestBody @Validated UserCourseNoProgressDto dto) {
-        UserSentenceProgress entity = Transform.dtoToEntity(dto);
         User user = (User) shiroHelp.getLoginUser();
         if (user == null) throw new AuthException("需要登录");
 

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

@@ -1530,7 +1530,7 @@ public class MyController {
         entity.setUserId(user.getId());
         entity.setStatus(0);
         if (entity.getNo() != null && entity.getNo() > 0){
-            TextbookTopic textbookTopic = textbookTopicService.getByNo(entity.getLibraryId(), entity.getQuestionSubject(), entity.getNo());
+            TextbookTopic textbookTopic = textbookTopicService.getByNo(entity.getLibraryId(), entity.getTextbookSubject(), entity.getNo());
             entity.setTopicId(textbookTopic.getId());
         }
         userTextbookFeedbackService.add(entity);

+ 78 - 50
server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java

@@ -4,22 +4,19 @@ import com.github.pagehelper.Page;
 import com.nuliji.tools.*;
 import com.nuliji.tools.exception.AuthException;
 import com.nuliji.tools.exception.ParameterException;
-import com.qxgmat.data.constants.enums.QuestionSubject;
 import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.ServiceKey;
+import com.qxgmat.data.constants.enums.TextbookSubject;
 import com.qxgmat.data.constants.enums.logic.TextbookLogic;
 import com.qxgmat.data.constants.enums.module.PaperOrigin;
+import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.module.QuestionModule;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.inline.UserQuestionStat;
 import com.qxgmat.data.relation.entity.QuestionNoRelation;
-import com.qxgmat.data.relation.entity.TextbookEnrollNumberRelation;
-import com.qxgmat.data.relation.entity.TextbookQuestionRelation;
-import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
-import com.qxgmat.dto.extend.UserReportExtendDto;
-import com.qxgmat.dto.extend.UserServiceRecordExtendDto;
-import com.qxgmat.dto.extend.UserTextbookGroupExtendDto;
+import com.qxgmat.data.relation.entity.MonthNumberRelation;
+import com.qxgmat.dto.extend.*;
 import com.qxgmat.dto.request.TextbookEnrollDto;
 import com.qxgmat.dto.request.TextbookSubscribeDto;
 import com.qxgmat.dto.response.TextbookEnrollTimeDto;
@@ -33,6 +30,7 @@ import com.qxgmat.service.UserServiceService;
 import com.qxgmat.service.UsersService;
 import com.qxgmat.service.extend.QuestionFlowService;
 import com.qxgmat.service.extend.TextbookService;
+import com.qxgmat.service.extend.ToolsService;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -43,6 +41,7 @@ import org.springframework.web.bind.annotation.*;
 import javax.servlet.http.HttpSession;
 import java.text.DateFormat;
 import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -103,6 +102,9 @@ public class TextbookController
     private TextbookService textbookService;
 
     @Autowired
+    private ToolsService toolsService;
+
+    @Autowired
     private UserTextbookEnrollService userTextbookEnrollService;
 
     @RequestMapping(value = "/progress", method = RequestMethod.GET)
@@ -224,13 +226,9 @@ public class TextbookController
             dto.setStartTime(userService != null ? userService.getStartTime() : null);
             dto.setExpireTime(userService != null ? userService.getExpireTime() : null);
         }
-        if (!dto.getHasService()){
-            // 移除数据
-            latest.setRc("");
-            latest.setIr("");
-            latest.setQuant("");
-        }
+        textbookService.refreshLibraryResource(user, latest);
         TextbookLibrary second = textbookLibraryService.getSecond();
+        textbookService.refreshLibraryResource(user, second);
         dto.setSecond(second);
 
         return ResponseHelp.success(dto);
@@ -241,6 +239,7 @@ public class TextbookController
     public Response<List<TextbookLibrary>> year(
             @RequestParam(required = false) String year,
             HttpSession session) {
+        User user = (User) shiroHelp.getLoginUser();
         Date start;
         try {
             start = DateFormat.getDateInstance().parse(String.format("%s-01-01", year));
@@ -249,7 +248,7 @@ public class TextbookController
         }
         Date end = Tools.addYear(start, 1);
         List<TextbookLibrary> libraryList = textbookLibraryService.listByTime(start, end);
-
+        textbookService.refreshLibraryResource(user, libraryList);
         return ResponseHelp.success(libraryList);
     }
 
@@ -265,12 +264,13 @@ public class TextbookController
         if (!userServiceService.hasService(user.getId(), ServiceKey.TEXTBOOK)){
             throw new ParameterException("没有机经查看权限");
         }
-        if (QuestionSubject.ValueOf(subject) == null){
+        if (TextbookSubject.ValueOf(subject) == null){
             throw new ParameterException("科目错误");
         }
         TextbookLibrary library = textbookLibraryService.getLatest();
-        List<TextbookLibraryHistory> p = textbookLibraryHistoryService.allByLibraryAndSubject(library.getId(), QuestionSubject.ValueOf(subject));
+        List<TextbookLibraryHistory> p = textbookLibraryHistoryService.allByLibraryAndSubject(library.getId(), TextbookSubject.ValueOf(subject));
 
+        textbookService.refreshHistoryResource(user, p);
         return ResponseHelp.success(p);
     }
 
@@ -298,9 +298,10 @@ public class TextbookController
     public Response<PageMessage<TextbookTopic>> listTopic(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
-            @RequestParam(required = true) boolean latest,
+            @RequestParam(required = false) boolean latest,
             @RequestParam(required = true) String subject,
-            @RequestParam(required = false) String[] qualitys,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) String quality,
             @RequestParam(required = false) Boolean isOld,
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
@@ -320,7 +321,7 @@ public class TextbookController
             library = textbookLibraryService.getSecond();
         }
 
-        Page<TextbookTopic> p = textbookTopicService.list(page, size, library.getId(), QuestionSubject.ValueOf(subject), qualitys, isOld, order, DirectionStatus.ValueOf(direction));
+        Page<TextbookTopic> p = textbookTopicService.list(page, size, library.getId(), TextbookSubject.ValueOf(subject), keyword, quality, isOld, order, DirectionStatus.ValueOf(direction));
 
         return ResponseHelp.success(p, page, size, p.getTotal());
     }
@@ -398,55 +399,82 @@ public class TextbookController
         if (user == null){
             throw new AuthException("请先登录");
         }
-        textbookService.enroll(user.getId(), dto.getMonth());
+        textbookService.enroll(user.getId(), dto.getDate());
+
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/enroll/cancel", method = RequestMethod.POST)
+    @ApiOperation(value = "取消报名", notes = "取消报名", httpMethod = "POST")
+    public Response<Boolean> cancelEnroll()  {
+        User user = (User) shiroHelp.getLoginUser();
+        if (user == null){
+            throw new AuthException("请先登录");
+        }
+        textbookService.unEnroll(user.getId());
 
         return ResponseHelp.success(true);
     }
 
     @RequestMapping(value = "/enroll/list", method = RequestMethod.GET)
     @ApiOperation(value = "报名记录", notes = "报名记录", httpMethod = "GET")
-    public Response<List<TextbookEnrollTimeDto>> enroll(
-            @RequestParam(required = false) String year,
+    public Response<TextbookEnrollTimeDto> enrollList(
+            @RequestParam(required = true) String startDate,
+            @RequestParam(required = true) String endDate,
             HttpSession session) {
         User user = (User) shiroHelp.getLoginUser();
+        Date end;
         Date start;
-        try {
-            start = DateFormat.getDateInstance().parse(String.format("%s-01-01", year));
-        } catch (ParseException e) {
+        try{
+            SimpleDateFormat sdf =   new SimpleDateFormat("yyyy-MM-dd");
+            end = sdf.parse(endDate);
+            start = sdf.parse(startDate);
+        }catch (Exception e){
             throw new ParameterException("日期格式错误");
         }
-        Date end = Tools.addYear(start, 1);
+        Integer useNumber = toolsService.getTextbookUseNumber();
+        if (useNumber == null){
+            useNumber = 0;
+        }
+
+        TextbookEnrollTimeDto dto = new TextbookEnrollTimeDto();
 
-        List<TextbookEnrollNumberRelation> relations = userTextbookEnrollService.groupByMonth(start.toString(), end.toString());
-        Map<Integer, Integer> monthMap = new HashMap<>();
-        for(TextbookEnrollNumberRelation relation : relations){
-            int month = Tools.monthNumber(relation.getMonth());
-            monthMap.put(month, relation.getNumber());
+        List<MonthNumberRelation> enrollRelation = userTextbookEnrollService.groupByMonth(startDate, endDate);
+        Map<Integer, Integer> enrollMonthMap = new HashMap<>();
+        for(MonthNumberRelation relation : enrollRelation){
+            int month = relation.getMonth();
+            enrollMonthMap.put(month, relation.getNumber());
         }
 
-        Map<Integer, Boolean> enrollMap = new HashMap<>();
+        List<MonthNumberRelation> recordRelation = userOrderRecordService.groupByMonth(startDate, endDate, ProductType.SERVICE, null, ServiceKey.TEXTBOOK);
+        Map<Integer, Integer> recordMonthMap = new HashMap<>();
+        for(MonthNumberRelation relation : recordRelation){
+            int month = relation.getMonth();
+            recordMonthMap.put(month, relation.getNumber());
+        }
+
+        // 获取用户报考日期
         if(user != null){
-            List<UserTextbookEnroll> enrolls = userTextbookEnrollService.allByUser(user.getId(), start.toString(), end.toString());
-            for(UserTextbookEnroll enroll : enrolls){
-                int month = Tools.monthNumber(enroll.getMonth());
-                enrollMap.put(month, true);
+            UserTextbookEnroll enroll = userTextbookEnrollService.getByUser(user.getId());
+            if (enroll != null){
+                dto.setDate(enroll.getDate());
             }
         }
 
-        List<TextbookEnrollTimeDto> dtos = new ArrayList<>();
-        Date now = Tools.addMonth(new Date(), 1);
-        Date monthTime = start;
-        while(monthTime.before(now)){
-            int month = Tools.monthNumber(monthTime);
-            int number = monthMap.getOrDefault(month, 0);
-            boolean status = enrollMap.getOrDefault(month, false);
-            TextbookEnrollTimeDto dto = new TextbookEnrollTimeDto();
-            dto.setMonth(monthTime);
-            dto.setNumber(number);
-            dto.setStatus(status);
-
-            monthTime = Tools.addMonth(monthTime, 1);
+        List<TextbookEnrollTimeExtendDto> dtos = new ArrayList<>();
+        while(start.before(end)){
+            int month = Tools.monthNumber(start);
+            int enrollNumber = enrollMonthMap.getOrDefault(month, 0);
+            int recordNumber = recordMonthMap.getOrDefault(month, 0);
+            TextbookEnrollTimeExtendDto extendDto = new TextbookEnrollTimeExtendDto();
+            extendDto.setMonth(start);
+            extendDto.setEnrollNumber(enrollNumber);
+            extendDto.setUseNumber(recordNumber + useNumber);
+            dtos.add(extendDto);
+
+            start = Tools.addMonth(start, 1);
         }
-        return ResponseHelp.success(dtos);
+        dto.setTimes(dtos);
+        return ResponseHelp.success(dto);
     }
 }

+ 9 - 9
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/TextbookTopicInfoDto.java

@@ -14,7 +14,7 @@ public class TextbookTopicInfoDto {
 
     private TextbookLibraryExtendDto library;
 
-    private String questionSubject;
+    private String textbookSubject;
 
     private Integer no;
 
@@ -52,14 +52,6 @@ public class TextbookTopicInfoDto {
         this.library = library;
     }
 
-    public String getQuestionSubject() {
-        return questionSubject;
-    }
-
-    public void setQuestionSubject(String questionSubject) {
-        this.questionSubject = questionSubject;
-    }
-
     public Integer getNo() {
         return no;
     }
@@ -107,4 +99,12 @@ public class TextbookTopicInfoDto {
     public void setUpdateTime(Date updateTime) {
         this.updateTime = updateTime;
     }
+
+    public String getTextbookSubject() {
+        return textbookSubject;
+    }
+
+    public void setTextbookSubject(String textbookSubject) {
+        this.textbookSubject = textbookSubject;
+    }
 }

+ 36 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/TextbookEnrollTimeExtendDto.java

@@ -0,0 +1,36 @@
+package com.qxgmat.dto.extend;
+
+
+import java.util.Date;
+
+public class TextbookEnrollTimeExtendDto {
+    private Date month;
+
+    private Integer enrollNumber;
+
+    private Integer useNumber;
+
+    public Date getMonth() {
+        return month;
+    }
+
+    public void setMonth(Date month) {
+        this.month = month;
+    }
+
+    public Integer getEnrollNumber() {
+        return enrollNumber;
+    }
+
+    public void setEnrollNumber(Integer enrollNumber) {
+        this.enrollNumber = enrollNumber;
+    }
+
+    public Integer getUseNumber() {
+        return useNumber;
+    }
+
+    public void setUseNumber(Integer useNumber) {
+        this.useNumber = useNumber;
+    }
+}

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

@@ -3,13 +3,13 @@ package com.qxgmat.dto.request;
 import java.util.Date;
 
 public class TextbookEnrollDto {
-    private Date month;
+    private Date date;
 
-    public Date getMonth() {
-        return month;
+    public Date getDate() {
+        return date;
     }
 
-    public void setMonth(Date month) {
-        this.month = month;
+    public void setDate(Date date) {
+        this.date = date;
     }
 }

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

@@ -5,7 +5,7 @@ import com.qxgmat.data.dao.entity.UserTextbookFeedback;
 
 @Dto(entity = UserTextbookFeedback.class)
 public class UserTextbookFeedbackDto {
-    private String questionSubject;
+    private String textbookSubject;
 
     private Integer no;
 
@@ -29,14 +29,6 @@ public class UserTextbookFeedbackDto {
         this.content = content;
     }
 
-    public String getQuestionSubject() {
-        return questionSubject;
-    }
-
-    public void setQuestionSubject(String questionSubject) {
-        this.questionSubject = questionSubject;
-    }
-
     public Integer getNo() {
         return no;
     }
@@ -44,4 +36,12 @@ public class UserTextbookFeedbackDto {
     public void setNo(Integer no) {
         this.no = no;
     }
+
+    public String getTextbookSubject() {
+        return textbookSubject;
+    }
+
+    public void setTextbookSubject(String textbookSubject) {
+        this.textbookSubject = textbookSubject;
+    }
 }

+ 13 - 20
server/gateway-api/src/main/java/com/qxgmat/dto/response/TextbookEnrollTimeDto.java

@@ -1,36 +1,29 @@
 package com.qxgmat.dto.response;
 
 
+import com.qxgmat.dto.extend.TextbookEnrollTimeExtendDto;
+
 import java.util.Date;
+import java.util.List;
 
 public class TextbookEnrollTimeDto {
-    private Date month;
-
-    private Integer number;
-
-    private Boolean status;
+    private Date date;
 
-    public Date getMonth() {
-        return month;
-    }
-
-    public void setMonth(Date month) {
-        this.month = month;
-    }
+    private List<TextbookEnrollTimeExtendDto> times;
 
-    public Integer getNumber() {
-        return number;
+    public List<TextbookEnrollTimeExtendDto> getTimes() {
+        return times;
     }
 
-    public void setNumber(Integer number) {
-        this.number = number;
+    public void setTimes(List<TextbookEnrollTimeExtendDto> times) {
+        this.times = times;
     }
 
-    public Boolean getStatus() {
-        return status;
+    public Date getDate() {
+        return date;
     }
 
-    public void setStatus(Boolean status) {
-        this.status = status;
+    public void setDate(Date date) {
+        this.date = date;
     }
 }

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

@@ -775,7 +775,7 @@ public class MessageExtendService {
         Map<String, String> map = new HashMap<>();
         map.put("time", getTime(textbookFeedback.getCreateTime()));
         map.put("content", textbookFeedback.getContent());
-        map.put("subject", QuestionSubject.ValueOf(textbookFeedback.getQuestionSubject()).title);
+        map.put("subject", TextbookSubject.ValueOf(textbookFeedback.getTextbookSubject()).title);
         map.put("no", String.valueOf(textbookFeedback.getNo()));
         FeedbackTarget target = FeedbackTarget.ValueOf(textbookFeedback.getTarget());
         map.put("target", target.title);

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

@@ -152,7 +152,7 @@ public class OrderFlowService {
             // 先加入列表,统一处理
             userOrderCheckoutList.add(checkout);
             // 判断是否符合套餐情况: 剩余的独立课程
-            List<UserOrderCheckout> courseCheckout = userOrderCheckoutList.stream().filter((in)-> in.getProductType().equals(ProductType.COURSE.key) && in.getParentId() == 0).collect(Collectors.toList());
+            List<UserOrderCheckout> courseCheckout = userOrderCheckoutList.stream().filter((in)-> in.getProductType().equals(ProductType.COURSE.key) && (in.getParentId() ==null || in.getParentId()==0)).collect(Collectors.toList());
             Collection courseIds = Transform.getIds(courseCheckout, UserOrderCheckout.class, "productId");
             CoursePackage coursePackage = coursePackageService.combineCourse(courseIds);
             if(coursePackage != null){
@@ -698,8 +698,10 @@ public class OrderFlowService {
                 userService = userServiceService.edit(userService);
             }
             if (serviceKey == ServiceKey.TEXTBOOK){
-                // 自动报名: 使用机经的后10天所在月份
-                textbookService.enroll(record.getUserId(), Tools.addDate(new Date(), 10));
+//                // 自动报名: 使用机经的后10天所在月份
+//                textbookService.enroll(record.getUserId(), Tools.addDate(new Date(), 10));
+                // 重置机经页面tips
+                usersService.edit(User.builder().id(record.getUserId()).textbookTips(0).build());
             }
 
             record.setUseStartTime(startTime);

+ 91 - 6
server/gateway-api/src/main/java/com/qxgmat/service/extend/TextbookService.java

@@ -1,6 +1,8 @@
 package com.qxgmat.service.extend;
 
 import com.nuliji.tools.Tools;
+import com.nuliji.tools.Transform;
+import com.qxgmat.data.constants.enums.ServiceKey;
 import com.qxgmat.data.constants.enums.TextbookSubject;
 import com.qxgmat.data.constants.enums.logic.SentenceLogic;
 import com.qxgmat.data.constants.enums.logic.TextbookLogic;
@@ -8,6 +10,7 @@ import com.qxgmat.data.constants.enums.module.QuestionModule;
 import com.qxgmat.data.constants.enums.module.QuestionNoModule;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.TextbookQuestionRelation;
+import com.qxgmat.service.UserServiceService;
 import com.qxgmat.service.inline.*;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
@@ -15,9 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @Service
@@ -50,6 +51,9 @@ public class TextbookService {
     @Resource
     private TextbookTopicService textbookTopicService;
 
+    @Resource
+    private UserServiceService userServiceService;
+
     /**
      * 报名
      * @param userId
@@ -57,18 +61,30 @@ public class TextbookService {
      */
     @Transactional
     public void enroll(Integer userId, Date date){
-        date = Tools.month(date);
-        UserTextbookEnroll in = userTextbookEnrollService.get(userId, date.toString());
+        UserTextbookEnroll in = userTextbookEnrollService.getByUser(userId);
         if (in != null){
             return;
         }
         userTextbookEnrollService.add(UserTextbookEnroll.builder()
                 .userId(userId)
-                .month(date)
+                .date(date)
                 .build());
     }
 
     /**
+     * 取消报名
+     * @param userId
+     */
+    @Transactional
+    public void unEnroll(Integer userId){
+        UserTextbookEnroll in = userTextbookEnrollService.getByUser(userId);
+        if (in == null){
+            return;
+        }
+        userTextbookEnrollService.delete(in.getId());
+    }
+
+    /**
      * 添加新一期的换库
      * @param entity
      * @return
@@ -188,6 +204,75 @@ public class TextbookService {
         }
     }
 
+    /**
+     * 根据用户权限更新资源信息
+     * @param user
+     * @param textbookLibrary
+     */
+    public void refreshLibraryResource(User user, TextbookLibrary textbookLibrary){
+        // 处理权限
+        if (user != null){
+            if (!userServiceService.hasService(user.getId(), ServiceKey.TEXTBOOK)){
+                // 移除数据
+                textbookLibrary.setRc("");
+                textbookLibrary.setIr("");
+                textbookLibrary.setQuant("");
+            }
+        }else{
+            textbookLibrary.setRc("");
+            textbookLibrary.setIr("");
+            textbookLibrary.setQuant("");
+        }
+    }
+
+    /**
+     * 根据用户权限更新资源信息
+     * @param user
+     * @param textbookLibraryList
+     */
+    public void refreshLibraryResource(User user, List<TextbookLibrary> textbookLibraryList){
+        // 处理权限
+        if (user != null){
+            if (!userServiceService.hasService(user.getId(), ServiceKey.TEXTBOOK)) {
+                for (TextbookLibrary textbookLibrary : textbookLibraryList) {
+                    textbookLibrary.setRc("");
+                    textbookLibrary.setIr("");
+                    textbookLibrary.setQuant("");
+                }
+            }
+        }else{
+            for(TextbookLibrary textbookLibrary : textbookLibraryList){
+                textbookLibrary.setRc("");
+                textbookLibrary.setIr("");
+                textbookLibrary.setQuant("");
+            }
+        }
+    }
+
+
+    /**
+     * 根据用户权限更新资源信息
+     * @param user
+     * @param textbookLibraryHistoryList
+     */
+    public void refreshHistoryResource(User user, List<TextbookLibraryHistory> textbookLibraryHistoryList){
+        // 处理权限
+        if (user != null){
+            if (!userServiceService.hasService(user.getId(), ServiceKey.TEXTBOOK)) {
+                for (TextbookLibraryHistory history : textbookLibraryHistoryList) {
+                    history.setRc("");
+                    history.setIr("");
+                    history.setQuant("");
+                }
+            }
+        }else{
+            for(TextbookLibraryHistory history : textbookLibraryHistoryList){
+                history.setRc("");
+                history.setIr("");
+                history.setQuant("");
+            }
+        }
+    }
     private void addQuestionToPaper(TextbookLibrary library, TextbookQuestion question, TextbookLogic logic){
         String prefixTitle = generatePrefixTitle(library);
         // 获取最后一个paper

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/service/extend/ToolsService.java

@@ -592,4 +592,14 @@ public class ToolsService {
             return info.getIntValue("price");
         }
     }
+
+    /**
+     * 获取机经每月基础使用人数
+     * @return
+     */
+    public Integer getTextbookUseNumber(){
+        Setting setting = settingService.getByKey(SettingKey.TEXTBOOK_CONFIG);
+        JSONObject value = setting.getValue();
+        return value.getInteger("use_number");
+    }
 }

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

@@ -5,7 +5,7 @@ import com.nuliji.tools.AbstractService;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
-import com.qxgmat.data.constants.enums.QuestionSubject;
+import com.qxgmat.data.constants.enums.TextbookSubject;
 import com.qxgmat.data.dao.TextbookLibraryHistoryMapper;
 import com.qxgmat.data.dao.TextbookLibraryMapper;
 import com.qxgmat.data.dao.entity.TextbookLibrary;
@@ -42,7 +42,7 @@ public class TextbookLibraryHistoryService extends AbstractService {
         return page(()->select(textbookLibraryHistoryMapper, example), page, size);
     }
 
-    public List<TextbookLibraryHistory> allByLibraryAndSubject(Integer libraryId, QuestionSubject subject){
+    public List<TextbookLibraryHistory> allByLibraryAndSubject(Integer libraryId, TextbookSubject subject){
         Example example = new Example(TextbookLibraryHistory.class);
         example.and(
                 example.createCriteria()

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

@@ -82,9 +82,10 @@ public class TextbookLibraryService extends AbstractService {
         Example example = new Example(TextbookLibrary.class);
         example.and(
                 example.createCriteria()
-                .andLessThanOrEqualTo("startDate", startTime)
+                .andGreaterThanOrEqualTo("startDate", startTime)
                 .andLessThan("startDate", endTime)
         );
+        example.orderBy("id").asc();
         return select(textbookLibraryMapper, example);
     }
 

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

@@ -5,13 +5,11 @@ import com.nuliji.tools.AbstractService;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
-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.status.DirectionStatus;
 import com.qxgmat.data.dao.TextbookTopicMapper;
-import com.qxgmat.data.dao.UserTextbookFeedbackMapper;
 import com.qxgmat.data.dao.entity.TextbookTopic;
-import com.qxgmat.data.dao.entity.UserTextbookFeedback;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -29,12 +27,12 @@ public class TextbookTopicService extends AbstractService {
     @Resource
     private TextbookTopicMapper textbookTopicMapper;
 
-    public Page<TextbookTopic> listAdmin(int page, int pageSize, String questionType, Number libraryId, String keyword, TopicQuality quality, String order, DirectionStatus direction){
+    public Page<TextbookTopic> listAdmin(int page, int pageSize, String textbookSubject, Number libraryId, String keyword, TopicQuality quality, String order, DirectionStatus direction){
         Example example = new Example(TextbookTopic.class);
-        if (questionType!=null){
+        if (textbookSubject!=null){
             example.and(
                     example.createCriteria()
-                            .andEqualTo("questionType", questionType)
+                            .andEqualTo("textbookSubject", textbookSubject)
             );
         }
         if (libraryId!=null){
@@ -76,23 +74,29 @@ public class TextbookTopicService extends AbstractService {
         example.and(
                 example.createCriteria()
                         .andEqualTo("libraryId", libraryId)
-                        .andEqualTo("questionSubject", questionSubject)
+                        .andEqualTo("textbookSubject", questionSubject)
         );
         example.orderBy("no").desc();
         return one(textbookTopicMapper, example);
     }
 
-    public Page<TextbookTopic> list(int page, int size, Integer libraryId, QuestionSubject subject, String[] qualitys, Boolean isOld, String order, DirectionStatus direction){
+    public Page<TextbookTopic> list(int page, int size, Integer libraryId, TextbookSubject subject, String keyword, String quality, Boolean isOld, String order, DirectionStatus direction){
         Example example = new Example(TextbookTopic.class);
         example.and(
                 example.createCriteria()
                 .andEqualTo("libraryId", libraryId)
-                .andEqualTo("questionSubject", subject.key)
+                .andEqualTo("textbookSubject", subject.key)
         );
-        if (qualitys != null){
+        if (keyword != null){
             example.and(
                     example.createCriteria()
-                    .andIn("quality", Arrays.stream(qualitys).collect(Collectors.toList()))
+                            .andEqualTo("keyword", keyword)
+            );
+        }
+        if (quality != null){
+            example.and(
+                    example.createCriteria()
+                    .andEqualTo("quality", quality)
             );
         }
         if (isOld != null){
@@ -122,7 +126,7 @@ public class TextbookTopicService extends AbstractService {
         example.and(
                 example.createCriteria()
                         .andEqualTo("libraryId", libraryId)
-                        .andEqualTo("questionSubject", subject)
+                        .andEqualTo("textbookSubject", subject)
                         .andEqualTo("no", no)
         );
         return one(textbookTopicMapper, example);

+ 5 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java

@@ -17,6 +17,7 @@ import com.qxgmat.data.dao.entity.UserOrder;
 import com.qxgmat.data.dao.entity.UserOrderRecord;
 import com.qxgmat.data.relation.UserOrderRecordRelationMapper;
 import com.qxgmat.data.relation.entity.CourseStudentNumberRelation;
+import com.qxgmat.data.relation.entity.MonthNumberRelation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -157,6 +158,10 @@ public class UserOrderRecordService extends AbstractService {
         return select(userOrderRecordMapper, example);
     }
 
+    public List<MonthNumberRelation> groupByMonth(String startTime, String endTime, ProductType productType, Integer productId, ServiceKey serviceKey){
+        return userOrderRecordRelationMapper.groupByMonth(startTime, endTime, productType != null ? productType.key : null, productId, serviceKey != null ? serviceKey.key : null);
+    }
+
     /**
      * 列出购买资料的记录
      * @param page

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

@@ -5,18 +5,15 @@ import com.nuliji.tools.AbstractService;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
-import com.qxgmat.data.dao.TextbookLibraryHistoryMapper;
 import com.qxgmat.data.dao.UserTextbookEnrollMapper;
-import com.qxgmat.data.dao.entity.TextbookLibraryHistory;
 import com.qxgmat.data.dao.entity.UserTextbookEnroll;
 import com.qxgmat.data.relation.UserTextbookEnrollRelationMapper;
-import com.qxgmat.data.relation.entity.TextbookEnrollNumberRelation;
+import com.qxgmat.data.relation.entity.MonthNumberRelation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
@@ -35,23 +32,22 @@ public class UserTextbookEnrollService extends AbstractService {
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
-                        .andGreaterThanOrEqualTo("month", startTime)
-                        .andLessThan("month", endTime)
+                        .andGreaterThanOrEqualTo("date", startTime)
+                        .andLessThan("date", endTime)
         );
-        example.orderBy("month").asc();
+        example.orderBy("date").asc();
         return select(userTextbookEnrollMapper, example);
     }
 
-    public List<TextbookEnrollNumberRelation> groupByMonth(String startTime, String endTime){
+    public List<MonthNumberRelation> groupByMonth(String startTime, String endTime){
         return userTextbookEnrollRelationMapper.groupByMonth(startTime, endTime);
     }
 
-    public UserTextbookEnroll get(Integer userId, String month){
+    public UserTextbookEnroll getByUser(Integer userId){
         Example example = new Example(UserTextbookEnroll.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
-                        .andEqualTo("month", month)
         );
         return one(userTextbookEnrollMapper, example);
     }

+ 1 - 1
server/tools/src/main/java/com/nuliji/tools/Tools.java

@@ -274,7 +274,7 @@ public class Tools {
     public static int monthNumber(Date date){
         Calendar calendar = Calendar.getInstance();
         calendar.setTime(date);
-        return calendar.get(Calendar.MONTH);
+        return calendar.get(Calendar.MONTH) + 1;
     }
 
     public static Date month(Date date){