Go 5 лет назад
Родитель
Сommit
0a03904a6c
74 измененных файлов с 6299 добавлено и 5429 удалено
  1. 4306 4702
      front/package-lock.json
  2. 2 2
      front/package.json
  3. 5 5
      front/project/Constant.js
  4. 10 10
      front/project/admin/routes/setting/index/page.js
  5. 3 3
      front/project/admin/routes/subject/exercise/page.js
  6. 23 4
      front/project/admin/routes/subject/sentence/page.js
  7. 42 7
      front/project/admin/routes/subject/sentenceArticle/page.js
  8. 17 8
      front/project/www/components/Continue/index.js
  9. 4 2
      front/project/www/components/IconButton/index.js
  10. 1 0
      front/project/www/components/IconButton/index.less
  11. 14 11
      front/project/www/components/List/index.js
  12. 14 0
      front/project/www/components/List/index.less
  13. 7 7
      front/project/www/components/ListTable/index.js
  14. 4 2
      front/project/www/components/RadioButton/index.js
  15. 6 4
      front/project/www/components/Select/index.js
  16. 13 6
      front/project/www/components/Step/index.js
  17. 1 0
      front/project/www/components/Step/index.less
  18. 1 1
      front/project/www/components/Tabs/index.js
  19. 3 3
      front/project/www/index.js
  20. 8 4
      front/project/www/local.json
  21. 2 1
      front/project/www/routes/examination/index.js
  22. 10 0
      front/project/www/routes/examination/main/index.js
  23. 68 0
      front/project/www/routes/examination/main/index.less
  24. 656 0
      front/project/www/routes/examination/main/page.js
  25. 2 1
      front/project/www/routes/exercise/index.js
  26. 9 0
      front/project/www/routes/exercise/main/index.less
  27. 383 56
      front/project/www/routes/exercise/main/page.js
  28. 5 5
      front/project/www/routes/page/demo/page.js
  29. 0 1
      front/project/www/routes/page/home/index.less
  30. 68 228
      front/project/www/routes/page/home/page.js
  31. 2 5
      front/project/www/routes/page/index.js
  32. 2 1
      front/project/www/routes/sentence/index.js
  33. 22 0
      front/project/www/routes/sentence/read/index.less
  34. 110 74
      front/project/www/routes/sentence/read/page.js
  35. 2 2
      front/project/www/stores/course.js
  36. 18 0
      front/project/www/stores/my.js
  37. 20 4
      front/project/www/stores/question.js
  38. 10 3
      front/project/www/stores/sentence.js
  39. 20 1
      front/project/www/stores/user.js
  40. 2 2
      front/src/components/DragList/index.js
  41. 22 14
      front/src/services/Tools.js
  42. 0 7
      server/data/src/main/java/com/qxgmat/data/dao/UserSentenceProcessMapper.java
  43. 7 0
      server/data/src/main/java/com/qxgmat/data/dao/UserSentenceProgressMapper.java
  44. 17 17
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserSentenceProcess.java
  45. 4 4
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserSentenceProcessMapper.xml
  46. 0 10
      server/data/src/main/java/com/qxgmat/data/relation/entity/UserExaminationPaperRelation.java
  47. 0 10
      server/data/src/main/java/com/qxgmat/data/relation/entity/UserExercisePaperRelation.java
  48. 30 0
      server/data/src/main/java/com/qxgmat/data/relation/entity/UserSentencePaperRelation.java
  49. 1 1
      server/data/src/main/resources/db/migration/V1__init_table.sql
  50. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/ExerciseController.java
  51. 2 2
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SentenceController.java
  52. 2 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java
  53. 2 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  54. 20 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  55. 4 4
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  56. 78 51
      server/gateway-api/src/main/java/com/qxgmat/controller/api/SentenceController.java
  57. 5 15
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserPaperBaseExtendDto.java
  58. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserPaperDetailExtendDto.java
  59. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserReportExtendDto.java
  60. 8 8
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserSentenceProcessDto.java
  61. 5 5
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentenceArticleDetailDto.java
  62. 5 5
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentenceArticleDto.java
  63. 15 8
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentenceInfoDto.java
  64. 32 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentencePaperDto.java
  65. 4 3
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserStudyDayDto.java
  66. 16 1
      server/gateway-api/src/main/java/com/qxgmat/service/UserPaperService.java
  67. 2 4
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExerciseService.java
  68. 2 6
      server/gateway-api/src/main/java/com/qxgmat/service/extend/PreviewService.java
  69. 11 3
      server/gateway-api/src/main/java/com/qxgmat/service/extend/SentenceService.java
  70. 6 6
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExaminationStructService.java
  71. 6 6
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExerciseStructService.java
  72. 13 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserReportService.java
  73. 55 58
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserSentenceProcessService.java
  74. 9 9
      server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java

Разница между файлами не показана из-за своего большого размера
+ 4306 - 4702
front/package-lock.json


+ 2 - 2
front/package.json

@@ -122,7 +122,7 @@
     "file-loader": "^0.9.0",
     "fs-extra": "^0.30.0",
     "html-webpack-plugin": "^2.22.0",
-    "http-proxy-middleware": "^0.17.3",
+    "http-proxy-middleware": "^0.19.1",
     "imports-loader": "^0.6.5",
     "json-loader": "^0.5.4",
     "less": "^2.7.2",
@@ -135,7 +135,7 @@
     "url-loader": "^0.5.6",
     "webpack": "^3.8.1",
     "webpack-dev-middleware": "^1.12.1",
-    "webpack-dev-server": "^2.9.4",
+    "webpack-dev-server": "^2.11.5",
     "webpack-hot-middleware": "^2.20.0",
     "yaml-loader": "^0.5.0"
   }

+ 5 - 5
front/project/Constant.js

@@ -115,23 +115,23 @@ export const ExaminationOrder = [{ list: ['awa', 'ir', 'quant', 'verbal'] }, { l
 // };
 
 // 模考题目数:/base/examination/number返回
-// const examinationNumber = { verbial: { number: '36', time: '65' }, ir: { number: '12', time: '30' }, awa: { number: '1', time: '30' }, quant: { number: '31', time: '62' } };
+// const examinationNumber = { verbal: { number: '36', time: '65' }, ir: { number: '12', time: '30' }, awa: { number: '1', time: '30' }, quant: { number: '31', time: '62' } };
 
 // 模考设置
 // const examinationSetting = {
 //   order: [],
 //   disorder: true,
 //   stage: '', // 当前阶段
-//   time: { verbial: 0 }, // 每个阶段时间
-//   number: { verbial: 0 }, // 每个阶段做题数
-//   verbial: { steps: [{ ids: [], level: 0 }] }, // 语文出题逻辑
+//   time: { verbal: 0 }, // 每个阶段时间
+//   number: { verbal: 0 }, // 每个阶段做题数
+//   verbal: { steps: [{ ids: [], level: 0 }] }, // 语文出题逻辑
 //   quant: { steps: [{ ids: [], level: 0 }] }, // 数学出题逻辑
 //   rc: { 5: 8 }, // 随机出题时,rc题的对应序号
 // };
 
 // 模考卷子分数
 // const examinationScore = {
-//   total: 123, totalRank:12, quant: 123, quantRank: 123, verbial: 123, verbialRank: 123, ir: 123, irRank: 123,
+//   total: 123, totalRank:12, quant: 123, quantRank: 123, verbal: 123, verbalRank: 123, ir: 123, irRank: 123,
 // };
 
 // 模考卷子报告

+ 10 - 10
front/project/admin/routes/setting/index/page.js

@@ -147,21 +147,21 @@ export default class extends Page {
   renderClass() {
     const { getFieldDecorator, getFieldValue, setFieldsValue } = this.props.form;
     const { data } = this.state;
-    const classes = data.class || [];
+    const course = data.course || [];
     return <Block>
       <h1>千行课堂</h1>
       <Form>
         <Row>
-          {classes.map((row, index) => {
-            const image = getFieldValue(`class.${index}.image`) || null;
+          {course.map((row, index) => {
+            const image = getFieldValue(`course.${index}.image`) || null;
             return <Col span={7} offset={index % 3 ? 1 : 0}><Card>
               <Button className="delete-button" size="small" onClick={() => {
-                this.deleteLength('class', index, 1);
+                this.deleteLength('course', index, 1);
               }}>
                 <Icon type="delete" />
               </Button>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='课程名称'>
-                {getFieldDecorator(`class.${index}.title`, {
+                {getFieldDecorator(`course.${index}.title`, {
                   rules: [
                     { required: true, message: '输入课程名称' },
                   ],
@@ -171,7 +171,7 @@ export default class extends Page {
                 )}
               </Form.Item>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='跳转链接'>
-                {getFieldDecorator(`class.${index}.link`, {
+                {getFieldDecorator(`course.${index}.link`, {
                   rules: [
                     { required: true, message: '输入跳转链接' },
                   ],
@@ -181,7 +181,7 @@ export default class extends Page {
                 )}
               </Form.Item>
               <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='背景图片'>
-                {getFieldDecorator(`class.${index}.image`, {
+                {getFieldDecorator(`course.${index}.image`, {
                   rules: [
                     { required: true, message: '上传图片' },
                   ],
@@ -190,7 +190,7 @@ export default class extends Page {
                     listType="picture-card"
                     showUploadList={false}
                     beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                      setFieldsValue({ [`class.${index}.image`]: result });
+                      setFieldsValue({ [`course.${index}.image`]: result });
                       return Promise.reject();
                     })}
                   >
@@ -208,9 +208,9 @@ export default class extends Page {
               </Form.Item>
             </Card></Col>;
           })}
-          <Col span={7} offset={classes.length % 3 ? 1 : 0}>
+          <Col span={7} offset={course.length % 3 ? 1 : 0}>
             <Card className="plus" onClick={() => {
-              this.addLength('class', { title: '', link: '', image: '' });
+              this.addLength('course', { title: '', link: '', image: '' });
             }}>
               <Icon type={'plus'} />
             </Card>

+ 3 - 3
front/project/admin/routes/subject/exercise/page.js

@@ -187,13 +187,13 @@ export default class extends Page {
     this.outPage();
     this.interval = setInterval(() => {
       Slient.exerciseAuto().then((result) => {
-        if (result.process == null || result.process === 100) {
+        if (result.progress == null || result.progress === 100) {
           this.actionList[1].disabled = false;
-          result.process = 100;
+          result.progress = 100;
         } else {
           this.actionList[1].disabled = true;
         }
-        this.setState({ process: result.process });
+        this.setState({ progress: result.progress });
       });
     }, 30000);
   }

+ 23 - 4
front/project/admin/routes/subject/sentence/page.js

@@ -1,6 +1,6 @@
 import React from 'react';
 import { Link } from 'react-router-dom';
-import { Button, Modal, Checkbox } from 'antd';
+import { Button, Modal, Checkbox, Form, InputNumber } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
@@ -42,6 +42,12 @@ export default class extends Page {
       key: 'auto',
       name: '重新组卷',
     }, {
+      key: 'introduction',
+      name: '编辑前言',
+      render: (item) => {
+        return <Link to='/subject/sentence/article?i=1'><Button>{item.name}</Button></Link>;
+      },
+    }, {
       key: 'article',
       name: '新建文章',
       render: (item) => {
@@ -114,13 +120,13 @@ export default class extends Page {
     this.outPage();
     this.interval = setInterval(() => {
       Slient.sentenceAuto().then((result) => {
-        if (result.process == null || result.process === 100) {
+        if (result.progress == null || result.progress === 100) {
           this.actionList[1].disabled = false;
-          result.process = 100;
+          result.progress = 100;
         } else {
           this.actionList[1].disabled = true;
         }
-        this.setState({ process: result.process });
+        this.setState({ progress: result.progress });
       });
     }, 30000);
   }
@@ -190,6 +196,12 @@ export default class extends Page {
     this.setState({ detail });
   }
 
+  changeTrail(number) {
+    const { detail } = this.state;
+    detail.trailPages = number;
+    this.setState({ detail });
+  }
+
   submitStruct() {
     const { detail } = this.state;
     Sentence.setStruct(detail).then(() => {
@@ -249,6 +261,13 @@ export default class extends Page {
       }} onOk={() => {
         this.submitStruct();
       }}>
+        <Form>
+          <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='试用页数'>
+            <InputNumber value={this.state.detail.trailPages || 0} onChange={(value) => {
+              this.changeTrail(value);
+            }} />
+          </Form.Item>
+        </Form>
         <TableLayout
           rowKey={'title'}
           columns={this.structColumns}

+ 42 - 7
front/project/admin/routes/subject/sentenceArticle/page.js

@@ -1,5 +1,5 @@
 import React from 'react';
-import { Form, Input, Button, Row, Col, InputNumber, Switch } from 'antd';
+import { Form, Input, Button, Row, Col, InputNumber } from 'antd';
 import './index.less';
 import Editor from '@src/components/Editor';
 import Page from '@src/containers/Page';
@@ -60,11 +60,29 @@ export default class extends Page {
     let handler;
     if (id) {
       handler = Sentence.getArticle({ id }).then((result) => {
-        this.refreshPart(result.chapter);
+        if (result.chapter === 0) {
+          // 前言模式
+          this.setState({ mode: 'introduction' });
+        } else {
+          this.refreshPart(result.chapter);
+        }
         return result;
       });
     } else {
-      handler = Promise.resolve({ part: 1 });
+      const { i } = this.state.search;
+      if (i) {
+        this.setState({ mode: 'introduction' });
+        // 查询前言对应的id
+        handler = Sentence.listArticle({ chapter: 0 })
+          .then(result => {
+            if (result.total > 0) {
+              return result[0];
+            }
+            return { part: 1, chapter: 0, title: '前言' };
+          });
+      } else {
+        handler = Promise.resolve({ part: 1 });
+      }
     }
     handler
       .then(result => {
@@ -112,8 +130,24 @@ export default class extends Page {
     }
   }
 
+  renderIntroduction() {
+    const { getFieldDecorator } = this.props.form;
+    return <Block>
+      <Form>
+        {getFieldDecorator('id')(<input hidden />)}
+        {getFieldDecorator('chapter')(<input hidden />)}
+        {getFieldDecorator('pater')(<input hidden />)}
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='前言名称'>
+          {getFieldDecorator('title')(
+            <Input placeholder='请输入名称' />,
+          )}
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
   renderBase() {
-    const { getFieldDecorator, getFieldValue } = this.props.form;
+    const { getFieldDecorator } = this.props.form;
     return <Block>
       <Form>
         {getFieldDecorator('id')(<input hidden />)}
@@ -152,7 +186,7 @@ export default class extends Page {
             <Input placeholder='请输入名称' />,
           )}
         </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='开放试用'>
+        {/* <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='开放试用'>
           <Row>
             <Col span={4}>
               {getFieldDecorator('isTrail', {
@@ -177,7 +211,7 @@ export default class extends Page {
               </Form.Item>
             </Col>
           </Row>
-        </Form.Item>
+        </Form.Item> */}
       </Form>
     </Block>;
   }
@@ -207,8 +241,9 @@ export default class extends Page {
   }
 
   renderView() {
+    const { mode } = this.state;
     return <div flex>
-      {this.renderBase()}
+      {mode === 'introduction' ? this.renderIntroduction() : this.renderBase()}
       {this.renderContent()}
       <Row type="flex" justify="center">
         <Col>

+ 17 - 8
front/project/www/components/Continue/index.js

@@ -1,37 +1,46 @@
 import React from 'react';
 import Assets from '@src/components/Assets';
+import { formatDate } from '@src/services/Tools';
 import './index.less';
 import Button from '../Button';
 
 function Continue(props) {
-  const { date, data } = props;
-  console.log(date, data);
+  const { data = {}, onContinue, onRestart, onNext, onClose } = props;
+  const { date, paper = {}, userNumber, questionNumber } = data;
   return (
     <div className="continue">
       <div className="body">
         <div className="left">
           <div className="text">上次做题</div>
-          <div className="date">2019-04-29 16:30</div>
+          <div className="date">{formatDate(date)}</div>
         </div>
         <div className="center">
-          <div className="text">OG18综合 第60-80题 80%</div>
+          <div className="text">{paper.title} {questionNumber > 0 ? userNumber * 100 / questionNumber : 0}%</div>
           <div className="list">
-            <Button radius theme="border" size="small">
+            <Button radius theme="border" size="small" onClick={() => {
+              onContinue();
+            }}>
               <Assets name="ico_24_continue" svg />
               Continue
             </Button>
-            <Button radius theme="border" size="small">
+            <Button radius theme="border" size="small" onClick={() => {
+              onRestart();
+            }}>
               <Assets name="ico_24_restart" svg />
               Restart
             </Button>
-            <Button radius theme="border" size="small">
+            <Button radius theme="border" size="small" onClick={() => {
+              onNext();
+            }}>
               <Assets name="ico_24_next" svg />
               Next
             </Button>
           </div>
         </div>
         <div className="right">
-          <Assets name="right" svg />
+          <Assets name="right" svg onClick={() => {
+            onClose();
+          }} />
         </div>
       </div>
     </div>

+ 4 - 2
front/project/www/components/IconButton/index.js

@@ -4,10 +4,12 @@ import Assets from '@src/components/Assets';
 import { Tooltip } from 'antd';
 
 function IconButton(props) {
-  const { className, type, tip } = props;
+  const { className, type, tip, onClick } = props;
   return (
     <Tooltip placement="top" title={tip}>
-      <div className={`icon-button ${className}`}>
+      <div className={`icon-button ${className}`} onClick={() => {
+        onClick();
+      }}>
         <Assets name={`ico_24_${type}`} svg />
       </div>
     </Tooltip>

+ 1 - 0
front/project/www/components/IconButton/index.less

@@ -13,6 +13,7 @@
   cursor: pointer;
 
   .assets {
+    margin-top: -4px;
     width: 14px;
   }
 }

+ 14 - 11
front/project/www/components/List/index.js

@@ -5,29 +5,32 @@ import IconButton from '../IconButton';
 import ProgressText from '../ProgressText';
 
 function List(props) {
-  const { style, title, subTitle, list = [] } = props;
+  const { style, position, title, list = [], onClick } = props;
   return (
     <Module style={style} className="list">
-      <div className="header">
-        <span className="title">{title}</span>
-        <span className="sub-title">{subTitle}</span>
-      </div>
-      <div className="body">
+      {title && <div className="header">
+        {position && <span className="title">{position}:</span>}
+        <span className="sub-title">{title}</span>
+      </div>}
+
+      {list.length > 0 && <div className="body">
         {list.map(item => {
           return (
-            <div className="item">
-              <div className="col part">{item.part}</div>
+            <div className={`item ${item.style || ''}`}>
+              <div className="col part">{item.position}</div>
               <div className="col title">{item.title}</div>
               <div className="col pg">
-                <ProgressText progress={item.progress} size="small" />
+                <ProgressText progress={item.progress || 0} size="small" />
               </div>
               <div className="col action">
-                <IconButton type="view" tip="View" />
+                <IconButton type="view" tip="View" onClick={() => {
+                  onClick(item);
+                }} />
               </div>
             </div>
           );
         })}
-      </div>
+      </div>}
     </Module>
   );
 }

+ 14 - 0
front/project/www/components/List/index.less

@@ -39,6 +39,20 @@
         color: @night-blue;
       }
 
+      &.introduction {
+        .part {
+          width: 0;
+          flex: 0;
+        }
+
+        .title {
+          margin-right: 9px;
+          flex: 6;
+          color: @night-blue;
+          font-size: 18px;
+        }
+      }
+
       .pg {
         flex: 3;
 

+ 7 - 7
front/project/www/components/ListTable/index.js

@@ -17,22 +17,22 @@ function getFilter(filter) {
 }
 
 function ListTable(props) {
-  const { style, title, subTitle, filters = [], columns = [], data = [] } = props;
+  const { style, position, title, filters = [], columns = [], data = [] } = props;
   return (
     <Module style={style} className="list-table">
-      <div hidden={!title && !subTitle} className="header">
-        <span className="title">{title}</span>
-        <span className="sub-title">{subTitle}</span>
+      <div hidden={!title} className="header">
+        <span className="title">{position}</span>
+        <span className="sub-title">{title}</span>
       </div>
-      <div className="filter">
+      {filters.length > 0 && <div className="filter">
         <span className="text">筛选</span>
         <div className="filter-list">
           {filters.map(filter => {
             return <div className="filter-item">{getFilter(filter)}</div>;
           })}
         </div>
-      </div>
-      <Table columns={columns} data={data} />
+      </div>}
+      {data && data.length > 0 && <Table columns={columns} data={data} />}
     </Module>
   );
 }

+ 4 - 2
front/project/www/components/RadioButton/index.js

@@ -3,12 +3,14 @@ import './index.less';
 import Button from '../Button';
 
 function RadioButton(props) {
-  const { list, checked } = props;
+  const { list, checked, onChange } = props;
   return (
     <div className="radio-button">
       {list.map(item => {
         return (
-          <Button theme={item.key === checked ? 'theme' : 'default'} size="small" radius>
+          <Button theme={item.key === checked ? 'theme' : 'default'} size="small" radius onClick={() => {
+            onChange(item);
+          }}>
             {item.title}
           </Button>
         );

+ 6 - 4
front/project/www/components/Select/index.js

@@ -8,9 +8,9 @@ export default class Select extends Component {
     this.state = { selecting: false };
   }
 
-  componentWillMount() {}
+  componentWillMount() { }
 
-  componentWillUnmount() {}
+  componentWillUnmount() { }
 
   open() {
     this.setState({ selecting: true });
@@ -22,7 +22,7 @@ export default class Select extends Component {
 
   render() {
     const { selecting } = this.state;
-    const { value, list = [], size = 'small', theme = 'theme' } = this.props;
+    const { value, list = [], size = 'small', theme = 'theme', onChange } = this.props;
     let index = 0;
     for (let i = 0; i < list.length; i += 1) {
       if (list[i].key === value) {
@@ -40,7 +40,9 @@ export default class Select extends Component {
           </Button>
           <div className={`select-body ${selecting ? 'select' : ''}`}>
             {list.map(item => {
-              return <div className="select-option">{item.title}</div>;
+              return <div className="select-option" onClick={() => {
+                onChange(item);
+              }}>{item.title}</div>;
             })}
           </div>
         </div>

+ 13 - 6
front/project/www/components/Step/index.js

@@ -1,16 +1,23 @@
 import React from 'react';
+import { Tooltip } from 'antd';
 import './index.less';
 
 function Step(props) {
-  const { list = [], step = 1 } = props;
+  const { list = [], step = 1, onClick, message, maxStep } = props;
   return (
     <div className="step">
       {list.map((item, index) => {
-        return (
-          <div className={`item ${index === step - 1 ? 'active' : ''} ${step - 1 > index ? 'over' : ''}`}>
-            <span className="text">{item}</span>
-          </div>
-        );
+        const info = <div className={`item ${index === step - 1 ? 'active' : ''}`} onClick={() => {
+          if ((maxStep && index < maxStep) || !maxStep) onClick(index + 1);
+        }}>
+          <span className="text">{item}</span>
+        </div>;
+        if ((maxStep && index < maxStep) || !maxStep) {
+          return info;
+        }
+        return <Tooltip title={message}>
+          {info}
+        </Tooltip>;
       })}
     </div>
   );

+ 1 - 0
front/project/www/components/Step/index.less

@@ -10,6 +10,7 @@
   .item {
     flex: 1;
     position: relative;
+    cursor: pointer;
 
     .text {
       margin-left: 5px;

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

@@ -10,7 +10,7 @@ function getItem(props, item, onChange) {
       style={{ width: width || '', marginLeft: space || '', marginRight: space || '' }}
       className={`tab ${active === item.key ? 'active' : ''}`}
     >
-      {item.name}
+      {item.name || item.title}
     </div>
   );
 }

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

@@ -8,9 +8,9 @@ export default {
   tabs: [
     { key: 'main', name: '首页', path: '/' },
     { key: 'ready', name: 'GetReady', path: '/' },
-    { key: 'practise', name: '练习', path: '/' },
-    { key: 'cat', name: 'CAT模考', path: '/' },
+    { key: 'practise', name: '练习', path: '/exercise' },
+    { key: 'cat', name: 'CAT模考', path: '/examination' },
     { key: 'item', name: '题库', path: '/' },
-    { key: 'machine', name: '换库&机经', path: '/' },
+    { key: 'machine', name: '换库机经', path: '/textbook' },
   ],
 };

+ 8 - 4
front/project/www/local.json

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

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

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

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

@@ -0,0 +1,10 @@
+export default {
+  path: '/examination',
+  key: 'examination',
+  title: '模考',
+  needLogin: false,
+  tab: 'examination',
+  component() {
+    return import('./page');
+  },
+};

+ 68 - 0
front/project/www/routes/examination/main/index.less

@@ -0,0 +1,68 @@
+@charset "utf-8";
+
+#exercise {
+  .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;
+      }
+    }
+  }
+
+  .sentence-code {
+    margin-top: -10px;
+    margin-bottom: 10px;
+
+    a {
+      margin: 3px;
+    }
+  }
+}

+ 656 - 0
front/project/www/routes/examination/main/page.js

@@ -0,0 +1,656 @@
+import React from 'react';
+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 Continue from '../../../components/Continue';
+import Step from '../../../components/Step';
+import List from '../../../components/List';
+import Tabs from '../../../components/Tabs';
+import Module from '../../../components/Module';
+import Input from '../../../components/Input';
+import Button from '../../../components/Button';
+import Division from '../../../components/Division';
+import Card from '../../../components/Card';
+import ListTable from '../../../components/ListTable';
+import ProgressText from '../../../components/ProgressText';
+import IconButton from '../../../components/IconButton';
+import { Main } from '../../../stores/main';
+import { My } from '../../../stores/my';
+import { Sentence } from '../../../stores/sentence';
+import { Question } from '../../../stores/question';
+import { Course } from '../../../stores/course';
+import { User } from '../../../stores/user';
+
+const SENTENCE = 'sentence';
+const PREVIEW = 'preview';
+const PREVIEW_CLASS = 'PREVIEW_CLASS';
+const PREVIEW_LIST = 'PREVIEW_LIST';
+
+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 ? 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 {
+  constructor(props) {
+    super(props);
+    this.sentenceColums = [
+      {
+        title: '练习册',
+        width: 250,
+        align: 'left',
+        render: (row) => {
+          return (
+            <div className="table-row">
+              <div className="night f-s-16">{row.title}</div>
+              <div>
+                <ProgressText progress={row.process} size="small" />
+              </div>
+            </div>
+          );
+        },
+      },
+      {
+        title: '正确率',
+        width: 150,
+        align: 'left',
+        render: () => {
+          return (
+            <div className="table-row">
+              <div className="night f-s-16 f-w-b">--</div>
+              <div className="f-s-12">全站55%</div>
+            </div>
+          );
+        },
+      },
+      {
+        title: '全站用时',
+        width: 150,
+        align: 'left',
+        render: () => {
+          return (
+            <div className="table-row">
+              <div className="night f-s-16 f-w-b">55%</div>
+              <div className="f-s-12">全站56s</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: () => {
+          return (
+            <div className="table-row p-t-1">
+              <IconButton className="m-r-2" type="continue" tip="Continue" />
+              <IconButton type="restart" tip="Restart" />
+            </div>
+          );
+        },
+      },
+      {
+        title: '报告',
+        width: 30,
+        align: 'right',
+        render: () => {
+          return (
+            <div className="table-row p-t-1">
+              <IconButton type="report" tip="Report" />
+            </div>
+          );
+        },
+      },
+    ];
+  }
+
+  initState() {
+    this.code = null;
+    this.columns = exerciseColumns;
+    this.exerciseProcess = {};
+    this.inited = false;
+    return {
+      tab1: SENTENCE,
+      tab2: '',
+      previewType: PREVIEW_CLASS,
+      tabs: [],
+      allClass: [],
+      classProcess: {},
+    };
+  }
+
+  init() {
+    Main.getExercise().then(result => {
+      const list = result.map((row) => {
+        row.title = `${row.titleZh}${row.titleEn}`;
+        row.key = row.extend;
+        return row;
+      });
+      const tabs = formatTreeData(list, 'id', 'title', 'parentId');
+      tabs.push({ key: PREVIEW, name: '预习作业' });
+      const map = getMap(tabs, 'key');
+      this.setState({ tabs, map });
+      this.inited = true;
+      this.refreshData();
+    });
+  }
+
+  initData() {
+    const { info = {} } = this.props.user;
+    if (info.latestExercise) {
+      // 获取最后一次做题记录
+      Question.baseReport(info.latestExercise).then((result) => {
+        this.setState({ latest: result });
+      });
+    }
+    if (this.inited) this.refreshData();
+  }
+
+  refreshData() {
+    const { tab1 } = this.state;
+    switch (tab1) {
+      case SENTENCE:
+        this.refreshSentence();
+        break;
+      case PREVIEW:
+        this.refreshPreview();
+        break;
+      default:
+        this.refreshExercise();
+    }
+  }
+
+  refreshSentence() {
+    const { sentence, articleMap, paperList } = this.state;
+    if (!sentence) {
+      Sentence.getInfo().then(result => {
+        const chapters = [];
+        const map = {};
+        let index = 0;
+        let exerciseChapter = null;
+        if (!result.code) {
+          chapters.push(`${index}」试用`);
+        }
+        index += 1;
+        result.chapters.forEach(row => {
+          map[row.value] = row;
+          chapters.push(`「${index}」${row.short}`);
+          index += 1;
+          if (row.exercise) exerciseChapter = row;
+        });
+        this.setState({ sentence: result, chapters, chapterMap: map, exerciseChapter });
+      });
+    }
+
+    if (!articleMap) {
+      Sentence.listArticle().then(result => {
+        const map = {};
+        result.forEach((article) => {
+          if (!map[article.chapter]) {
+            map[article.chapter] = [];
+          }
+          map[article.chapter].push(article);
+        });
+        this.setState({ articleMap: map });
+      });
+    }
+
+    if (!paperList) {
+      Sentence.listPaper().then(result => {
+        this.setState({ paperList: result, paperFilterList: result });
+      });
+    }
+  }
+
+  refreshPreview() {
+    const { previewType } = this.state;
+    switch (previewType) {
+      case PREVIEW_LIST:
+        this.refreshListPreview();
+        break;
+      case PREVIEW_CLASS:
+      default:
+        this.refreshClassProcess();
+        break;
+    }
+  }
+
+  refreshClassProcess() {
+    Course.classProcess().then(result => {
+      const classProcess = {};
+      for (let i = 0; i < result.length; i += 1) {
+        const item = result[i];
+        classProcess[item.category].push(item);
+      }
+      this.setState({ classProcess });
+    });
+  }
+
+  refreshListPreview() {
+    Question.listPreview().then(result => {
+      this.setState({ previews: result });
+    });
+  }
+
+  refreshExercise() {
+    const { map, tab1 } = this.state;
+    let { tab2 } = this.state;
+    if (!map) {
+      // 等待数据加载
+      return;
+    }
+    if (tab1 === '') {
+      return;
+    }
+    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[`tab${level}`] = tab;
+    this.setState(state);
+    this.refresh();
+  }
+
+  previewAction(type, item) {
+    switch (type) {
+      case 'start':
+        this.start('preview', item);
+        break;
+      case 'restart':
+        this.restart(item);
+        break;
+      case 'continue':
+        this.continue('preview', item);
+        break;
+      default:
+        break;
+    }
+  }
+
+  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}`);
+  }
+
+  activeSentence() {
+    Sentence.active(this.code)
+      .then(() => {
+        // 重新获取长难句信息
+        this.clearSentenceTrail();
+        this.setState({ sentence: null, articleMap: null, paperList: null });
+        this.refresh();
+      });
+  }
+
+  trailSentence() {
+    this.setState({ sentenceInput: false });
+    User.sentenceTrail();
+  }
+
+  sentenceRead(article) {
+    linkTo(`/sentence/read?chapter=${article.chapter}&part=${article.part}`);
+  }
+
+  sentenceFilter() {
+    const { paperList } = this.state;
+    const list = paperList.filter(row => {
+      return !!row;
+    });
+    this.setState({ paperFilterList: list });
+  }
+
+  clearExercise() {
+    My.clearLatestExercise();
+    this.setState({ latest: null });
+  }
+
+  renderView() {
+    const { tab1 = {}, tab2 = {}, tabs, map = {}, latest } = this.state;
+    const children = (map[tab1] || {}).children || [];
+    return (
+      <div>
+        {latest && <Continue
+          data={latest}
+          onClose={() => {
+            this.clearExercise();
+          }}
+          onContinue={() => {
+
+          }}
+          onRestart={() => {
+
+          }}
+          onNext={() => {
+
+          }} />}
+        <div className="content">
+          <Module className="m-t-2">
+            <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>
+          {tab1 !== SENTENCE && tab1 !== PREVIEW && this.renderExercise()}
+          {tab1 === SENTENCE && this.renderSentence()}
+          {tab1 === PREVIEW && this.renderPreview()}
+        </div>
+      </div>
+    );
+  }
+
+  renderPreview() {
+    const { previewType } = this.state;
+    switch (previewType) {
+      case PREVIEW_CLASS:
+        return this.renderPreviewClass();
+      case PREVIEW_LIST:
+        return this.renderPreviewList();
+      default:
+        return <div />;
+    }
+  }
+
+  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_LIST)}>
+            全部作业 >
+          </div>
+        </div>
+        <Division col="3">
+          {allClass.map(item => {
+            return <Card data={item} process={classProcess[item.id]} previewAction={this.previewAction} />;
+          })}
+        </Division>
+      </div>
+    );
+  }
+
+  renderPreviewList() {
+    const { previews } = 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_CLASS)}>
+            我的课程 >
+          </div>
+        </div>
+        <ListTable
+          filters={[
+            {
+              type: 'radio',
+              checked: 'today',
+              list: [{ key: 'today', title: '今日需完成' }, { key: 'tomorrow', title: '明日需完成' }],
+            },
+            {
+              type: 'radio',
+              checked: 'unfinish',
+              list: [{ key: 'unfinish', title: '未完成' }, { key: 'finish', title: '已完成' }],
+            },
+            { type: 'select', checked: 'all', list: [{ key: 'all', title: '全部' }] },
+          ]}
+          data={previews}
+          columns={this.columns}
+        />
+      </div>
+    );
+  }
+
+  renderSentence() {
+    const { sentence = {}, sentenceInput } = this.state;
+    const { sentenceTrail } = this.props.user;
+    if (sentenceInput !== true && (sentence.code || sentenceTrail)) {
+      return this.renderSentenceArticle();
+    }
+    return this.renderInputCode();
+  }
+
+  renderSentenceArticle() {
+    const { sentence = {}, chapters, chapter, exerciseChapter = {}, chapterMap = {}, articleMap = {}, paperFilterList = [], paperList = [], paperChecked } = this.state;
+    const { sentenceTrail } = this.props.user;
+    let maxStep = 0;
+    if (sentenceTrail) {
+      // 试用只能访问第一step
+      maxStep = 1;
+      // 查找练习章节
+    }
+    const chapterInfo = chapterMap[chapter] || {};
+    let isExercise = false;
+    if (chapterInfo && chapterInfo.exercise) {
+      isExercise = true;
+    }
+    return <div>
+      {sentence.code && <div className='sentence-code'>CODE: {sentence.code}</div>}
+      {sentenceTrail && <div className='sentence-code'>CODE: <Link to=''>去获取</Link><a onClick={() => {
+        this.setState({ sentenceInput: true });
+      }}>输入</a></div>}
+      <Module>
+        <Step
+          list={chapters}
+          step={chapter}
+          onClick={(step) => {
+            this.setState({ chapter: step });
+          }}
+          message='请购买后访问'
+          maxStep={maxStep}
+        />
+      </Module>
+      {/* 正常文章 */}
+      {sentence.code && !isExercise && <List
+        title={`Chapter${chapter}`}
+        subTitle={chapterInfo.title}
+        list={articleMap[chapter]}
+        onClick={(part) => {
+          this.sentenceRead(part);
+        }}
+      />}
+      {/* 正常练习 */}
+      {sentence.code && isExercise && <ListTable
+        title={`Chapter${chapter}`}
+        subTitle={chapterInfo.title}
+        filters={[{
+          type: 'radio',
+          checked: paperChecked,
+          list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
+          onChange: (item) => {
+            console.log(item);
+            this.sentenceFilter(item);
+          },
+        }]}
+        data={paperFilterList}
+        columns={this.sentenceColums}
+      />}
+      {/* 试读文章 */}
+      {sentenceTrail && <List
+        list={[]}
+        onClick={(part) => {
+          this.sentenceRead(part);
+        }}
+      />}
+      {/* 试练 */}
+      {sentenceTrail && <ListTable
+        title={`Chapter${exerciseChapter.value}`}
+        subTitle={exerciseChapter.title}
+        data={paperList}
+        columns={this.sentenceColums}
+      />}
+    </div>;
+  }
+
+  renderInputCode() {
+    return (
+      <Module className="code-module">
+        <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
+        <div className="input-block">
+          <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">
+            什么是CODE?
+          </Link>
+          <span>没有 CODE?</span>
+          <Link to="/" className="link">
+            去获取 >>
+          </Link>
+          <a onClick={() => {
+            this.trailSentence();
+          }} className="right link">
+            试用 >>
+          </a>
+        </div>
+      </Module>
+    );
+  }
+
+  renderExercise() {
+    return <div />;
+  }
+}

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

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

+ 9 - 0
front/project/www/routes/exercise/main/index.less

@@ -56,4 +56,13 @@
       }
     }
   }
+
+  .sentence-code {
+    margin-top: -10px;
+    margin-bottom: 10px;
+
+    a {
+      margin: 3px;
+    }
+  }
 }

+ 383 - 56
front/project/www/routes/exercise/main/page.js

@@ -3,7 +3,10 @@ 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 { formatTreeData, getMap, formatSeconds, formatDate } from '@src/services/Tools';
+import Continue from '../../../components/Continue';
+import Step from '../../../components/Step';
+import List from '../../../components/List';
 import Tabs from '../../../components/Tabs';
 import Module from '../../../components/Module';
 import Input from '../../../components/Input';
@@ -14,16 +17,18 @@ import ListTable from '../../../components/ListTable';
 import ProgressText from '../../../components/ProgressText';
 import IconButton from '../../../components/IconButton';
 import { Main } from '../../../stores/main';
+import { My } from '../../../stores/my';
 import { Sentence } from '../../../stores/sentence';
 import { Question } from '../../../stores/question';
-// import { Course } from '../../../stores/course';
+import { Course } from '../../../stores/course';
+import { User } from '../../../stores/user';
 
 const SENTENCE = 'sentence';
 const PREVIEW = 'preview';
 const PREVIEW_CLASS = 'PREVIEW_CLASS';
 const PREVIEW_LIST = 'PREVIEW_LIST';
 
-const columns = [
+const exerciseColumns = [
   {
     title: '练习册',
     width: 250,
@@ -88,19 +93,19 @@ const columns = [
     render: item => {
       return (
         <div className="table-row p-t-1">
-          {!item.repport.id && (
-            <IconButton type="start" tip="Start" onClick={() => this.previewAction('start', item)} />
+          {!item.report && (
+            <IconButton type="start" tip="Start" onClick={() => this.start('preview', item)} />
           )}
-          {item.repport.id && (
+          {item.report.id && !item.report.isFinish && (
             <IconButton
               className="m-r-2"
               type="continue"
               tip="Continue"
-              onClick={() => this.previewAction('continue', item)}
+              onClick={() => this.continue('preview', item)}
             />
           )}
-          {item.repport.id && (
-            <IconButton type="restart" tip="Restart" onClick={() => this.previewAction('restart', item)} />
+          {item.report.id && (
+            <IconButton type="restart" tip="Restart" onClick={() => this.restart('preview', item)} />
           )}
         </div>
       );
@@ -113,7 +118,7 @@ const columns = [
     render: item => {
       return (
         <div className="table-row p-t-1">
-          {item.report.userNumber === item.report.questionNumber && <IconButton type="report" tip="Report" />}
+          {item.report.isFinish && <IconButton type="report" tip="Report" onClick={() => this.viewReport(item)} />}
         </div>
       );
     },
@@ -121,38 +126,162 @@ const columns = [
 ];
 
 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 = record.report.userNumber * 100 / 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 = `${record.report.userCorrect * 100 / record.report.userNumber}%`;
+          }
+          return (
+            <div className="table-row">
+              <div className="night f-s-16 f-w-b">{correct}</div>
+              <div className="f-s-12">全站{record.stat.totalCorrect * 100 / record.stat.totalNumber}%</div>
+            </div>
+          );
+        },
+      },
+      {
+        title: '全站用时',
+        width: 150,
+        align: 'left',
+        render: (record) => {
+          let time = '--';
+          if (record.paper) {
+            time = record.paper.report.userTime / record.paper.report.userNumber;
+          }
+          return (
+            <div className="table-row">
+              <div className="night f-s-16 f-w-b">{formatSeconds(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: 180,
+        align: 'left',
+        render: (record) => {
+          return (
+            <div className="table-row p-t-1">
+              {!record.report && <IconButton type="start" tip="Start" onClick={() => {
+                this.start('sentence', record);
+              }} />}
+              {!record.report.isFinish && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
+                this.continue('sentence', record);
+              }} />}
+              <IconButton type="restart" tip="Restart" onClick={() => {
+                this.restart(record);
+              }} />
+            </div>
+          );
+        },
+      },
+      {
+        title: '报告',
+        dataIndex: 'report',
+        width: 30,
+        align: 'right',
+        render: (text, record) => {
+          if (!record.report || !record.report.isFinish) return null;
+          return (
+            <div className="table-row p-t-1">
+              <IconButton type="report" tip="Report" onClick={() => {
+                this.viewReport(record);
+              }} />
+            </div>
+          );
+        },
+      },
+    ];
+  }
+
   initState() {
     this.code = null;
-    this.columns = columns;
-    this.exerciseProcess = {};
+    this.columns = exerciseColumns;
+    this.exerciseProgress = {};
+    this.inited = false;
     return {
-      tab1: PREVIEW,
+      tab1: SENTENCE,
       tab2: '',
-      previewType: PREVIEW_CLASS,
+      pt: PREVIEW_CLASS,
       tabs: [],
       allClass: [],
       classProcess: {},
     };
   }
 
-  initData() {
+  init() {
     Main.getExercise().then(result => {
       const list = result.map((row) => {
         row.title = `${row.titleZh}${row.titleEn}`;
         row.key = row.extend;
         return row;
       });
-      const tabs = formatTreeData(list);
+      const map = getMap(list, 'key');
+      const tabs = formatTreeData(list, 'id', 'title', 'parentId');
       tabs.push({ key: PREVIEW, name: '预习作业' });
-      const map = getMap(tabs, 'key');
       this.setState({ tabs, map });
+      this.inited = true;
+      this.refreshData();
     });
-    this.refresh();
   }
 
-  refresh() {
+  initData() {
+    const { info = {} } = this.props.user;
+    if (info.latestExercise) {
+      // 获取最后一次做题记录
+      Question.baseReport(info.latestExercise).then((result) => {
+        this.setState({ latest: result });
+      });
+    }
+    const data = Object.assign(this.state, this.state.search);
+    this.setState(data);
+    if (this.inited) this.refreshData();
+  }
+
+  refreshData(tab) {
     const { tab1 } = this.state;
-    switch (tab1) {
+    switch (tab || tab1) {
       case SENTENCE:
         this.refreshSentence();
         break;
@@ -160,29 +289,92 @@ export default class extends Page {
         this.refreshPreview();
         break;
       default:
-        this.refreshExercise();
+        this.refreshExercise(tab || tab1);
     }
   }
 
   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 });
-    });
+    const { sentence } = this.state;
+    if (!sentence) {
+      // User.clearSentenceTrail();
+      Sentence.getInfo().then(result => {
+        // result.code = '123123';
+        result.trailPages = 20;
+        this.setState({ sentence: result });
+        return result;
+      })
+        .then(({ code, trailPages, chapters }) => {
+          return Sentence.listArticle().then(result => {
+            const chapterSteps = [];
+            const chapterMap = {};
+            const map = {};
+            const trailArticles = [];
+            let totalPage = 0;
+            let introduction = null;
+            let exerciseChapter = null;
+            let index = 0;
+            let lastChapter = -1;
+
+            chapters.forEach(row => {
+              chapterMap[row.value] = row;
+              if (row.exercise) exerciseChapter = row;
+            });
+
+            result.forEach((article) => {
+              if (article.chapter === 0) introduction = article;
+              if (!map[article.chapter]) {
+                map[article.chapter] = [];
+              }
+              article.startPage = totalPage + 1;
+              article.endPage = totalPage + article.pages;
+              if (article.chapter) {
+                article.position = `${article.chapter}.${article.part}`;
+              } else {
+                // 设置list中的样式
+                article.style = 'introduction';
+              }
+              totalPage += article.pages;
+              if (article.startPage < trailPages) {
+                if (lastChapter !== article.chapter) {
+                  lastChapter = article.chapter;
+                  trailArticles.push(Object.assign({ articles: [] }, chapterMap[article.chapter] || {}));
+                }
+                trailArticles[trailArticles.length - 1].articles.push(article);
+              }
+              map[article.chapter].push(article);
+            });
+
+            if (!code) {
+              chapterSteps.push(`「${index}」试用`);
+            }
+            // 添加前言
+            if (introduction) {
+              index += 1;
+              chapterSteps.push(`「${index}」${introduction.title}`);
+              chapterMap[0] = {
+                title: introduction.title,
+                value: 0,
+              };
+            }
+            index += 1;
+            chapters.forEach(row => {
+              chapterSteps.push(`「${index}」${row.short}`);
+              index += 1;
+            });
+            this.setState({ articleMap: map, trailArticles, chapterSteps, introduction, chapterMap, exerciseChapter });
+          });
+        })
+        .then(() => {
+          return Sentence.listPaper().then(result => {
+            this.setState({ paperList: result, paperFilterList: result });
+          });
+        });
+    }
   }
 
   refreshPreview() {
-    const { previewType } = this.state;
-    switch (previewType) {
+    const { pt } = this.state;
+    switch (pt) {
       case PREVIEW_LIST:
         this.refreshListPreview();
         break;
@@ -194,7 +386,7 @@ export default class extends Page {
   }
 
   refreshClassProcess() {
-    Question.getClassProcess().then(result => {
+    Course.classProcess().then(result => {
       const classProcess = {};
       for (let i = 0; i < result.length; i += 1) {
         const item = result[i];
@@ -210,36 +402,39 @@ export default class extends Page {
     });
   }
 
-  refreshExercise() {
+  refreshExercise(tab) {
     const { map, tab1 } = this.state;
     let { tab2 } = this.state;
-    const subject = map[tab1];
-    if (tab2 === '') {
-      tab2 = subject.children[0].key;
-      this.onChangeTab(2, tab2);
+    if (!map) {
+      // 等待数据加载
       return;
     }
+    const subject = map[tab];
+    // 切换tab1的情况
+    if (tab2 === '' || tab1 !== tab) {
+      tab2 = subject.children[0].key;
+      this.setState({ tab2 });
+    }
     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 });
+    Question.getExerciseProgress(type.id).then((r => {
+      const exerciseProgress = getMap(r, 'id');
+      this.setState({ exerciseProgress });
     }));
   }
 
   onChangePreviewType(type) {
-    this.setState({ previewType: type });
+    this.setState({ pt: type });
     this.refreshPreview();
   }
 
   onChangeTab(level, tab) {
-    const state = {};
-    state[`tab${level}`] = tab;
-    this.setState(state);
-    this.refresh();
+    const { tab1, tab2 } = this.state;
+    // this.refreshData(tab);
+    this.refreshQuery(Object.assign({ tab1, tab2 }, { [`tab${level}`]: tab }));
   }
 
   previewAction(type, item) {
@@ -274,18 +469,69 @@ export default class extends Page {
     linkTo(`/paper/process/${type}/${item.id}?r=${item.report.id}`);
   }
 
+  viewReport(item) {
+    linkTo(`/paper/report/${item.report.id}`);
+  }
+
   activeSentence() {
     Sentence.active(this.code)
       .then(() => {
+        // 重新获取长难句信息
+        User.clearSentenceTrail();
+        this.setState({ sentence: null, articleMap: null, paperList: null });
         this.refresh();
       });
   }
 
+  trailSentence() {
+    this.setState({ sentenceInput: false });
+    User.sentenceTrail();
+  }
+
+  sentenceRead(article) {
+    if (article) {
+      linkTo(`/sentence/read?chapter=${article.chapter}&part=${article.part}`);
+    } else {
+      linkTo('/sentence/read');
+    }
+  }
+
+  sentenceFilter(value) {
+    const { paperList } = this.state;
+    const list = paperList.filter(row => {
+      const finish = row.paper ? row.paper.times > 0 : false;
+      if (value === 0) {
+        return !finish;
+      }
+      return finish;
+    });
+    this.setState({ paperFilterList: list, paperChecked: value });
+  }
+
+  clearExercise() {
+    My.clearLatestExercise();
+    this.setState({ latest: null });
+  }
+
   renderView() {
-    const { tab1 = {}, tab2 = {}, tabs, map = {} } = this.state;
+    const { tab1 = {}, tab2 = {}, tabs, map = {}, latest } = this.state;
     const children = (map[tab1] || {}).children || [];
     return (
       <div>
+        {latest && <Continue
+          data={latest}
+          onClose={() => {
+            this.clearExercise();
+          }}
+          onContinue={() => {
+
+          }}
+          onRestart={() => {
+
+          }}
+          onNext={() => {
+
+          }} />}
         <div className="content">
           <Module className="m-t-2">
             <Tabs type="card" active={tab1} tabs={tabs} onChange={key => {
@@ -365,16 +611,97 @@ export default class extends Page {
   }
 
   renderSentence() {
-    const { sentence = {}, trail = false } = this.state;
-    if (sentence.code || trail) {
+    const { sentence = {}, sentenceInput } = this.state;
+    const { sentenceTrail } = this.props.user;
+    if (sentenceInput !== true && (sentence.code || sentenceTrail)) {
       return this.renderSentenceArticle();
     }
     return this.renderInputCode();
   }
 
   renderSentenceArticle() {
-    // const { sentence = {}, trail } = this.state;
-    return <div />;
+    const { sentence = {}, introduction, chapterSteps, chapterStep = 1, exerciseChapter = {}, chapterMap = {}, articleMap = {}, trailArticles = [], paperFilterList = [], paperList = [], paperChecked } = this.state;
+    const { sentenceTrail } = this.props.user;
+    let maxStep = 0;
+    if (sentenceTrail) {
+      // 试用只能访问第一step
+      maxStep = 1;
+      // 查找练习章节
+    }
+    // 减去前言计算chapter
+    const chapter = introduction ? chapterStep - 1 : chapterStep;
+    const chapterInfo = chapterMap[chapter] || {};
+    let isExercise = false;
+    if (chapterInfo && chapterInfo.exercise) {
+      isExercise = true;
+    }
+    return <div>
+      {sentence.code && <div className='sentence-code'>CODE: {sentence.code}</div>}
+      {sentenceTrail && <div className='sentence-code'>CODE: <Link to=''>去获取</Link><a onClick={() => {
+        this.setState({ sentenceInput: true });
+      }}>输入</a></div>}
+      <Module>
+        <Step
+          list={chapterSteps}
+          step={chapterStep}
+          onClick={(step) => {
+            this.setState({ chapterStep: step });
+          }}
+          message='请购买后访问'
+          maxStep={maxStep}
+        />
+      </Module>
+      {/* 正常前言 */}
+      {sentence.code && chapter === 0 && <List
+        // title={chapterInfo.title}
+        list={[introduction]}
+        onClick={() => {
+          this.sentenceRead();
+        }}
+      />}
+      {/* 正常文章 */}
+      {sentence.code && chapter && !isExercise && <List
+        position={`Chapter${chapter}`}
+        title={chapterInfo.title}
+        list={articleMap[chapter]}
+        onClick={(part) => {
+          this.sentenceRead(part);
+        }}
+      />}
+      {/* 正常练习 */}
+      {sentence.code && isExercise && <ListTable
+        position={`Chapter${chapter}`}
+        title={chapterInfo.title}
+        filters={[{
+          type: 'radio',
+          checked: paperChecked,
+          list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
+          onChange: (item) => {
+            this.sentenceFilter(item.key);
+          },
+        }]}
+        data={paperFilterList}
+        columns={this.sentenceColums}
+      />}
+      {/* 试读文章 */}
+      {sentenceTrail && trailArticles.map((info) => {
+        return <List
+          position={info.value ? `Chapter${info.value}` : null}
+          title={info.title}
+          list={info.articles}
+          onClick={(part) => {
+            this.sentenceRead(part);
+          }}
+        />;
+      })}
+      {/* 试练 */}
+      {sentenceTrail && <ListTable
+        position={`Chapter${exerciseChapter.value}`}
+        title={exerciseChapter.title}
+        data={paperList}
+        columns={this.sentenceColums}
+      />}
+    </div>;
   }
 
   renderInputCode() {
@@ -398,7 +725,7 @@ export default class extends Page {
             去获取 >>
           </Link>
           <a onClick={() => {
-            this.setState({ trail: true });
+            this.trailSentence();
           }} className="right link">
             试用 >>
           </a>

+ 5 - 5
front/project/www/routes/page/demo/page.js

@@ -112,13 +112,13 @@ export default class extends Page {
             <Card title="句改 SC" data={{ status: 'end' }} />
           </Division>
           <List
-            title="Chapter4"
-            subTitle="简单句如何变长难句"
-            list={[{ progress: 30, title: '什么样的句子叫长难句,长难句的基本特征。', part: 'Part 1' }]}
+            position="Chapter4"
+            title="简单句如何变长难句"
+            list={[{ progress: 30, title: '什么样的句子叫长难句,长难句的基本特征。', position: 'Part 1' }]}
           />
           <ListTable
-            title="Chapter5"
-            subTitle="练习"
+            position="Chapter5"
+            title="练习"
             filters={[
               { type: 'radio', checked: 'first', list: [{ key: 'first', title: 123 }, { key: 'two', title: 321 }] },
               { type: 'select', checked: 'first', list: [{ key: 'first', title: 123 }, { key: 'two', title: 321 }] },

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

@@ -432,7 +432,6 @@
     height: 640px;
     padding-left: 150px;
     padding-top: 280px;
-    background-image: url('/assets/banner1.png');
     background-repeat: no-repeat;
     background-position: center;
     background-color: linear-gradient(241deg, rgba(132, 93, 72, 0) 0%, rgba(88, 50, 29, 1) 100%);

+ 68 - 228
front/project/www/routes/page/home/page.js

@@ -1,17 +1,25 @@
 import React from 'react';
+import { Carousel } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
+import { formatDate } from '@src/services/Tools';
 import Button from '../../../components/Button';
 import Footer from '../../../components/Footer';
+import { Main } from '../../../stores/main';
+
+const courseIconList = ['grammar', 'read', 'logic', 'math'];
 
 export default class extends Page {
   constructor(props) {
     super(props);
-    this.state = { index: 0 };
+    this.state = { courseIndex: 0 };
   }
 
   initData() {
+    Main.getIndex().then(result => {
+      this.setState({ result });
+    });
     setTimeout(() => {
       this.createLayout();
     }, 1);
@@ -24,7 +32,12 @@ export default class extends Page {
     msnry.layout();
   }
 
+  location(url) {
+    window.location.href = url;
+  }
+
   renderView() {
+    const { prepare = {}, user = {}, course = [], activity = [], evaluation = [], contact = {} } = this.state;
     return (
       <div>
         <div className="block block-1">
@@ -61,10 +74,14 @@ export default class extends Page {
                       自学指南<span className="sub">Self-guided</span>
                     </div>
                     <div className="btn">
-                      <Button className="btn-shadow" size="lager">
+                      <Button className="btn-shadow" size="lager" onClick={() => {
+                        this.location(prepare.first);
+                      }}>
                         从零开始
                       </Button>
-                      <Button className="btn-shadow" theme="white" size="lager">
+                      <Button className="btn-shadow" theme="white" size="lager" onClick={() => {
+                        this.location(prepare.continue);
+                      }}>
                         继续学习
                       </Button>
                     </div>
@@ -74,10 +91,14 @@ export default class extends Page {
                       参与课程<span className="sub">With DUKB24</span>
                     </div>
                     <div className="btn">
-                      <Button theme="error" size="lager" radius>
+                      <Button theme="error" size="lager" radius onClick={() => {
+                        this.location(prepare.classJunior);
+                      }}>
                         从零开始
                       </Button>
-                      <Button theme="warn" size="lager" radius>
+                      <Button theme="warn" size="lager" radius onClick={() => {
+                        this.location(prepare.classMiddle);
+                      }}>
                         继续学习
                       </Button>
                     </div>
@@ -118,21 +139,21 @@ export default class extends Page {
                 <div className="list">
                   <div className="item m-r-1-5">
                     <div className="title" style={{ color: '#4292F0' }}>
-                      123342
+                      {user.numberOffline}
                     </div>
                     <div className="desc">注册用户</div>
                     <Assets className="foot-1" name="foot1" />
                   </div>
                   <div className="item m-r-1-5 m-l-1-5">
                     <div className="title" style={{ color: '#FFB676' }}>
-                      2234
+                      {user.number700}
                     </div>
                     <div className="desc">700+分学员</div>
                     <Assets className="foot-2" name="foot2" />
                   </div>
                   <div className="item m-l-1-5">
                     <div className="title" style={{ color: '#F36565' }}>
-                      680
+                      {user.numberScore}
                     </div>
                     <div className="desc">学员均分</div>
                     <Assets className="foot-3" name="foot1" />
@@ -145,53 +166,23 @@ export default class extends Page {
                 </div>
                 <div className="box">
                   <div className="detail">
-                    <Assets name="class_img1" />
+                    {course[this.state.courseIndex] && <a href={course[this.state.courseIndex].link} target='_blank'><Assets src={course[this.state.courseIndex].image} /></a>}
                   </div>
                   <div className="list">
-                    <div
-                      className={`tab ${this.state.index === 0 ? 'active' : ''}`}
-                      onMouseEnter={() => this.setState({ index: 0 })}
-                    >
-                      <Assets name="grammar" />
-                      语法SC
-                      <div className="place place-1">
-                        <span className="right-arrow" />
-                      </div>
-                      <i className="left-arrow" />
-                    </div>
-                    <div
-                      className={`tab ${this.state.index === 1 ? 'active' : ''}`}
-                      onMouseEnter={() => this.setState({ index: 1 })}
-                    >
-                      <Assets name="read" />
-                      阅读RC
-                      <div className="place place-2">
-                        <span className="right-arrow" />
-                      </div>
-                      <i className="left-arrow" />
-                    </div>
-                    <div
-                      className={`tab ${this.state.index === 2 ? 'active' : ''}`}
-                      onMouseEnter={() => this.setState({ index: 2 })}
-                    >
-                      <Assets name="logic" />
-                      逻辑CR
-                      <div className="place place-3">
-                        <span className="right-arrow" />
-                      </div>
-                      <i className="left-arrow" />
-                    </div>
-                    <div
-                      className={`tab ${this.state.index === 3 ? 'active' : ''}`}
-                      onMouseEnter={() => this.setState({ index: 3 })}
-                    >
-                      <Assets name="math" />
-                      数学Quant
-                      <div className="place place-4">
-                        <span className="right-arrow" />
-                      </div>
-                      <i className="left-arrow" />
-                    </div>
+                    {course.map((row, index) => {
+                      if (index >= 4) return null;
+                      return <div
+                        className={`tab ${this.state.courseIndex === index ? 'active' : ''}`}
+                        onMouseEnter={() => this.setState({ courseIndex: index })}
+                      >
+                        <Assets name={courseIconList[index]} />
+                        {row.title}
+                        <div className={`place place-${index + 1}`}>
+                          <span className="right-arrow" />
+                        </div>
+                        <i className="left-arrow" />
+                      </div>;
+                    })}
                   </div>
                 </div>
               </div>
@@ -230,180 +221,29 @@ export default class extends Page {
             </div>
           </div>
         </div>
-        <div className="block block-4" />
+        <Carousel autoplay>
+          {activity.map((row) => {
+            return <div className="block block-4" style={{ backgroundImage: row.image }} onClick={() => {
+              this.location(row.link);
+            }} />;
+          })}
+        </Carousel>
         <div className="block block-5">
           <div className="grid">
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦勤奋的练习,第一次 考试就过关了。
-                  真的是太推荐了。掌握了学习方法以后法以后,再加上刻
-                </div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦勤奋的练习,第一次 考试就过关了。 真的是太推荐了。掌握了学习方法以后过关
-                  了。 真的是太推荐了。掌握了学习方法以后,再加上刻
-                </div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦过关了。掌握了 学习方法以后,再加上刻苦勤奋的练习,第一次考试就过关 了。
-                  真的是太推荐了。掌握了学习方法以后,再加上刻
-                </div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">试就过关 了。 真的是太推荐了。掌握了学习方法以后,再加上刻</div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦勤奋的练习,第一次 考试就过关了。 真的是太推荐了。掌握了学习方法以后,
-                  再加上刻苦勤奋的练习,第一次考试就过关了。掌握了 学习方法以后,再加上刻苦勤奋的练习,第一次考试就过关
-                </div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦勤奋的练习,第一次 考试就过关了。 真的是太推荐了。掌握了学习方法以后,
-                  再加上刻苦勤奋的练习,第一次以后,再加上刻
-                </div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">第一次考试就过关 了。 真的是太推荐了。掌握了学习方法以后,再加上刻</div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">了。 真的是太推荐了。掌握了学习方法以后,再加上刻</div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦第一次考试就过关了。掌握了
-                  学习方法以后,再加上刻苦勤奋的练习,第一次考试就过关 了。 真的是太推荐了。掌握了学习方法以后,再加上刻
-                </div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦勤奋的练习,第一次 考试就过关了。 真的是太推荐了。掌握了学习方法以后,
-                  再加上刻苦勤奋的练习,第一次考试就过关 了。 真的是太推荐了。掌握了学习方法以后,再加上刻
-                </div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦勤奋的练习关 了。 真的是太推荐了。掌握了学习方法以后,再加上刻
-                </div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦勤奋的练习,第 了。 真的是太推荐了。掌握了学习方法以后,再加上刻
-                </div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦勤奋的练习,第一次 考试就过关了。 真的是太推荐了。掌握了学习方法以后,
-                </div>
-              </div>
-            </div>
-            <div className="grid-item">
-              <div className="item">
-                <div className="item-header">
-                  <Assets name="" />
-                  <div className="name">好想去旅行</div>
-                  <div className="date">2018年11月2日</div>
-                </div>
-                <div className="item-body">
-                  掌握了学习方法以后,再加上刻苦勤奋的练习,第一次 考试就过关了。 真的是太推荐了。掌握了学习方法以后,
-                  再加上刻苦勤奋的练习,第一次考试就过关了。掌握了 学习方法以后,再加上刻苦勤奋的练习,第一次考试就过关
-                  了。 真的是太推荐了。掌握了学习方法以后,再加上刻
+            {evaluation.map((row) => {
+              return <div className="grid-item">
+                <div className="item">
+                  <div className="item-header">
+                    <Assets src={row.avatar} />
+                    <div className="name">{row.nickname}</div>
+                    <div className="date">{formatDate(row.date, 'yyyy年mm月dd日')}</div>
+                  </div>
+                  <div className="item-body">
+                    {row.content}
+                  </div>
                 </div>
-              </div>
-            </div>
+              </div>;
+            })}
           </div>
         </div>
         <div className="block block-6">
@@ -448,14 +288,14 @@ export default class extends Page {
               </div>
               <div className="step" style={{ paddingLeft: 80 }}>
                 <div className="title">联系我们</div>
-                <div className="desc">(400) - 800 8888</div>
-                <div className="desc">service@cat.com</div>
-                <div className="desc">catgmat</div>
+                <div className="desc">{contact.phone}</div>
+                <div className="desc">{contact.email}</div>
+                <div className="desc">{contact.wechat}</div>
               </div>
               <div className="step" style={{ paddingLeft: 140 }}>
                 <div className="title">关注我们</div>
                 <div className="qrcode">
-                  <Assets name="qrcode" />
+                  <Assets src={contact.wechatImage} />
                 </div>
               </div>
             </div>

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

@@ -1,8 +1,5 @@
 import home from './home';
-import practise from './practise';
-import report from './report';
-import start from './start';
-import result from './result';
 import login from './login';
+import demo from './demo';
 
-export default [home, practise, report, start, result, login];
+export default [home, login, demo];

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

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

+ 22 - 0
front/project/www/routes/sentence/read/index.less

@@ -91,6 +91,20 @@
         font-size: 20px;
         color: #333333;
         border-bottom: 1px solid #E5E7F0;
+        cursor: pointer;
+
+        &.trail {
+          color: #999;
+        }
+      }
+
+      .chapter-item:hover,
+      .chapter-item.active {
+        color: #4292F0;
+
+        &trail {
+          color: #999;
+        }
       }
 
       .part-item {
@@ -101,11 +115,19 @@
         color: #5E677B;
         border-bottom: 1px solid #E5E7F0;
         cursor: pointer;
+
+        &.trail {
+          color: #999;
+        }
       }
 
       .part-item:hover,
       .part-item.active {
         color: #4292F0;
+
+        &trail {
+          color: #999;
+        }
       }
 
       .page {

+ 110 - 74
front/project/www/routes/sentence/read/page.js

@@ -1,4 +1,5 @@
 import React from 'react';
+import { Tooltip } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Icon from '../../../components/Icon';
@@ -14,14 +15,16 @@ export default class extends Page {
     this.timeout = null;
     this.articleMap = {};
     this.pageLine = 100;
-
-    this.state = { showJump: true, showMenu: true };
   }
 
   init() {
     this.lastTime = new Date();
   }
 
+  initState() {
+    return { showJump: false, showMenu: false, currentPage: 0, totalPage: 0 };
+  }
+
   initData() {
     const { chapter = 0, part = 0 } = this.state.search;
     let { page = 0 } = this.state.search;
@@ -41,27 +44,41 @@ export default class extends 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);
+    return Sentence.listArticle().then(result => {
+      const articleMap = {};
+      let totalPage = 0;
+      let maxChapter = 0;
+      let introduction = null;
+      result.forEach((article) => {
+        if (article.chapter === 0) introduction = 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 });
+      return introduction;
+    }).then((introduction) => {
+      return Sentence.getInfo().then(result => {
+        const map = {};
+        // 添加前言
+        if (introduction) {
+          result.chapters.unshift({
+            title: introduction.title,
+            value: 0,
+          });
+        }
+
+        result.chapters.forEach((row) => {
+          map[row.value] = row;
         });
-        this.setState({ articleMap, totalPage, maxChapter });
-      }),
-    ]).then(() => {
+        this.setState({ sentence: result, chapterMap: map });
+      });
+    }).then(() => {
       this.inited = true;
     });
   }
@@ -76,14 +93,14 @@ export default class extends Page {
 
   prevPage() {
     const { currentPage } = this.state;
-    if (currentPage >= 1) {
+    if (currentPage > 1) {
       this.jumpPage(currentPage - 1);
     }
   }
 
   nextPage() {
     const { currentPage, totalPage } = this.state;
-    if (currentPage + 1 >= totalPage) {
+    if (currentPage < totalPage) {
       this.jumpPage(currentPage + 1);
     }
   }
@@ -91,12 +108,15 @@ export default class extends Page {
   jumpPage(targetPage) {
     // 计算哪篇文章
     const { target, index, allow } = this.computeArticle(targetPage);
+    console.log(targetPage, target, index);
     if (!allow) {
       // todo 无法访问:非试用
       return;
     }
+    this.page = targetPage;
+    this.setState({ inputPage: targetPage });
     const { article = {} } = this.state;
-    this.updateProcess(target, index, article);
+    this.updateProgress(target, index, article);
     this.refreshArticle(target.id).then((row) => {
       this.setState({ article: row, index, currentPage: targetPage });
     });
@@ -106,53 +126,55 @@ export default class extends 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 allow = true || sentence.code || page <= sentence.trailPages;
+    for (let i = 0; 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) {
+        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;
+          // if (!sentence.code) {
+          //   if (!article.isTrail) {
+          //     allow = false;
+          //   } else if (index < article.trailStart - 1) {
+          //     allow = false;
+          //   } else if (index > article.trailEnd - 1) {
+          //     allow = false;
+          //   }
+          // }
+          return { target, index, allow };
         }
       }
     }
-    return { target, index, allow };
+    return { allow };
   }
 
   searchArticle(chapter, part) {
-    const { articleMap, sentence } = this.state;
+    const { articleMap } = this.state;
     let target = null;
     let page = 0;
+    if (!part) part = 1;
     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;
-        }
+        page = article.startPage;
+        // if (sentence.code) {
+        //   page = article.startPage;
+        // } else {
+        //   // 试用章节页码
+        //   page = article.startPage + article.trailPage - 1;
+        // }
         break;
       }
     }
     return { target, page };
   }
 
-  updateProcess(target, index, current) {
+  updateProgress(target, index, current) {
     if (this.timeout) {
       clearTimeout(this.timeout);
       this.timeout = null;
@@ -160,11 +182,11 @@ export default class extends Page {
     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);
+    const progress = index + 1 * 100 / target.pages;
+    Sentence.updateProgress(target.chapter, target.part, progress, time, current.chapter, current.page);
     this.timeout = setTimeout(() => {
       // 最长5分钟阅读时间
-      Sentence.updateProcess(0, 0, 0, 5 * 60, target.chapter, target.part);
+      Sentence.updateProgress(0, 0, 0, 5 * 60, target.chapter, target.part);
     }, 5 * 60 * 1000);
   }
 
@@ -180,11 +202,11 @@ export default class extends Page {
   }
 
   renderBody() {
-    const { showMenu, article, index } = this.state;
-    return (
+    const { showMenu, article, index, chapterMap = {} } = this.state;
+    return article && (
       <div className="layout-body">
-        <div className="crumb">千行长难句解析 >> Chapter{article.chapter}:{}</div>
-        <div className="title">Part{article.part}:{article.title}</div>
+        <div className="crumb">千行长难句解析 >> {(chapterMap[article.chapter] || {}).title}</div>
+        {article.chapter > 0 && <div className="title">{article.title}</div>}
         <div className="overload" style={{ top: index * -20 * this.pageLine }}>
           <div className="text" dangerouslySetInnerHTML={{ __html: article.content }} />
         </div>
@@ -199,6 +221,7 @@ export default class extends Page {
     let page = 1;
     const code = !!sentence.code;
     // todo 鼠标移上显示需要购买后访问
+    const message = '购买后访问';
     return (
       <div className="layout-menu">
         <div className="title">目录</div>
@@ -207,22 +230,33 @@ export default class extends Page {
         }}>x</div>
         <div className="chapter">
           {chapters.map(chapter => {
-            if (chapter.exercise) return [];
+            if (chapter.exercise) {
+              return [<div className={'chapter-item trail'}>
+                {chapter.title}
+              </div>];
+            }
             chapter.startPage = page;
-            const list = [<div className={`chapter-item ${code ? '' : 'trail'}`} onClick={() => {
-              if (code) this.jumpPage(chapter.startPage);
+            const list = code ? [<div className={'chapter-item'} onClick={() => {
+              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>;
-            });
+              {chapter.title}<div className="page">{chapter.startPage}</div>
+            </div>] : [<Tooltip title={message}><div className={'chapter-item trail'}>
+              {chapter.title}<div className="page">{chapter.startPage}</div>
+            </div></Tooltip>];
+            if (chapter.value) {
+              (articleMap[chapter.value] || []).forEach((article) => {
+                // 得到下一章节page
+                page += article.pages;
+                const item = code ? <div className={'part-item'} onClick={() => {
+                  if (code) this.jumpPage(article.startPage);
+                }}>
+                  {article.title}<div className="page">{article.startPage}</div>
+                </div> : <Tooltip title={message}><div className={'part-item trail'}>
+                  {article.title}<div className="page">{article.startPage}</div>
+                </div></Tooltip>;
+                list.push(item);
+              });
+            }
             return list;
           })}
         </div>
@@ -256,19 +290,21 @@ export default class extends Page {
     const { showJump, currentPage, totalPage } = this.state;
     return (
       <div className="layout-bottom">
-        <span className="per">{parseInt(currentPage * 100 / totalPage, 10)}%</span>
+        <span className="per">{totalPage ? parseInt(currentPage * 100 / totalPage, 10) : 0}%</span>
         <span className="num">{currentPage}/{totalPage}</span>
         <span className="btn">
-          <Assets name="unfold_icon_up" onClick={() => {
+          <Assets name={showJump ? 'unfold_icon_down' : '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;
+            <input className="input" value={this.state.inputPage} onChange={(e) => {
+              this.page = Number(e.target.value);
+              this.setState({ inputPage: e.target.value });
             }} />
             <Assets name="yes_icon" onClick={() => {
-              this.jumpPage(Number(this.page));
+              if (this.page < 1 || this.page > totalPage) return;
+              this.jumpPage(this.page);
             }} />
           </div>
         </span>
@@ -280,7 +316,7 @@ export default class extends Page {
     const { currentPage, totalPage } = this.state;
     return (
       <div className="layout-progress">
-        <Progress size="small" theme="theme" radius={false} process={parseInt(currentPage * 100 / totalPage, 10)} />
+        <Progress size="small" theme="theme" radius={false} progress={totalPage ? parseInt(currentPage * 100 / totalPage, 10) : 0} />
       </div>
     );
   }

+ 2 - 2
front/project/www/stores/course.js

@@ -4,8 +4,8 @@ export default class CourseStore extends BaseStore {
   /**
    * 获取课程进度
    */
-  classProcess() {
-    return this.apiGet('/course/process');
+  classProgress() {
+    return this.apiGet('/course/progress');
   }
 }
 

+ 18 - 0
front/project/www/stores/my.js

@@ -51,6 +51,24 @@ export default class MyStore extends BaseStore {
   }
 
   /**
+   * 清除最后一次练习记录
+   */
+  clearLatestExercise() {
+    return this.apiPut('/my/clear/exercise/latest').then(() => {
+      this.setState({ info: { latestExercise: 0 } });
+    });
+  }
+
+  /**
+   * 清除最后一次错误组卷记录
+   */
+  clearLatestError() {
+    return this.apiPut('/my/clear/error/latest').then(() => {
+      this.setState({ info: { latestError: 0 } });
+    });
+  }
+
+  /**
    * 修改备考信息
    * @param {*} info prepareStatus: 身份  prepareGoal: 目标分数 prepareExaminationTime: 考试时间 prepareScoreTime: 出分时间
    */

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

@@ -5,8 +5,8 @@ export default class QuestionStore extends BaseStore {
    * 练习进度
    * @param {*} structId
    */
-  getExerciseProcess(structId) {
-    return this.apiGet('/question/exercise/process', { structId });
+  getExerciseProgress(structId) {
+    return this.apiGet('/question/exercise/progress', { structId });
   }
 
   /**
@@ -35,8 +35,8 @@ export default class QuestionStore extends BaseStore {
    * @param {*} page
    * @param {*} size
    */
-  getExaminationProcess(page, size) {
-    return this.apiGet('/question/examination/process', { page, size });
+  getExaminationProgress(page, size) {
+    return this.apiGet('/question/examination/progress', { page, size });
   }
 
   /**
@@ -107,6 +107,22 @@ export default class QuestionStore extends BaseStore {
   }
 
   /**
+   * 获取做题记录
+   * @param {*} userReportId
+   */
+  baseReport(userReportId) {
+    return this.apiGet('/question/report/base', { userReportId });
+  }
+
+  /**
+   * 获取做题详细记录
+   * @param {*} userReportId
+   */
+  detailReport(userReportId) {
+    return this.apiGet('/question/report/detail', { userReportId });
+  }
+
+  /**
    * 开始考试
    * @param {*} type
    * @param {*} paperId

+ 10 - 3
front/project/www/stores/sentence.js

@@ -36,13 +36,20 @@ export default class SentenceStore extends BaseStore {
    * 更新长难句文章进度
    * @param {*} chapter
    * @param {*} part
-   * @param {*} process
+   * @param {*} progress
    * @param {*} time
    * @param {*} currentChapter
    * @param {*} currentPart
    */
-  updateProcess(chapter, part, process, time, currentChapter, currentPart) {
-    return this.apiPut('/sentence/article/process', { chapter, part, process, time, currentChapter, currentPart });
+  updateProgress(chapter, part, progress, time, currentChapter, currentPart) {
+    return this.apiPut('/sentence/article/progress', { chapter, part, progress, time, currentChapter, currentPart });
+  }
+
+  /**
+   * 获取练习卷
+   */
+  listPaper() {
+    return this.apiGet('/sentence/paper/list');
   }
 }
 

+ 20 - 1
front/project/www/stores/user.js

@@ -1,13 +1,32 @@
 import BaseStore from '@src/stores/base';
 import * as querystring from 'querystring';
 
+const SENTENCE_TRAIL = 'SENTENCE_TRAIL';
+
 export default class UserStore extends BaseStore {
   initState() {
     const { token } = querystring.parse(window.location.search.replace('?', ''));
     if (token) {
       this.setToken(token);
     }
-    return { login: !!token };
+    const sentenceTrail = this.getLocal(SENTENCE_TRAIL);
+    return { login: !!token, sentenceTrail };
+  }
+
+  /**
+   * 设置长难句试用
+   */
+  sentenceTrail() {
+    this.saveLocal(SENTENCE_TRAIL, true);
+    this.setState({ sentenceTrail: true });
+  }
+
+  /**
+   * 清除长难句试用
+   */
+  clearSentenceTrail() {
+    this.removeLocal(SENTENCE_TRAIL);
+    this.setState({ sentenceTrail: null });
   }
 
   /**

+ 2 - 2
front/src/components/DragList/index.js

@@ -2,7 +2,7 @@ import React from 'react';
 import { Spin } from 'antd';
 import Sortable from 'sortablejs';
 import './index.less';
-import { uuid } from '../../services/Tools';
+import { generateUUID } from '../../services/Tools';
 
 export default class DragSortingTable extends React.Component {
   // sortableContainersDecorator = (componentBackingInstance) => {
@@ -21,7 +21,7 @@ export default class DragSortingTable extends React.Component {
       const options = Object.assign({
         // draggable: 'div.drag', // Specifies which items inside the element should be sortable
         handle: this.props.handle || '.drag', // this.props.handle, // Restricts sort start click/touch to the specified element
-        group: this.props.group || uuid(),
+        group: this.props.group || generateUUID(),
         ghostClass: 'ghost',
         onEnd: (event) => {
           this.props.onMove(event.oldIndex, event.newIndex);

+ 22 - 14
front/src/services/Tools.js

@@ -248,9 +248,20 @@ export function formatDate(time, format = 'YYYY-MM-DD HH:mm:ss') {
   return format;
 }
 
+export function formatSeconds(seconds) {
+  const time = parseInt(seconds, 10);
+  if (time < 60) {
+    return `${time}s`;
+  }
+  if (time >= 60 && time < 3600) {
+    return `${parseInt(time / 60, 10)}m${formatSeconds(time % 60)}`;
+  }
+  return `${parseInt(time / 3600, 10)}h${formatSecond(time % 3600)}`;
+}
+
 export function formatPercent(child, mother) {
   if (!mother || !child) return '0%';
-  return `${Math.floor((child * 100) / mother)}%`;
+  return `${Math.floor((child * 100) / mother)}% `;
 }
 
 export function formatTreeData(list, key = 'id', title = 'title', index = 'parent_id') {
@@ -259,7 +270,7 @@ export function formatTreeData(list, key = 'id', title = 'title', index = 'paren
   list.forEach(row => {
     row.children = [];
     row.title = row[title];
-    row.key = `${row[key]}`;
+    if (!row.key) row.key = `${row[key]} `;
     row.value = row[key];
   });
   list.forEach(row => {
@@ -280,10 +291,10 @@ export function flattenObject(ob, prefix = '') {
     if (typeof ob[i] === 'object' && ob[i] !== null) {
       const flatObject = flattenObject(ob[i]);
       Object.keys(flatObject).forEach(x => {
-        toReturn[`${prefix}${i}.${x}`] = flatObject[x];
+        toReturn[`${prefix} ${i}.${x} `] = flatObject[x];
       });
     } else {
-      toReturn[`${prefix}${i}`] = ob[i];
+      toReturn[`${prefix} ${i} `] = ob[i];
     }
   });
   return toReturn;
@@ -292,7 +303,7 @@ export function flattenObject(ob, prefix = '') {
 function _formatMoney(s, n) {
   if (!s) s = 0;
   n = n > 0 && n <= 20 ? n : 2;
-  s = `${parseFloat(`${s}`.replace(/[^\d.-]/g, '')).toFixed(n)}`;
+  s = `${parseFloat(`${s}`.replace(/[^\d.-]/g, '')).toFixed(n)} `;
   const l = s
     .split('.')[0]
     .split('')
@@ -302,17 +313,14 @@ function _formatMoney(s, n) {
   for (let i = 0; i < l.length; i += 1) {
     t += l[i] + ((i + 1) % 3 === 0 && i + 1 !== l.length ? ',' : '');
   }
-  return `${t
-    .split('')
-    .reverse()
-    .join('')}.${r}`;
+  return `${t.split('').reverse().join('')}.${r} `;
 }
 
 export function formatMoney(price) {
   if (typeof price === 'object') {
-    return `${price.symbol}${_formatMoney(price.value, 2)}`;
+    return `${price.symbol} ${_formatMoney(price.value, 2)} `;
   }
-  return `¥${_formatMoney(price, 2)}`;
+  return `¥${_formatMoney(price, 2)} `;
 }
 
 export function bindTags(targetList, field, render, def, notFound) {
@@ -331,7 +339,7 @@ export function bindSearch(targetList, field, Component, listFunc, render, def,
   targetList.forEach((row, i) => {
     if (row.key === field) index = i;
   });
-  const key = `lastFetchId${field}${index}`;
+  const key = `lastFetchId${field} ${index} `;
   if (!Component[key]) Component[key] = 0;
   const searchFunc = data => {
     Component[key] += 1;
@@ -372,7 +380,7 @@ export function bindSearch(targetList, field, Component, listFunc, render, def,
 }
 
 export function generateSearch(field, props, Component, listFunc, render, def, notFound = null) {
-  const key = `lastFetchId${field}`;
+  const key = `lastFetchId${field} `;
   if (!Component[key]) Component[key] = 0;
   let item = {
     showSearch: true,
@@ -418,7 +426,7 @@ export function getHtmlText(text) {
   let html = '';
   text.split('\r').forEach(item => {
     item.split(' ').forEach(t => {
-      html += `<i uuid="${generateUUID(4)}">${t}</i>`;
+      html += `< i uuid = "${generateUUID(4)}" > ${t}</i > `;
     });
     html += '<br/>';
   });

+ 0 - 7
server/data/src/main/java/com/qxgmat/data/dao/UserSentenceProcessMapper.java

@@ -1,7 +0,0 @@
-package com.qxgmat.data.dao;
-
-import com.nuliji.tools.mybatis.Mapper;
-import com.qxgmat.data.dao.entity.UserSentenceProcess;
-
-public interface UserSentenceProcessMapper extends Mapper<UserSentenceProcess> {
-}

+ 7 - 0
server/data/src/main/java/com/qxgmat/data/dao/UserSentenceProgressMapper.java

@@ -0,0 +1,7 @@
+package com.qxgmat.data.dao;
+
+import com.nuliji.tools.mybatis.Mapper;
+import com.qxgmat.data.dao.entity.UserSentenceProgress;
+
+public interface UserSentenceProgressMapper extends Mapper<UserSentenceProgress> {
+}

+ 17 - 17
server/data/src/main/java/com/qxgmat/data/dao/entity/UserSentenceProcess.java

@@ -4,7 +4,7 @@ import java.io.Serializable;
 import javax.persistence.*;
 
 @Table(name = "user_sentence_process")
-public class UserSentenceProcess implements Serializable {
+public class UserSentenceProgress implements Serializable {
     @Id
     @Column(name = "`id`")
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -31,8 +31,8 @@ public class UserSentenceProcess implements Serializable {
     /**
      * 进度:0-100
      */
-    @Column(name = "`process`")
-    private Integer process;
+    @Column(name = "`progress`")
+    private Integer progress;
 
     private static final long serialVersionUID = 1L;
 
@@ -109,17 +109,17 @@ public class UserSentenceProcess implements Serializable {
      *
      * @return process - 进度:0-100
      */
-    public Integer getProcess() {
-        return process;
+    public Integer getProgress() {
+        return progress;
     }
 
     /**
      * 设置进度:0-100
      *
-     * @param process 进度:0-100
+     * @param progress 进度:0-100
      */
-    public void setProcess(Integer process) {
-        this.process = process;
+    public void setProgress(Integer progress) {
+        this.progress = progress;
     }
 
     @Override
@@ -132,20 +132,20 @@ public class UserSentenceProcess implements Serializable {
         sb.append(", userId=").append(userId);
         sb.append(", chapter=").append(chapter);
         sb.append(", part=").append(part);
-        sb.append(", process=").append(process);
+        sb.append(", process=").append(progress);
         sb.append("]");
         return sb.toString();
     }
 
-    public static UserSentenceProcess.Builder builder() {
-        return new UserSentenceProcess.Builder();
+    public static UserSentenceProgress.Builder builder() {
+        return new UserSentenceProgress.Builder();
     }
 
     public static class Builder {
-        private UserSentenceProcess obj;
+        private UserSentenceProgress obj;
 
         public Builder() {
-            this.obj = new UserSentenceProcess();
+            this.obj = new UserSentenceProgress();
         }
 
         /**
@@ -189,14 +189,14 @@ public class UserSentenceProcess implements Serializable {
         /**
          * 设置进度:0-100
          *
-         * @param process 进度:0-100
+         * @param progress 进度:0-100
          */
-        public Builder process(Integer process) {
-            obj.setProcess(process);
+        public Builder progress(Integer progress) {
+            obj.setProgress(progress);
             return this;
         }
 
-        public UserSentenceProcess build() {
+        public UserSentenceProgress build() {
             return this.obj;
         }
     }

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

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.qxgmat.data.dao.UserSentenceProcessMapper">
-  <resultMap id="BaseResultMap" type="com.qxgmat.data.dao.entity.UserSentenceProcess">
+<mapper namespace="com.qxgmat.data.dao.UserSentenceProgressMapper">
+  <resultMap id="BaseResultMap" type="com.qxgmat.data.dao.entity.UserSentenceProgress">
     <!--
       WARNING - @mbg.generated
     -->
@@ -9,12 +9,12 @@
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="chapter" jdbcType="INTEGER" property="chapter" />
     <result column="part" jdbcType="INTEGER" property="part" />
-    <result column="process" jdbcType="INTEGER" property="process" />
+    <result column="progress" jdbcType="INTEGER" property="progress" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `chapter`, `part`, `process`
+    `id`, `user_id`, `chapter`, `part`, `progress`
   </sql>
 </mapper>

+ 0 - 10
server/data/src/main/java/com/qxgmat/data/relation/entity/UserExaminationPaperRelation.java

@@ -10,16 +10,6 @@ public class UserExaminationPaperRelation extends UserPaper {
 
     private UserReport report;
 
-    private User user;
-
-    public User getUser() {
-        return user;
-    }
-
-    public void setUser(User user) {
-        this.user = user;
-    }
-
     public UserReport getReport() {
         return report;
     }

+ 0 - 10
server/data/src/main/java/com/qxgmat/data/relation/entity/UserExercisePaperRelation.java

@@ -10,16 +10,6 @@ public class UserExercisePaperRelation extends UserPaper {
 
     private UserReport report;
 
-    private User user;
-
-    public User getUser() {
-        return user;
-    }
-
-    public void setUser(User user) {
-        this.user = user;
-    }
-
     public UserReport getReport() {
         return report;
     }

+ 30 - 0
server/data/src/main/java/com/qxgmat/data/relation/entity/UserSentencePaperRelation.java

@@ -0,0 +1,30 @@
+package com.qxgmat.data.relation.entity;
+
+import com.qxgmat.data.dao.entity.SentencePaper;
+import com.qxgmat.data.dao.entity.UserPaper;
+import com.qxgmat.data.dao.entity.UserReport;
+
+/**
+ * Created by gaojie on 2017/11/9.
+ */
+public class UserSentencePaperRelation extends UserPaper {
+    private SentencePaper paper;
+
+    private UserReport report;
+
+    public UserReport getReport() {
+        return report;
+    }
+
+    public void setReport(UserReport report) {
+        this.report = report;
+    }
+
+    public SentencePaper getPaper() {
+        return paper;
+    }
+
+    public void setPaper(SentencePaper paper) {
+        this.paper = paper;
+    }
+}

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

@@ -13,7 +13,7 @@ CREATE TABLE manager (
 INSERT INTO `exercise_struct` (`id`, `title_zh`, `title_en`, `parent_id`, `order`, `level`, `question_status`, `is_sentence`, `is_examination`, `description`, `extend`)
 VALUES
 	(1, '长难句', '', 0, 0, 1, 0, 1, 0, '', 'sentence'),
-	(2, '语文', 'Verbial', 0, 0, 1, 0, 0, 1, '', 'verbial'),
+	(2, '语文', 'Verbial', 0, 0, 1, 0, 0, 1, '', 'verbal'),
 	(3, '数学', 'Quant', 0, 0, 1, 0, 0, 1, '', 'quant'),
 	(4, '综合推理', 'IR', 0, 0, 1, 0, 0, 1, '', 'ir'),
 	(5, '作文', 'AWA', 0, 0, 1, 0, 0, 1, '', 'awa'),

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

@@ -159,7 +159,7 @@ public class ExerciseController {
         // 判断当前组卷状态
         Setting setting = settingService.getByKey(SettingKey.EXERCISE_PAPER_STATUS);
         JSONObject status = setting.getValue();
-        if (status.getInteger("process")<100){
+        if (status.getInteger("progress")<100){
             throw new ParameterException("组卷进行中,请稍后再试");
         }
         asyncTask.autoExercisePaper();

+ 2 - 2
server/gateway-api/src/main/java/com/qxgmat/controller/admin/SentenceController.java

@@ -82,7 +82,7 @@ public class SentenceController {
 
     @RequestMapping(value = "/article/detail", method = RequestMethod.GET)
     @ApiOperation(value = "获取长难句文章", httpMethod = "GET")
-    public Response<SentenceArticle> detailArticle(@RequestParam int id, HttpSession session) {
+    public Response<SentenceArticle> detailArticle(@RequestParam int id,HttpSession session) {
         SentenceArticle entity = sentenceArticleService.get(id);
         return ResponseHelp.success(Transform.convert(entity, SentenceArticle.class));
     }
@@ -167,7 +167,7 @@ public class SentenceController {
         // 判断当前组卷状态
         Setting setting = settingService.getByKey(SettingKey.SENTENCE_PAPER_STATUS);
         JSONObject status = setting.getValue();
-        if (status.getInteger("process")<100){
+        if (status.getInteger("progress")<100){
             throw new ParameterException("组卷进行中,请稍后再试");
         }
         asyncTask.autoSentencePaper();

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

@@ -90,7 +90,7 @@ public class BaseController {
             @RequestParam(required = true) Integer id,
             @RequestParam(required = false, defaultValue = "false") boolean children,
             HttpSession session) {
-        List<ExerciseStruct> p = exerciseStructService.children(id, children);
+        List<ExerciseStruct> p = exerciseStructService.children(id, children? 1 : 0);
         return ResponseHelp.success(p);
     }
 
@@ -116,7 +116,7 @@ public class BaseController {
             @RequestParam(required = true) Integer id,
             @RequestParam(required = false, defaultValue = "false") boolean children,
             HttpSession session) {
-        List<ExaminationStruct> p = examinationStructService.children(id, children);
+        List<ExaminationStruct> p = examinationStructService.children(id, children ? 1 :0);
         return ResponseHelp.success(p);
     }
 

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

@@ -47,9 +47,9 @@ public class CourseController {
     private UserPayService userPayService;
 
 
-    @RequestMapping(value = "/process", method = RequestMethod.GET)
+    @RequestMapping(value = "/progress", method = RequestMethod.GET)
     @ApiOperation(value = "获取课程进度", notes = "获取所有课程及状态进度", httpMethod = "GET")
-    public Response<List<UserClassDetailDto>> classProcess()  {
+    public Response<List<UserClassDetailDto>> classProgress()  {
         User user = (User) shiroHelp.getLoginUser();
         List<UserClass> userClassList = userClassService.getByUser(user.getId());
         List<UserClassDetailDto> dtos = Transform.convert(userClassList, UserClassDetailDto.class);

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

@@ -213,6 +213,24 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/clear/exercise/latest", method = RequestMethod.PUT)
+    @ApiOperation(value = "清除最后一次做题记录", notes = "清除最后一次做题记录", httpMethod = "PUT")
+    public Response<Boolean> clearLatestExercise()  {
+        User user = (User) shiroHelp.getLoginUser();
+        usersService.edit(User.builder().id(user.getId()).latestExercise(0).build());
+
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/clear/error/latest", method = RequestMethod.PUT)
+    @ApiOperation(value = "清除最后一次错题组卷做题记录", notes = "清除最后一次错题组卷做题记录", httpMethod = "PUT")
+    public Response<Boolean> clearLatestError()  {
+        User user = (User) shiroHelp.getLoginUser();
+        usersService.edit(User.builder().id(user.getId()).latestError(0).build());
+
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/prepare", method = RequestMethod.PUT)
     @ApiOperation(value = "修改备考信息", notes = "修改用户备考信息", httpMethod = "PUT")
     public Response<Boolean> editPrepare(@RequestBody @Validated UserPrepareDto dto)  {
@@ -289,11 +307,11 @@ public class MyController {
         Collection paperIds = Transform.getIds(userReportList, UserReport.class, "paperId");
         List<UserPaper> userPaperList = userPaperService.select(paperIds);
         Map userPaper = Transform.getMap(userPaperList, UserPaper.class, "id");
-        List<UserPaperBaseExtendDto> examinationPaperList = new ArrayList<>(userReportList.size());
+        List<UserPaperDetailExtendDto> examinationPaperList = new ArrayList<>(userReportList.size());
         for(UserReport report: userReportList){
             examinationTime += report.getUserTime();
             examinationPaper += 1;
-            UserPaperBaseExtendDto d = Transform.convert(userPaper.get(report.getPaperId()), UserPaperBaseExtendDto.class);
+            UserPaperDetailExtendDto d = Transform.convert(userPaper.get(report.getPaperId()), UserPaperDetailExtendDto.class);
             d.setReport(Transform.convert(report, UserReportExtendDto.class));
             examinationPaperList.add(d);
         }

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

@@ -92,9 +92,9 @@ public class QuestionController {
     private QuestionFlowService questionFlowService;
 
 
-    @RequestMapping(value = "/exercise/process", method = RequestMethod.GET)
+    @RequestMapping(value = "/exercise/progress", method = RequestMethod.GET)
     @ApiOperation(value = "练习进度", httpMethod = "GET")
-    public Response<List<UserExerciseGroupDto>> exerciseProcess(
+    public Response<List<UserExerciseGroupDto>> exerciseProgress(
             @RequestParam(required = true) Integer structId, // 第二层,查询第4层,以及第三层汇总
             HttpSession session) {
         Page<UserExerciseGroupDto> p=null;
@@ -141,9 +141,9 @@ public class QuestionController {
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
-    @RequestMapping(value = "/examination/process", method = RequestMethod.GET)
+    @RequestMapping(value = "/examination/progress", method = RequestMethod.GET)
     @ApiOperation(value = "模考进度", httpMethod = "GET")
-    public Response<PageMessage<ExercisePaper>> examinationProcess(
+    public Response<PageMessage<ExercisePaper>> examinationProgress(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             HttpSession session) {

+ 78 - 51
server/gateway-api/src/main/java/com/qxgmat/controller/api/SentenceController.java

@@ -9,9 +9,14 @@ import com.nuliji.tools.exception.AuthException;
 import com.qxgmat.data.constants.enums.SettingKey;
 import com.qxgmat.data.constants.enums.logic.SentenceLogic;
 import com.qxgmat.data.dao.entity.*;
+import com.qxgmat.data.relation.entity.UserSentencePaperRelation;
+import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
+import com.qxgmat.dto.extend.UserReportExtendDto;
 import com.qxgmat.dto.request.*;
 import com.qxgmat.dto.response.*;
 import com.qxgmat.help.ShiroHelp;
+import com.qxgmat.service.UserPaperService;
+import com.qxgmat.service.extend.SentenceService;
 import com.qxgmat.service.inline.SentencePaperService;
 import com.qxgmat.service.UserCollectQuestionService;
 import com.qxgmat.service.UsersService;
@@ -20,10 +25,12 @@ 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.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpSession;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -49,6 +56,9 @@ public class SentenceController
     private SentenceCodeService sentenceCodeService;
 
     @Autowired
+    private SentenceService sentenceService;
+
+    @Autowired
     private QuestionService questionService;
 
     @Autowired
@@ -58,7 +68,13 @@ public class SentenceController
     private UsersService usersService;
 
     @Autowired
-    private UserSentenceProcessService userSentenceProcessService;
+    private UserPaperService userPaperService;
+
+    @Autowired
+    private UserReportService userReportService;
+
+    @Autowired
+    private UserSentenceProgressService userSentenceProgressService;
 
     @Autowired
     private UserSentenceRecordService userSentenceRecordService;
@@ -74,16 +90,18 @@ public class SentenceController
     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){
-            dto.setCode(code.getCode());
+        if (user != null){
+            // 用户code状态
+            SentenceCode code = sentenceCodeService.isActive(user.getId());
+            if (code != null){
+                dto.setCode(code.getCode());
+            }
+            // 查询用户进度
+            List<UserSentenceProgress> processList = userSentenceProgressService.listTotal(user.getId());
+            Map process = Transform.getMap(processList, UserSentenceProgress.class, "chapter", "process");
+            dto.setProgress(process);
         }
-        // 查询用户进度
-        List<UserSentenceProcess> processList = userSentenceProcessService.listTotal(user.getId());
-        Map process = Transform.getMap(processList,UserSentenceProcess.class, "chapter", "process");
-        dto.setProcess(process);
 
         // 章节信息
         Setting entity = settingService.getByKey(SettingKey.SENTENCE);
@@ -97,7 +115,6 @@ public class SentenceController
     @RequestMapping(value = "/active", method = RequestMethod.PUT)
     @ApiOperation(value = "激活长难句", httpMethod = "PUT")
     public Response<Boolean> active(@RequestBody @Validated UserSentenceCodeDto dto) {
-        UserSentenceProcess entity = Transform.dtoToEntity(dto);
         User user = (User) shiroHelp.getLoginUser();
         if (user == null) throw new AuthException("需要登录");
 
@@ -114,60 +131,49 @@ public class SentenceController
             HttpSession session) {
         User user = (User) shiroHelp.getLoginUser();
 
-        // 查询用户code
-        if (user != null && sentenceCodeService.isActive(user.getId()) != null){
-            List<SentenceArticle> p = sentenceArticleService.all();
-            List<UserSentenceArticleDto> pr = Transform.convert(p, UserSentenceArticleDto.class);
+        List<SentenceArticle> p = sentenceArticleService.all();
+        List<UserSentenceArticleDto> pr = Transform.convert(p, UserSentenceArticleDto.class);
 
+        if (user != null){
             // 查询文章进度
-            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());
+            List<UserSentenceProgress> progressList = userSentenceProgressService.all(user.getId());
+
+            Map<String, Integer> progressMap = new HashMap<>();
+            for (UserSentenceProgress progress:progressList){
+                progressMap.put(String.format("%d-%d", progress.getChapter(), progress.getPart()), progress.getProgress());
             }
             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();
-            List<UserSentenceArticleDto> pr = Transform.convert(p, UserSentenceArticleDto.class);
-
-            if (user != null){
-                // 查询文章进度
-                List<UserSentenceProcess> processList = userSentenceProcessService.listAllByTrail(user.getId());
-                Map process = Transform.getMap(processList,UserSentenceProcess.class, "part", "process");
-                Transform.combine(pr, process, UserSentenceArticleDto.class, "part", "process");
+                Integer process = progressMap.get(String.format("%d-%d", dto.getChapter(), dto.getPart()));
+                dto.setProgress(process == null ? 0 : process);
             }
-
-            return ResponseHelp.success(pr);
         }
+        return ResponseHelp.success(pr);
     }
 
-    @RequestMapping(value = "/article/detail", method = RequestMethod.PUT)
+    @RequestMapping(value = "/article/detail", method = RequestMethod.GET)
     @ApiOperation(value = "长难句文章详情", httpMethod = "PUT")
     public Response<UserSentenceArticleDetailDto> detailArticle(
-            @RequestParam(required = true, name="id") Integer articleId
+            @RequestParam(required = true) Integer articleId
     ) {
         User user = (User) shiroHelp.getLoginUser();
-        if (user == null) throw new AuthException("需要登录");
 
         SentenceArticle article = sentenceArticleService.get(articleId);
-        // 查询文章进度
-        UserSentenceProcess process = userSentenceProcessService.get(user.getId(), article.getChapter(), article.getPart());
-
         UserSentenceArticleDetailDto dto = Transform.convert(article, UserSentenceArticleDetailDto.class);
 
-        dto.setProcess(process.getProcess());
+        if (user != null){
+            // 查询文章进度
+            UserSentenceProgress progress = userSentenceProgressService.get(user.getId(), article.getChapter(), article.getPart());
+
+            dto.setProgress(progress.getProgress());
+        }
 
         return ResponseHelp.success(dto);
     }
 
-    @RequestMapping(value = "/article/process", method = RequestMethod.PUT)
+    @RequestMapping(value = "/article/progress", method = RequestMethod.PUT)
     @ApiOperation(value = "更新长难句文章进度", httpMethod = "PUT")
-    public Response<Boolean> articleProcess(@RequestBody @Validated UserSentenceProcessDto dto) {
-        UserSentenceProcess entity = Transform.dtoToEntity(dto);
+    public Response<Boolean> articleProcess(@RequestBody @Validated UserSentenceProgressDto dto) {
+        UserSentenceProgress entity = Transform.dtoToEntity(dto);
         User user = (User) shiroHelp.getLoginUser();
         if (user == null) throw new AuthException("需要登录");
 
@@ -185,7 +191,7 @@ public class SentenceController
         Integer max = sentenceArticleService.maxPart(dto.getChapter());
 
         // 更新阅读进度
-        userSentenceProcessService.updateProcess(user.getId(), dto.getChapter(), dto.getPart(), dto.getProcess(), max);
+        userSentenceProgressService.updateProgress(user.getId(), dto.getChapter(), dto.getPart(), dto.getProgress(), max);
         return ResponseHelp.success(true);
     }
 
@@ -194,18 +200,39 @@ public class SentenceController
     public Response<List<UserSentencePaperDto>> listPaper(
             HttpSession session) {
         User user = (User) shiroHelp.getLoginUser();
+        List<SentencePaper> p;
         // 查询用户code
         if (user != null && sentenceCodeService.isActive(user.getId()) != null){
-            List<SentencePaper> p = sentencePaperService.listByLogic(SentenceLogic.NO);
-            List<UserSentencePaperDto> pr = Transform.convert(p, UserSentencePaperDto.class);
-
-            return ResponseHelp.success(pr);
+            p = sentencePaperService.listByLogic(SentenceLogic.NO);
         } else {
-            List<SentencePaper> p = sentencePaperService.listByLogic(SentenceLogic.TRAIL);
-            List<UserSentencePaperDto> pr = Transform.convert(p, UserSentencePaperDto.class);
+            p = sentencePaperService.listByLogic(SentenceLogic.TRAIL);
+        }
 
-            return ResponseHelp.success(pr);
+        // 获取试卷统计信息
+        Map<Integer, Integer[]> questionNoIdsMap = new HashMap<>();
+        for(SentencePaper paper : p){
+            questionNoIdsMap.put(paper.getId(), paper.getQuestionNoIds());
         }
+        Map statMap = sentenceQuestionService.statPaperMap(questionNoIdsMap);
+        List<UserSentencePaperDto> pr = Transform.convert(p, UserSentencePaperDto.class);
+        Transform.combine(pr, statMap, UserSentencePaperDto.class, "id", "stat");
+
+        if (user != null){
+            // 获取做题记录
+            Collection ids = Transform.getIds(p, SentencePaper.class, "id");
+            List<UserPaper> paperList = userPaperService.listWithSentence(user.getId(), ids);
+            Transform.combine(pr, paperList, UserSentencePaperDto.class, "id", "paper", UserPaper.class, "originId", UserPaperBaseExtendDto.class);
+            // 绑定userPaperId,用于关联report
+            Map userPaperMap = Transform.getMap(paperList, UserPaper.class, "originId", "id");
+            Transform.combine(pr, userPaperMap, UserSentencePaperDto.class, "id", "paperId");
+
+            // 获取最后一次作业结果
+            Collection paperIds = Transform.getIds(paperList, UserPaper.class, "id");
+            List<UserReport> reportList = userReportService.listWithLater(paperIds);
+            Transform.combine(pr, reportList, UserSentencePaperRelation.class, "id", "report", UserReport.class, "paperId", UserReportExtendDto.class);
+        }
+
+        return ResponseHelp.success(pr);
     }
 
     @RequestMapping(value = "/paper", method = RequestMethod.GET)

+ 5 - 15
server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserPaperBaseExtendDto.java

@@ -11,9 +11,7 @@ public class UserPaperBaseExtendDto {
 
     private Integer times;
 
-    private String title;
-
-    private UserReportExtendDto report;
+    private Integer questionNumber;
 
     public Integer getId() {
         return id;
@@ -23,14 +21,6 @@ public class UserPaperBaseExtendDto {
         this.id = id;
     }
 
-    public String getTitle() {
-        return title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
     public Integer getTimes() {
         return times;
     }
@@ -39,11 +29,11 @@ public class UserPaperBaseExtendDto {
         this.times = times;
     }
 
-    public UserReportExtendDto getReport() {
-        return report;
+    public Integer getQuestionNumber() {
+        return questionNumber;
     }
 
-    public void setReport(UserReportExtendDto report) {
-        this.report = report;
+    public void setQuestionNumber(Integer questionNumber) {
+        this.questionNumber = questionNumber;
     }
 }

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

@@ -12,6 +12,8 @@ public class UserPaperDetailExtendDto {
 
     private String title;
 
+    private UserReportExtendDto report;
+
     public Integer getId() {
         return id;
     }
@@ -35,4 +37,12 @@ public class UserPaperDetailExtendDto {
     public void setTimes(Integer times) {
         this.times = times;
     }
+
+    public UserReportExtendDto getReport() {
+        return report;
+    }
+
+    public void setReport(UserReportExtendDto report) {
+        this.report = report;
+    }
 }

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

@@ -24,6 +24,8 @@ public class UserReportExtendDto {
 
     private Date finishTime;
 
+    private Integer isFinish;
+
     private Date updateTime;
 
     private JSONObject score;
@@ -99,4 +101,12 @@ public class UserReportExtendDto {
     public void setScore(JSONObject score) {
         this.score = score;
     }
+
+    public Integer getIsFinish() {
+        return isFinish;
+    }
+
+    public void setIsFinish(Integer isFinish) {
+        this.isFinish = isFinish;
+    }
 }

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

@@ -1,16 +1,16 @@
 package com.qxgmat.dto.request;
 
 import com.nuliji.tools.annotation.Dto;
-import com.qxgmat.data.dao.entity.UserSentenceProcess;
+import com.qxgmat.data.dao.entity.UserSentenceProgress;
 
-@Dto(entity = UserSentenceProcess.class)
-public class UserSentenceProcessDto {
+@Dto(entity = UserSentenceProgress.class)
+public class UserSentenceProgressDto {
 
     private Integer chapter;
 
     private Integer part;
 
-    private Integer process;
+    private Integer progress;
 
     private Integer time;
 
@@ -58,11 +58,11 @@ public class UserSentenceProcessDto {
         this.currentPart = currentPart;
     }
 
-    public Integer getProcess() {
-        return process;
+    public Integer getProgress() {
+        return progress;
     }
 
-    public void setProcess(Integer process) {
-        this.process = process;
+    public void setProgress(Integer progress) {
+        this.progress = progress;
     }
 }

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

@@ -7,7 +7,7 @@ import com.qxgmat.data.dao.entity.SentenceArticle;
 public class UserSentenceArticleDetailDto {
     private Integer id;
 
-    private Integer process;
+    private Integer progress;
 
     private String title;
 
@@ -25,12 +25,12 @@ public class UserSentenceArticleDetailDto {
 
     private Integer trailEnd;
 
-    public Integer getProcess() {
-        return process;
+    public Integer getProgress() {
+        return progress;
     }
 
-    public void setProcess(Integer process) {
-        this.process = process;
+    public void setProgress(Integer progress) {
+        this.progress = progress;
     }
 
     public String getTitle() {

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

@@ -7,7 +7,7 @@ import com.qxgmat.data.dao.entity.SentenceArticle;
 public class UserSentenceArticleDto {
     private Integer id;
 
-    private Integer process;
+    private Integer progress;
 
     private String title;
 
@@ -23,12 +23,12 @@ public class UserSentenceArticleDto {
 
     private Integer trailEnd;
 
-    public Integer getProcess() {
-        return process;
+    public Integer getProgress() {
+        return progress;
     }
 
-    public void setProcess(Integer process) {
-        this.process = process;
+    public void setProgress(Integer progress) {
+        this.progress = progress;
     }
 
     public String getTitle() {

+ 15 - 8
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentenceInfoDto.java

@@ -1,18 +1,17 @@
 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 Number trailPages;
+
     private String code;
 
-    private Map process;
+    private Map progress;
 
     public String getCode() {
         return code;
@@ -22,12 +21,12 @@ public class UserSentenceInfoDto {
         this.code = code;
     }
 
-    public Map getProcess() {
-        return process;
+    public Map getProgress() {
+        return progress;
     }
 
-    public void setProcess(Map process) {
-        this.process = process;
+    public void setProgress(Map progress) {
+        this.progress = progress;
     }
 
     public JSONArray getChapters() {
@@ -37,4 +36,12 @@ public class UserSentenceInfoDto {
     public void setChapters(JSONArray chapters) {
         this.chapters = chapters;
     }
+
+    public Number getTrailPages() {
+        return trailPages;
+    }
+
+    public void setTrailPages(Number trailPages) {
+        this.trailPages = trailPages;
+    }
 }

+ 32 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserSentencePaperDto.java

@@ -3,11 +3,19 @@ package com.qxgmat.dto.response;
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.SentencePaper;
 import com.qxgmat.data.inline.PaperStat;
+import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
+import com.qxgmat.dto.extend.UserReportExtendDto;
 
 @Dto(entity = SentencePaper.class)
 public class UserSentencePaperDto {
     private PaperStat stat;
 
+    private UserPaperBaseExtendDto paper;
+
+    private UserReportExtendDto report;
+
+    private Integer paperId;
+
     public PaperStat getStat() {
         return stat;
     }
@@ -15,4 +23,28 @@ public class UserSentencePaperDto {
     public void setStat(PaperStat stat) {
         this.stat = stat;
     }
+
+    public UserPaperBaseExtendDto getPaper() {
+        return paper;
+    }
+
+    public void setPaper(UserPaperBaseExtendDto paper) {
+        this.paper = paper;
+    }
+
+    public UserReportExtendDto getReport() {
+        return report;
+    }
+
+    public void setReport(UserReportExtendDto report) {
+        this.report = report;
+    }
+
+    public Integer getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(Integer paperId) {
+        this.paperId = paperId;
+    }
 }

+ 4 - 3
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserStudyDayDto.java

@@ -4,6 +4,7 @@ import com.qxgmat.data.relation.entity.UserRankStatRelation;
 import com.qxgmat.dto.extend.UserCourseResultExtendDto;
 import com.qxgmat.dto.extend.UserExerciseExtendDto;
 import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
+import com.qxgmat.dto.extend.UserPaperDetailExtendDto;
 
 import java.util.List;
 
@@ -22,7 +23,7 @@ public class UserStudyDayDto {
 
     private UserRankStatRelation examinationExceed;
 
-    private List<UserPaperBaseExtendDto> examinationList;
+    private List<UserPaperDetailExtendDto> examinationList;
 
     private Integer classTime;
 
@@ -88,11 +89,11 @@ public class UserStudyDayDto {
         this.examinationExceed = examinationExceed;
     }
 
-    public List<UserPaperBaseExtendDto> getExaminationList() {
+    public List<UserPaperDetailExtendDto> getExaminationList() {
         return examinationList;
     }
 
-    public void setExaminationList(List<UserPaperBaseExtendDto> examinationList) {
+    public void setExaminationList(List<UserPaperDetailExtendDto> examinationList) {
         this.examinationList = examinationList;
     }
 

+ 16 - 1
server/gateway-api/src/main/java/com/qxgmat/service/UserPaperService.java

@@ -70,7 +70,22 @@ public class UserPaperService extends AbstractService {
         return new PageResult<>(null, 0);
     }
 
-
+    /**
+     * 获取用户长难句做题记录
+     * @param userId
+     * @param ids
+     * @return
+     */
+    public List<UserPaper> listWithSentence(Integer userId, Collection ids){
+        Example example = new Example(UserPaper.class);
+        example.and(
+                example.createCriteria()
+                .andEqualTo("userId", userId)
+                .andEqualTo("paperOrigin", PaperOrigin.SENTENCE.key)
+                .andIn("originId", ids)
+        );
+        return select(userPaperMapper, example);
+    }
 
     /**
      * 获取用户做题组卷

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

@@ -100,7 +100,7 @@ public class ExerciseService extends AbstractService {
 
 
     /**
-     * 查找用户作业, 并关联用户最后一次作业
+     * 查找用户做题, 并关联用户最后一次做题记录
      * @param page
      * @param size
      * @param structId
@@ -123,9 +123,7 @@ public class ExerciseService extends AbstractService {
         List<UserExercisePaperRelation> pr = Transform.convert(list, UserExercisePaperRelation.class);
 
         // 获取最后一次作业结果
-        List<UserReport> reportList = userReportRelationMapper.listLast(ids);
-        Collection reportIds = Transform.getIds(reportList, UserReport.class, "id");
-        Transform.replace(reportList, userReportService.select(reportIds), UserReport.class, "id");
+        List<UserReport> reportList = userReportService.listWithLater(ids);
 
         Transform.combine(p, reportList, UserExercisePaperRelation.class, "id", "report", UserReport.class, "paperId");
 

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

@@ -104,9 +104,7 @@ public class PreviewService extends AbstractService {
         List<UserPreviewPaperRelation> pr = Transform.convert(list, UserPreviewPaperRelation.class);
 
         // 获取最后一次作业结果
-        List<UserReport> reportList = userReportRelationMapper.listLast(ids);
-        Collection reportIds = Transform.getIds(reportList, UserReport.class, "id");
-        Transform.replace(reportList, userReportService.select(reportIds), UserReport.class, "id");
+        List<UserReport> reportList = userReportService.listWithLater(ids);
 
         Transform.combine(p, reportList, UserPreviewPaperRelation.class, "id", "report", UserReport.class, "paperId");
 
@@ -132,9 +130,7 @@ public class PreviewService extends AbstractService {
         List<UserPreviewPaperRelation> pr = Transform.convert(list, UserPreviewPaperRelation.class);
 
         // 获取最后一次作业结果
-        List<UserReport> reportList = userReportRelationMapper.listLast(ids);
-        Collection reportIds = Transform.getIds(reportList, UserReport.class, "id");
-        Transform.replace(reportList, userReportService.select(reportIds), UserReport.class, "id");
+        List<UserReport> reportList = userReportService.listWithLater(ids);
 
         Transform.combine(pr, reportList, UserPreviewPaperRelation.class, "id", "report", UserReport.class, "paperId");
 

+ 11 - 3
server/gateway-api/src/main/java/com/qxgmat/service/extend/SentenceService.java

@@ -4,13 +4,15 @@ import com.nuliji.tools.Transform;
 import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.logic.SentenceLogic;
-import com.qxgmat.data.dao.entity.Question;
-import com.qxgmat.data.dao.entity.SentencePaper;
-import com.qxgmat.data.dao.entity.SentenceQuestion;
+import com.qxgmat.data.dao.entity.*;
+import com.qxgmat.data.relation.UserReportRelationMapper;
 import com.qxgmat.data.relation.entity.SentenceQuestionRelation;
+import com.qxgmat.data.relation.entity.UserSentencePaperRelation;
+import com.qxgmat.service.UserPaperService;
 import com.qxgmat.service.inline.SentencePaperService;
 import com.qxgmat.service.inline.QuestionService;
 import com.qxgmat.service.inline.SentenceQuestionService;
+import com.qxgmat.service.inline.UserReportService;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -35,6 +37,12 @@ public class SentenceService {
     @Resource
     private SentencePaperService sentencePaperService;
 
+    @Resource
+    private UserReportService userReportService;
+
+    @Resource
+    private UserPaperService userPaperService;
+
 
     /**
      * 添加长难句题目:加入题库,关联题目,并关联长难句paper

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

@@ -37,18 +37,18 @@ public class ExaminationStructService extends AbstractService {
                 .collect(Collectors.toList());
     }
 
-    @Cacheable(value = "examination_struct", key="children/#id/#children")
-    public List<ExaminationStruct> children(Integer id, boolean children){
+    @Cacheable(value = "examination_struct/children", key="#id/#children")
+    public List<ExaminationStruct> children(Integer id, Integer children){
         List<ExaminationStruct> list = all();
         List<Integer> ids = new ArrayList<>();
 
         return list.stream()
                 .filter((ExaminationStruct b) -> {
                     if (b.getParentId().equals(id)){
-                        if (children) ids.add(b.getId());
+                        if (children>0) ids.add(b.getId());
                         return true;
                     }else if(ids.contains(b.getParentId())){
-                        if (children) ids.add(b.getId());
+                        if (children>0) ids.add(b.getId());
                         return true;
                     }
                     return false;
@@ -56,7 +56,7 @@ public class ExaminationStructService extends AbstractService {
                 .collect(Collectors.toList());
     }
 
-    @Cacheable(value = "examination_struct", key="parent/#id")
+    @Cacheable(value = "examination_struct/parent", key="#id")
     public List<ExaminationStruct> parent(Integer id){
         List<ExaminationStruct> list = all();
         Collections.reverse(list);
@@ -85,7 +85,7 @@ public class ExaminationStructService extends AbstractService {
         return select(examinationStructMapper);
     }
 
-    @CacheEvict(value={"examination_struct", "examination_struct/main", "examination_struct/all"}, allEntries = true)
+    @CacheEvict(value={"examination_struct/children", "examination_struct/parent", "examination_struct/main", "examination_struct/all"}, allEntries = true)
     public ExaminationStruct add(ExaminationStruct struct){
         if(struct.getParentId() > 0){
             ExaminationStruct parent = get(struct.getParentId());

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

@@ -34,18 +34,18 @@ public class ExerciseStructService extends AbstractService {
                 .collect(Collectors.toList());
     }
 
-    @Cacheable(value = "exercise_struct", key="children/#id/#children")
-    public List<ExerciseStruct> children(Integer id, boolean children){
+    @Cacheable(value = "exercise_struct/children", key="#id/#children")
+    public List<ExerciseStruct> children(Integer id, Integer children){
         List<ExerciseStruct> list = all();
         List<Integer> ids = new ArrayList<>();
 
         return list.stream()
                 .filter((ExerciseStruct b) -> {
                     if (b.getParentId().equals(id)){
-                        if (children) ids.add(b.getId());
+                        if (children>0) ids.add(b.getId());
                         return true;
                     }else if(ids.contains(b.getParentId())){
-                        if(children) ids.add(b.getId());
+                        if(children>0) ids.add(b.getId());
                         return true;
                     }
                     return false;
@@ -53,7 +53,7 @@ public class ExerciseStructService extends AbstractService {
                 .collect(Collectors.toList());
     }
 
-    @Cacheable(value = "exercise_struct", key="parent/#id")
+    @Cacheable(value = "exercise_struct/parent", key="#id")
     public List<ExerciseStruct> parent(Integer id){
         List<ExerciseStruct> list = all();
         Collections.reverse(list);
@@ -82,7 +82,7 @@ public class ExerciseStructService extends AbstractService {
         return select(exerciseStructMapper);
     }
 
-    @CacheEvict(value={"exercise_struct", "exercise_strut/main", "exercise_strut/all"}, allEntries=true)
+    @CacheEvict(value={"exercise_struct/children", "exercise_struct/parent", "exercise_strut/main", "exercise_strut/all"}, allEntries=true)
     public ExerciseStruct add(ExerciseStruct struct){
         if(struct.getParentId() > 0){
             ExerciseStruct parent = get(struct.getParentId());

+ 13 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserReportService.java

@@ -3,6 +3,7 @@ package com.qxgmat.service.inline;
 import com.alibaba.fastjson.JSONObject;
 import com.github.pagehelper.Page;
 import com.nuliji.tools.AbstractService;
+import com.nuliji.tools.Transform;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
@@ -33,6 +34,18 @@ public class UserReportService extends AbstractService {
     private UserReportRelationMapper userReportRelationMapper;
 
     /**
+     * 查找userPaper最后一次做题记录
+     * @param paperIds
+     */
+    public List<UserReport> listWithLater(Collection paperIds){
+        List<UserReport> list = userReportRelationMapper.listLast(paperIds);
+
+        Collection reportIds = Transform.getIds(list, UserReport.class, "id");
+        Transform.replace(list, select(reportIds), UserReport.class, "id");
+        return list;
+    }
+
+    /**
      * 获取所属组卷的所有报告
      * @param paperIds
      * @return

+ 55 - 58
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserSentenceProcessService.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.dao.SentenceQuestionMapper;
-import com.qxgmat.data.dao.UserSentenceProcessMapper;
-import com.qxgmat.data.dao.entity.SentenceQuestion;
-import com.qxgmat.data.dao.entity.User;
-import com.qxgmat.data.dao.entity.UserSentenceProcess;
+import com.qxgmat.data.dao.UserSentenceProgressMapper;
+import com.qxgmat.data.dao.entity.UserSentenceProgress;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -20,34 +17,34 @@ import java.util.Collection;
 import java.util.List;
 
 @Service
-public class UserSentenceProcessService extends AbstractService {
-    private static final Logger logger = LoggerFactory.getLogger(UserSentenceProcessService.class);
+public class UserSentenceProgressService extends AbstractService {
+    private static final Logger logger = LoggerFactory.getLogger(UserSentenceProgressService.class);
 
     @Resource
-    private UserSentenceProcessMapper userSentenceProcessMapper;
+    private UserSentenceProgressMapper userSentenceProgressMapper;
 
     /**
      * 更新长难句文章进度
      * @param userId
      * @param chapter
      * @param part
-     * @param process
+     * @param progress
      * @return
      */
     @Transactional
-    public UserSentenceProcess updateProcess(Integer userId, Integer chapter, Integer part, Integer process, Integer maxPart){
-        Example example = new Example(UserSentenceProcess.class);
+    public UserSentenceProgress updateProgress(Integer userId, Integer chapter, Integer part, Integer progress, Integer maxPart){
+        Example example = new Example(UserSentenceProgress.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("chapter", chapter)
                         .andEqualTo("part", part)
         );
-        UserSentenceProcess entity;
-        UserSentenceProcess in = one(userSentenceProcessMapper, example);
+        UserSentenceProgress entity;
+        UserSentenceProgress in = one(userSentenceProgressMapper, example);
         if (in != null){
-            if (in.getProcess() <= process){
-                in.setProcess(process);
+            if (in.getProgress() <= progress){
+                in.setProgress(progress);
                 entity = edit(in);
                 return entity;
             }else{
@@ -56,19 +53,19 @@ public class UserSentenceProcessService extends AbstractService {
                 return entity;
             }
         }else{
-            entity = add(UserSentenceProcess.builder()
+            entity = add(UserSentenceProgress.builder()
             .userId(userId)
             .chapter(chapter)
             .part(part)
-            .process(process).build());
+            .progress(progress).build());
         }
         // 计算本章节的总体进度
-        List<UserSentenceProcess> list = listByChapter(userId, chapter);
+        List<UserSentenceProgress> list = listByChapter(userId, chapter);
         Integer total = 0;
-        for (UserSentenceProcess p: list) {
-            total += p.getProcess();
+        for (UserSentenceProgress p: list) {
+            total += p.getProgress();
         }
-        updateTotalProcess(userId, chapter, (total * 100) / (maxPart * 100));
+        updateTotalProgress(userId, chapter, (total * 100) / (maxPart * 100));
         return entity;
     }
 
@@ -76,28 +73,28 @@ public class UserSentenceProcessService extends AbstractService {
      * 更新单个章节总体进度
      * @param userId
      * @param chapter
-     * @param process
+     * @param progress
      */
-    private void updateTotalProcess(Integer userId, Integer chapter, Integer process){
-        Example example = new Example(UserSentenceProcess.class);
+    private void updateTotalProgress(Integer userId, Integer chapter, Integer progress){
+        Example example = new Example(UserSentenceProgress.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("chapter", chapter)
                         .andEqualTo("part", 0)
         );
-        UserSentenceProcess in = one(userSentenceProcessMapper, example);
+        UserSentenceProgress in = one(userSentenceProgressMapper, example);
         if (in != null){
-            if (in.getProcess() <= process){
-                in.setProcess(process);
+            if (in.getProgress() <= progress){
+                in.setProgress(progress);
                 edit(in);
             }
         }else{
-            add(UserSentenceProcess.builder()
+            add(UserSentenceProgress.builder()
                     .userId(userId)
                     .chapter(chapter)
                     .part(0)
-                    .process(process).build());
+                    .progress(progress).build());
         }
 
     }
@@ -107,14 +104,14 @@ public class UserSentenceProcessService extends AbstractService {
      * @param userId
      * @return
      */
-    public List<UserSentenceProcess> all(Integer userId){
-        Example example = new Example(UserSentenceProcess.class);
+    public List<UserSentenceProgress> all(Integer userId){
+        Example example = new Example(UserSentenceProgress.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andNotEqualTo("part", 0)
         );
-        return select(userSentenceProcessMapper, example);
+        return select(userSentenceProgressMapper, example);
     }
 
     /**
@@ -123,15 +120,15 @@ public class UserSentenceProcessService extends AbstractService {
      * @param chapter
      * @return
      */
-    public List<UserSentenceProcess> listByChapter(Integer userId, Integer chapter){
-        Example example = new Example(UserSentenceProcess.class);
+    public List<UserSentenceProgress> listByChapter(Integer userId, Integer chapter){
+        Example example = new Example(UserSentenceProgress.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("chapter", chapter)
                         .andNotEqualTo("part", 0)
         );
-        return select(userSentenceProcessMapper, example);
+        return select(userSentenceProgressMapper, example);
     }
 
     /**
@@ -139,14 +136,14 @@ public class UserSentenceProcessService extends AbstractService {
      * @param userId
      * @return
      */
-    public List<UserSentenceProcess> listAllByTrail(Integer userId){
-        Example example = new Example(UserSentenceProcess.class);
+    public List<UserSentenceProgress> listAllByTrail(Integer userId){
+        Example example = new Example(UserSentenceProgress.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andNotEqualTo("part", 0)
         );
-        return select(userSentenceProcessMapper, example);
+        return select(userSentenceProgressMapper, example);
     }
 
     /**
@@ -154,14 +151,14 @@ public class UserSentenceProcessService extends AbstractService {
      * @param userId
      * @return
      */
-    public List<UserSentenceProcess> listTotal(Integer userId){
-        Example example = new Example(UserSentenceProcess.class);
+    public List<UserSentenceProgress> listTotal(Integer userId){
+        Example example = new Example(UserSentenceProgress.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("part", 0)
         );
-        return select(userSentenceProcessMapper, example);
+        return select(userSentenceProgressMapper, example);
     }
 
     /**
@@ -171,46 +168,46 @@ public class UserSentenceProcessService extends AbstractService {
      * @param part
      * @return
      */
-    public UserSentenceProcess get(Integer userId, Integer chapter, Integer part){
-        Example example = new Example(UserSentenceProcess.class);
+    public UserSentenceProgress get(Integer userId, Integer chapter, Integer part){
+        Example example = new Example(UserSentenceProgress.class);
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("chapter", chapter)
                         .andEqualTo("part", part)
         );
-        return one(userSentenceProcessMapper, example);
+        return one(userSentenceProgressMapper, example);
     }
 
-    public UserSentenceProcess add(UserSentenceProcess process){
-        int result = insert(userSentenceProcessMapper, process);
-        process = one(userSentenceProcessMapper, process.getId());
+    public UserSentenceProgress add(UserSentenceProgress process){
+        int result = insert(userSentenceProgressMapper, process);
+        process = one(userSentenceProgressMapper, process.getId());
         if(process == null){
             throw new SystemException("进度添加失败");
         }
         return process;
     }
 
-    public UserSentenceProcess edit(UserSentenceProcess process){
-        UserSentenceProcess in = one(userSentenceProcessMapper, process.getId());
+    public UserSentenceProgress edit(UserSentenceProgress process){
+        UserSentenceProgress in = one(userSentenceProgressMapper, process.getId());
         if(in == null){
             throw new ParameterException("进度不存在");
         }
-        int result = update(userSentenceProcessMapper, process);
+        int result = update(userSentenceProgressMapper, process);
         return process;
     }
 
     public boolean delete(Number id){
-        UserSentenceProcess in = one(userSentenceProcessMapper, id);
+        UserSentenceProgress in = one(userSentenceProgressMapper, id);
         if(in == null){
             throw new ParameterException("进度不存在");
         }
-        int result = delete(userSentenceProcessMapper, id);
+        int result = delete(userSentenceProgressMapper, id);
         return result > 0;
     }
 
-    public UserSentenceProcess get(Number id){
-        UserSentenceProcess in = one(userSentenceProcessMapper, id);
+    public UserSentenceProgress get(Number id){
+        UserSentenceProgress in = one(userSentenceProgressMapper, id);
 
         if(in == null){
             throw new ParameterException("进度不存在");
@@ -218,12 +215,12 @@ public class UserSentenceProcessService extends AbstractService {
         return in;
     }
 
-    public Page<UserSentenceProcess> select(int page, int pageSize){
-        return select(userSentenceProcessMapper, page, pageSize);
+    public Page<UserSentenceProgress> select(int page, int pageSize){
+        return select(userSentenceProgressMapper, page, pageSize);
     }
 
-    public List<UserSentenceProcess> select(Collection ids){
-        return select(userSentenceProcessMapper, ids);
+    public List<UserSentenceProgress> select(Collection ids){
+        return select(userSentenceProgressMapper, ids);
     }
 
 }

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

@@ -53,7 +53,7 @@ public class AsyncTask {
         long start = System.currentTimeMillis();
         Setting setting = settingService.getByKey(SettingKey.EXERCISE_PAPER_STATUS);
         JSONObject status = setting.getValue();
-        status.put("process", 10);
+        status.put("progress", 10);
         // 按所有4级结构
         List<ExerciseStruct> p = exerciseStructService.all();
         for(ExerciseStruct struct : p){
@@ -179,32 +179,32 @@ public class AsyncTask {
         long start = System.currentTimeMillis();
         Setting setting = settingService.getByKey(SettingKey.SENTENCE_PAPER_STATUS);
         JSONObject status = setting.getValue();
-        status.put("process", 10);
+        status.put("progress", 10);
         settingService.edit(setting);
         // 获取所有组卷长难句题目
         List<SentenceQuestion> list = sentenceQuestionService.allPaper();
-        status.put("process", 20);
+        status.put("progress", 20);
         settingService.edit(setting);
         // 组正常卷
         List<SentencePaper> noPapers = sentenceService.createPaper("长难句", SentenceLogic.NO, list);
-        status.put("process", 30);
+        status.put("progress", 30);
         settingService.edit(setting);
         Collection noIds = Transform.getIds(noPapers, SentencePaper.class, "id");
-        status.put("process", 40);
+        status.put("progress", 40);
         settingService.edit(setting);
         sentenceService.switchPaper(SentenceLogic.NO, noIds);
-        status.put("process", 60);
+        status.put("progress", 60);
         settingService.edit(setting);
         // 组试用卷
         List<SentenceQuestion> trailList = list.stream().filter((question)-> question.getIsTrail() > 0).collect(Collectors.toList());
         List<SentencePaper> trailPapers = sentenceService.createPaper("长难句试用", SentenceLogic.TRAIL, trailList);
-        status.put("process", 70);
+        status.put("progress", 70);
         settingService.edit(setting);
         Collection trailIds = Transform.getIds(trailPapers, SentencePaper.class, "id");
-        status.put("process", 80);
+        status.put("progress", 80);
         settingService.edit(setting);
         sentenceService.switchPaper(SentenceLogic.TRAIL, trailIds);
-        status.put("process", 100);
+        status.put("progress", 100);
         settingService.edit(setting);
         long end = System.currentTimeMillis();
         logger.info("自动长难句组卷,耗时:" + (end - start) + "毫秒");