Преглед изворни кода

feat(front): 模考进度、机经进度

Go пре 5 година
родитељ
комит
4cdee7d061
20 измењених фајлова са 543 додато и 710 уклоњено
  1. 23 21
      front/project/admin/routes/setting/struct/page.js
  2. 45 25
      front/project/www/components/Card/index.js
  3. 4 1
      front/project/www/components/Header/index.js
  4. 17 14
      front/project/www/components/Panel/index.js
  5. 266 596
      front/project/www/routes/examination/main/page.js
  6. 4 4
      front/project/www/routes/exercise/main/page.js
  7. 5 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/ServiceKey.java
  8. 2 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  9. 10 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java
  10. 2 1
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/AdDto.java
  11. 22 3
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/ExaminationStructDto.java
  12. 30 3
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/ExerciseStructDto.java
  13. 2 1
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/ManagerRoleDto.java
  14. 2 1
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserAskCourseDto.java
  15. 2 1
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserAskQuestionDto.java
  16. 30 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserExaminationGroupDto.java
  17. 72 36
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserTextbookGroupDto.java
  18. 1 0
      server/gateway-api/src/main/java/com/qxgmat/service/UserServiceService.java
  19. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExaminationService.java
  20. 3 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryService.java

+ 23 - 21
front/project/admin/routes/setting/struct/page.js

@@ -53,13 +53,11 @@ export default class extends Page {
       name: '英文名称',
       type: 'input',
       placeholder: '请输入英文名称',
-      required: true,
     }, {
       key: 'description',
       name: '描述',
       type: 'textarea',
       placeholder: '请输入描述',
-      required: true,
     }, {
       key: 'isSentence',
       type: 'hidden',
@@ -81,13 +79,11 @@ export default class extends Page {
       name: '英文名称',
       type: 'input',
       placeholder: '请输入英文名称',
-      required: true,
     }, {
       key: 'description',
       name: '描述',
       type: 'textarea',
       placeholder: '请输入描述',
-      required: true,
     }, {
       key: 'isSentence',
       type: 'hidden',
@@ -134,13 +130,11 @@ export default class extends Page {
       name: '英文名称',
       type: 'input',
       placeholder: '请输入英文名称',
-      required: true,
     }, {
       key: 'description',
       name: '描述',
       type: 'textarea',
       placeholder: '请输入描述',
-      required: true,
     }, {
       key: 'isAdapt',
       type: 'hidden',
@@ -160,13 +154,11 @@ export default class extends Page {
       name: '英文名称',
       type: 'input',
       placeholder: '请输入英文名称',
-      required: true,
     }, {
       key: 'description',
       name: '描述',
       type: 'textarea',
       placeholder: '请输入描述',
-      required: true,
     }, {
       key: 'isAdapt',
       type: 'hidden',
@@ -220,14 +212,14 @@ export default class extends Page {
         examinationMap: getMap(list, 'id'),
         examinationStruct: formatTreeData(list.map(row => {
           if (row.level !== 2) return row;
-          // row = Object.assign({}, row);
-          // row.title = <div className='node'>{row.title}<Button className='after-node' size='small' type={row.questionStatus > 0 ? 'primary' : 'ghost'} onClick={(e) => {
-          //   e.preventDefault();
-          //   row.payStatus = row.payStatus > 0 ? 0 : 1;
-          //   Examination.editStruct(row).then(() => {
-          //     this.refresh();
-          //   });
-          // }}>{row.payStatus > 0 ? [<Icon type="pay-circle" />, <span>付费</span>] : [<Icon type="eye" />, <span>免费</span>]}</Button></div>;
+          row = Object.assign({}, row);
+          row.title = <div className='node'>{row.title}<Button className='after-node' size='small' type={row.questionStatus > 0 ? 'primary' : 'ghost'} onClick={(e) => {
+            e.preventDefault();
+            row.questionStatus = row.questionStatus > 0 ? 0 : 1;
+            Examination.editStruct(row).then(() => {
+              this.refresh();
+            });
+          }}>{row.questionStatus > 0 ? [<Icon type='pause' />, <span>提问中</span>] : [<Icon type="caret-right" />, <span>提问关闭</span>]}</Button></div>;
           return row;
         }), 'id', 'title', 'parentId'),
       });
@@ -249,7 +241,11 @@ export default class extends Page {
         asyncSMessage('不允许添加该节点的子节点', 'warn');
         return;
       }
-      dep = {};
+      dep = {
+        isSentence: node.isSentence,
+        isCourse: node.isCourse,
+        isData: node.isData,
+      };
     } else {
       itemList = this.examinationItemList;
       if (selectedKeys.length > 0) {
@@ -264,8 +260,9 @@ export default class extends Page {
       };
     }
 
-    asyncForm('新增', itemList, Object.assign({ parentId: `${node.id}`, parentName: node.title }, dep), data => {
+    asyncForm('新增', itemList, Object.assign({ parentId: node.id, parentName: node.title }, dep), data => {
       let handler;
+      data.parentId = Number(data.parentId);
       if (tab === 'exercise') {
         handler = Exercise.addStruct(data);
       } else {
@@ -293,6 +290,9 @@ export default class extends Page {
         return;
       }
       dep = {
+        isSentence: node.isSentence,
+        isCourse: node.isCourse,
+        isData: node.isData,
         parentName: this.state.exerciseMap[node.parentId].title,
       };
     } else {
@@ -310,8 +310,9 @@ export default class extends Page {
       };
     }
 
-    asyncForm('新增', itemList, Object.assign({ parentId: `${node.parentId}`, parentName: node.title }, dep), data => {
+    asyncForm('新增', itemList, Object.assign({ parentId: node.parentId, parentName: node.title }, dep), data => {
       let handler;
+      data.parentId = Number(data.parentId);
       if (tab === 'exercise') {
         handler = Exercise.addStruct(data);
       } else {
@@ -331,7 +332,7 @@ export default class extends Page {
     if (tab === 'exercise') {
       itemList = this.exerciseItemList;
       ([node] = exerciseList.filter(row => row.id === Number(selectedKeys[0])).map(row => {
-        row.parentId = `${row.parentId}`;
+        // row.parentId = `${row.parentId}`;
         return row;
       }));
       if (node.level <= 2) {
@@ -345,7 +346,7 @@ export default class extends Page {
     } else {
       itemList = this.examinationItemList;
       ([node] = examinationList.filter(row => row.id === Number(selectedKeys[0])).map(row => {
-        row.parentId = `${row.parentId}`;
+        // row.parentId = `${row.parentId}`;
         return row;
       }));
       if (node.level <= 1) {
@@ -361,6 +362,7 @@ export default class extends Page {
 
     asyncForm('编辑', itemList, node, data => {
       let handler;
+      data.parentId = Number(data.parentId);
       if (tab === 'exercise') {
         handler = Exercise.editStruct(data);
       } else {

+ 45 - 25
front/project/www/components/Card/index.js

@@ -132,7 +132,7 @@ export class Card1 extends Component {
           <div className="t-1">课程已经结束啦</div>
         </div>
         <div className="bottom">
-          有效期: {formatDate(useStartTime, 'YYYY-MM-DD')}至{formatDate(useEndTime, 'YYYY-MM-DD')} <a onClick={() => onPreview && onPreview()}>全部作业></a>
+          有效期: {useStartTime && formatDate(useStartTime, 'YYYY-MM-DD')}至{useEndTime && formatDate(useEndTime, 'YYYY-MM-DD')} <a onClick={() => onPreview && onPreview()}>全部作业></a>
         </div>
       </div>
     );
@@ -150,30 +150,50 @@ export class Card1 extends Component {
   }
 
   getOpenBody() {
+    const { checked } = this.state;
     const { data, onOpen } = this.props;
-    const { teacher, endTime } = data;
-    return !teacher ? (
-      <div className="body">
-        <div className="text">
-          <Checkbox />
-          <span>
-            我已阅读并同意<Link to="">《千行课程协议》</Link>
-          </span>
-        </div>
-        <div className="btn">
-          <Button size="lager" radius onClick={() => onOpen && onOpen()}>
-            开通作业
-          </Button>
-        </div>
-      </div>
-    ) : (<div className="body">
-      <div className="t-1">请尽快与老师预约上课时间</div>
-      <div className="t-2">请于 {formatDate(endTime, 'YYYY-MM-DD')} 前开通课程</div>
-      <div className="qr-code">
-        <Assets name="qrcode" src={teacher.qr} />
-      </div>
-    </div>
-    );
+    const { teacher, endTime, courseModule } = data;
+    switch (courseModule) {
+      case 'video':
+        return <div className="body">
+          <div className="text">
+            <span>
+              请于{endTime && formatDate(endTime, 'YYYY-MM-DD')}前开通课程
+            </span>
+          </div>
+          <div className="btn">
+            <Button size="lager" radius onClick={() => onOpen && onOpen()}>
+              开通作业
+            </Button>
+          </div>
+        </div>;
+      case 'online':
+        return <div className="body">
+          <div className="text">
+            <Checkbox checked={checked} onChange={() => {
+              this.setState({ checked: !checked });
+            }} />
+            <span>
+              我已阅读并同意<Link to="">《千行课程协议》</Link>
+            </span>
+          </div>
+          <div className="btn">
+            <Button size="lager" radius onClick={() => onOpen && onOpen()}>
+              开通作业
+              </Button>
+          </div>
+        </div>;
+      case 'vs':
+        return <div className="body">
+          <div className="t-1">请尽快与老师预约上课时间</div>
+          <div className="t-2">请于 {endTime && formatDate(endTime, 'YYYY-MM-DD')} 前开通课程</div>
+          <div className="qr-code">
+            {teacher && <Assets name="qrcode" src={teacher.qr} />}
+          </div>
+        </div>;
+      default:
+        return <div />;
+    }
   }
 
   getIngBody() {
@@ -226,7 +246,7 @@ export class Card1 extends Component {
         </div>
         )}
         <div className="bottom">
-          有效期: {formatDate(useStartTime, 'YYYY-MM-DD')}至{formatDate(useEndTime, 'YYYY-MM-DD')} <a onClick={() => onPreview && onPreview()}>全部作业></a>
+          有效期: {useStartTime && formatDate(useStartTime, 'YYYY-MM-DD')}至{useEndTime && formatDate(useEndTime, 'YYYY-MM-DD')} <a onClick={() => onPreview && onPreview()}>全部作业></a>
         </div>
       </div>
     );

+ 4 - 1
front/project/www/components/Header/index.js

@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
 import Assets from '@src/components/Assets';
 import Button from '../Button';
 import './index.less';
+import { User } from '../../stores/user';
 
 function Header(props) {
   const { tabs = [], active } = props;
@@ -24,7 +25,9 @@ function Header(props) {
           </div>
         </div>
         <div className="right">
-          <Button>登录</Button>
+          <Button onClick={() => {
+            User.needLogin();
+          }}>登录</Button>
         </div>
       </div>
     </div>

+ 17 - 14
front/project/www/components/Panel/index.js

@@ -2,6 +2,7 @@ import React from 'react';
 import { Tooltip } from 'antd';
 import './index.less';
 import Assets from '@src/components/Assets';
+import { formatDate } from '@src/services/Tools';
 import Module from '../Module';
 import ProgressButton from '../ProgressButton';
 import Button from '../Button';
@@ -62,7 +63,7 @@ export default function Panel(props) {
   );
 }
 export function WaitPanel(props) {
-  const { style, message, data = {}, col = 3, title, onClick } = props;
+  const { style, message, data = {}, col = 3, title, onClick, onOpen } = props;
   return (
     <Module style={style} className="panel wait-panel">
       <div className="header">
@@ -113,7 +114,7 @@ export function WaitPanel(props) {
           })}
         </div>
 
-        <Button size="lager" radius>
+        <Button size="lager" radius onClick={() => onOpen && onOpen()}>
           立即开通
         </Button>
       </div>
@@ -121,7 +122,7 @@ export function WaitPanel(props) {
   );
 }
 export function BuyPanel(props) {
-  const { style, message, title } = props;
+  const { style, message, title, onBuy } = props;
   return (
     <Module style={style} className="panel buy-panel">
       <div className="header">
@@ -135,7 +136,7 @@ export function BuyPanel(props) {
       <div className="body">
         <Assets name="banner" />
         <div className="text">您还未购买本月机经</div>
-        <Button radius size="small" width={80}>
+        <Button radius size="small" width={80} onClick={() => onBuy && onBuy()}>
           立即购买
         </Button>
       </div>
@@ -144,14 +145,15 @@ export function BuyPanel(props) {
 }
 
 export function SmallPanel(props) {
-  const { style, title, lock, data = {} } = props;
+  const { style, title, lock, data = {}, onClick } = props;
+  const { useEndTime, needService } = data;
   return (
     <Module style={style} className="panel small-panel">
       <div className="header">
         <span>{title}</span>
         {lock && <Assets name="lock" />}
       </div>
-      <div className="body">
+      <div className="body" onClick={() => onClick && onClick()}>
         <div className="chart-info">
           <div className="chart" />
           <div className="info">
@@ -166,7 +168,7 @@ export function SmallPanel(props) {
                 </div>
               );
             })}
-            <div className="date">有效期至:2019-11-13</div>
+            {needService && <div className="date">有效期至:{useEndTime && formatDate(useEndTime, 'YYYY-MM-DD')}</div>}
           </div>
         </div>
       </div>
@@ -175,17 +177,18 @@ export function SmallPanel(props) {
 }
 
 export function SmallWaitPanel(props) {
-  const { style, title } = props;
+  const { style, title, lock, data, onOpen } = props;
+  const { endTime } = data;
   return (
     <Module style={style} className="panel small-wait-panel">
       <div className="header">
         <span>{title}</span>
-        <Assets name="lock" />
+        {lock && <Assets name="lock" />}
       </div>
       <div className="body">
-        <div className="title">请于20190-07-05前开通</div>
+        <div className="title">请于{endTime && formatDate(endTime, 'YYYY-MM-DD')}前开通</div>
         <div className="btn">
-          <Button size="lager" width={120} radius>
+          <Button size="lager" width={120} radius onClick={() => onOpen && onOpen()}>
             立即开通
           </Button>
         </div>
@@ -195,16 +198,16 @@ export function SmallWaitPanel(props) {
 }
 
 export function SmallBuyPanel(props) {
-  const { style, title } = props;
+  const { style, title, lock, onBuy } = props;
   return (
     <Module style={style} className="panel small-buy-panel">
       <div className="header">
         <span>{title}</span>
-        <Assets name="lock" />
+        {lock && <Assets name="lock" />}
       </div>
       <div className="body">
         <Assets name="banner_1" />
-        <Button radius size="small" width={80}>
+        <Button radius size="small" width={80} onClick={() => onBuy && onBuy()}>
           立即购买
         </Button>
       </div>

+ 266 - 596
front/project/www/routes/examination/main/page.js

@@ -1,463 +1,293 @@
 import React from 'react';
 import './index.less';
-import { Link } from 'react-router-dom';
-import { Tooltip } from 'antd';
 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 { asyncConfirm, asyncSMessage } from '@src/services/AsyncTools';
+import { formatTreeData, formatSeconds, formatPercent, formatDate } from '@src/services/Tools';
+import Panel, { WaitPanel, BuyPanel, SmallPanel, SmallWaitPanel, SmallBuyPanel } from '../../../components/Panel';
 import Tabs from '../../../components/Tabs';
 import Module from '../../../components/Module';
-import 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 { My } from '../../../stores/my';
 import { Question } from '../../../stores/question';
-import { Course } from '../../../stores/course';
-import { User } from '../../../stores/user';
+import { Textbook } from '../../../stores/textbook';
+// import { User } from '../../../stores/user';
+// import { CourseModuleShow, CourseModule } from '../../../../Constant';
+import { Order } from '../../../stores/order';
 
-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>
-      );
-    },
-  },
-];
+const TEXTBOOK = 'textbook';
 
 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>
+    this.examinationColumns = [{
+      title: '练习册',
+      width: 250,
+      align: 'left',
+      render: item => {
+        return (
+          <div className="table-row">
+            <div className="night f-s-16">{item.title}</div>
+            <div>
+              <ProgressText
+                progress={item.report.id ? formatPercent(item.repport.userNumber, item.report.questionNumber) : 0}
+                size="small"
+              />
             </div>
-          );
-        },
+          </div>
+        );
       },
-      {
-        title: '正确率',
-        width: 150,
-        align: 'left',
-        render: () => {
-          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: item => {
+        return (
+          <div className="table-row">
+            <div className="night f-s-16 f-w-b">--</div>
+            <div className="f-s-12">{formatPercent(item.stat.totalCorrect, item.stat.totalNumber, false)}</div>
+          </div>
+        );
       },
-      {
-        title: '全站用时',
-        width: 150,
-        align: 'left',
-        render: () => {
-          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: item => {
+        return (
+          <div className="table-row">
+            <div className="night f-s-16 f-w-b">--</div>
+            <div className="f-s-12">全站{formatSeconds(item.stat.totalTime / item.stat.totalNumber)}</div>
+          </div>
+        );
       },
-      {
-        title: '最近做题',
-        width: 150,
-        align: 'left',
-        render: () => {
-          return (
-            <div className="table-row">
-              <div>2019-04-28</div>
-              <div>07:30</div>
-            </div>
-          );
-        },
+    },
+    {
+      title: '最近做题',
+      width: 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: 180,
+      align: 'left',
+      render: item => {
+        return (
+          <div className="table-row p-t-1">
+            {!item.report && <IconButton type="start" tip="Start" onClick={() => Question.startLink('preview', item)} />}
+            {item.report.id && !item.report.isFinish && (
+              <IconButton
+                className="m-r-2"
+                type="continue"
+                tip="Continue"
+                onClick={() => Question.continueLink('preview', item)}
+              />
+            )}
+            {item.report.id && <IconButton type="restart" tip="Restart" onClick={() => this.restart('preview', item)} />}
+          </div>
+        );
       },
-      {
-        title: '报告',
-        width: 30,
-        align: 'right',
-        render: () => {
-          return (
-            <div className="table-row p-t-1">
-              <IconButton type="report" tip="Report" />
-            </div>
-          );
-        },
+    },
+    {
+      title: '报告',
+      width: 30,
+      align: 'right',
+      render: item => {
+        return (
+          <div className="table-row p-t-1">
+            {item.report.isFinish && <IconButton type="report" tip="Report" onClick={() => Question.reportLink(item)} />}
+          </div>
+        );
       },
-    ];
+    }];
   }
 
   initState() {
-    this.code = null;
-    this.columns = exerciseColumns;
-    this.exerciseProcess = {};
+    this.examinationProgress = {};
+    this.textbookProgress = {};
     this.inited = false;
     return {
-      tab1: SENTENCE,
+      tab1: '',
       tab2: '',
-      previewType: PREVIEW_CLASS,
       tabs: [],
-      allClass: [],
-      classProcess: {},
     };
   }
 
   init() {
-    Main.getExercise().then(result => {
-      const list = result.map(row => {
+    Main.getExamination().then(result => {
+      const list = result.filter(row => row.level === 1).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 });
+      tabs.push({ key: TEXTBOOK, name: '数学机经' });
+      this.setState({
+        tabs,
+      });
       this.inited = true;
       this.refreshData();
     });
   }
 
   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);
+    if (!data.tab1) {
+      data.tab1 = TEXTBOOK;
     }
+    this.setState(data);
     if (this.inited) this.refreshData();
   }
 
-  refreshData() {
+  refreshData(tab) {
     const { tab1 } = this.state;
-    switch (tab1) {
-      case SENTENCE:
-        this.refreshSentence();
-        break;
-      case PREVIEW:
-        this.refreshPreview();
+    switch (tab || tab1) {
+      case TEXTBOOK:
+        this.refreshTextbook();
         break;
       default:
-        this.refreshExercise();
+        this.refreshExamination(tab || tab1);
     }
   }
 
-  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);
+  refreshTextbook() {
+    Textbook.progress().then(result => {
+      // const exerciseProgress = getMap(r, 'id');
+      result = result.map(row => {
+        row.info = [
+          {
+            title: '已做',
+            number: row.userNumber || '-',
+            unit: '题',
+          },
+          {
+            title: '剩余',
+            number: row.userNumber ? row.questionNumber - row.userNumber : '-',
+            unit: '题',
+          },
+          {
+            title: '正确率',
+            number: row.userNumber ? formatPercent(row.userStat.userCorrect, row.userStat.userNumber, false) : '-%',
+            unit: '',
+          },
+          {
+            title: '全站',
+            number: row.userNumber ? formatPercent(row.stat.totalCorrect, row.stat.totalNumber, false) : '-%',
+            unit: '题',
+          },
+        ];
+
+        row.progress = formatPercent(row.questionNumber - row.userNumber || 0, row.questionNumber);
+        row.totalCorrect = formatPercent(row.stat.totalCorrect, row.stat.totalNumber, false);
+
+        row.children = row.children.map(r => {
+          r.title = r.title || r.titleZh;
+          r.progress = formatPercent(r.userNumber, r.questionNumber);
+          return r;
         });
-        this.setState({ articleMap: map });
-      });
-    }
 
-    if (!paperList) {
-      Sentence.listPaper().then(result => {
-        this.setState({ paperList: result, paperFilterList: result });
+        if (row.isLatest) {
+          const day = parseInt((new Date().getTime() - new Date(row.startDate).getTime()) / 86400000, 10);
+          row.desc = [`最近换库:${formatDate(row.startDate, 'YYYY-MM-DD')} ,已换库${day}天`, `最后更新:${formatDate(row.updateTime)}`];
+        }
+        return row;
       });
-    }
-  }
-
-  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 });
+      this.setState({ textbookProgress: result });
     });
   }
 
-  refreshListPreview() {
-    Question.listPreview().then(result => {
-      this.setState({ previews: result });
-    });
-  }
-
-  refreshExercise() {
-    const { map, tab1 } = this.state;
-    let { tab2 } = this.state;
-    if (!map) {
+  refreshExamination(tab) {
+    const { tabs, tab1 } = this.state;
+    if (!tabs) {
       // 等待数据加载
       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 });
+    const [subject] = tabs.filter(row => row.key === tab || row.key === tab1);
+    Question.getExaminationProgress(subject.id).then(result => {
+      // const exerciseProgress = getMap(r, 'id');
+      result = result.map(row => {
+        row.title = `${row.titleZh}${row.titleEn}`;
+        row.info = [
+          {
+            title: '已做',
+            number: row.userNumber || '-',
+            unit: '套',
+          },
+          {
+            title: '剩余',
+            number: row.userNumber ? row.questionNumber - row.userNumber : '-',
+            unit: '套',
+          },
+        ];
+        return row;
+      });
+      this.setState({ examinationProgress: result });
     });
   }
 
-  onChangePreviewType(type) {
-    this.setState({ previewType: type });
-    this.refreshPreview();
+  onChangeTab(level, tab) {
+    const { tab1 } = this.state;
+    const data = {};
+    if (level > 1) {
+      data.tab1 = tab1;
+      data.tab2 = tab;
+    } else {
+      data.tab1 = tab;
+    }
+    // this.refreshData(tab);
+    this.refreshQuery(data);
   }
 
-  onChangeTab(level, tab) {
-    const state = {};
-    state[`tab${level}`] = tab;
-    this.setState(state);
-    this.refresh();
+  onTextbook() {
+    const { tab1, tab2, struct } = this.state;
+    const data = {
+      tab1, tab2, struct,
+    };
+    this.refreshQuery(data);
   }
 
-  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;
-    }
+  // 开通模考或者机经
+  open(recordId) {
+    Order.useRecord(recordId).then(() => {
+      asyncSMessage('开通成功');
+      this.refresh();
+    });
   }
 
   restart(item) {
     asyncConfirm('提示', '是否重置', () => {
-      Question.restart(item.report.id).then(() => {
+      Question.restart(item.paper.id).then(() => {
         this.refresh();
       });
     });
   }
 
-  start(type, item) {
-    linkTo(`/paper/process/${type}/${item.id}`);
+  examinationList(item) {
+    linkTo(`/examination/list/${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 });
+  textbookList(item) {
+    linkTo(`/textbook/list/${item.id}`);
   }
 
   renderView() {
-    const { tab1 = {}, tab2 = {}, tabs, map = {}, latest } = this.state;
-    const children = (map[tab1] || {}).children || [];
+    const { tab1, tab2, tabs } = this.state;
+    const [subject] = tabs.filter(row => row.key === tab1);
+    const children = (subject && subject.children) ? subject.children : [];
     return (
       <div>
-        {latest && (
-          <Continue
-            data={latest}
-            onClose={() => {
-              this.clearExercise();
-            }}
-            onContinue={() => { }}
-            onRestart={() => { }}
-            onNext={() => { }}
-          />
-        )}
         <div className="content">
           <Module className="m-t-2">
             <Tabs
@@ -468,245 +298,85 @@ export default class extends Page {
                 this.onChangeTab(1, key);
               }}
             />
-            {children.length > 1 && <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />}
+            {children && 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()}
+          {tab1 !== TEXTBOOK && this.renderExamination()}
+          {tab1 === TEXTBOOK && this.renderTextbook()}
         </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;
+  renderTextbook() {
+    const { textbookProgress = [] } = 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} />;
+      <div>
+        <Division col={2}>
+          {(textbookProgress || []).map(struct => {
+            if (struct.needService && !struct.hasService) {
+              if (struct.unUseRecord) {
+                return <WaitPanel
+                  title={struct.isLatest ? '最新' : '往期'}
+                  col="3"
+                  data={struct}
+                />;
+              }
+              return <BuyPanel
+                title={struct.isLatest ? '最新' : '往期'}
+                onBuy={() => {
+                  this.buyTextbook();
+                }}
+              />;
+            }
+            return <Panel
+              title={struct.isLatest ? '最新' : '往期'}
+              col="3"
+              data={struct}
+              onClick={(item) => {
+                this.textbookList(item);
+              }}
+            />;
           })}
         </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: '全部' }] },
-          ]}
-          rightAction={
-            <div>
-              有效期至:2019-11-13{' '}
-              <Tooltip overlayClassName="gray" placement="top" title="全部模考做完才可重置">
-                <a>
-                  <Button size="small" disabled radius>
-                    Reset
-                  </Button>
-                </a>
-              </Tooltip>
-            </div>
-          }
-          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;
-    }
+  renderExamination() {
+    const { examinationProgress = [] } = this.state;
     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 });
+        <Division col={3} type="compact">
+          {(examinationProgress || []).map(struct => {
+            if (struct.hasService) {
+              return <SmallPanel
+                title={struct.title}
+                data={struct}
+                onClick={() => {
+                  this.examinationList(struct);
+                }}
+              />;
+            } if (struct.unUseRecord) {
+              return <SmallWaitPanel
+                title={struct.title}
+                data={struct}
+                onOpen={() => {
+                  this.open(struct.unUseRecord);
+                }}
+              />;
+            }
+            return <SmallBuyPanel
+              title={struct.title}
+              data={struct}
+              onBuy={() => {
+                this.buyQxCat();
               }}
-            >
-              输入
-            </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}
-          />
-        )}
+            />;
+          })}
+        </Division>
       </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 />;
-  }
 }

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

@@ -448,8 +448,9 @@ export default class extends Page {
       const courseMap = {};
       const now = new Date().getTime();
       courseMap.open = result.filter(row => !row.isUsed);
-      courseMap.end = result.filter(row => row.isUsed && new Date(row.useEndTime).getTime() < now);
-      courseMap.process = result.filter(row => row.isUsed && new Date(row.useEndTime).getTime() >= now);
+      courseMap.end = result.filter(row => row.isUsed && (!row.isStop || (row.isStop && row.restoreTime)) && new Date(row.useEndTime).getTime() < now);
+      // todo 排序:sc,rc,cr
+      courseMap.process = result.filter(row => row.isUsed && (row.isStop || row.restoreTime) && new Date(row.useEndTime).getTime() >= now);
       this.setState({ courseMap });
     });
   }
@@ -669,7 +670,6 @@ export default class extends Page {
             {children && 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()}
@@ -1058,7 +1058,7 @@ export default class extends Page {
     const { exerciseProgress = [] } = this.state;
     return (
       <div>
-        <Division col="2">
+        <Division col={2}>
           {(exerciseProgress || []).map(struct => {
             const [first] = struct.children;
             let col = 3;

+ 5 - 1
server/data/src/main/java/com/qxgmat/data/constants/enums/ServiceKey.java

@@ -22,6 +22,10 @@ public enum ServiceKey {
 
     public static ServiceKey ValueOf(String name){
         if (name == null || name.isEmpty()) return null;
-        return ServiceKey.valueOf(name.toUpperCase());
+        try{
+            return ServiceKey.valueOf(name.toUpperCase());
+        }catch (Exception e){
+            return null;
+        }
     }
 }

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

@@ -308,6 +308,7 @@ public class QuestionController {
         for(ExaminationStruct struct : two){
             UserExaminationGroupDto dto = Transform.convert(struct, UserExaminationGroupDto.class);
             ServiceKey serviceKey = ServiceKey.ValueOf(struct.getExtend());
+            dto.setNeedService(serviceKey != null);
             dto.setHasService(true);
             // 获取第三层节点
             // 以下属的paper作为children
@@ -341,6 +342,7 @@ public class QuestionController {
                     dto.setUserNumber(userNumber);
                 }
             }
+            p.add(dto);
         }
 
         return ResponseHelp.success(p);

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java

@@ -99,6 +99,9 @@ public class TextbookController
 
         for(TextbookLibrary library : new ArrayList<TextbookLibrary>(2){{add(latest);add(second);}}){
             UserTextbookGroupDto dto = Transform.convert(library, UserTextbookGroupDto.class);
+            dto.setIsLatest(library.getEndDate() == null ? 1 : 0);
+            dto.setNeedService(library.getEndDate() == null);
+            dto.setHasService(true);
             // 获取第三层所有题目,并获取题目统计
             List<TextbookQuestion> list = textbookQuestionService.listByLibrary(library.getId());
             List<TextbookQuestionRelation> relations = textbookQuestionService.relation(list);
@@ -106,6 +109,12 @@ public class TextbookController
             dto.setQuestionNumber(list.size());
             Map<Object, UserQuestionStat> userQuestionStatMap = null;
             if(user != null){
+                if (dto.getNeedService()){
+                    dto.setHasService(userServiceService.hasService(user.getId(), ServiceKey.TEXTBOOK));
+                    // 服务, 判断对应服务状态
+                    UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), ServiceKey.TEXTBOOK);
+                    dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
+                }
                 Collection questionNoIds = Transform.getIds(list, QuestionNo.class, "id");
                 List<UserQuestion> userQuestionList = userQuestionService.listByQuestionNo(user.getId(), questionNoIds);
                 userQuestionStatMap = userQuestionService.statQuestionNoMap(userQuestionList);
@@ -134,6 +143,7 @@ public class TextbookController
             for(TextbookLogic logic : TextbookLogic.all()){
                 UserTextbookGroupExtendDto extendDto = new UserTextbookGroupExtendDto();
                 extendDto.setLogic(logic.key);
+                extendDto.setTitle(logic.key.toUpperCase());
                 List<TextbookQuestionRelation> childQuestionList = relations.stream().filter((q)-> logic.contain(QuestionType.ValueOf(q.getQuestion().getQuestionType()))).collect(Collectors.toList());
                 extendDto.setQuestionNumber(childQuestionList.size());
                 if (user != null){

+ 2 - 1
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/AdDto.java

@@ -5,6 +5,7 @@ import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.Ad;
 
 import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
 import java.util.Date;
 
 @Dto(entity = Ad.class)
@@ -15,7 +16,7 @@ public class AdDto {
     @NotEmpty(message = "广告名称不能为空!")
     private String title;
 
-    @NotEmpty(message = "位置不能为空!")
+    @NotNull(message = "位置不能为空!")
     private Integer position;
 
     private Date startTime;

+ 22 - 3
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/ExaminationStructDto.java

@@ -4,6 +4,7 @@ import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.ExaminationStruct;
 
 import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
 
 @Dto(entity= ExaminationStruct.class)
 public class ExaminationStructDto {
@@ -12,15 +13,17 @@ public class ExaminationStructDto {
     @NotEmpty(message = "中文名称不能为空!")
     private String titleZh;
 
-    @NotEmpty(message = "英文名称不能为空!")
     private String titleEn;
 
-    @NotEmpty(message = "描述不能为空!")
     private String description;
 
-    @NotEmpty(message = "父级不能为空!")
+    @NotNull(message = "父级不能为空!")
     private Integer parentId;
 
+    private Integer questionStatus;
+
+    private Integer isAdapt;
+
     public Integer getId() {
         return id;
     }
@@ -60,4 +63,20 @@ public class ExaminationStructDto {
     public void setTitleEn(String titleEn) {
         this.titleEn = titleEn;
     }
+
+    public Integer getIsAdapt() {
+        return isAdapt;
+    }
+
+    public void setIsAdapt(Integer isAdapt) {
+        this.isAdapt = isAdapt;
+    }
+
+    public Integer getQuestionStatus() {
+        return questionStatus;
+    }
+
+    public void setQuestionStatus(Integer questionStatus) {
+        this.questionStatus = questionStatus;
+    }
 }

+ 30 - 3
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/ExerciseStructDto.java

@@ -13,18 +13,21 @@ public class ExerciseStructDto {
     @NotEmpty(message = "中文名称不能为空!")
     private String titleZh;
 
-    @NotEmpty(message = "英文名称不能为空!")
     private String titleEn;
 
-    @NotEmpty(message = "描述不能为空!")
     private String description;
 
     @NotNull(message = "父级不能为空!")
-
     private Integer parentId;
 
     private Integer questionStatus;
 
+    private Integer isSentence;
+
+    private Integer isCourse;
+
+    private Integer isData;
+
     public Integer getId() {
         return id;
     }
@@ -71,4 +74,28 @@ public class ExerciseStructDto {
     public void setTitleZh(String titleZh) {
         this.titleZh = titleZh;
     }
+
+    public Integer getIsSentence() {
+        return isSentence;
+    }
+
+    public void setIsSentence(Integer isSentence) {
+        this.isSentence = isSentence;
+    }
+
+    public Integer getIsCourse() {
+        return isCourse;
+    }
+
+    public void setIsCourse(Integer isCourse) {
+        this.isCourse = isCourse;
+    }
+
+    public Integer getIsData() {
+        return isData;
+    }
+
+    public void setIsData(Integer isData) {
+        this.isData = isData;
+    }
 }

+ 2 - 1
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/ManagerRoleDto.java

@@ -5,6 +5,7 @@ import com.qxgmat.data.dao.entity.Manager;
 import com.qxgmat.data.dao.entity.ManagerRole;
 
 import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
 
 @Dto(entity = ManagerRole.class)
 public class ManagerRoleDto {
@@ -14,7 +15,7 @@ public class ManagerRoleDto {
     @NotEmpty(message = "角色名称不能为空!")
     private String title;
 
-    @NotEmpty(message = "权限不能为空!")
+    @NotNull(message = "权限不能为空!")
     private String[] permissionList;
 
     public Integer getId() {

+ 2 - 1
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserAskCourseDto.java

@@ -4,11 +4,12 @@ import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.UserAskCourse;
 
 import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
 
 @Dto(entity = UserAskCourse.class)
 public class UserAskCourseDto {
 
-    @NotEmpty(message = "提问id不允许为空!")
+    @NotNull(message = "提问id不允许为空!")
     private Integer id;
 
     @NotEmpty(message = "回答不允许为空")

+ 2 - 1
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserAskQuestionDto.java

@@ -4,11 +4,12 @@ import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.UserAskQuestion;
 
 import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
 
 @Dto(entity = UserAskQuestion.class)
 public class UserAskQuestionDto {
 
-    @NotEmpty(message = "提问id不允许为空!")
+    @NotNull(message = "提问id不允许为空!")
     private Integer id;
 
     @NotEmpty(message = "回答不允许为空")

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

@@ -8,12 +8,18 @@ public class UserExaminationGroupDto {
     private String titleEn;
     private String title;
 
+    private String description;
+
+    private Integer isAdapt;
+
     private Integer paperNumber;
 
     private Integer userNumber;
 
     private Integer minTimes;
 
+    private Boolean needService;
+
     private Boolean hasService;
 
     private UserServiceRecordExtendDto unUseRecord;
@@ -89,4 +95,28 @@ public class UserExaminationGroupDto {
     public void setUnUseRecord(UserServiceRecordExtendDto unUseRecord) {
         this.unUseRecord = unUseRecord;
     }
+
+    public Integer getIsAdapt() {
+        return isAdapt;
+    }
+
+    public void setIsAdapt(Integer isAdapt) {
+        this.isAdapt = isAdapt;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Boolean getNeedService() {
+        return needService;
+    }
+
+    public void setNeedService(Boolean needService) {
+        this.needService = needService;
+    }
 }

+ 72 - 36
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserTextbookGroupDto.java

@@ -1,20 +1,32 @@
 package com.qxgmat.dto.response;
 
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.TextbookLibrary;
 import com.qxgmat.data.inline.PaperStat;
 import com.qxgmat.data.inline.UserQuestionStat;
+import com.qxgmat.dto.extend.UserServiceRecordExtendDto;
 import com.qxgmat.dto.extend.UserTextbookGroupExtendDto;
 
+import java.util.Date;
 import java.util.List;
+
+@Dto(entity = TextbookLibrary.class)
 public class UserTextbookGroupDto {
     private Integer id;
 
-    private String titleZh;
+    private Date startDate;
+
+    private Date endDate;
+
+    private Date updateTime;
 
-    private String titleEn;
+    private Integer isLatest;
 
-    private String description;
+    private Boolean needService;
 
-    private String extend;
+    private Boolean hasService;
+
+    private UserServiceRecordExtendDto unUseRecord;
 
     private Integer questionNumber;
 
@@ -36,30 +48,6 @@ public class UserTextbookGroupDto {
         this.id = id;
     }
 
-    public String getTitleZh() {
-        return titleZh;
-    }
-
-    public void setTitleZh(String titleZh) {
-        this.titleZh = titleZh;
-    }
-
-    public String getTitleEn() {
-        return titleEn;
-    }
-
-    public void setTitleEn(String titleEn) {
-        this.titleEn = titleEn;
-    }
-
-    public String getExtend() {
-        return extend;
-    }
-
-    public void setExtend(String extend) {
-        this.extend = extend;
-    }
-
     public Integer getQuestionNumber() {
         return questionNumber;
     }
@@ -92,14 +80,6 @@ public class UserTextbookGroupDto {
         this.userStat = userStat;
     }
 
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
     public Integer getMinTimes() {
         return minTimes;
     }
@@ -115,4 +95,60 @@ public class UserTextbookGroupDto {
     public void setChildren(List<UserTextbookGroupExtendDto> children) {
         this.children = children;
     }
+
+    public Boolean getNeedService() {
+        return needService;
+    }
+
+    public void setNeedService(Boolean needService) {
+        this.needService = needService;
+    }
+
+    public Boolean getHasService() {
+        return hasService;
+    }
+
+    public void setHasService(Boolean hasService) {
+        this.hasService = hasService;
+    }
+
+    public UserServiceRecordExtendDto getUnUseRecord() {
+        return unUseRecord;
+    }
+
+    public void setUnUseRecord(UserServiceRecordExtendDto unUseRecord) {
+        this.unUseRecord = unUseRecord;
+    }
+
+    public Date getStartDate() {
+        return startDate;
+    }
+
+    public void setStartDate(Date startDate) {
+        this.startDate = startDate;
+    }
+
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    public void setEndDate(Date endDate) {
+        this.endDate = endDate;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public Integer getIsLatest() {
+        return isLatest;
+    }
+
+    public void setIsLatest(Integer isLatest) {
+        this.isLatest = isLatest;
+    }
 }

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

@@ -33,6 +33,7 @@ public class UserServiceService extends AbstractService {
      * @return
      */
     public boolean hasService(Integer userId, ServiceKey key){
+        if (key == null) return false;
         Example example = new Example(UserService.class);
         example.and(
                 example.createCriteria()

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

@@ -114,7 +114,7 @@ public class ExaminationService extends AbstractService {
 
     @Transactional
     public ExaminationStruct editPaper(ExaminationStruct entity){
-        entity = examinationStructService.add(entity);
+        entity = examinationStructService.edit(entity);
         if (entity.getLevel() == 3){
             ExaminationPaper paper = examinationPaperService.getByThree(entity.getId());
             if(paper == null){

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

@@ -57,7 +57,9 @@ public class TextbookLibraryService extends AbstractService {
     public TextbookLibrary getSecond(){
         Example example = new Example(TextbookLibrary.class);
         example.orderBy("id").desc();
-        return one(textbookLibraryMapper, example);
+
+        List<TextbookLibrary> list = select(textbookLibraryMapper, example, 2, 1);
+        return list.get(0);
     }
 
     /**