Explorar o código

feat(front): 机经

Go %!s(int64=4) %!d(string=hai) anos
pai
achega
e0a7f7d34e
Modificáronse 55 ficheiros con 1185 adicións e 358 borrados
  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. 6 6
      front/project/www/components/Item/index.js
  9. 9 2
      front/project/www/components/Login/index.js
  10. 1 1
      front/project/www/components/Other/index.js
  11. 25 11
      front/project/www/components/OtherModal/index.js
  12. 2 2
      front/project/www/components/UserPagination/index.js
  13. 1 1
      front/project/www/routes/my/tools/page.js
  14. 329 30
      front/project/www/routes/textbook/main/page.js
  15. 1 1
      front/project/www/routes/textbook/topic/index.js
  16. 109 14
      front/project/www/routes/textbook/topic/page.js
  17. 1 1
      front/project/www/routes/textbook/topicDetail/index.js
  18. 91 30
      front/project/www/routes/textbook/topicDetail/page.js
  19. 81 7
      front/project/www/routes/textbook/year/page.js
  20. 7 7
      front/project/www/static/login.html
  21. 3 3
      front/project/www/stores/my.js
  22. 10 6
      front/project/www/stores/textbook.js
  23. 5 7
      front/project/www/stores/user.js
  24. 1 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/SettingKey.java
  25. 12 12
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookTopic.java
  26. 16 16
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserTextbookEnroll.java
  27. 12 12
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserTextbookFeedback.java
  28. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookTopicMapper.xml
  29. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserTextbookEnrollMapper.xml
  30. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserTextbookFeedbackMapper.xml
  31. 9 0
      server/data/src/main/java/com/qxgmat/data/relation/UserOrderRecordRelationMapper.java
  32. 2 5
      server/data/src/main/java/com/qxgmat/data/relation/UserTextbookEnrollRelationMapper.java
  33. 9 10
      server/data/src/main/java/com/qxgmat/data/relation/entity/TextbookEnrollNumberRelation.java
  34. 37 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserOrderRecordRelationMapper.xml
  35. 16 12
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserTextbookEnrollRelationMapper.xml
  36. 5 4
      server/data/src/main/resources/db/migration/V1__init_table.sql
  37. 17 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  38. 5 5
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/TextbookController.java
  39. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  40. 78 50
      server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java
  41. 9 9
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/TextbookTopicInfoDto.java
  42. 36 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/TextbookEnrollTimeExtendDto.java
  43. 5 5
      server/gateway-api/src/main/java/com/qxgmat/dto/request/TextbookEnrollDto.java
  44. 9 9
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserTextbookFeedbackDto.java
  45. 13 20
      server/gateway-api/src/main/java/com/qxgmat/dto/response/TextbookEnrollTimeDto.java
  46. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java
  47. 4 2
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  48. 91 6
      server/gateway-api/src/main/java/com/qxgmat/service/extend/TextbookService.java
  49. 10 0
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ToolsService.java
  50. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryHistoryService.java
  51. 2 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryService.java
  52. 16 12
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookTopicService.java
  53. 5 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java
  54. 6 10
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserTextbookEnrollService.java
  55. 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;
     }
   }

+ 6 - 6
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';
@@ -292,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) {

+ 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>

+ 25 - 11
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) {
@@ -932,7 +932,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) {
@@ -946,7 +960,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: {} });
     });
@@ -960,7 +974,7 @@ export class TextbookFeedbackModal extends Component {
 
   render() {
     const { show } = this.props;
-    const { data } = this.state;
+    const { data, targetList, textbookSubject } = this.state;
     return (
       <Modal
         show={show}
@@ -971,20 +985,20 @@ export class TextbookFeedbackModal extends Component {
       >
         <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 });
             }}
           />
           反馈类型: <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/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>
         )}

+ 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>

+ 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>

+ 5 - 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 (
@@ -1466,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();

+ 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);

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

@@ -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){