Browse Source

feat(front): 长难句阅读页逻辑

Go 5 years ago
parent
commit
497bae8b7d
57 changed files with 1420 additions and 402 deletions
  1. 1 0
      front/project/admin/routes/subject/sentenceArticle/index.less
  2. 2 0
      front/project/www/routes/examination/index.js
  3. 3 0
      front/project/www/routes/exercise/index.js
  4. 10 0
      front/project/www/routes/exercise/list/index.js
  5. 59 0
      front/project/www/routes/exercise/list/index.less
  6. 231 0
      front/project/www/routes/exercise/list/page.js
  7. 4 4
      front/project/www/routes/page/practise/index.js
  8. 1 1
      front/project/www/routes/page/practise/index.less
  9. 140 69
      front/project/www/routes/page/practise/page.js
  10. 8 1
      front/project/www/routes/index.js
  11. 2 0
      front/project/www/routes/my/index.js
  12. 1 1
      front/project/www/routes/page/home/index.js
  13. 1 3
      front/project/www/routes/page/index.js
  14. 0 107
      front/project/www/routes/page/read/page.js
  15. 5 0
      front/project/www/routes/paper/index.js
  16. 3 3
      front/project/www/routes/page/start/index.js
  17. 1 1
      front/project/www/routes/page/start/index.less
  18. 0 0
      front/project/www/routes/paper/process/page.js
  19. 2 2
      front/project/www/routes/page/result/index.js
  20. 1 1
      front/project/www/routes/page/result/index.less
  21. 0 0
      front/project/www/routes/paper/question/page.js
  22. 2 2
      front/project/www/routes/page/report/index.js
  23. 1 1
      front/project/www/routes/page/report/index.less
  24. 0 0
      front/project/www/routes/paper/report/page.js
  25. 2 0
      front/project/www/routes/preview/index.js
  26. 3 0
      front/project/www/routes/sentence/index.js
  27. 2 2
      front/project/www/routes/page/hard/index.js
  28. 1 1
      front/project/www/routes/page/hard/index.less
  29. 0 0
      front/project/www/routes/sentence/process/page.js
  30. 2 2
      front/project/www/routes/page/read/index.js
  31. 15 1
      front/project/www/routes/page/read/index.less
  32. 287 0
      front/project/www/routes/sentence/read/page.js
  33. 2 0
      front/project/www/routes/textbook/index.js
  34. 12 0
      front/project/www/stores/course.js
  35. 3 1
      front/project/www/stores/index.js
  36. 54 10
      front/project/www/stores/main.js
  37. 98 39
      front/project/www/stores/my.js
  38. 94 37
      front/project/www/stores/question.js
  39. 15 51
      front/project/www/stores/sentence.js
  40. 12 0
      front/project/www/stores/textbook.js
  41. 3 1
      server/data/src/main/java/com/qxgmat/data/relation/ExercisePaperRelationMapper.java
  42. 3 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/ExercisePaperRelationMapper.xml
  43. 18 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java
  44. 21 22
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  45. 57 3
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  46. 33 19
      server/gateway-api/src/main/java/com/qxgmat/controller/api/SentenceController.java
  47. 28 8
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserSentenceProcessDto.java
  48. 30 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentenceArticleDetailDto.java
  49. 31 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentenceArticleDto.java
  50. 40 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentenceInfoDto.java
  51. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExaminationService.java
  52. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java
  53. 23 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExaminationStructService.java
  54. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExercisePaperService.java
  55. 23 4
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExerciseStructService.java
  56. 10 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/SentenceArticleService.java
  57. 15 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserSentenceProcessService.java

+ 1 - 0
front/project/admin/routes/subject/sentenceArticle/index.less

@@ -7,6 +7,7 @@
 
   .simulation {
     width: 940px;
+    // padding-top: 66px; // Part头占位
     line-height: 20px;
 
     p {

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

@@ -0,0 +1,2 @@
+
+export default [];

+ 3 - 0
front/project/www/routes/exercise/index.js

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

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

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

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

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

+ 231 - 0
front/project/www/routes/exercise/list/page.js

@@ -0,0 +1,231 @@
+import React from 'react';
+import './index.less';
+import Page from '@src/containers/Page';
+import { asyncConfirm } from '@src/services/AsyncTools';
+import Tabs from '../../../components/Tabs';
+import Module from '../../../components/Module';
+import ProgressText from '../../../components/ProgressText';
+import IconButton from '../../../components/IconButton';
+import { Main } from '../../../stores/main';
+import { Question } from '../../../stores/question';
+import { QuestionDifficult } from '../../../../Constant';
+
+const LOGIC_NO = 'no';
+const LOGIC_PLACE = 'place';
+const LOGIC_DIFFICULT = 'difficult';
+const LOGIC_ERROR = 'error';
+
+const columns = [
+  {
+    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 ? 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">{item.stat.totalCorrect / item.stat.totalNumber}</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">全站{item.stat.totalTime / item.stat.totalNumber}s</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.repport.id && (
+            <IconButton type="start" tip="Start" onClick={() => this.previewAction('start', item)} />
+          )}
+          {item.repport.id && (
+            <IconButton
+              className="m-r-2"
+              type="continue"
+              tip="Continue"
+              onClick={() => this.previewAction('continue', item)}
+            />
+          )}
+          {item.repport.id && (
+            <IconButton type="restart" tip="Restart" onClick={() => this.previewAction('restart', item)} />
+          )}
+        </div>
+      );
+    },
+  },
+  {
+    title: '报告',
+    width: 30,
+    align: 'right',
+    render: item => {
+      return (
+        <div className="table-row p-t-1">
+          {item.report.userNumber === item.report.questionNumber && <IconButton type="report" tip="Report" />}
+        </div>
+      );
+    },
+  },
+];
+
+export default class extends Page {
+  initState() {
+    this.columns = columns;
+    this.placeList = [];
+    return {
+      logic: LOGIC_NO,
+      logicExtend: '',
+      tabs: [{
+        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.setState({ navs });
+    });
+  }
+
+  initData() {
+    this.refresh();
+  }
+
+  refresh() {
+    const { logic } = this.state;
+    let handler = null;
+    switch (logic) {
+      case LOGIC_PLACE:
+        handler = this.refreshPlace();
+        break;
+      case LOGIC_DIFFICULT:
+        handler = this.refreshDifficult();
+        break;
+      default:
+        handler = Promise.resolve();
+    }
+    handler.then(() => {
+      this.refreshExercise();
+    });
+  }
+
+  refreshPlace() {
+    if (this.placeList.length > 0) {
+      this.setState({ logicExtend: this.placeList[0] });
+      return Promise.resolve();
+    }
+    const { id } = this.params;
+    return Question.getExercisePlace(id).then(result => {
+      this.placeList = result;
+      this.setState({ logicExtend: this.placeList[0] });
+    });
+  }
+
+  refreshDifficult() {
+    this.setState({ logicExtend: QuestionDifficult[0].value });
+    return Promise.resolve();
+  }
+
+  refreshExercise() {
+    Question.getExerciseList(this.state.search)
+      .then((result) => {
+        this.setState({ list: result.list, total: result.total });
+      });
+  }
+
+  onChangeTab(level, tab) {
+    const state = {};
+    state[`level${level}Tab`] = tab;
+    this.setState(state);
+    this.refresh();
+  }
+
+  restart(item) {
+    asyncConfirm('提示', '是否重置', () => {
+      Question.restart(item.report.id).then(() => {
+        this.refresh();
+      });
+    });
+  }
+
+  start(type, item) {
+    linkTo(`/paper/process/${type}/${item.id}`);
+  }
+
+  continue(type, item) {
+    linkTo(`/paper/process/${type}/${item.id}?r=${item.report.id}`);
+  }
+
+  renderView() {
+    const { level1Tab = {}, level2Tab = {}, tabs } = this.state;
+    return (
+      <div>
+        <div className="content">
+          <Module className="m-t-2">
+            <Tabs type="card" active={level1Tab.key} tabs={tabs} onChange={key => {
+              this.onChangeTab(1, key);
+            }} />
+            {level1Tab.children > 1 && <Tabs active={level2Tab.key} tabs={level1Tab.children} onChange={key => this.onChangeTab(2, key)} />}
+
+          </Module>
+        </div>
+      </div>
+    );
+  }
+}

+ 4 - 4
front/project/www/routes/page/practise/index.js

@@ -1,9 +1,9 @@
 export default {
-  path: '/practise',
-  key: 'practise',
+  path: '/exercise',
+  key: 'exercise',
   title: '练习',
-  needLogin: true,
-  tab: 'practise',
+  needLogin: false,
+  tab: 'exercise',
   component() {
     return import('./page');
   },

+ 1 - 1
front/project/www/routes/page/practise/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#practise {
+#exercise {
   .code-module {
     padding: 80px 250px;
     text-align: center;

+ 140 - 69
front/project/www/routes/page/practise/page.js

@@ -3,6 +3,7 @@ import './index.less';
 import { Link } from 'react-router-dom';
 import Page from '@src/containers/Page';
 import { asyncConfirm } from '@src/services/AsyncTools';
+import { formatTreeData, getMap } from '@src/services/Tools';
 import Tabs from '../../../components/Tabs';
 import Module from '../../../components/Module';
 import Input from '../../../components/Input';
@@ -13,12 +14,14 @@ import ListTable from '../../../components/ListTable';
 import ProgressText from '../../../components/ProgressText';
 import IconButton from '../../../components/IconButton';
 import { Main } from '../../../stores/main';
+import { Sentence } from '../../../stores/sentence';
 import { Question } from '../../../stores/question';
+// import { Course } from '../../../stores/course';
 
-const HARD = 'HARD';
-const PREVIEW = 'PREVIEW';
+const SENTENCE = 'sentence';
+const PREVIEW = 'preview';
 const PREVIEW_CLASS = 'PREVIEW_CLASS';
-const PREVIEW_TASK = 'PREVIEW_TASK';
+const PREVIEW_LIST = 'PREVIEW_LIST';
 
 const columns = [
   {
@@ -119,10 +122,12 @@ const columns = [
 
 export default class extends Page {
   initState() {
+    this.code = null;
     this.columns = columns;
+    this.exerciseProcess = {};
     return {
-      level1Tab: PREVIEW,
-      level2Tab: '',
+      tab1: PREVIEW,
+      tab2: '',
       previewType: PREVIEW_CLASS,
       tabs: [],
       allClass: [],
@@ -132,44 +137,60 @@ export default class extends Page {
 
   initData() {
     Main.getExercise().then(result => {
-      const list = result;
-      const map = {};
-      for (let i = 0; i < result.length; i += 1) {
-        const item = result[i];
-        if (!map[item.parentId]) map[item.parentId] = [];
-        map[item.parentId].push(item);
-      }
-      const tabs = [];
-      let allClass = [];
-      tabs.push({ key: HARD, name: '长难句' });
-      if (map[0]) {
-        for (let i = 0; i < map[0].length; i += 1) {
-          const item = map[0][i];
-          tabs.push({ key: item.id, name: `${item.titleZh} ${item.titleEn}` });
-          if (map[item.id]) {
-            allClass = allClass.concat(map[item.id]);
-          }
-        }
-      }
+      const list = result.map((row) => {
+        row.title = `${row.titleZh}${row.titleEn}`;
+        row.key = row.extend;
+        return row;
+      });
+      const tabs = formatTreeData(list);
       tabs.push({ key: PREVIEW, name: '预习作业' });
-      this.setState({ tabs, allClass, list, map });
+      const map = getMap(tabs, 'key');
+      this.setState({ tabs, map });
     });
-    this.refreshClassProcess();
+    this.refresh();
   }
 
-  refreshPreview() {
-    const { previewType } = this.state;
-    if (previewType === PREVIEW_CLASS) {
-      this.refreshClassProcess();
-    }
-    if (previewType === PREVIEW_TASK) {
-      this.refreshListPreview();
+  refresh() {
+    const { tab1 } = this.state;
+    switch (tab1) {
+      case SENTENCE:
+        this.refreshSentence();
+        break;
+      case PREVIEW:
+        this.refreshPreview();
+        break;
+      default:
+        this.refreshExercise();
     }
   }
 
-  onChangePreviewType(type) {
-    this.setState({ previewType: type });
-    this.refreshPreview();
+  refreshSentence() {
+    Sentence.getInfo().then(result => {
+      this.setState({ sentence: result });
+    });
+    Sentence.listArticle().then(result => {
+      const articleMap = {};
+      result.forEach((article) => {
+        if (!articleMap[article.chapter]) {
+          articleMap[article.chapter] = [];
+        }
+        articleMap[article.chapter].push(article);
+      });
+      this.setState({ articleMap });
+    });
+  }
+
+  refreshPreview() {
+    const { previewType } = this.state;
+    switch (previewType) {
+      case PREVIEW_LIST:
+        this.refreshListPreview();
+        break;
+      case PREVIEW_CLASS:
+      default:
+        this.refreshClassProcess();
+        break;
+    }
   }
 
   refreshClassProcess() {
@@ -189,82 +210,117 @@ export default class extends Page {
     });
   }
 
+  refreshExercise() {
+    const { map, tab1 } = this.state;
+    let { tab2 } = this.state;
+    const subject = map[tab1];
+    if (tab2 === '') {
+      tab2 = subject.children[0].key;
+      this.onChangeTab(2, tab2);
+      return;
+    }
+    const type = map[tab2];
+    Main.getExerciseChildren(type.id, true).then(result => {
+      const exerciseChild = result;
+      this.setState({ exerciseChild });
+    });
+    Question.getExerciseProcess(type.id).then((r => {
+      const exerciseProcess = getMap(r, 'id');
+      this.setState({ exerciseProcess });
+    }));
+  }
+
+  onChangePreviewType(type) {
+    this.setState({ previewType: type });
+    this.refreshPreview();
+  }
+
   onChangeTab(level, tab) {
     const state = {};
-    state[`level${level}Tab`] = tab;
+    state[`tab${level}`] = tab;
     this.setState(state);
+    this.refresh();
   }
 
   previewAction(type, item) {
     switch (type) {
       case 'start':
-        this.startPreview(item);
+        this.start('preview', item);
         break;
       case 'restart':
-        this.restartPreview(item);
+        this.restart(item);
         break;
       case 'continue':
-        this.continuePreview(item);
+        this.continue('preview', item);
         break;
       default:
         break;
     }
   }
 
-  startPreview(item) {
-    linkTo(`/start/${item.id}?type=preview`);
-  }
-
-  restartPreview(item) {
+  restart(item) {
     asyncConfirm('提示', '是否重置', () => {
       Question.restart(item.report.id).then(() => {
-        this.refreshPreview();
+        this.refresh();
       });
     });
   }
 
-  continuePreview(item) {
-    linkTo(`/start/${item.id}?type=preview&r=${item.report.id}`);
+  start(type, item) {
+    linkTo(`/paper/process/${type}/${item.id}`);
+  }
+
+  continue(type, item) {
+    linkTo(`/paper/process/${type}/${item.id}?r=${item.report.id}`);
+  }
+
+  activeSentence() {
+    Sentence.active(this.code)
+      .then(() => {
+        this.refresh();
+      });
   }
 
   renderView() {
-    const { level1Tab, level2Tab, tabs, map } = this.state;
+    const { tab1 = {}, tab2 = {}, tabs, map = {} } = this.state;
+    const children = (map[tab1] || {}).children || [];
     return (
       <div>
         <div className="content">
           <Module className="m-t-2">
-            <Tabs type="card" active={level1Tab} tabs={tabs} onChange={key => this.onChangeTab(1, key)} />
-            {level1Tab !== HARD && level1Tab !== PREVIEW && (
-              <Tabs active={level2Tab} tabs={map[level2Tab]} onChange={key => this.onChangeTab(2, key)} />
-            )}
+            <Tabs type="card" active={tab1} tabs={tabs} onChange={key => {
+              this.onChangeTab(1, key);
+            }} />
+            {children.length > 1 && <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />}
+
           </Module>
-          {level1Tab !== HARD && level1Tab !== PREVIEW && this.renderType()}
-          {level1Tab === HARD && this.renderHard()}
-          {level1Tab === PREVIEW && this.renderWork()}
+          {tab1 !== SENTENCE && tab1 !== PREVIEW && this.renderExercise()}
+          {tab1 === SENTENCE && this.renderSentence()}
+          {tab1 === PREVIEW && this.renderPreview()}
         </div>
       </div>
     );
   }
 
-  renderWork() {
+  renderPreview() {
     const { previewType } = this.state;
     switch (previewType) {
       case PREVIEW_CLASS:
-        return this.renderAllClass();
-      case PREVIEW_TASK:
-        return this.renderAllTask();
+        return this.renderPreviewClass();
+      case PREVIEW_LIST:
+        return this.renderPreviewList();
       default:
         return <div />;
     }
   }
 
-  renderAllClass() {
+  renderPreviewClass() {
     const { allClass, classProcess } = this.state;
     return (
       <div className="work-body">
         <div className="work-nav">
           <div className="left">完成情况</div>
-          <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_TASK)}>
+          <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_LIST)}>
             全部作业 >
           </div>
         </div>
@@ -277,7 +333,7 @@ export default class extends Page {
     );
   }
 
-  renderAllTask() {
+  renderPreviewList() {
     const { previews } = this.state;
     return (
       <div className="work-body">
@@ -308,11 +364,16 @@ export default class extends Page {
     );
   }
 
-  renderHard() {
+  renderSentence() {
+    const { sentence = {}, trail = false } = this.state;
+    if (sentence.code || trail) {
+      return this.renderSentenceArticle();
+    }
     return this.renderInputCode();
   }
 
-  renderType() {
+  renderSentenceArticle() {
+    // const { sentence = {}, trail } = this.state;
     return <div />;
   }
 
@@ -321,8 +382,12 @@ export default class extends Page {
       <Module className="code-module">
         <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
         <div className="input-block">
-          <Input size="lager" placeholder="请输入CODE" />
-          <Button size="lager">解锁</Button>
+          <Input size="lager" placeholder="请输入CODE" onChange={(value) => {
+            this.code = value;
+          }} />
+          <Button size="lager" onClick={() => {
+            this.activeSentence();
+          }}>解锁</Button>
         </div>
         <div className="tip">
           <Link to="/" className="left link">
@@ -332,11 +397,17 @@ export default class extends Page {
           <Link to="/" className="link">
             去获取 >>
           </Link>
-          <Link to="/" className="right link">
+          <a onClick={() => {
+            this.setState({ trail: true });
+          }} className="right link">
             试用 >>
-          </Link>
+          </a>
         </div>
       </Module>
     );
   }
+
+  renderExercise() {
+    return <div />;
+  }
 }

+ 8 - 1
front/project/www/routes/index.js

@@ -1,5 +1,12 @@
 // We only need to import the modules necessary for initial render
 
 import Page from './page';
+import Examination from './examination';
+import Exercise from './exercise';
+import My from './my';
+import Paper from './paper';
+import Preview from './preview';
+import Sentence from './sentence';
+import Textbook from './textbook';
 
-export default [...Page];
+export default [...Page, ...Examination, ...Exercise, ...My, ...Paper, ...Preview, ...Sentence, ...Textbook];

+ 2 - 0
front/project/www/routes/my/index.js

@@ -0,0 +1,2 @@
+
+export default [];

+ 1 - 1
front/project/www/routes/page/home/index.js

@@ -2,7 +2,7 @@ export default {
   path: '/',
   key: 'index',
   title: '首页',
-  needLogin: true,
+  needLogin: false,
   tab: 'main',
   component() {
     return import('./page');

+ 1 - 3
front/project/www/routes/page/index.js

@@ -3,8 +3,6 @@ import practise from './practise';
 import report from './report';
 import start from './start';
 import result from './result';
-import hard from './hard';
-import read from './read';
 import login from './login';
 
-export default [home, practise, report, start, result, hard, read, login];
+export default [home, practise, report, start, result, login];

+ 0 - 107
front/project/www/routes/page/read/page.js

@@ -1,107 +0,0 @@
-import React from 'react';
-import './index.less';
-import Page from '@src/containers/Page';
-import Icon from '../../../components/Icon';
-import Progress from '../../../components/Progress';
-import Assets from '../../../../../src/components/Assets';
-
-export default class extends Page {
-  constructor(props) {
-    super(props);
-    this.state = { showJump: true, showMenu: true };
-  }
-
-  renderView() {
-    return (
-      <div className="layout">
-        {this.renderBody()}
-        {this.renderRight()}
-        {this.renderBottom()}
-        {this.renderProgress()}
-      </div>
-    );
-  }
-
-  renderBody() {
-    const { showMenu } = this.state;
-    return (
-      <div className="layout-body">
-        <div className="crumb">千行长难句解析 >> Chapter4:简单句变长难句</div>
-        <div className="title">Part1:什么样的句子叫做长难句,长难句基本特征</div>
-        <div className="text">
-          1文章初读:《各段首句) 第一段首句: In the seventeenth-century Florentine textile industry, women were
-          employed primarily in low- paying, low-skill jobs. To explain this segregation of labor by gender, economists
-          have relied on the useful theory of human capital
-          翻译:在17世纪的佛罗伦萨纺织业中,女性主要受雇于低报酬、低技能的工作。经济学家依靠人力资本的有用理论
-          来解释这种有性别造成的劳动歧视
-          评:陈述了一种现象;在17世纪的佛罗伦萨纺织业中,女性主要受雇于低报酬、低技能的工作。可以推测本文是现
-          象解释型文章,果然紧接看介绍了一种理论theory of human capital。推断文章就是围绕这个现象和这个理论展开. There
-          were, however, differences in pay scales that cannot be explained by the human capital theory
-          翻译:然而.支付规模上存在的差异不能用这种人力资本理论解释。
-          评:以转折开头,提出人力资本理论存在的问题,把握文章整体结构二第一段用该理论解释了开头陈述的现象,本段
-          则指出该理论的问题作者对该理论的态度比较全面和笋辛证。
-        </div>
-        {showMenu && this.renderMenu()}
-      </div>
-    );
-  }
-
-  renderMenu() {
-    return (
-      <div className="layout-menu">
-        <div className="title">目录</div>
-        <div className="close">x</div>
-        <div className="chapter">
-          <div className="chapter-item">
-            Chapter1:简单句变长难句<div className="page">1</div>
-          </div>
-          <div className="part-item">
-            Part1:什么样的句子叫做长难句,长难句基本特征<div className="page">1</div>
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-  renderRight() {
-    return (
-      <div className="layout-right">
-        <div className="m-b-1">
-          <Icon name="menu" />
-        </div>
-        <div className="m-b-1">
-          <Icon name="down_turn" />
-        </div>
-        <div className="m-b-1">
-          <Icon name="up_turn" />
-        </div>
-      </div>
-    );
-  }
-
-  renderBottom() {
-    const { showJump } = this.state;
-    return (
-      <div className="layout-bottom">
-        <span className="per">50%</span>
-        <span className="num">9/18</span>
-        <span className="btn">
-          <Assets name="unfold_icon_up" />
-          <div hidden={!showJump} className="jump">
-            <span className="text">当前页</span>
-            <input className="input" />
-            <Assets name="yes_icon" />
-          </div>
-        </span>
-      </div>
-    );
-  }
-
-  renderProgress() {
-    return (
-      <div className="layout-progress">
-        <Progress size="small" theme="theme" radius={false} />
-      </div>
-    );
-  }
-}

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

@@ -0,0 +1,5 @@
+import process from './process';
+import question from './question';
+import report from './report';
+
+export default [process, question, report];

+ 3 - 3
front/project/www/routes/page/start/index.js

@@ -1,7 +1,7 @@
 export default {
-  path: '/start/:id',
-  key: 'start',
-  title: '开始考试',
+  path: '/paper/process/:type/:id',
+  key: 'paper-process',
+  title: '考试',
   needLogin: true,
   hideHeader: true,
   component() {

+ 1 - 1
front/project/www/routes/page/start/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#start {
+#paper-process {
   height: 100%;
   color: #000;
 

front/project/www/routes/page/start/page.js → front/project/www/routes/paper/process/page.js


+ 2 - 2
front/project/www/routes/page/result/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/result/:id',
-  key: 'result',
+  path: '/paper/question/:id',
+  key: 'paper-question',
   title: '查看结果',
   needLogin: true,
   hideHeader: true,

+ 1 - 1
front/project/www/routes/page/result/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#result {
+#paper-question {
   height: 100%;
 
   .layout {

front/project/www/routes/page/result/page.js → front/project/www/routes/paper/question/page.js


+ 2 - 2
front/project/www/routes/page/report/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/report',
-  key: 'report',
+  path: '/paper/report/:id',
+  key: 'paper-report',
   title: '报告',
   needLogin: true,
   component() {

+ 1 - 1
front/project/www/routes/page/report/index.less

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-#report {}
+#paper-report {}

front/project/www/routes/page/report/page.js → front/project/www/routes/paper/report/page.js


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

@@ -0,0 +1,2 @@
+
+export default [];

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

@@ -0,0 +1,3 @@
+import read from './read';
+
+export default [read];

+ 2 - 2
front/project/www/routes/page/hard/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/hard/:id',
-  key: 'hard',
+  path: '/sentence/:id',
+  key: 'sentence',
   title: '长难句',
   needLogin: true,
   hideHeader: true,

+ 1 - 1
front/project/www/routes/page/hard/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#hard {
+#sentence {
   height: 100%;
 
   .layout {

front/project/www/routes/page/hard/page.js → front/project/www/routes/sentence/process/page.js


+ 2 - 2
front/project/www/routes/page/read/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/read/:id',
-  key: 'read',
+  path: '/sentence/read',
+  key: 'sentence-read',
   title: '长难句阅读',
   needLogin: true,
   hideHeader: true,

+ 15 - 1
front/project/www/routes/page/read/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#read {
+#sentence-read {
   height: 100%;
   background: rgba(221, 227, 237, 1);
 
@@ -31,7 +31,21 @@
 
       .text {
         color: #000000;
+        line-height: 20px;
+      }
+
+      .overload {
+        position: relative;
+        margin: 0 20px;
+
+        .text {
+          position: absolute;
+          left: 0;
+          top: 0;
+          width: 960px;
+        }
       }
+
     }
 
     .layout-menu {

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

@@ -0,0 +1,287 @@
+import React from 'react';
+import './index.less';
+import Page from '@src/containers/Page';
+import Icon from '../../../components/Icon';
+import Progress from '../../../components/Progress';
+import Assets from '../../../../../src/components/Assets';
+import { Sentence } from '../../../stores/sentence';
+
+export default class extends Page {
+  constructor(props) {
+    super(props);
+    this.page = 0;
+    this.inited = false;
+    this.timeout = null;
+    this.articleMap = {};
+    this.pageLine = 100;
+
+    this.state = { showJump: true, showMenu: true };
+  }
+
+  init() {
+    this.lastTime = new Date();
+  }
+
+  initData() {
+    const { chapter = 0, part = 0 } = this.state.search;
+    let { page = 0 } = this.state.search;
+
+    const handler = this.refreshSentence();
+    handler.then(() => {
+      if (chapter > 0) {
+        if (!part) {
+          ({ page } = this.searchArticle(chapter, 1));
+        } else {
+          ({ page } = this.searchArticle(chapter, part));
+        }
+      }
+      this.jumpPage(page);
+    });
+  }
+
+  refreshSentence() {
+    if (this.inited) return Promise.resolve();
+    return Promise.all([
+      Sentence.getInfo().then(result => {
+        this.setState({ sentence: result });
+      }),
+      Sentence.listArticle().then(result => {
+        const articleMap = {};
+        let totalPage = 0;
+        let maxChapter = 0;
+        result.forEach((article) => {
+          if (!articleMap[article.chapter]) {
+            articleMap[article.chapter] = [];
+            if (article.chapter > maxChapter) maxChapter = article.chapter;
+          }
+          article.startPage = totalPage + 1;
+          article.endPage = totalPage + article.pages;
+          totalPage += article.pages;
+          articleMap[article.chapter].push(article);
+        });
+        this.setState({ articleMap, totalPage, maxChapter });
+      }),
+    ]).then(() => {
+      this.inited = true;
+    });
+  }
+
+  refreshArticle(articleId) {
+    if (this.articleMap[articleId]) return Promise.resolve(this.articleMap[articleId]);
+    return Sentence.detailArticle(articleId).then(result => {
+      this.articleMap[articleId] = result;
+      return result;
+    });
+  }
+
+  prevPage() {
+    const { currentPage } = this.state;
+    if (currentPage >= 1) {
+      this.jumpPage(currentPage - 1);
+    }
+  }
+
+  nextPage() {
+    const { currentPage, totalPage } = this.state;
+    if (currentPage + 1 >= totalPage) {
+      this.jumpPage(currentPage + 1);
+    }
+  }
+
+  jumpPage(targetPage) {
+    // 计算哪篇文章
+    const { target, index, allow } = this.computeArticle(targetPage);
+    if (!allow) {
+      // todo 无法访问:非试用
+      return;
+    }
+    const { article = {} } = this.state;
+    this.updateProcess(target, index, article);
+    this.refreshArticle(target.id).then((row) => {
+      this.setState({ article: row, index, currentPage: targetPage });
+    });
+  }
+
+  computeArticle(page) {
+    const { articleMap, maxChapter, sentence } = this.state;
+    let target = null;
+    let index = 0;
+    let allow = true;
+    for (let i = 1; i < maxChapter; i += 1) {
+      const list = articleMap[i];
+      if (!list || list.length === 0) continue;
+      for (let j = 0; j < list.length; j += 1) {
+        const article = list[j];
+        if (article.endPage > page) {
+          target = article;
+          index = page - article.startPage;
+          if (!sentence.code) {
+            if (!article.isTrail) {
+              allow = false;
+            } else if (index < article.trailStart - 1) {
+              allow = false;
+            } else if (index > article.trailEnd - 1) {
+              allow = false;
+            }
+          }
+          break;
+        }
+      }
+    }
+    return { target, index, allow };
+  }
+
+  searchArticle(chapter, part) {
+    const { articleMap, sentence } = this.state;
+    let target = null;
+    let page = 0;
+    const list = articleMap[chapter];
+    for (let j = 0; j < list.length; j += 1) {
+      const article = list[j];
+      if (article.part === Number(part)) {
+        target = article;
+        if (sentence.code) {
+          page = article.startPage;
+        } else {
+          // 试用章节页码
+          page = article.startPage + article.trailPage - 1;
+        }
+        break;
+      }
+    }
+    return { target, page };
+  }
+
+  updateProcess(target, index, current) {
+    if (this.timeout) {
+      clearTimeout(this.timeout);
+      this.timeout = null;
+    }
+    const now = new Date();
+    const time = (now.getTime() - this.lastTime.getTime()) / 1000;
+    this.lastTime = now;
+    const process = index + 1 * 100 / target.pages;
+    Sentence.updateProcess(target.chapter, target.part, process, time, current.chapter, current.page);
+    this.timeout = setTimeout(() => {
+      // 最长5分钟阅读时间
+      Sentence.updateProcess(0, 0, 0, 5 * 60, target.chapter, target.part);
+    }, 5 * 60 * 1000);
+  }
+
+  renderView() {
+    return (
+      <div className="layout">
+        {this.renderBody()}
+        {this.renderRight()}
+        {this.renderBottom()}
+        {this.renderProgress()}
+      </div>
+    );
+  }
+
+  renderBody() {
+    const { showMenu, article, index } = this.state;
+    return (
+      <div className="layout-body">
+        <div className="crumb">千行长难句解析 >> Chapter{article.chapter}:{}</div>
+        <div className="title">Part{article.part}:{article.title}</div>
+        <div className="overload" style={{ top: index * -20 * this.pageLine }}>
+          <div className="text" dangerouslySetInnerHTML={{ __html: article.content }} />
+        </div>
+        {showMenu && this.renderMenu()}
+      </div>
+    );
+  }
+
+  renderMenu() {
+    const { sentence = {}, articleMap } = this.state;
+    const { chapters = [] } = sentence;
+    let page = 1;
+    const code = !!sentence.code;
+    // todo 鼠标移上显示需要购买后访问
+    return (
+      <div className="layout-menu">
+        <div className="title">目录</div>
+        <div className="close" onClick={() => {
+          this.setState({ showMenu: false });
+        }}>x</div>
+        <div className="chapter">
+          {chapters.map(chapter => {
+            if (chapter.exercise) return [];
+            chapter.startPage = page;
+            const list = [<div className={`chapter-item ${code ? '' : 'trail'}`} onClick={() => {
+              if (code) this.jumpPage(chapter.startPage);
+            }}>
+              Chapter{chapter.value}:{chapter.title}<div className="page">{chapter.startPage}</div>
+            </div>];
+            (articleMap[chapter.value] || []).map((article) => {
+              // 得到下一章节page
+              page += article.pages;
+              return <div className={`part-item ${code ? '' : 'trail'}`} onClick={() => {
+                if (code) this.jumpPage(article.startPage);
+              }}>
+                Part{article.part}:{article.title}<div className="page">{article.startPage}</div>
+              </div>;
+            });
+            return list;
+          })}
+        </div>
+      </div>
+    );
+  }
+
+  renderRight() {
+    return (
+      <div className="layout-right">
+        <div className="m-b-1" onClick={() => {
+          this.setState({ showMenu: true });
+        }}>
+          <Icon name="menu" />
+        </div>
+        <div className="m-b-1" onClick={() => {
+          this.prevPage();
+        }}>
+          <Icon name="down_turn" />
+        </div>
+        <div className="m-b-1" onClick={() => {
+          this.nextPage();
+        }}>
+          <Icon name="up_turn" />
+        </div>
+      </div>
+    );
+  }
+
+  renderBottom() {
+    const { showJump, currentPage, totalPage } = this.state;
+    return (
+      <div className="layout-bottom">
+        <span className="per">{parseInt(currentPage * 100 / totalPage, 10)}%</span>
+        <span className="num">{currentPage}/{totalPage}</span>
+        <span className="btn">
+          <Assets name="unfold_icon_up" onClick={() => {
+            this.setState({ showJump: !showJump });
+          }} />
+          <div hidden={!showJump} className="jump">
+            <span className="text">当前页</span>
+            <input className="input" onChnage={(value) => {
+              this.page = value;
+            }} />
+            <Assets name="yes_icon" onClick={() => {
+              this.jumpPage(Number(this.page));
+            }} />
+          </div>
+        </span>
+      </div>
+    );
+  }
+
+  renderProgress() {
+    const { currentPage, totalPage } = this.state;
+    return (
+      <div className="layout-progress">
+        <Progress size="small" theme="theme" radius={false} process={parseInt(currentPage * 100 / totalPage, 10)} />
+      </div>
+    );
+  }
+}

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

@@ -0,0 +1,2 @@
+
+export default [];

+ 12 - 0
front/project/www/stores/course.js

@@ -0,0 +1,12 @@
+import BaseStore from '@src/stores/base';
+
+export default class CourseStore extends BaseStore {
+  /**
+   * 获取课程进度
+   */
+  classProcess() {
+    return this.apiGet('/course/process');
+  }
+}
+
+export const Course = new CourseStore({ key: 'course' });

+ 3 - 1
front/project/www/stores/index.js

@@ -1,8 +1,10 @@
 import { Common } from './common';
 import { Main } from './main';
+import { Course } from './course';
+import { Textbook } from './textbook';
 import { My } from './my';
 import { Question } from './question';
 import { Sentence } from './sentence';
 import { User } from './user';
 
-export default [Common, Main, My, Question, Sentence, User];
+export default [Common, Main, Course, Textbook, My, Question, Sentence, User];

+ 54 - 10
front/project/www/stores/main.js

@@ -16,6 +16,14 @@ export default class MainStore extends BaseStore {
   }
 
   /**
+   * 获取对应位置的提示tips
+   * @param {*} position
+   */
+  getTips(position) {
+    return this.apiGet('/base/tips', { position });
+  }
+
+  /**
    * 获取考分排行信息
    */
   getScore(total, quant) {
@@ -23,28 +31,64 @@ export default class MainStore extends BaseStore {
   }
 
   /**
-   * 所有练习层
+   * 所有练习头2
    */
   getExercise() {
     return this.getApiCache('API:main:getExercise', () => {
-      return this.apiGet('/base/exercise/struct');
+      return this.apiGet('/base/exercise/main');
     });
   }
 
-  getExerciseSingle(id) {
-    return this.getExercise().then(result => {
-      for (let i = 0; i < result.length; i += 1) {
-        if (result[i].id === id) return result[i];
-      }
-      return {};
-    });
+  /**
+   * 获取对应节点下的子节点
+   * @param {*} id
+   * @param {*} children
+   */
+  getExerciseChildren(id, children) {
+    return this.apiGet('/base/exercise/children', { id, children });
+  }
+
+  /**
+   * 获取对应节点的所有父级节点
+   * @param {*} id
+   */
+  getExerciseParent(id) {
+    return this.apiGet('/base/exercise/parent', { id });
   }
 
   /**
    * 所有模考层级
    */
   getExamination() {
-    return this.apiGet('/base/examination/struct');
+    return this.getApiCache('API:main:getExamination', () => {
+      return this.apiGet('/base/examination/main');
+    });
+  }
+
+  /**
+   * 获取对应节点下的子节点
+   * @param {*} id
+   * @param {*} children
+   */
+  getExaminationChildren(id, children) {
+    return this.apiGet('/base/examination/children', { id, children });
+  }
+
+  /**
+   * 获取对应节点的所有父级节点
+   * @param {*} id
+   */
+  getExaminationParent(id) {
+    return this.apiGet('/base/examination/parent', { id });
+  }
+
+  /**
+   * 获取模考题目数
+   */
+  getExaminationNumber() {
+    return this.getApiCache('API:main:getExaminationNumber', () => {
+      return this.apiGet('/base/examination/number');
+    });
   }
 }
 

+ 98 - 39
front/project/www/stores/my.js

@@ -6,7 +6,7 @@ export default class MyStore extends BaseStore {
    * @param {*} email 邮箱
    */
   bindEmail(email) {
-    return this.apiPost('/auth/email', { email });
+    return this.apiPost('/my/email', { email });
   }
 
   /**
@@ -14,7 +14,7 @@ export default class MyStore extends BaseStore {
    * @param {*} info  nickname avatar
    */
   editInfo(info) {
-    return this.apiPost('/auth/info', { ...info });
+    return this.apiPost('/my/info', { ...info });
   }
 
   /**
@@ -22,7 +22,7 @@ export default class MyStore extends BaseStore {
    * @param {*} file
    */
   real(file) {
-    return this.apiForm('/auth/real', { file });
+    return this.apiForm('/my/real', { file });
   }
 
   /**
@@ -33,21 +33,21 @@ export default class MyStore extends BaseStore {
    * @param {*} read
    */
   message(page, size, type, read) {
-    return this.apiGet('/auth/message', { page, size, type, read });
+    return this.apiGet('/my/message', { page, size, type, read });
   }
 
   /**
    * 读取用户消息/全部
    */
   readAllMessage() {
-    return this.apiPut('/auth/message/read', { all: true });
+    return this.apiPut('/my/message/read', { all: true });
   }
 
   /**
    * 读取用户消息
    */
   readMessage(id) {
-    return this.apiPut('/auth/message/read', { all: false, id });
+    return this.apiPut('/my/message/read', { all: false, id });
   }
 
   /**
@@ -55,14 +55,14 @@ export default class MyStore extends BaseStore {
    * @param {*} info prepareStatus: 身份  prepareGoal: 目标分数 prepareExaminationTime: 考试时间 prepareScoreTime: 出分时间
    */
   editPrepare(info) {
-    return this.apiPut('/auth/prepare', { ...info });
+    return this.apiPut('/my/prepare', { ...info });
   }
 
   /**
    * 获取备考信息
    */
   getPrepare() {
-    return this.apiGet('/auth/prepare');
+    return this.apiGet('/my/prepare');
   }
 
   /**
@@ -70,37 +70,48 @@ export default class MyStore extends BaseStore {
    * @param {*} date 时间
    */
   getStudy(date) {
-    return this.apiGet('/auth/study', { date });
+    return this.apiGet('/my/study', { date });
+  }
+
+  /**
+   * 获取总学习记录
+   */
+  getStudyTotal() {
+    return this.apiGet('/my/study/total');
   }
 
   /**
    * 添加收藏
+   * @param {*} questionModule
    * @param {*} questionNoId
    */
-  addCollect(module, questionNoId) {
-    return this.apiPut('/auth/collect', { module, questionNoId });
+  addQuestionCollect(questionModule, questionNoId) {
+    return this.apiPut('/my/collect/question/add', { questionModule, questionNoId });
   }
 
   /**
    * 删除收藏
+   * @param {*} questionModule
    * @param {*} questionNoId
    */
-  delCollect(module, questionNoId) {
-    return this.apiDel('/auth/collect', { module, id: questionNoId });
+  delQuestionCollect(questionModule, questionNoId) {
+    return this.apiDel('/my/collect/question/delete', { questionModule, questionNoId });
   }
 
   /**
    * 收藏卷组
-   * @param {*} ids
+   * @param {*} questionModule
+   * @param {*} questionNoIds
+   * @param {*} filterTimes
    */
-  bindCollect(ids) {
-    return this.apiPost('/auth/collect/bind', { questionNoIds: ids });
+  bindQuestionCollect(questionModule, questionNoIds, filterTimes) {
+    return this.apiPost('/my/collect/question/bind', { questionModule, questionNoIds, filterTimes });
   }
 
   /**
    * 获取收藏题目列表
-   * @param {*} module
-   * @param {*} type
+   * @param {*} questionModule
+   * @param {*} questionType
    * @param {*} page
    * @param {*} size
    * @param {*} startTime
@@ -108,28 +119,28 @@ export default class MyStore extends BaseStore {
    * @param {*} order
    * @param {*} direction
    */
-  listCollect(module, type, page, size, startTime, endTime, order, direction) {
-    return this.apiGet('/auth/collect/question', { module, type, page, size, startTime, endTime, order, direction });
+  listQuestionCollect(questionModule, questionType, page, size, startTime, endTime, order, direction) {
+    return this.apiGet('/my/collect/question/list', { questionModule, questionType, page, size, startTime, endTime, order, direction });
   }
 
   /**
    * 获取错题列表
-   * @param {*} module
-   * @param {*} type
+   * @param {*} questionModule
    * @param {*} page
    * @param {*} size
    */
-  listError(module, type, page, size) {
-    return this.apiGet('/auth/error/list', { module, type, page, size });
+  listError(questionModule, page, size) {
+    return this.apiGet('/my/error/list', { questionModule, page, size });
   }
 
   /**
    * 错题组卷
-   * @param {*} ids
-   * @param {*} times
+   * @param {*} questionModule
+   * @param {*} questionNoIds
+   * @param {*} filterTimes
    */
-  bindError(ids, times) {
-    return this.apiPost('/auth/error/bind', { questionNoIds: ids, filterTimes: times });
+  bindError(questionModule, questionNoIds, filterTimes) {
+    return this.apiPost('/my/error/bind', { questionModule, questionNoIds, filterTimes });
   }
 
   /**
@@ -137,22 +148,35 @@ export default class MyStore extends BaseStore {
    * @param {*} ids
    */
   clearError(ids) {
-    return this.apiPost('/auth/error/clear', { questionNoIds: ids });
+    return this.apiPost('/my/error/clear', { questionNoIds: ids });
+  }
+
+  /**
+   * 移除正确题
+   * @param {*} userReportId
+   */
+  removeError(userReportId) {
+    return this.apiPost('/my/error/remove', { userReportId });
   }
 
   /**
    * 更新笔记
+   * @param {*} questionModule
    * @param {*} questionNoId
    * @param {*} content
+   * @param {*} qxContent
+   * @param {*} officialContent
+   * @param {*} associationContent
+   * @param {*} qaContent
    */
-  updateNote(questionNoId, content) {
-    return this.apiPut('/auth/note', { questionNoId, content });
+  updateQuestionNote(questionModule, questionNoId, content, qxContent, officialContent, associationContent, qaContent) {
+    return this.apiPut('/my/note/question', { questionModule, questionNoId, content, qxContent, officialContent, associationContent, qaContent });
   }
 
   /**
    * 获取笔记列表
-   * @param {*} module
-   * @param {*} type
+   * @param {*} questionModule
+   * @param {*} questionType
    * @param {*} page
    * @param {*} size
    * @param {*} startTime
@@ -160,14 +184,14 @@ export default class MyStore extends BaseStore {
    * @param {*} order
    * @param {*} direction
    */
-  noteList(module, type, page, size, startTime, endTime, order, direction) {
-    return this.apiGet('/auth/note/list', { module, type, page, size, startTime, endTime, order, direction });
+  noteList(questionModule, questionType, page, size, startTime, endTime, order, direction) {
+    return this.apiGet('/my/note/question/list', { questionModule, questionType, page, size, startTime, endTime, order, direction });
   }
 
   /**
    * 获取报告列表
-   * @param {*} module
-   * @param {*} type
+   * @param {*} origin
+   * @param {*} structId
    * @param {*} page
    * @param {*} size
    * @param {*} startTime
@@ -175,8 +199,43 @@ export default class MyStore extends BaseStore {
    * @param {*} order
    * @param {*} direction
    */
-  reportList(module, type, page, size, startTime, endTime, order, direction) {
-    return this.apiGet('/auth/report/list', { module, type, page, size, startTime, endTime, order, direction });
+  reportList(origin, structId, page, size, startTime, endTime, order, direction) {
+    return this.apiGet('/my/report/list', { origin, structId, page, size, startTime, endTime, order, direction });
+  }
+
+  /**
+   * 添加提问
+   * @param {*} target
+   * @param {*} questionModule
+   * @param {*} questionNoId
+   * @param {*} content
+   */
+  addQuestionAsk(target, questionModule, questionNoId, content) {
+    return this.apiPost('/my/ask/question', { target, questionModule, questionNoId, content });
+  }
+
+  /**
+   * 添加题目勘误
+   * @param {*} moduleId
+   * @param {*} title
+   * @param {*} position
+   * @param {*} originContent
+   * @param {*} content
+   */
+  addErrorQuestion(moduleId, title, position, originContent, content) {
+    return this.apiPost('/my/feedback/error/question', { moduleId, title, position, originContent, content });
+  }
+
+  /**
+   * 添加数据勘误
+   * @param {*} moduleId
+   * @param {*} title
+   * @param {*} position
+   * @param {*} originContent
+   * @param {*} content
+   */
+  addErrorData(moduleId, title, position, originContent, content) {
+    return this.apiPost('/my/feedback/error/question', { moduleId, title, position, originContent, content });
   }
 }
 

+ 94 - 37
front/project/www/stores/question.js

@@ -2,44 +2,32 @@ import BaseStore from '@src/stores/base';
 
 export default class QuestionStore extends BaseStore {
   /**
-   * 获取课程进度
-   */
-  getClassProcess() {
-    return this.apiGet('/question/class/process');
-  }
-
-  /**
-   * 获取预习作业列表
-   * @param {*} page
-   * @param {*} size
-   * @param {*} category
-   * @param {*} endTime
-   * @param {*} finish
+   * 练习进度
+   * @param {*} structId
    */
-  listPreview(page, size, category, endTime, finish) {
-    return this.apiGet('/question/preview/list', { page, size, category, endTime, finish });
-  }
-
-  getPaper(id) {
-    return this.apiGet('/question/class/process', { id });
+  getExerciseProcess(structId) {
+    return this.apiGet('/question/exercise/process', { structId });
   }
 
   /**
-   * 练习进度
-   * @param {*} page
-   * @param {*} size
+   * 查询第4层考点信息
+   * @param {*} structId
    */
-  getExerciseProcess(page, size) {
-    return this.apiGet('/question/exercise/process', { page, size });
+  getExercisePlace(structId) {
+    return this.apiGet('/question/exercise/place', { structId });
   }
 
   /**
    * 练习组卷
    * @param {*} page
    * @param {*} size
+   * @param {*} structId
+   * @param {*} logic
+   * @param {*} logicExtend
+   * @param {*} finish: true完成,false未完成
    */
-  getExercisePaper(page, size) {
-    return this.apiGet('/question/exercise/paper', { page, size });
+  getExerciseList(page, size, structId, logic, logicExtend, finish) {
+    return this.apiGet('/question/exercise/list', { page, size, structId, logic, logicExtend, times: finish ? 1 : null });
   }
 
   /**
@@ -56,16 +44,66 @@ export default class QuestionStore extends BaseStore {
    * @param {*} page
    * @param {*} size
    */
-  getExaminationPaper(page, size) {
-    return this.apiGet('/question/examination/paper', { page, size });
+  getExaminationList(page, size) {
+    return this.apiGet('/question/examination/list', { page, size });
   }
 
   /**
-   * 获取题目详情
-   * @param {*} questionNoId
+   * 通过题目Id获取详情
+   * @param {*} userQuestionId
    */
-  getDetail(questionNoId) {
-    return this.apiGet('/question/detail', { questionNoId });
+  getDetailById(userQuestionId) {
+    return this.apiGet('/question/detail', { userQuestionId });
+  }
+
+  /**
+   * 通过记录及序号获取详情
+   * @param {*} userReportId
+   * @param {*} no
+   */
+  getDetailByNo(userReportId, no) {
+    return this.apiGet('/question/detail', { userReportId, no });
+  }
+
+  /**
+   * 获取练习卷
+   * @param {*} paperId
+   */
+  getExercisePaper(paperId) {
+    return this.apiGet('/question/exercise/paper', { paperId });
+  }
+
+  /**
+   * 获取模考卷
+   * @param {*} paperId
+   */
+  getExaminationPaper(paperId) {
+    return this.apiGet('/question/examination/paper', { paperId });
+  }
+
+  /**
+   * 获取错题组卷
+   * @param {*} paperId
+   */
+  getErrorPaper(paperId) {
+    return this.apiGet('/question/error/paper', { paperId });
+  }
+
+  /**
+   * 获取收藏组卷
+   * @param {*} paperId
+   */
+  getCollectPaper(paperId) {
+    return this.apiGet('/quesiton/collect/paper', { paperId });
+  }
+
+  /**
+   * 获取组卷
+   * @param {*} type
+   * @param {*} paperId
+   */
+  getPaper(type, paperId) {
+    return this.apiGet(`/question/${type}/paper`, { paperId });
   }
 
   /**
@@ -73,9 +111,10 @@ export default class QuestionStore extends BaseStore {
    * @param {*} type
    * @param {*} paperId
    * @param {*} disorder
+   * @param {*} order: 模考
    */
-  start(type, paperId, disorder) {
-    return this.apiPost(`/question/${type}/start`, { paperId, disorder });
+  start(type, paperId, disorder, order) {
+    return this.apiPost(`/question/${type}/start`, { paperId, disorder, order });
   }
 
   /**
@@ -90,9 +129,11 @@ export default class QuestionStore extends BaseStore {
    * 提交题目答案
    * @param {*} userQuestionId
    * @param {*} answer
+   * @param {*} time
+   * @param {*} setting
    */
-  submit(userQuestionId, answer) {
-    return this.apiPost('/question/submit', { userQuestionId, answer });
+  submit(userQuestionId, answer, time, setting) {
+    return this.apiPost('/question/submit', { userQuestionId, answer, time, setting });
   }
 
   /**
@@ -112,12 +153,28 @@ export default class QuestionStore extends BaseStore {
   }
 
   /**
-   * 重新考试
+   * 模考:下一阶段
+   * @param {*} userPaperId
+   */
+  stage(userPaperId) {
+    return this.apiPost('/question/stage', { userPaperId });
+  }
+
+  /**
+   * 重置考试
    * @param {*} userPaperId
    */
   restart(userPaperId) {
     return this.apiPost('/question/restart', { userPaperId });
   }
+
+  /**
+   * 重置整套模考卷
+   * @param {*} structId
+   */
+  restartExamination(structId) {
+    return this.apiPost('/question/restart/examination', { structId });
+  }
 }
 
 export const Question = new QuestionStore({ key: 'question' });

+ 15 - 51
front/project/www/stores/sentence.js

@@ -20,65 +20,29 @@ export default class SentenceStore extends BaseStore {
    * 文章列表
    * @param {*} chapter
    */
-  listArticle(chapter) {
-    return this.apiGet('/sentence/article/list', { chapter });
+  listArticle() {
+    return this.apiGet('/sentence/article/list');
   }
 
   /**
-   * 更新长难句文章进度
-   * @param {*} chapter
-   * @param {*} part
-   * @param {*} process
-   */
-  updateProcess(chapter, part, process) {
-    return this.apiPut('/sentence/article/process', { chapter, part, process });
-  }
-
-  /**
-   * 长难句组卷列表
-   */
-  listPaper() {
-    return this.apiGet('/sentence/paper/list');
-  }
-
-  /**
-   * 获取题目详情
-   * @param {*} questionNoId
-   */
-  detail(questionNoId) {
-    return this.apiGet('/sentence/question/detail', { questionNoId });
-  }
-
-  /**
-   * 开始做题
-   * @param {*} paperId
-   */
-  start(paperId) {
-    return this.apiPost('/sentence/paper/start', { paperId });
-  }
-
-  /**
-   * 获取下一题
-   * @param {*} reportId
-   */
-  next(userReportId) {
-    return this.apiPost('/sentence/paper/next', { userReportId });
-  }
-
-  /**
-   * 提交题目答案
-   * @param {*} questionNoId
+   * 获取文章详情
+   * @param {*} articleId
    */
-  submit(userQuestionId, answer) {
-    return this.apiPost('/sentence/paper/submit', { userQuestionId, answer });
+  detailArticle(articleId) {
+    return this.apiGet('/sentence/article/detail', { articleId });
   }
 
   /**
-   * 完成考试
-   * @param {*} paperId
+   * 更新长难句文章进度
+   * @param {*} chapter
+   * @param {*} part
+   * @param {*} process
+   * @param {*} time
+   * @param {*} currentChapter
+   * @param {*} currentPart
    */
-  finish(userReportId) {
-    return this.apiPost('/sentence/paper/finish', { userReportId });
+  updateProcess(chapter, part, process, time, currentChapter, currentPart) {
+    return this.apiPut('/sentence/article/process', { chapter, part, process, time, currentChapter, currentPart });
   }
 }
 

+ 12 - 0
front/project/www/stores/textbook.js

@@ -0,0 +1,12 @@
+import BaseStore from '@src/stores/base';
+
+export default class TextbookStore extends BaseStore {
+  /**
+   * 所有机经信息
+   */
+  getInfo() {
+    return this.apiGet('/textbook/info');
+  }
+}
+
+export const Textbook = new TextbookStore({ key: 'textbook' });

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

@@ -10,5 +10,7 @@ import java.util.List;
  * Created by gaojie on 2017/11/9.
  */
 public interface ExercisePaperRelationMapper {
-    List<ExercisePaper> groupPlace();
+    List<ExercisePaper> groupPlace(
+            @Param("structId") Number structId
+    );
 }

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

@@ -20,6 +20,9 @@
     <include refid="Id_Column_List" />
     from `exercise_paper` ep
     where ep.`logic` = "place" and ep.`status` = 1
+    <if test="structId != null">
+      and ep.`struct_three` = #{structId,jdbcType=VARCHAR}
+    </if>
     group by ep.`logic_extend`
   </select>
 </mapper>

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

@@ -94,6 +94,15 @@ public class BaseController {
         return ResponseHelp.success(p);
     }
 
+    @RequestMapping(value = "/exercise/parent", method = RequestMethod.GET)
+    @ApiOperation(value = "练习层级父级", httpMethod = "GET")
+    public Response<List<ExerciseStruct>> exerciseParent(
+            @RequestParam(required = true) Integer id,
+            HttpSession session) {
+        List<ExerciseStruct> p = exerciseStructService.parent(id);
+        return ResponseHelp.success(p);
+    }
+
     @RequestMapping(value = "/examination/main", method = RequestMethod.GET)
     @ApiOperation(value = "所有模考头2层", httpMethod = "GET")
     public Response<List<ExaminationStruct>> examinationMain(HttpSession session) {
@@ -111,6 +120,15 @@ public class BaseController {
         return ResponseHelp.success(p);
     }
 
+    @RequestMapping(value = "/examination/parent", method = RequestMethod.GET)
+    @ApiOperation(value = "模考层级父级", httpMethod = "GET")
+    public Response<List<ExaminationStruct>> examinationParent(
+            @RequestParam(required = true) Integer id,
+            HttpSession session) {
+        List<ExaminationStruct> p = examinationStructService.parent(id);
+        return ResponseHelp.success(p);
+    }
+
     @RequestMapping(value = "/examination/number", method = RequestMethod.GET)
     @ApiOperation(value = "模考题目数", notes = "获取模考题目数", httpMethod = "GET")
     public Response<JSONObject> examinationNumber()  {

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

@@ -433,9 +433,9 @@ public class MyController {
         return ResponseHelp.success(dto);
     }
 
-    @RequestMapping(value = "/collect/add", method = RequestMethod.PUT)
+    @RequestMapping(value = "/collect/question/add", method = RequestMethod.PUT)
     @ApiOperation(value = "添加收藏", notes = "添加收藏", httpMethod = "PUT")
-    public Response<Boolean> addCollect(@RequestBody @Validated UserCollectDto dto)  {
+    public Response<Boolean> addQuestionCollect(@RequestBody @Validated UserCollectDto dto)  {
         UserCollectQuestion entity = Transform.dtoToEntity(dto);
         User user = (User) shiroHelp.getLoginUser();
         switch (QuestionModule.ValueOf(dto.getQuestionModule())){
@@ -464,18 +464,18 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
-    @RequestMapping(value = "/collect/delete", method = RequestMethod.DELETE)
+    @RequestMapping(value = "/collect/question/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "移除收藏", notes = "移除收藏", httpMethod = "DELETE")
-    public Response<Boolean> deleteCollect(String questionModule, Integer questionNoId)  {
+    public Response<Boolean> deleteQuestionCollect(String questionModule, Integer questionNoId)  {
         User user = (User) shiroHelp.getLoginUser();
         Boolean result = userCollectQuestionService.deleteQuestion(user.getId(), QuestionModule.ValueOf(questionModule), questionNoId);
 
         return ResponseHelp.success(result);
     }
 
-    @RequestMapping(value = "/collect/bind", method = RequestMethod.POST)
+    @RequestMapping(value = "/collect/question/bind", method = RequestMethod.POST)
     @ApiOperation(value = "收藏组卷", notes = "收藏组卷", httpMethod = "POST")
-    public Response<Boolean> bindCollect(@RequestBody @Validated UserCustomBindDto dto)  {
+    public Response<Boolean> bindQuestionCollect(@RequestBody @Validated UserCustomBindDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
 
         questionFlowService.makePaper(
@@ -489,21 +489,21 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
-    @RequestMapping(value = "/collect/question", method = RequestMethod.GET)
+    @RequestMapping(value = "/collect/question/list", method = RequestMethod.GET)
     @ApiOperation(value = "获取收藏题目列表", notes = "获取收藏题目列表", httpMethod = "GET")
-    public Response<PageMessage<UserCollectQuestionDto>> listCollect(
+    public Response<PageMessage<UserCollectQuestionDto>> listQuestionCollect(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
-            @RequestParam(required = true) String module,
-            @RequestParam(required = false) String type,
+            @RequestParam(required = true) String questionModule,
+            @RequestParam(required = false) String questionType,
             @RequestParam(required = false) String startTime,
             @RequestParam(required = false) String endTime,
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session)  {
         User user = (User) shiroHelp.getLoginUser();
-        QuestionModule questionModule = QuestionModule.ValueOf(module);
-        PageResult<UserCollectQuestionRelation> p = userCollectQuestionService.listQuestion(page, size, user.getId(), questionModule, QuestionType.ValueOf(type), startTime, endTime, order, DirectionStatus.ValueOf(direction));
+        QuestionModule qm = QuestionModule.ValueOf(questionModule);
+        PageResult<UserCollectQuestionRelation> p = userCollectQuestionService.listQuestion(page, size, user.getId(), qm, QuestionType.ValueOf(questionType), startTime, endTime, order, DirectionStatus.ValueOf(direction));
 
         List<UserCollectQuestionDto> pr = Transform.convert(p, UserCollectQuestionDto.class);
 
@@ -513,7 +513,7 @@ public class MyController {
         Transform.combine(pr, questionList, UserCollectQuestionDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);
 
         Collection questionNoIds = Transform.getIds(pr, UserCollectQuestionDto.class, "questionNoId");
-        switch(questionModule){
+        switch(qm){
             case BASE:
                 List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
                 Transform.combine(pr, questionNoList, UserCollectQuestionDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
@@ -540,11 +540,10 @@ public class MyController {
     public Response<PageMessage<UserQuestionErrorListDto>> listError(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
-            @RequestParam(required = true) String module,
-            @RequestParam(required = false) Number category
+            @RequestParam(required = true) String questionModule
     )  {
         User user = (User) shiroHelp.getLoginUser();
-        QuestionModule questionModule = QuestionModule.ValueOf(module);
+        QuestionModule qm = QuestionModule.ValueOf(questionModule);
         PageResult<UserQuestion> p = userQuestionService.listError(page, size, user.getId());
         List<UserQuestionErrorListDto> pr = Transform.convert(p, UserQuestionErrorListDto.class);
 
@@ -554,7 +553,7 @@ public class MyController {
         Transform.combine(pr, questionList, UserQuestionErrorListDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);
 
         Collection questionNoIds = Transform.getIds(pr, UserQuestionErrorListDto.class, "questionNoId");
-        switch(questionModule){
+        switch(qm){
             case BASE:
                 List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
                 Transform.combine(pr, questionNoList, UserQuestionErrorListDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
@@ -651,16 +650,16 @@ public class MyController {
     public Response<PageMessage<UserNoteQuestionDto>> listNoteQuestion(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
-            @RequestParam(required = true) String module,
-            @RequestParam(required = false) String type,
+            @RequestParam(required = true) String questionModule,
+            @RequestParam(required = false) String questionType,
             @RequestParam(required = false) String startTime,
             @RequestParam(required = false) String endTime,
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session)  {
         User user = (User) shiroHelp.getLoginUser();
-        QuestionModule questionModule = QuestionModule.ValueOf(module);
-        PageResult<UserNoteQuestionRelation> p = userNoteQuestionService.list(page, size, user.getId(), questionModule, QuestionType.ValueOf(type), startTime, endTime, order, DirectionStatus.ValueOf(direction));
+        QuestionModule qm = QuestionModule.ValueOf(questionModule);
+        PageResult<UserNoteQuestionRelation> p = userNoteQuestionService.list(page, size, user.getId(), qm, QuestionType.ValueOf(questionType), startTime, endTime, order, DirectionStatus.ValueOf(direction));
         List<UserNoteQuestionDto> pr = Transform.convert(p, UserNoteQuestionDto.class);
 
         // 获取题目信息
@@ -669,7 +668,7 @@ public class MyController {
         Transform.combine(pr, questionList, UserNoteQuestionDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);
 
         Collection questionNoIds = Transform.getIds(pr, UserQuestionErrorListDto.class, "questionNoId");
-        switch(questionModule){
+        switch(qm){
             case BASE:
                 List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
                 Transform.combine(pr, questionNoList, UserQuestionErrorListDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);

+ 57 - 3
server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java

@@ -18,12 +18,14 @@ import com.qxgmat.dto.request.*;
 import com.qxgmat.dto.response.*;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.*;
+import com.qxgmat.service.extend.ExaminationService;
 import com.qxgmat.service.extend.ExerciseService;
 import com.qxgmat.service.extend.PreviewService;
 import com.qxgmat.service.extend.QuestionFlowService;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import net.bytebuddy.asm.Advice;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -51,12 +53,24 @@ public class QuestionController {
     private ExerciseService exerciseService;
 
     @Autowired
+    private ExaminationPaperService examinationPaperService;
+
+    @Autowired
+    private ExaminationService examinationService;
+
+    @Autowired
     private QuestionNoService questionNoService;
 
     @Autowired
     private QuestionService questionService;
 
     @Autowired
+    private SentencePaperService sentencePaperService;
+
+    @Autowired
+    private TextbookPaperService textbookPaperService;
+
+    @Autowired
     private UserQuestionService userQuestionService;
 
     @Autowired
@@ -81,7 +95,7 @@ public class QuestionController {
     @RequestMapping(value = "/exercise/process", method = RequestMethod.GET)
     @ApiOperation(value = "练习进度", httpMethod = "GET")
     public Response<List<UserExerciseGroupDto>> exerciseProcess(
-            @RequestParam(required = true) Integer structId, // 第层,查询第4层,以及第三层汇总
+            @RequestParam(required = true) Integer structId, // 第层,查询第4层,以及第三层汇总
             HttpSession session) {
         Page<UserExerciseGroupDto> p=null;
 
@@ -92,8 +106,10 @@ public class QuestionController {
 
     @RequestMapping(value = "/exercise/place", method = RequestMethod.GET)
     @ApiOperation(value = "练习组卷考点分组条件", httpMethod = "GET")
-    public Response<List<String>> exercisePlace(HttpSession session) {
-        return ResponseHelp.success(exercisePaperService.groupPlace());
+    public Response<List<String>> exercisePlace(
+            @RequestParam(required = true) Integer structId, // 查询第4层
+            HttpSession session) {
+        return ResponseHelp.success(exercisePaperService.groupPlace(structId));
     }
 
     @RequestMapping(value = "/exercise/list", method = RequestMethod.GET)
@@ -183,6 +199,18 @@ public class QuestionController {
         return ResponseHelp.success(paperDto);
     }
 
+    @RequestMapping(value = "/examination/paper", method = RequestMethod.GET)
+    @ApiOperation(value = "获取模考卷", notes = "获取模考卷", httpMethod = "GET")
+    public Response<PaperBaseDto> detailExamination(
+            @RequestParam(required = true, name="id") Integer paperId
+    )  {
+        User user = (User) shiroHelp.getLoginUser();
+        ExaminationPaper paper = examinationPaperService.get(paperId);
+        PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
+
+        return ResponseHelp.success(paperDto);
+    }
+
     @RequestMapping(value = "/report/base", method = RequestMethod.GET)
     @ApiOperation(value = "获取练习记录", notes = "获取练习记录", httpMethod = "GET")
     public Response<UserReportBaseDto> baseReport(
@@ -258,6 +286,19 @@ public class QuestionController {
         return ResponseHelp.success(userReportBaseDto);
     }
 
+    @RequestMapping(value = "/textbook/paper", method = RequestMethod.GET)
+    @ApiOperation(value = "获取机经练习卷", notes = "获取练习卷", httpMethod = "GET")
+    public Response<PaperBaseDto> detailTextbookPaper(
+            @RequestParam(required = true, name="id") Integer paperId
+    )  {
+        User user = (User) shiroHelp.getLoginUser();
+        TextbookPaper paper = textbookPaperService.get(paperId);
+
+        PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
+
+        return ResponseHelp.success(paperDto);
+    }
+
     @RequestMapping(value = "/textbook/start", method = RequestMethod.POST)
     @ApiOperation(value = "开始: 机经", notes = "提交考试设置", httpMethod = "POST")
     public Response<UserReportBaseDto> startTextbook(@RequestBody @Validated TextbookStartDto dto)  {
@@ -271,6 +312,19 @@ public class QuestionController {
         return ResponseHelp.success(userReportBaseDto);
     }
 
+    @RequestMapping(value = "/sentence/paper", method = RequestMethod.GET)
+    @ApiOperation(value = "获取长难句练习卷", notes = "获取练习卷", httpMethod = "GET")
+    public Response<PaperBaseDto> detailSentencePaper(
+            @RequestParam(required = true, name="id") Integer paperId
+    )  {
+        User user = (User) shiroHelp.getLoginUser();
+        SentencePaper paper = sentencePaperService.get(paperId);
+
+        PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
+
+        return ResponseHelp.success(paperDto);
+    }
+
     @RequestMapping(value = "/sentence/start", method = RequestMethod.POST)
     @ApiOperation(value = "开始: 长难句", notes = "提交考试设置", httpMethod = "POST")
     public Response<UserReportBaseDto> startSentence(@RequestBody @Validated SentenceStartDto dto)  {

+ 33 - 19
server/gateway-api/src/main/java/com/qxgmat/controller/api/SentenceController.java

@@ -1,5 +1,6 @@
 package com.qxgmat.controller.api;
 
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.nuliji.tools.Response;
 import com.nuliji.tools.ResponseHelp;
@@ -23,6 +24,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpSession;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -69,22 +71,27 @@ public class SentenceController
 
     @RequestMapping(value = "/info", method = RequestMethod.GET)
     @ApiOperation(value = "所有长难句信息", httpMethod = "GET")
-    public Response<JSONObject> info(HttpSession session) {
-        Setting entity = settingService.getByKey(SettingKey.SENTENCE);
-        JSONObject value = entity.getValue();
+    public Response<UserSentenceInfoDto> info(HttpSession session) {
+        UserSentenceInfoDto dto = new UserSentenceInfoDto();
 
         // 用户code状态
         User user = (User) shiroHelp.getLoginUser();
         SentenceCode code = sentenceCodeService.isActive(user.getId());
         if (code != null){
-            value.put("code", code.getCode());
+            dto.setCode(code.getCode());
         }
         // 查询用户进度
         List<UserSentenceProcess> processList = userSentenceProcessService.listTotal(user.getId());
         Map process = Transform.getMap(processList,UserSentenceProcess.class, "chapter", "process");
-        value.put("process", process);
+        dto.setProcess(process);
 
-        return ResponseHelp.success(value);
+        // 章节信息
+        Setting entity = settingService.getByKey(SettingKey.SENTENCE);
+        JSONObject value = entity.getValue();
+        JSONArray chapters = value.getJSONArray("chapters");
+        dto.setChapters(chapters);
+
+        return ResponseHelp.success(dto);
     }
 
     @RequestMapping(value = "/active", method = RequestMethod.PUT)
@@ -104,19 +111,24 @@ public class SentenceController
     @RequestMapping(value = "/article/list", method = RequestMethod.GET)
     @ApiOperation(value = "长难句文章列表", httpMethod = "GET")
     public Response<List<UserSentenceArticleDto>> listArticle(
-            @RequestParam(required = true) Integer chapter,
             HttpSession session) {
         User user = (User) shiroHelp.getLoginUser();
 
         // 查询用户code
         if (user != null && sentenceCodeService.isActive(user.getId()) != null){
-            List<SentenceArticle> p = sentenceArticleService.listByChapter(chapter);
+            List<SentenceArticle> p = sentenceArticleService.all();
             List<UserSentenceArticleDto> pr = Transform.convert(p, UserSentenceArticleDto.class);
 
             // 查询文章进度
-            List<UserSentenceProcess> processList = userSentenceProcessService.listByChapter(user.getId(), chapter);
-            Map process = Transform.getMap(processList,UserSentenceProcess.class, "part", "process");
-            Transform.combine(pr, process, UserSentenceArticleDto.class, "part", "process");
+            List<UserSentenceProcess> processList = userSentenceProcessService.all(user.getId());
+            Map<String, Integer> processMap = new HashMap<>();
+            for (UserSentenceProcess process:processList){
+                processMap.put(String.format("%d-%d", process.getChapter(), process.getPart()), process.getProcess());
+            }
+            for (UserSentenceArticleDto dto : pr){
+                Integer process = processMap.get(String.format("%d-%d", dto.getChapter(), dto.getPart()));
+                dto.setProcess(process == null ? 0 : process);
+            }
             return ResponseHelp.success(pr);
         } else {
             List<SentenceArticle> p = sentenceArticleService.listByTrail();
@@ -159,17 +171,19 @@ public class SentenceController
         User user = (User) shiroHelp.getLoginUser();
         if (user == null) throw new AuthException("需要登录");
 
+        // 添加阅读记录
+        if (dto.getCurrentChapter() != null && dto.getCurrentPart() != null && dto.getTime() != null && dto.getTime() > 0){
+            userSentenceRecordService.add(UserSentenceRecord.builder()
+                    .userId(user.getId())
+                    .chapter(dto.getCurrentChapter())
+                    .part(dto.getCurrentPart())
+                    .userTime(dto.getTime())
+                    .build());
+        }
+
         // 获取本章节的最大part数
         Integer max = sentenceArticleService.maxPart(dto.getChapter());
 
-        // 添加阅读记录
-        userSentenceRecordService.add(UserSentenceRecord.builder()
-                .userId(user.getId())
-                .chapter(dto.getChapter())
-                .part(dto.getPart())
-                .userTime(dto.getTime())
-                .build());
-
         // 更新阅读进度
         userSentenceProcessService.updateProcess(user.getId(), dto.getChapter(), dto.getPart(), dto.getProcess(), max);
         return ResponseHelp.success(true);

+ 28 - 8
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserSentenceProcessDto.java

@@ -14,6 +14,10 @@ public class UserSentenceProcessDto {
 
     private Integer time;
 
+    private Integer currentChapter;
+
+    private Integer currentPart;
+
     public Integer getChapter() {
         return chapter;
     }
@@ -30,14 +34,6 @@ public class UserSentenceProcessDto {
         this.part = part;
     }
 
-    public Integer getProcess() {
-        return process;
-    }
-
-    public void setProcess(Integer process) {
-        this.process = process;
-    }
-
     public Integer getTime() {
         return time;
     }
@@ -45,4 +41,28 @@ public class UserSentenceProcessDto {
     public void setTime(Integer time) {
         this.time = time;
     }
+
+    public Integer getCurrentChapter() {
+        return currentChapter;
+    }
+
+    public void setCurrentChapter(Integer currentChapter) {
+        this.currentChapter = currentChapter;
+    }
+
+    public Integer getCurrentPart() {
+        return currentPart;
+    }
+
+    public void setCurrentPart(Integer currentPart) {
+        this.currentPart = currentPart;
+    }
+
+    public Integer getProcess() {
+        return process;
+    }
+
+    public void setProcess(Integer process) {
+        this.process = process;
+    }
 }

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

@@ -5,16 +5,22 @@ import com.qxgmat.data.dao.entity.SentenceArticle;
 
 @Dto(entity = SentenceArticle.class)
 public class UserSentenceArticleDetailDto {
+    private Integer id;
+
     private Integer process;
 
     private String title;
 
     private Integer isTrail;
 
+    private Integer chapter;
+
     private Integer part;
 
     private String content;
 
+    private Integer pages;
+
     private Integer trailStart;
 
     private Integer trailEnd;
@@ -74,4 +80,28 @@ public class UserSentenceArticleDetailDto {
     public void setTrailEnd(Integer trailEnd) {
         this.trailEnd = trailEnd;
     }
+
+    public Integer getPages() {
+        return pages;
+    }
+
+    public void setPages(Integer pages) {
+        this.pages = pages;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getChapter() {
+        return chapter;
+    }
+
+    public void setChapter(Integer chapter) {
+        this.chapter = chapter;
+    }
 }

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

@@ -6,16 +6,23 @@ import com.qxgmat.data.dao.entity.SentenceArticle;
 @Dto(entity = SentenceArticle.class)
 public class UserSentenceArticleDto {
     private Integer id;
+
     private Integer process;
 
     private String title;
 
     private Integer isTrail;
 
+    private Integer chapter;
+
     private Integer part;
 
     private Integer pages;
 
+    private Integer trailStart;
+
+    private Integer trailEnd;
+
     public Integer getProcess() {
         return process;
     }
@@ -63,4 +70,28 @@ public class UserSentenceArticleDto {
     public void setPages(Integer pages) {
         this.pages = pages;
     }
+
+    public Integer getChapter() {
+        return chapter;
+    }
+
+    public void setChapter(Integer chapter) {
+        this.chapter = chapter;
+    }
+
+    public Integer getTrailStart() {
+        return trailStart;
+    }
+
+    public void setTrailStart(Integer trailStart) {
+        this.trailStart = trailStart;
+    }
+
+    public Integer getTrailEnd() {
+        return trailEnd;
+    }
+
+    public void setTrailEnd(Integer trailEnd) {
+        this.trailEnd = trailEnd;
+    }
 }

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

@@ -0,0 +1,40 @@
+package com.qxgmat.dto.response;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.SentenceArticle;
+
+import java.util.Map;
+
+public class UserSentenceInfoDto {
+    private JSONArray chapters;
+
+    private String code;
+
+    private Map process;
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public Map getProcess() {
+        return process;
+    }
+
+    public void setProcess(Map process) {
+        this.process = process;
+    }
+
+    public JSONArray getChapters() {
+        return chapters;
+    }
+
+    public void setChapters(JSONArray chapters) {
+        this.chapters = chapters;
+    }
+}

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

@@ -387,8 +387,8 @@ public class ExaminationService extends AbstractService {
         do{
             selectedList = randomList(difficultList, number);
             targetLevel = computeDifficultLevel(selectedList);
-        }while(targetLevel >= verbalInitLevel);
-        List<Integer> targetIds = new ArrayList<>(verbalPre);
+        }while(targetLevel >= quantInitLevel);
+        List<Integer> targetIds = new ArrayList<>(number);
         for(QuestionDifficultRelation relation : selectedList){
             targetIds.add(relation.getId());
         }

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

@@ -686,7 +686,7 @@ public class QuestionFlowService {
             // 下一阶段
             Integer level = examinationService.quantNextLevel(info.getInteger("level"), examinationService.quantBaseLevel[step], correct, step);
             Map<String, Integer> typeNumber = examinationService.statTypeNumber(subQuestionList);
-            info = examinationService.generateQuant(paper.getStructThree(), level, typeNumber, ids);
+            info = examinationService.generateQuant(paper.getStructThree(), level, typeNumber, ids, nextStep);
             steps.add(info);
         }else{
             info = steps.getJSONObject(step);

+ 23 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/ExaminationStructService.java

@@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -55,6 +56,28 @@ public class ExaminationStructService extends AbstractService {
                 .collect(Collectors.toList());
     }
 
+    @Cacheable(value = "examination_struct", key="parent/#id")
+    public List<ExaminationStruct> parent(Integer id){
+        List<ExaminationStruct> list = all();
+        Collections.reverse(list);
+        List<Integer> ids = new ArrayList<>();
+
+        list = list.stream()
+                .filter((ExaminationStruct b) -> {
+                    if (b.getId().equals(id)){
+                        ids.add(b.getParentId());
+                        return true;
+                    }else if(ids.contains(b.getId())){
+                        ids.add(b.getParentId());
+                        return true;
+                    }
+                    return false;
+                } )
+                .collect(Collectors.toList());
+        Collections.reverse(list);
+        return list;
+    }
+
     @Cacheable(value = "examination_struct/all")
     public List<ExaminationStruct> all(){
         Example example = new Example(ExaminationStruct.class);

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

@@ -86,8 +86,8 @@ public class ExercisePaperService extends AbstractService {
      * 获取考点分组所有可用考点信息
      * @return
      */
-    public List<String> groupPlace(){
-        List<ExercisePaper> p = exercisePaperRelationMapper.groupPlace();
+    public List<String> groupPlace(Integer structId){
+        List<ExercisePaper> p = exercisePaperRelationMapper.groupPlace(structId);
 
         Collection ids = Transform.getIds(p, ExercisePaper.class, "id");
 

+ 23 - 4
server/gateway-api/src/main/java/com/qxgmat/service/inline/ExerciseStructService.java

@@ -16,10 +16,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @Service
@@ -56,6 +53,28 @@ public class ExerciseStructService extends AbstractService {
                 .collect(Collectors.toList());
     }
 
+    @Cacheable(value = "exercise_struct", key="parent/#id")
+    public List<ExerciseStruct> parent(Integer id){
+        List<ExerciseStruct> list = all();
+        Collections.reverse(list);
+        List<Integer> ids = new ArrayList<>();
+
+        list = list.stream()
+                .filter((ExerciseStruct b) -> {
+                    if (b.getId().equals(id)){
+                        ids.add(b.getParentId());
+                        return true;
+                    }else if(ids.contains(b.getId())){
+                        ids.add(b.getParentId());
+                        return true;
+                    }
+                    return false;
+                } )
+                .collect(Collectors.toList());
+        Collections.reverse(list);
+        return list;
+    }
+
     @Cacheable(value = "exercise_struct/all")
     public List<ExerciseStruct> all(){
         Example example = new Example(ExerciseStruct.class);

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

@@ -24,6 +24,16 @@ public class SentenceArticleService extends AbstractService {
     private SentenceArticleMapper sentenceArticleMapper;
 
     /**
+     * 获取长难句所有文章: 做目录
+     * @return
+     */
+    public List<SentenceArticle> all(){
+        Example example = new Example(SentenceArticle.class);
+        example.setOrderByClause("chapter asc,part asc");
+        return select(sentenceArticleMapper, example);
+    }
+
+    /**
      * 获取长难句单个章节所有文章
      * @param chapter
      * @return

+ 15 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserSentenceProcessService.java

@@ -103,6 +103,21 @@ public class UserSentenceProcessService extends AbstractService {
     }
 
     /**
+     * 获取用户所有章节的所有进度
+     * @param userId
+     * @return
+     */
+    public List<UserSentenceProcess> all(Integer userId){
+        Example example = new Example(UserSentenceProcess.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andNotEqualTo("part", 0)
+        );
+        return select(userSentenceProcessMapper, example);
+    }
+
+    /**
      * 获取用户单个章节的所有进度
      * @param userId
      * @param chapter