소스 검색

feat(front): 模考机经列表

Go 5 년 전
부모
커밋
cf8bfe3291
46개의 변경된 파일1318개의 추가작업 그리고 1211개의 파일을 삭제
  1. 1 1
      front/project/admin/routes/interaction/comment/page.js
  2. 1 1
      front/project/admin/routes/interaction/faq/page.js
  3. 1 1
      front/project/admin/routes/show/comment/page.js
  4. 1 1
      front/project/admin/routes/show/faq/page.js
  5. 0 16
      front/project/admin/routes/show/readDetail/index.js
  6. 0 3
      front/project/admin/routes/show/readDetail/index.less
  7. 0 169
      front/project/admin/routes/show/readDetail/page.js
  8. 4 0
      front/project/www/components/ListTable/index.less
  9. 1 0
      front/project/www/components/Select/index.js
  10. 2 1
      front/project/www/routes/examination/index.js
  11. 5 0
      front/project/www/routes/examination/list/index.less
  12. 432 220
      front/project/www/routes/examination/list/page.js
  13. 15 103
      front/project/www/routes/examination/main/page.js
  14. 4 0
      front/project/www/routes/exercise/list/index.less
  15. 106 102
      front/project/www/routes/exercise/list/page.js
  16. 217 221
      front/project/www/routes/exercise/main/page.js
  17. 0 1
      front/project/www/routes/paper/question/page.js
  18. 2 2
      front/project/www/routes/textbook/list/index.js
  19. 16 0
      front/project/www/routes/textbook/list/index.less
  20. 170 227
      front/project/www/routes/textbook/list/page.js
  21. 4 0
      front/project/www/stores/main.js
  22. 14 4
      front/project/www/stores/question.js
  23. 6 2
      front/project/www/stores/textbook.js
  24. 1 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/logic/TextbookLogic.java
  25. 0 18
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/FaqModule.java
  26. 18 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/InsideModule.java
  27. 0 35
      server/data/src/main/java/com/qxgmat/data/dao/entity/Faq.java
  28. 2 4
      server/data/src/main/java/com/qxgmat/data/dao/mapping/FaqMapper.xml
  29. 1 0
      server/data/src/main/java/com/qxgmat/data/relation/TextbookPaperRelationMapper.java
  30. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/ExaminationPaperRelationMapper.xml
  31. 5 2
      server/data/src/main/java/com/qxgmat/data/relation/mapping/TextbookPaperRelationMapper.xml
  32. 5 5
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  33. 21 3
      server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java
  34. 52 13
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  35. 26 8
      server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java
  36. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserReportExtendDto.java
  37. 13 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/TextbookSubscribeDto.java
  38. 57 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserExaminationInfoDto.java
  39. 49 9
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserExaminationPaperDto.java
  40. 17 5
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserTextbookInfoDto.java
  41. 1 0
      server/gateway-api/src/main/java/com/qxgmat/service/UserQuestionService.java
  42. 12 3
      server/gateway-api/src/main/java/com/qxgmat/service/UserServiceService.java
  43. 3 2
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  44. 10 7
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CommentService.java
  45. 10 18
      server/gateway-api/src/main/java/com/qxgmat/service/inline/FaqService.java
  46. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookPaperService.java

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

@@ -177,7 +177,7 @@ export default class extends Page {
   }
 
   initData() {
-    System.listComment(this.state.search).then(result => {
+    System.listComment(Object.assign({ user: true }, this.state.search)).then(result => {
       this.setTableData(result.list, result.total);
     });
   }

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

@@ -153,7 +153,7 @@ export default class extends Page {
   }
 
   initData() {
-    System.listFAQ(Object.assign({ faqModule: 'consult' }, this.state.search)).then(result => {
+    System.listFAQ(Object.assign({ user: true }, this.state.search)).then(result => {
       this.setTableData(result.list, result.total);
     });
   }

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

@@ -177,7 +177,7 @@ export default class extends Page {
   }
 
   initData() {
-    System.listComment(this.state.search).then(result => {
+    System.listComment(Object.assign({ isSpecial: true }, this.state.search)).then(result => {
       this.setTableData(result.list, result.total);
     });
   }

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

@@ -119,7 +119,7 @@ export default class extends Page {
   }
 
   initData() {
-    System.listFAQ(Object.assign({ faqModule: 'system' }, this.state.search)).then(result => {
+    System.listFAQ(Object.assign({ isSpecial: true }, this.state.search)).then(result => {
       this.setTableData(result.list, result.total);
     });
   }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 0 - 1
front/project/www/routes/paper/question/page.js

@@ -394,7 +394,6 @@ export default class extends Page {
   renderAnswer() {
     const { question = { content: {} }, showAnswer, userQuestion = {} } = this.state;
     const { questions = [], type, typeset = 'one' } = question.content;
-    console.log(userQuestion);
     return <div className="block block-answer">
       {typeset === 'two' ? <Switch checked={showAnswer} onChange={(value) => {
         this.setState({ showAnswer: value });

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

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

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

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

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

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

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

@@ -112,6 +112,10 @@ export default class MainStore extends BaseStore {
   listComment(page, size, channel, position) {
     return this.apiGet('/base/comment/list', { page, size, channel, position });
   }
+
+  listArticle(page, size, channel, position) {
+    return this.apiGet('/base/article/list', { page, size, channel, position });
+  }
 }
 
 export const Main = new MainStore({ key: 'main' });

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

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

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

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

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

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

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

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

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

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

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

@@ -12,12 +12,6 @@ public class Faq implements Serializable {
     private Integer id;
 
     /**
-     * faq模块
-     */
-    @Column(name = "`faq_module`")
-    private String faqModule;
-
-    /**
      * 用户id
      */
     @Column(name = "`user_id`")
@@ -121,24 +115,6 @@ public class Faq implements Serializable {
     }
 
     /**
-     * 获取faq模块
-     *
-     * @return faq_module - faq模块
-     */
-    public String getFaqModule() {
-        return faqModule;
-    }
-
-    /**
-     * 设置faq模块
-     *
-     * @param faqModule faq模块
-     */
-    public void setFaqModule(String faqModule) {
-        this.faqModule = faqModule;
-    }
-
-    /**
      * 获取用户id
      *
      * @return user_id - 用户id
@@ -411,7 +387,6 @@ public class Faq implements Serializable {
         sb.append(" [");
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
-        sb.append(", faqModule=").append(faqModule);
         sb.append(", userId=").append(userId);
         sb.append(", email=").append(email);
         sb.append(", phone=").append(phone);
@@ -451,16 +426,6 @@ public class Faq implements Serializable {
         }
 
         /**
-         * 设置faq模块
-         *
-         * @param faqModule faq模块
-         */
-        public Builder faqModule(String faqModule) {
-            obj.setFaqModule(faqModule);
-            return this;
-        }
-
-        /**
          * 设置用户id
          *
          * @param userId 用户id

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

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

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

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

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

@@ -39,7 +39,7 @@
     </if>
     where 1
     <if test="structId != null">
-      and (ep.`struct_three` = #{structId,jdbcType=VARCHAR} or ep.`struct_four` = #{structId,jdbcType=VARCHAR})
+      and (ep.`struct_three` = #{structId,jdbcType=VARCHAR} or ep.`struct_two` = #{structId,jdbcType=VARCHAR})
     </if>
     <if test="userId != null">
       <if test="times == 0">

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

@@ -11,7 +11,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    ep.`id`
+    tp.`id`
   </sql>
 
   <!--
@@ -22,7 +22,7 @@
     <include refid="Id_Column_List" />
     from `textbook_paper` tp
     <if test="userId != null">
-    left join `user_paper` up on ep.`id` = up.`origin_id`
+    left join `user_paper` up on tp.`id` = up.`origin_id`
       and up.`paper_origin` = 'textbook'
       and up.`user_id` = #{userId,jdbcType=VARCHAR}
       <if test="times != null">
@@ -38,6 +38,9 @@
     <if test="libraryId != null">
       and tp.`library_id` = #{libraryId,jdbcType=VARCHAR}
     </if>
+    <if test="year != null">
+      and tp.`year` = #{year,jdbcType=VARCHAR}
+    </if>
     <if test="userId != null">
       <if test="times == 0">
         and (up.`id` &gt; 0 or up.`id` is null)

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

@@ -4,8 +4,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.github.pagehelper.Page;
 import com.nuliji.tools.*;
 import com.qxgmat.data.constants.enums.SettingKey;
-import com.qxgmat.data.constants.enums.module.ChannelModule;
-import com.qxgmat.data.constants.enums.module.FaqModule;
+import com.qxgmat.data.constants.enums.module.InsideModule;
 import com.qxgmat.data.constants.enums.status.AskStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.entity.*;
@@ -428,6 +427,7 @@ public class SettingController {
     private Response<PageMessage<CommentDto>> listComment(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) Boolean user,
             @RequestParam(required = false) String channel,
             @RequestParam(required = false) String position,
             @RequestParam(required = false) Integer userId,
@@ -435,7 +435,7 @@ public class SettingController {
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction
     ){
-        Page<Comment> p = commentService.listAdmin(page, size, ChannelModule.ValueOf(channel), position, userId, isSpecial);
+        Page<Comment> p = commentService.listAdmin(page, size, user, channel, position, userId, isSpecial);
         List<CommentDto> pr = Transform.convert(p, CommentDto.class);
 
         // 绑定用户
@@ -499,7 +499,7 @@ public class SettingController {
     private Response<PageMessage<Faq>> listFaq(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
-            @RequestParam(required = false) String faqModule,
+            @RequestParam(required = false) Boolean user,
             @RequestParam(required = false) String channel,
             @RequestParam(required = false) String position,
             @RequestParam(required = false) Integer answerStatus,
@@ -507,7 +507,7 @@ public class SettingController {
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction
     ){
-        Page<Faq> p = faqService.listAdmin(page, size, FaqModule.ValueOf(faqModule), ChannelModule.ValueOf(channel), position, answerStatus, isSpecial);
+        Page<Faq> p = faqService.listAdmin(page, size, user, channel, position, answerStatus, isSpecial);
         return ResponseHelp.success(p, page, size, p.getTotal());
     }
 

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

@@ -10,6 +10,7 @@ import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.ServiceKey;
 import com.qxgmat.data.constants.enums.SettingKey;
 import com.qxgmat.data.constants.enums.module.ChannelModule;
+import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.dto.extend.UserExtendDto;
 import com.qxgmat.dto.response.CommentDto;
@@ -58,6 +59,9 @@ public class BaseController {
     private CommentService commentService;
 
     @Autowired
+    private ArticleService articleService;
+
+    @Autowired
     private UsersService usersService;
 
     @RequestMapping(value = "/index", method = RequestMethod.GET)
@@ -209,7 +213,7 @@ public class BaseController {
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<Faq> p = faqService.list(page, size, ChannelModule.ValueOf(channel), position);
+        Page<Faq> p = faqService.list(page, size, channel, position);
         List<FaqDto> pr = Transform.convert(p, FaqDto.class);
 
         Collection userIds = Transform.getIds(p, Faq.class, "userId");
@@ -220,7 +224,7 @@ public class BaseController {
     }
 
     @RequestMapping(value = "/comment/list", method = RequestMethod.GET)
-    @ApiOperation(value = "faq列表", httpMethod = "GET")
+    @ApiOperation(value = "评论列表", httpMethod = "GET")
     public Response<PageMessage<CommentDto>> listComment(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
@@ -229,7 +233,7 @@ public class BaseController {
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<Comment> p = commentService.list(page, size, ChannelModule.ValueOf(channel), position);
+        Page<Comment> p = commentService.list(page, size, channel, position);
         List<CommentDto> pr = Transform.convert(p, CommentDto.class);
 
         Collection userIds = Transform.getIds(p, Comment.class, "userId");
@@ -238,4 +242,18 @@ public class BaseController {
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
+
+    @RequestMapping(value = "/article/list", method = RequestMethod.GET)
+    @ApiOperation(value = "推荐阅读", httpMethod = "GET")
+    public Response<PageMessage<Article>> listArticle(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = true) String channel,
+            @RequestParam(required = true) String position,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
+            HttpSession session) {
+        Page<Article> p = articleService.list(page, size, channel, position, order, DirectionStatus.ValueOf(direction));
+        return ResponseHelp.success(p, page, size, p.getTotal());
+    }
 }

+ 52 - 13
server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java

@@ -193,17 +193,18 @@ public class QuestionController {
                     childrenDtos.add(extendDto);
                 }
 
-                Collection ids = Transform.getIds(paperList, ExercisePaper.class, "id");
-                List<UserPaper> userPaperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.EXERCISE, ids, null);
-                // 绑定userPaperId,用于关联report
-                Map userPaperMap = Transform.getMap(userPaperList, UserPaper.class, "originId", "id");
-                Transform.combine(childrenDtos, userPaperMap, UserExerciseGroupExtendDto.class, "id", "userPaperId");
-
-                // 获取最后一次作业结果
-                Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
-                List<UserReport> reportList = userReportService.listWithLater(paperIds);
-                Transform.combine(childrenDtos, reportList, UserExerciseGroupExtendDto.class, "userPaperId", "report", UserReport.class, "paperId", UserReportExtendDto.class);
+                if (user != null){
+                    Collection ids = Transform.getIds(paperList, ExercisePaper.class, "id");
+                    List<UserPaper> userPaperList = userPaperService.listWithOrigin(user.getId(), PaperOrigin.EXERCISE, ids, null);
+                    // 绑定userPaperId,用于关联report
+                    Map userPaperMap = Transform.getMap(userPaperList, UserPaper.class, "originId", "id");
+                    Transform.combine(childrenDtos, userPaperMap, UserExerciseGroupExtendDto.class, "id", "userPaperId");
 
+                    // 获取最后一次作业结果
+                    Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
+                    List<UserReport> reportList = userReportService.listWithLater(paperIds);
+                    Transform.combine(childrenDtos, reportList, UserExerciseGroupExtendDto.class, "userPaperId", "report", UserReport.class, "paperId", UserReportExtendDto.class);
+                }
                 dto.setChildren(childrenDtos);
             }else{
                 // 以struct作为children
@@ -296,6 +297,27 @@ public class QuestionController {
     }
 
 
+    @RequestMapping(value = "/examination/info", method = RequestMethod.GET)
+    @ApiOperation(value = "模考信息", httpMethod = "GET")
+    public Response<UserExaminationInfoDto> info(HttpSession session) {
+        User user = (User) shiroHelp.getLoginUser();
+        UserExaminationInfoDto dto = new UserExaminationInfoDto();
+
+        if (user != null){
+            UserService userService = userServiceService.getService(user.getId(), ServiceKey.QX_CAT);
+            dto.setHasService(userService != null);
+            UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), ServiceKey.QX_CAT);
+            dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
+
+            dto.setReset(userService != null && userService.getIsReset() > 0);
+            dto.setExpireTime(userService != null ? userService.getExpireTime() : null);
+
+            dto.setCanReset(examinationService.isFinishCat(user.getId()));
+        }
+
+        return ResponseHelp.success(dto);
+    }
+
     @RequestMapping(value = "/examination/progress", method = RequestMethod.GET)
     @ApiOperation(value = "模考进度", httpMethod = "GET")
     public Response<List<UserExaminationGroupDto>> examinationProgress(
@@ -317,9 +339,9 @@ public class QuestionController {
             dto.setMinTimes(0);
 
             if(user != null){
-                dto.setHasService(userServiceService.hasService(user.getId(), serviceKey));
                 if (serviceKey != null){
                     // 服务, 判断对应服务状态
+                    dto.setHasService(userServiceService.hasService(user.getId(), serviceKey));
                     UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), serviceKey);
                     dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
                 }
@@ -359,10 +381,27 @@ public class QuestionController {
         User user = (User) shiroHelp.getLoginUser();
         ExaminationStruct struct = examinationStructService.get(structId);
         ServiceKey serviceKey = ServiceKey.ValueOf(struct.getExtend());
+        if (serviceKey == ServiceKey.QX_CAT){
+            if (user == null){
+                throw new ParameterException("请先登录");
+            }
+            if (!userServiceService.hasService(user.getId(), serviceKey)){
+                throw new ParameterException("请先开通模考");
+            }
+        }
         PageResult<ExaminationPaper> p = examinationService.list(page, size, structId, user != null ? user.getId():null, serviceKey == ServiceKey.QX_CAT && user != null ? user.getQxCat() : null, times);
 
         List<UserExaminationPaperDto> pr = Transform.convert(p, UserExaminationPaperDto.class);
 
+        // 获取试卷统计信息
+        Map<Integer, Integer[]> questionNoIdsMap = new HashMap<>();
+        for(ExaminationPaper paper : p){
+            questionNoIdsMap.put(paper.getId(), paper.getQuestionNoIds());
+        }
+        Map statMap = questionNoService.statPaperMap(questionNoIdsMap);
+        Transform.combine(pr, statMap, UserExercisePaperDto.class, "id", "stat");
+
+
         if (user != null){
             // 获取做题记录
             Collection ids = Transform.getIds(p, ExaminationPaper.class, "id");
@@ -385,7 +424,7 @@ public class QuestionController {
 
             if (serviceKey == ServiceKey.QX_CAT && user.getQxCat() > 0){
                 // 获取上一遍模考成绩
-                UserService userService = userServiceService.getService(user.getId(), serviceKey);
+                UserService userService = userServiceService.getServiceBase(user.getId(), serviceKey);
                 if (userService.getIsReset() > 0){
                     List<UserPaper> prevPaperList = userPaperService.listWithCat(user.getId(), ids, user.getQxCat() - 1);
                     Transform.combine(pr, prevPaperList, UserExaminationPaperDto.class, "id", "prevPaper", UserPaper.class, "originId", UserPaperBaseExtendDto.class);
@@ -811,7 +850,7 @@ public class QuestionController {
     public Response<Boolean> resetCat()  {
         User user = (User) shiroHelp.getLoginUser();
 
-        UserService userService = userServiceService.getService(user.getId(), ServiceKey.QX_CAT);
+        UserService userService = userServiceService.getServiceBase(user.getId(), ServiceKey.QX_CAT);
         if (userService == null){
             throw new ParameterException("无重置权限");
         }

+ 26 - 8
server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java

@@ -17,6 +17,7 @@ import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
 import com.qxgmat.dto.extend.UserReportExtendDto;
 import com.qxgmat.dto.extend.UserServiceRecordExtendDto;
 import com.qxgmat.dto.extend.UserTextbookGroupExtendDto;
+import com.qxgmat.dto.request.TextbookSubscribeDto;
 import com.qxgmat.dto.response.UserTextbookGroupDto;
 import com.qxgmat.dto.response.UserTextbookInfoDto;
 import com.qxgmat.dto.response.UserTextbookPaperDto;
@@ -30,6 +31,7 @@ import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpSession;
@@ -184,9 +186,13 @@ public class TextbookController
         TextbookLibrary latest = textbookLibraryService.getLatest();
         dto.setLatest(latest);
         if (user != null){
-            dto.setHasService(userServiceService.hasService(user.getId(), ServiceKey.TEXTBOOK));
+            UserService userService = userServiceService.getService(user.getId(), ServiceKey.TEXTBOOK);
+            dto.setHasService(userService != null);
             UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), ServiceKey.TEXTBOOK);
             dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
+
+            dto.setSubscribe(userService != null && userService.getIsSubscribe() > 0);
+            dto.setExpireTime(userService != null ? userService.getExpireTime() : null);
         }
         TextbookLibrary second = textbookLibraryService.getSecond();
         dto.setSecond(second);
@@ -272,23 +278,22 @@ public class TextbookController
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = true) boolean latest,
             @RequestParam(required = true) String logic,
-            @RequestParam(required = false)  Integer times,
+            @RequestParam(required = false) String year,
+            @RequestParam(required = false) Integer times,
             HttpSession session) {
         User user = (User) shiroHelp.getLoginUser();
-        TextbookLibrary library;
+        TextbookLibrary library= null;
         if (latest){
             if (user == null){
                 throw new AuthException("请先登录");
             }
             if (!userServiceService.hasService(user.getId(), ServiceKey.TEXTBOOK)){
-                throw new ParameterException("没有机经查看权限");
+                throw new ParameterException("请先开通本期机经");
             }
             library = textbookLibraryService.getLatest();
-        }else{
-            // 获取往期:倒数第二
-            library = textbookLibraryService.getSecond();
         }
-        List<TextbookPaper> p = textbookPaperService.list(page, size, library.getId(), user != null ? user.getId():null, TextbookLogic.ValueOf(logic), times);
+
+        List<TextbookPaper> p = textbookPaperService.list(page, size, library != null ? library.getId():null, user != null ? user.getId():null, TextbookLogic.ValueOf(logic), year, times);
         List<UserTextbookPaperDto> pr = Transform.convert(p, UserTextbookPaperDto.class);
 
         // 获取试卷统计信息
@@ -316,4 +321,17 @@ public class TextbookController
 
         return ResponseHelp.success(pr);
     }
+
+    @RequestMapping(value = "/subscribe", method = RequestMethod.POST)
+    @ApiOperation(value = "订阅机经", notes = "订阅机经开关", httpMethod = "POST")
+    public Response<Boolean> subscribe(@RequestBody @Validated TextbookSubscribeDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+
+        UserService userService = userServiceService.getServiceBase(user.getId(), ServiceKey.TEXTBOOK);
+        if (userService == null){
+            throw new ParameterException("无订阅权限");
+        }
+        userServiceService.edit(UserService.builder().id(userService.getId()).isSubscribe(dto.getSubscribe() ? 1 : 0).build());
+        return ResponseHelp.success(true);
+    }
 }

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserReportExtendDto.java

@@ -28,6 +28,8 @@ public class UserReportExtendDto {
 
     private Date updateTime;
 
+    private JSONObject setting;
+
     private JSONObject score;
 
     public Integer getId() {
@@ -109,4 +111,12 @@ public class UserReportExtendDto {
     public void setUserCorrect(Integer userCorrect) {
         this.userCorrect = userCorrect;
     }
+
+    public JSONObject getSetting() {
+        return setting;
+    }
+
+    public void setSetting(JSONObject setting) {
+        this.setting = setting;
+    }
 }

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

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

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

@@ -0,0 +1,57 @@
+package com.qxgmat.dto.response;
+
+import com.qxgmat.dto.extend.UserServiceRecordExtendDto;
+
+import java.util.Date;
+
+public class UserExaminationInfoDto {
+    private Date expireTime;
+
+    private Boolean hasService;
+
+    private UserServiceRecordExtendDto unUseRecord;
+
+    private Boolean reset;
+
+    private Boolean canReset;
+
+    public Boolean getHasService() {
+        return hasService;
+    }
+
+    public void setHasService(Boolean hasService) {
+        this.hasService = hasService;
+    }
+
+    public UserServiceRecordExtendDto getUnUseRecord() {
+        return unUseRecord;
+    }
+
+    public void setUnUseRecord(UserServiceRecordExtendDto unUseRecord) {
+        this.unUseRecord = unUseRecord;
+    }
+
+    public Boolean getReset() {
+        return reset;
+    }
+
+    public void setReset(Boolean reset) {
+        this.reset = reset;
+    }
+
+    public Boolean getCanReset() {
+        return canReset;
+    }
+
+    public void setCanReset(Boolean canReset) {
+        this.canReset = canReset;
+    }
+
+    public Date getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(Date expireTime) {
+        this.expireTime = expireTime;
+    }
+}

+ 49 - 9
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserExaminationPaperDto.java

@@ -12,7 +12,15 @@ public class UserExaminationPaperDto {
 
     private Integer title;
 
-    private PaperStat stat;
+    private Integer totalScore;
+
+    private Integer totalTimes;
+
+    private Integer quantScore;
+
+    private Integer verbalScore;
+
+    private Integer irScore;
 
     private UserPaperBaseExtendDto paper;
 
@@ -26,14 +34,6 @@ public class UserExaminationPaperDto {
 
     private Integer prevUserPaperId;
 
-    public PaperStat getStat() {
-        return stat;
-    }
-
-    public void setStat(PaperStat stat) {
-        this.stat = stat;
-    }
-
     public UserPaperBaseExtendDto getPaper() {
         return paper;
     }
@@ -97,4 +97,44 @@ public class UserExaminationPaperDto {
     public void setPrevUserPaperId(Integer prevUserPaperId) {
         this.prevUserPaperId = prevUserPaperId;
     }
+
+    public Integer getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Integer totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public Integer getTotalTimes() {
+        return totalTimes;
+    }
+
+    public void setTotalTimes(Integer totalTimes) {
+        this.totalTimes = totalTimes;
+    }
+
+    public Integer getQuantScore() {
+        return quantScore;
+    }
+
+    public void setQuantScore(Integer quantScore) {
+        this.quantScore = quantScore;
+    }
+
+    public Integer getVerbalScore() {
+        return verbalScore;
+    }
+
+    public void setVerbalScore(Integer verbalScore) {
+        this.verbalScore = verbalScore;
+    }
+
+    public Integer getIrScore() {
+        return irScore;
+    }
+
+    public void setIrScore(Integer irScore) {
+        this.irScore = irScore;
+    }
 }

+ 17 - 5
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserTextbookInfoDto.java

@@ -3,7 +3,11 @@ package com.qxgmat.dto.response;
 import com.qxgmat.data.dao.entity.TextbookLibrary;
 import com.qxgmat.dto.extend.UserServiceRecordExtendDto;
 
+import java.util.Date;
+
 public class UserTextbookInfoDto {
+    private Date expireTime;
+
     private TextbookLibrary latest;
 
     private TextbookLibrary second;
@@ -12,7 +16,7 @@ public class UserTextbookInfoDto {
 
     private UserServiceRecordExtendDto unUseRecord;
 
-    private Boolean emailSubscribe;
+    private Boolean subscribe;
 
     public Boolean getHasService() {
         return hasService;
@@ -38,12 +42,12 @@ public class UserTextbookInfoDto {
         this.second = second;
     }
 
-    public Boolean getEmailSubscribe() {
-        return emailSubscribe;
+    public Boolean getSubscribe() {
+        return subscribe;
     }
 
-    public void setEmailSubscribe(Boolean emailSubscribe) {
-        this.emailSubscribe = emailSubscribe;
+    public void setSubscribe(Boolean subscribe) {
+        this.subscribe = subscribe;
     }
 
     public UserServiceRecordExtendDto getUnUseRecord() {
@@ -53,4 +57,12 @@ public class UserTextbookInfoDto {
     public void setUnUseRecord(UserServiceRecordExtendDto unUseRecord) {
         this.unUseRecord = unUseRecord;
     }
+
+    public Date getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(Date expireTime) {
+        this.expireTime = expireTime;
+    }
 }

+ 1 - 0
server/gateway-api/src/main/java/com/qxgmat/service/UserQuestionService.java

@@ -84,6 +84,7 @@ public class UserQuestionService extends AbstractService {
     }
 
     public List<UserQuestion> listByQuestionNo(Integer userId, Collection questionNoIds){
+        if (questionNoIds == null || questionNoIds.size() == 0) return new ArrayList<>();
         Example example = new Example(UserQuestion.class);
         example.and(
                 example.createCriteria()

+ 12 - 3
server/gateway-api/src/main/java/com/qxgmat/service/UserServiceService.java

@@ -58,17 +58,26 @@ public class UserServiceService extends AbstractService {
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("service", key.key)
+                        .andGreaterThanOrEqualTo("startTime", new Date())
+                        .andLessThan("expireTime", new Date())
         );
         return one(userServiceMapper, example);
     }
 
     /**
-     * 给用户添加服务:系统自动
+     * 获取用户服务
      * @param userId
      * @param key
+     * @return
      */
-    public void addService(Integer userId, ServiceKey key, String param){
-
+    public UserService getServiceBase(Integer userId, ServiceKey key){
+        Example example = new Example(UserService.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("service", key.key)
+        );
+        return one(userServiceMapper, example);
     }
 
     /**

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

@@ -464,7 +464,7 @@ public class OrderFlowService {
                 // 设置结束有效期
                 endTime = Tools.addDate(startTime, expireDay);
             }
-            UserService userService = userServiceService.getService(record.getUserId(), serviceKey);
+            UserService userService = userServiceService.getServiceBase(record.getUserId(), serviceKey);
             if (userService == null){
                 userService = UserService.builder()
                         .userId(record.getUserId())
@@ -501,6 +501,7 @@ public class OrderFlowService {
                 userService.setIsSubscribe(record.getIsSubscribe());
                 userService = userServiceService.edit(userService);
             }
+
             record.setUseStartTime(startTime);
             record.setUseEndTime(endTime);
             return record;
@@ -570,7 +571,7 @@ public class OrderFlowService {
 
             Date startTime = time;
             Date endTime = Tools.addDate(startTime, expireDay);
-            UserService userService = userServiceService.getService(record.getUserId(), serviceKey);
+            UserService userService = userServiceService.getServiceBase(record.getUserId(), serviceKey);
             if (userService == null){
                 userService = UserService.builder()
                         .userId(record.getUserId())

+ 10 - 7
server/gateway-api/src/main/java/com/qxgmat/service/inline/CommentService.java

@@ -23,12 +23,16 @@ public class CommentService extends AbstractService {
     @Resource
     private CommentMapper commentMapper;
 
-    public Page<Comment> listAdmin(int page, int size, ChannelModule channel, String position, Integer userId, Boolean isSpecial){
+    public Page<Comment> listAdmin(int page, int size, Boolean user, String channel, String position, Integer userId, Boolean isSpecial){
         Example example = new Example(Comment.class);
-
+        if (user != null){
+            example.and(user ? example.createCriteria().andGreaterThan("userId", 0):
+                    example.createCriteria().andEqualTo("user", 0)
+            );
+        }
         if (channel != null){
             example.and(
-                    example.createCriteria().andEqualTo("channel", channel.key)
+                    example.createCriteria().andEqualTo("channel", channel)
             );
             if (position != null){
                 example.and(
@@ -50,12 +54,12 @@ public class CommentService extends AbstractService {
         return select(commentMapper, example, page, size);
     }
 
-    public Page<Comment> list(int page, int size, ChannelModule channel, String position){
+    public Page<Comment> list(int page, int size, String channel, String position){
         Example example = new Example(Comment.class);
 
         if (channel != null){
             example.and(
-                    example.createCriteria().andEqualTo("channel", channel.key)
+                    example.createCriteria().andEqualTo("channel", channel)
             );
             if (position != null){
                 example.and(
@@ -65,8 +69,7 @@ public class CommentService extends AbstractService {
         }
         example.and(
                 example.createCriteria()
-                    .orEqualTo("isSpecial", 1)
-                    .orEqualTo("isSystem", 1)
+                    .orEqualTo("isShow", 1)
         );
         return select(commentMapper, example, page, size);
     }

+ 10 - 18
server/gateway-api/src/main/java/com/qxgmat/service/inline/FaqService.java

@@ -5,11 +5,8 @@ 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.module.ChannelModule;
-import com.qxgmat.data.constants.enums.module.FaqModule;
-import com.qxgmat.data.dao.CommentMapper;
+import com.qxgmat.data.constants.enums.module.InsideModule;
 import com.qxgmat.data.dao.FaqMapper;
-import com.qxgmat.data.dao.entity.Comment;
 import com.qxgmat.data.dao.entity.Faq;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -26,16 +23,16 @@ public class FaqService extends AbstractService {
     @Resource
     private FaqMapper faqMapper;
 
-    public Page<Faq> listAdmin(int page, int size, FaqModule faqModule, ChannelModule channel, String position, Integer answerStatus, Boolean isSpecial){
+    public Page<Faq> listAdmin(int page, int size, Boolean user, String channel, String position, Integer answerStatus, Boolean isSpecial){
         Example example = new Example(Faq.class);
-        if (faqModule != null) {
-            example.and(
-                    example.createCriteria().andEqualTo("faqModule", faqModule.key)
-            );
+        if (user != null){
+            example.and(user ? example.createCriteria().andGreaterThan("userId", 0):
+                        example.createCriteria().andEqualTo("user", 0)
+                    );
         }
         if (channel != null){
             example.and(
-                    example.createCriteria().andEqualTo("channel", channel.key)
+                    example.createCriteria().andEqualTo("channel", channel)
             );
             if (position != null){
                 example.and(
@@ -57,12 +54,12 @@ public class FaqService extends AbstractService {
         return select(faqMapper, example, page, size);
     }
 
-    public Page<Faq> list(int page, int size, ChannelModule channel, String position){
+    public Page<Faq> list(int page, int size, String channel, String position){
         Example example = new Example(Faq.class);
 
         if (channel != null){
             example.and(
-                    example.createCriteria().andEqualTo("channel", channel.key)
+                    example.createCriteria().andEqualTo("channel", channel)
             );
             if (position != null){
                 example.and(
@@ -72,12 +69,7 @@ public class FaqService extends AbstractService {
         }
         example.and(
                 example.createCriteria()
-                        .andEqualTo("answerStatus", 1)
-        );
-        example.and(
-                example.createCriteria()
-                        .orEqualTo("isSpecial", 1)
-                        .orEqualTo("isSystem", 1)
+                        .orEqualTo("isShow", 1)
         );
         return select(faqMapper, example, page, size);
     }

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

@@ -38,10 +38,10 @@ public class TextbookPaperService extends AbstractService {
      * @param times
      * @return
      */
-    public Page<TextbookPaper> list(int page, int size, Number libraryId, Number userId, TextbookLogic logic, Integer times){
+    public Page<TextbookPaper> list(int page, int size, Number libraryId, Number userId, TextbookLogic logic, String year, Integer times){
         String logicKey = logic != null ? logic.key : "";
         Page<TextbookPaper> p = page(()->{
-            textbookPaperRelationMapper.listWithUser(libraryId, userId, logicKey, times);
+            textbookPaperRelationMapper.listWithUser(libraryId, userId, logicKey, year, times);
         },page, size);
 
         Collection ids = Transform.getIds(p, TextbookPaper.class, "id");