Ver código fonte

fix(all): 修复bug

Go 5 anos atrás
pai
commit
ca91ec6ff2
67 arquivos alterados com 696 adições e 271 exclusões
  1. 20 3
      front/project/admin/routes/course/previewDetail/page.js
  2. 1 1
      front/project/admin/routes/subject/question/page.js
  3. 1 0
      front/project/h5/routes/page/identity/index.js
  4. 22 1
      front/project/h5/routes/page/identity/page.js
  5. BIN
      front/project/www/assets/package1.png
  6. BIN
      front/project/www/assets/package2.png
  7. 6 5
      front/project/www/components/Calculator/index.js
  8. 18 0
      front/project/www/components/Header/index.js
  9. 8 0
      front/project/www/components/Header/index.less
  10. 1 0
      front/project/www/components/Item/index.less
  11. 1 1
      front/project/www/components/Note/index.js
  12. 47 2
      front/project/www/components/PayModal/index.js
  13. 10 1
      front/project/www/index.js
  14. 1 0
      front/project/www/routes/course/data/index.js
  15. 1 1
      front/project/www/routes/course/dataDetail/index.js
  16. 1 1
      front/project/www/routes/course/detail/index.less
  17. 1 1
      front/project/www/routes/course/experience/index.js
  18. 1 1
      front/project/www/routes/course/experienceDetail/index.js
  19. 0 1
      front/project/www/routes/course/online/page.js
  20. 2 2
      front/project/www/routes/course/packageDetail/page.js
  21. 55 29
      front/project/www/routes/examination/list/page.js
  22. 23 14
      front/project/www/routes/exercise/list/page.js
  23. 3 0
      front/project/www/routes/exercise/main/index.less
  24. 1 3
      front/project/www/routes/exercise/main/page.js
  25. 6 0
      front/project/www/routes/my/course/index.less
  26. 61 29
      front/project/www/routes/my/course/page.js
  27. 15 1
      front/project/www/routes/my/main/page.js
  28. 1 1
      front/project/www/routes/my/report/page.js
  29. 4 0
      front/project/www/routes/page/ready/index.less
  30. 5 2
      front/project/www/routes/page/ready/page.js
  31. 3 3
      front/project/www/routes/paper/process/base/index.js
  32. 10 2
      front/project/www/routes/paper/process/page.js
  33. 3 3
      front/project/www/routes/paper/process/sentence/index.js
  34. 10 7
      front/project/www/routes/paper/question/detail/index.js
  35. 13 11
      front/project/www/routes/paper/report/page.js
  36. 5 3
      front/project/www/routes/question/search/page.js
  37. 4 1
      front/project/www/routes/textbook/list/page.js
  38. 2 2
      front/project/www/stores/textbook.js
  39. 9 13
      front/src/components/FileUpload/index.js
  40. 3 3
      front/src/services/Tools.js
  41. 7 2
      server/data/src/main/java/com/qxgmat/data/relation/QuestionNoRelationMapper.java
  42. 36 8
      server/data/src/main/java/com/qxgmat/data/relation/mapping/QuestionNoRelationMapper.xml
  43. 3 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/TextbookPaperRelationMapper.xml
  44. 21 5
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/PreviewController.java
  45. 4 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  46. 4 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  47. 22 8
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  48. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/CourseExtendDto.java
  49. 11 4
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserCourseAppointmentInfoDto.java
  50. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/BasePaperExtendDto.java
  51. 28 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/PaperBaseDto.java
  52. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java
  53. 26 7
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExaminationService.java
  54. 45 19
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExerciseService.java
  55. 2 3
      server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java
  56. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  57. 2 3
      server/gateway-api/src/main/java/com/qxgmat/service/extend/PreviewService.java
  58. 38 15
      server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java
  59. 4 3
      server/gateway-api/src/main/java/com/qxgmat/service/extend/TextbookService.java
  60. 3 3
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ToolsService.java
  61. 4 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExercisePaperService.java
  62. 14 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewAssignService.java
  63. 0 14
      server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewPaperService.java
  64. 11 25
      server/gateway-api/src/main/java/com/qxgmat/service/inline/QuestionNoService.java
  65. 1 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderCheckoutService.java
  66. 9 0
      server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java
  67. 1 1
      server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java

+ 20 - 3
front/project/admin/routes/course/previewDetail/page.js

@@ -64,7 +64,12 @@ export default class extends Page {
       .then(result => {
         const { questionNoIds } = result;
         generateSearch('courseId', {}, this, (search) => {
-          return Course.list(Object.assign({ courseModule: result.courseModule }, search));
+          return Course.list(Object.assign({ courseModule: result.courseModule }, search)).then(r => {
+            if (r.list) {
+              r.list = r.list.filter(row => !row.vsType || row.vsType === 'system');
+            }
+            return r;
+          });
         }, (row) => {
           return {
             title: row.title,
@@ -207,10 +212,22 @@ export default class extends Page {
       return User.listCourseAppointment({ courseId: data.courseId, userId: info.userId, startTime: info.startTime, endTime: info.endTime }).then(result => {
         if (result.total === 0) {
           return Promise.reject(new Error('没有找到相关的预约记录'));
-        } if (result.total > 1) {
+        }
+        const list = result.list.filter((row) => {
+          if (new Date(row.endTime).getTime() < new Date().getTime()) return false;
+          if (row.previewAssign) return false;
+          if (!row.course) return false;
+          if (row.course.courseModule !== 'vs') return false;
+          if (row.course.vsType !== 'system') return false;
+          return true;
+        });
+        if (list.length > 1) {
           return Promise.reject(new Error('时间段内存在多条预约记录'));
         }
-        return Preview.addAssign(Object.assign({ paperModule: data.paperModule, paperId: data.id, courseModule: data.courseModule, courseAppointment: result.list[0].id }, info)).then(() => {
+        if (list.length === 0) {
+          return Promise.reject(new Error('没有找到相关的预约记录'));
+        }
+        return Preview.addAssign(Object.assign({ paperModule: data.paperModule, paperId: data.id, courseModule: data.courseModule, courseAppointment: list[0].id }, info)).then(() => {
           asyncSMessage('添加成功!');
           this.refresh();
         });

+ 1 - 1
front/project/admin/routes/subject/question/page.js

@@ -424,7 +424,7 @@ export default class extends Page {
 
   bindRelationQuestion(relationQuestion) {
     generateSearch('relationQuestion', { mode: 'multiple' }, this, (search) => {
-      return this.searchQuestion(search);
+      return this.searchQuestion(Object.assign(search, { questionType: 'rc' }));
     }, (row) => {
       return {
         title: row.title,

+ 1 - 0
front/project/h5/routes/page/identity/index.js

@@ -2,6 +2,7 @@ export default {
   path: '/identity',
   key: 'identity',
   title: '身份认证',
+  repeat: true,
   needLogin: true,
   component() {
     return import('./page');

+ 22 - 1
front/project/h5/routes/page/identity/page.js

@@ -6,7 +6,7 @@ import { asyncSMessage } from '@src/services/AsyncTools';
 import { dataURLtoBlob, formatDate } from '@src/services/Tools';
 import Icon from '../../../components/Icon';
 import { Common } from '../../../stores/common';
-// import { Main } from '../../../stores/main';
+// import { User } from '../../../stores/user';
 import { My } from '../../../stores/my';
 
 export default class extends Page {
@@ -21,6 +21,13 @@ export default class extends Page {
     });
   }
 
+  initData() {
+    console.log(this.props.user);
+    if (this.props.user.info.bindReal) {
+      this.setState({ state: 'already' });
+    }
+  }
+
   submitFront() {
     const self = this;
     if (this.wx) {
@@ -157,9 +164,23 @@ export default class extends Page {
     );
   }
 
+  renderAlready() {
+    return (
+      <div className="finish">
+        <div className="icon">
+          <Icon type="check-circle" />
+        </div>
+        <div className="title">已通过</div>
+      </div>
+    );
+  }
+
   renderView() {
     const { state } = this.state;
+    console.log(state);
     switch (state) {
+      case 'already':
+        return this.renderAlready();
       case 'finish':
         return this.renderFinish();
       default:

BIN
front/project/www/assets/package1.png


BIN
front/project/www/assets/package2.png


+ 6 - 5
front/project/www/components/Calculator/index.js

@@ -50,6 +50,8 @@ export default class Calculator extends Component {
         value = this.f(Number(value), Number(this.valueList[i + 1]), this.typeList[i]);
       }
     }
+    this.valueList = [];
+    this.typeList = [];
     return value;
   }
 
@@ -59,8 +61,7 @@ export default class Calculator extends Component {
       case '=':
         if (this.change && this.typeList.length > 0) {
           this.valueList.push(value);
-          this.setState({ value: this.compute() });
-          this.change = false;
+          this.setState({ value: `${this.compute()}` });
         }
         break;
       case '+':
@@ -72,7 +73,7 @@ export default class Calculator extends Component {
           this.typeList.push(text);
           this.change = false;
           if (this.valueList.length > 1) {
-            this.setState({ value: this.compute() });
+            this.setState({ value: `${this.compute()}` });
           }
         }
         break;
@@ -96,10 +97,10 @@ export default class Calculator extends Component {
           if (value !== '0') {
             this.setState({ value: `${value}${text}` });
           } else {
-            this.setState({ value: text });
+            this.setState({ value: `${text}` });
           }
         } else {
-          this.setState({ value: text });
+          this.setState({ value: `${text}` });
           this.change = true;
         }
         break;

+ 18 - 0
front/project/www/components/Header/index.js

@@ -24,6 +24,24 @@ function Header(props) {
         <div className="center">
           <div className="tabs">
             {tabs.map(item => {
+              if (item.children && item.children.length > 0) {
+                return <Dropdown
+                  trigger="hover"
+                  overlayClassName="header-user-overlay"
+                  overlay={
+                    <div className="list" >
+                      {item.children.map(row => {
+                        return <Link class="item" to={row.path}>
+                          <div className={`${active === row.key ? 'active' : ''}`}>{row.name}</div>
+                        </Link>;
+                      })}
+                    </div>
+                  }>
+                  <Link to={item.path}>
+                    <div className={`tab ${item.children.map(row => row.key).indexOf(active) >= 0 ? 'active' : ''}`}>{item.name}</div>
+                  </Link>
+                </Dropdown>;
+              }
               return (
                 <Link to={item.path}>
                   <div className={`tab ${active === item.key ? 'active' : ''}`}>{item.name}</div>

+ 8 - 0
front/project/www/components/Header/index.less

@@ -62,6 +62,7 @@
       .tab:hover {
         color: #57A4FEFF;
       }
+
       .tab.active {
         color: @theme_color;
         font-weight: 500;
@@ -143,5 +144,12 @@
     .item:last-child {
       border-bottom: none;
     }
+
+    .item .active {}
+
+    a.item {
+      display: block;
+      color: #303036 !important;
+    }
   }
 }

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

@@ -152,6 +152,7 @@
   border-radius: 4px;
   border: 1px solid rgba(229, 229, 229, 1);
   padding: 10px;
+  margin: 0 20px;
 
   .action {
     margin-bottom: 10px;

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

@@ -13,7 +13,7 @@ export default class Note extends Component {
       <div className={`note-detail ${theme}`}>
         <Assets className="note-avatar" name="sun_blue" src={data.userId ? info.avatar : teacher.avatar} />
         {onAction && actionList && <More menu={actionList} onClick={onAction} />}
-        {onAction && reply && <GIcon className="reply" onClick={() => onAction('reply')} />}
+        {onAction && reply && <GIcon className="reply" onClick={() => onAction({ key: 'reply' })} />}
         <div className="t-2 t-s-12 m-b-1">
           {data.userId ? info.nickname : teacher.realname} <span className="t-3">{formatDate(data.createTime, 'YYYY-MM-DD HH:mm')}</span>
         </div>

+ 47 - 2
front/project/www/components/PayModal/index.js

@@ -102,7 +102,11 @@ export class PayModal extends Component {
       } else if (order.checkouts.length === 1 && checkout.productType === 'data') {
         this.setState({ show: false, showDataEnd: true, order: result, checkout });
       } else if (order.checkouts.length === 1) {
-        this.setState({ show: false, showEnd: true, order: result, checkout });
+        if (checkout.productType === 'course' && checkout.number > 0) {
+          this.setState({ show: false, showVsEnd: true, order: result, checkout });
+        } else {
+          this.setState({ show: false, showEnd: true, order: result, checkout });
+        }
       } else {
         User.closePay();
       }
@@ -128,19 +132,27 @@ export class PayModal extends Component {
   }
 
   read() {
+    Order.allCheckout();
     User.closePay();
     linkTo('/my/data');
   }
 
+  vs() {
+    Order.allCheckout();
+    User.closePay();
+    linkTo('/my/course?tab=vs');
+  }
+
   close() {
     const { showEnd, showBank } = this.state;
+    Order.allCheckout();
     User.closePay(showEnd || showBank ? null : new Error('支付失败'));
     this.setState({ show: true, pay: null, showEnd: false, showBank: false });
   }
 
   render() {
     const { needPay, order } = this.props.user;
-    const { show, showBank, showEnd, showDataEnd, showVipEnd, contract, payInfo } = this.state;
+    const { show, showBank, showEnd, showVsEnd, showDataEnd, showVipEnd, contract, payInfo } = this.state;
     if (!needPay) return [];
     return [
       showBank && <PayKBankModal
@@ -157,6 +169,14 @@ export class PayModal extends Component {
         onClose={() => this.close()}
         onConfirm={() => this.open()}
       />,
+      showVsEnd && <PayMEndModal
+        show
+        order={order}
+        checkout={order.checkouts[0]}
+        onCancel={() => this.close()}
+        onClose={() => this.close()}
+        onConfirm={() => this.vs()}
+      />,
       showDataEnd && <PayMDataEndModal
         show
         order={order}
@@ -425,6 +445,31 @@ export class PayMEndModal extends Component {
   }
 }
 
+export class PayVsEndModal extends Component {
+  render() {
+    const { show, onConfirm, onCancel, checkout = {} } = this.props;
+    const { info } = checkout;
+    return (
+      <Modal
+        show={show}
+        width={630}
+        title="付款成功"
+        confirmText="立即开通"
+        cancelText="稍后开通"
+        onConfirm={onConfirm}
+        onCancel={onCancel}
+      >
+        <div className="t-2 ws-pl">
+          <Icon className="t-5 m-r-5" type="check" />{info.result}
+        </div>
+        {info.tips && <div style={{ bottom: 10, left: 0 }} className="p-a t-3 t-s-14">
+          *{info.tips}
+        </div>}
+      </Modal>
+    );
+  }
+}
+
 export class PayMDataEndModal extends Component {
   render() {
     const { show, onConfirm, onCancel, checkout = {} } = this.props;

+ 10 - 1
front/project/www/index.js

@@ -13,6 +13,15 @@ export default {
     { key: 'examination', name: 'CAT模考', path: '/examination' },
     { key: 'question', name: '题库', path: '/question' },
     { key: 'textbook', name: '换库机经', path: '/textbook' },
-    { key: 'course', name: '课堂', path: '/course' },
+    {
+      key: 'course',
+      name: '课堂',
+      path: '/course',
+      children: [
+        { key: 'course', name: '在线课堂', path: '/course' },
+        { key: 'data', name: '原创资料', path: '/course/data' },
+        { key: 'experience', name: '学员表现', path: '/course/experience' },
+      ],
+    },
   ],
 };

+ 1 - 0
front/project/www/routes/course/data/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'course-data',
   title: '课堂-资料',
   needLogin: false,
+  tab: 'data',
   component() {
     return import('./page');
   },

+ 1 - 1
front/project/www/routes/course/dataDetail/index.js

@@ -4,7 +4,7 @@ export default {
   title: '课堂-资料详情',
   needLogin: false,
   repeat: true,
-  tab: 'course',
+  tab: 'data',
   component() {
     return import('./page');
   },

+ 1 - 1
front/project/www/routes/course/detail/index.less

@@ -51,7 +51,7 @@
             display: none;
             position: absolute;
             font-size: 2%;
-            color: #fff;
+            color: rgba(90, 90, 90, 0.47);
             width: 100%;
             user-select: none;
             transition-timing-function: linear;

+ 1 - 1
front/project/www/routes/course/experience/index.js

@@ -4,7 +4,7 @@ export default {
   title: '课堂-心经',
   needLogin: false,
   repeat: true,
-  tab: 'course',
+  tab: 'experience',
   component() {
     return import('./page');
   },

+ 1 - 1
front/project/www/routes/course/experienceDetail/index.js

@@ -4,7 +4,7 @@ export default {
   title: '课堂-心经详情',
   needLogin: false,
   repeat: true,
-  tab: 'course',
+  tab: 'experience',
   component() {
     return import('./page');
   },

+ 0 - 1
front/project/www/routes/course/online/page.js

@@ -31,7 +31,6 @@ export default class extends Page {
       .then(result => {
         this.setState({ base: result });
       });
-
     Main.courseStruct().then(result => {
       const courseStruct = result.map(row => {
         return {

+ 2 - 2
front/project/www/routes/course/packageDetail/page.js

@@ -99,10 +99,10 @@ export default class extends Page {
           <div className="main-title">配套服务</div>
           <div className="list">
             <div className="other-item d-i-b">
-              <Assets name="" />
+              <Assets name="package1" />
             </div>
             <div className="other-item d-i-b">
-              <Assets name="" />
+              <Assets name="package2" />
             </div>
           </div>
           {hasGift && <div className="main-title">赠送服务</div>}

+ 55 - 29
front/project/www/routes/examination/list/page.js

@@ -25,22 +25,19 @@ export default class extends Page {
             progress = formatPercent(record.report.userNumber, record.report.questionNumber);
           }
           return [
-            <div className="table-row" style={{ paddingRight: 40 }}>
+            <div hidden={this.state.finish === '1' && progress < 100} className="table-row" style={{ paddingRight: 40 }}>
               <div className="night f-s-16">
                 {record.title}
-                {record.paper && record.paper.paperNo > 1 ? String.fromCharCode(64 + record.paper.paperNo - 1) : ''}
+                {record.prevReport ? String.fromCharCode(65) : ''}
               </div>
               <div>
                 <ProgressText progress={progress} size="small" />
               </div>
             </div>,
             this.state.showPrev && record.prevReport && (
-              <div className="table-row prev t-3">
-                <div className="night f-s-16">
+              <div hidden={Number(this.state.finish) === 0} className="table-row prev t-3">
+                <div className="f-s-16">
                   {record.title}
-                  {record.prevPaper && record.prevPaper.paperNo > 0
-                    ? String.fromCharCode(64 + record.prevPaper.paperNo)
-                    : ''}
                 </div>
               </div>
             ),
@@ -52,16 +49,20 @@ export default class extends Page {
         width: 100,
         align: 'left',
         render: record => {
+          let progress = 0;
+          if (record.report) {
+            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+          }
           return [
-            <div className="table-row">
+            <div hidden={this.state.finish === '1' && progress < 100} className="table-row">
               <div className="night f-s-16 f-w-b">
                 {record.report && record.report.score ? `${record.report.score.totalScore || 0}分${record.report.score.totalRank || 0}th` : '-分-th'}
               </div>
               <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.totalScore / record.totalTimes) : '-'}分</div>
             </div>,
             this.state.showPrev && record.prevReport && (
-              <div className="table-row prev t-3">
-                <div className="night f-s-16 f-w-b">
+              <div hidden={Number(this.state.finish) === 0} className="table-row prev t-3">
+                <div className="f-s-16 f-w-b">
                   {record.prevReport.score.totalScore}分{record.prevReport.score.totalRank}th
                 </div>
                 <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.secondTotalScore / record.secondTotalTimes) : '-'}分</div>
@@ -75,16 +76,20 @@ export default class extends Page {
         width: 100,
         align: 'left',
         render: record => {
+          let progress = 0;
+          if (record.report) {
+            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+          }
           return [
-            <div className="table-row">
+            <div hidden={this.state.finish === '1' && progress < 100} className="table-row">
               <div className="night f-s-16 f-w-b">
                 {record.report && record.report.score ? `${record.report.score.verbalScore || 0}分${record.report.score.verbalRank || 0}th` : '-分-th'}
               </div>
               <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.verbalScore / record.totalTimes) : '-'}分</div>
             </div>,
             this.state.showPrev && record.prevReport && (
-              <div className="table-row prev t-3">
-                <div className="night f-s-16 f-w-b">
+              <div hidden={Number(this.state.finish) === 0} className="table-row prev t-3">
+                <div className="f-s-16 f-w-b">
                   {record.prevReport.score.verbalScore}分{record.prevReport.score.verbalRank}th
                 </div>
                 <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.secondVerbalScore / record.secondTotalTimes) : '-'}分</div>
@@ -98,15 +103,19 @@ export default class extends Page {
         width: 100,
         align: 'left',
         render: record => {
-          return [<div className="table-row">
+          let progress = 0;
+          if (record.report) {
+            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+          }
+          return [<div hidden={this.state.finish === '1' && progress < 100} className="table-row">
             <div className="night f-s-16 f-w-b">
               {record.report && record.report.score ? `${record.report.score.quantScore || 0}分${record.report.score.quantRank || 0}th` : '-分-th'}
             </div>
             <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.quantScore / record.totalTimes) : '-'}分</div>
           </div>,
           this.state.showPrev && record.prevReport && (
-            <div className="table-row prev t-3">
-              <div className="night f-s-16 f-w-b">
+            <div hidden={Number(this.state.finish) === 0} className="table-row prev t-3">
+              <div className="f-s-16 f-w-b">
                 {record.prevReport.score.quantScore}分{record.prevReport.score.quantRank}th
               </div>
               <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.secondQuantScore / record.secondTotalTimes) : '-'}分</div>
@@ -120,15 +129,19 @@ export default class extends Page {
         width: 100,
         align: 'left',
         render: record => {
-          return [<div className="table-row">
+          let progress = 0;
+          if (record.report) {
+            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+          }
+          return [<div hidden={this.state.finish === '1' && progress < 100} className="table-row">
             <div className="night f-s-16 f-w-b">
               {record.report && record.report.score ? `${record.report.score.irScore || 0}分${record.report.score.irRank || 0}th` : '-分-th'}
             </div>
             <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.irScore / record.totalTimes) : '-'}分</div>
           </div>,
           this.state.showPrev && record.prevReport && (
-            <div className="table-row prev t-3">
-              <div className="night f-s-16 f-w-b">
+            <div hidden={Number(this.state.finish) === 0} className="table-row prev t-3">
+              <div className="f-s-16 f-w-b">
                 {record.prevReport.score.irScore}分{record.prevReport.score.irRank}th
               </div>
               <div className="f-s-12">全站: {record.totalTimes > 0 ? Math.round(record.secondIrScore / record.secondTotalTimes) : '-'}分</div>
@@ -142,12 +155,16 @@ export default class extends Page {
         width: 100,
         align: 'left',
         render: record => {
-          return [<div className="table-row">
+          let progress = 0;
+          if (record.report) {
+            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+          }
+          return [<div hidden={this.state.finish === '1' && progress < 100} className="table-row">
             <div>{record.report && formatDate(record.report.updateTime, 'YYYY-MM-DD')}</div>
             <div>{record.report && formatDate(record.report.updateTime, 'HH:mm')}</div>
           </div>,
           this.state.showPrev && record.prevReport && (
-            <div className="table-row prev t-3">
+            <div hidden={Number(this.state.finish) === 0} className="table-row prev t-3">
               <div>{formatDate(record.prevReport.updateTime, 'YYYY-MM-DD')}</div>
               <div>{formatDate(record.prevReport.updateTime, 'HH:mm')}</div>
             </div>
@@ -160,7 +177,11 @@ export default class extends Page {
         width: 110,
         align: 'left',
         render: record => {
-          return [<div className="table-row p-t-1">
+          let progress = 0;
+          if (record.report) {
+            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+          }
+          return [<div hidden={this.state.finish === '1' && progress < 100} className="table-row p-t-1">
             {!record.report && (
               <IconButton
                 className="m-r-5"
@@ -182,7 +203,7 @@ export default class extends Page {
             )}
           </div>,
           this.state.showPrev && record.prevReport && (
-            <div className="table-row prev" />
+            <div hidden={Number(this.state.finish) === 0} className="table-row prev" />
           ),
           ];
         },
@@ -192,7 +213,11 @@ export default class extends Page {
         width: 50,
         align: 'right',
         render: record => {
-          return [<div className="table-row p-t-1">
+          let progress = 0;
+          if (record.report) {
+            progress = formatPercent(record.report.userNumber, record.report.questionNumber);
+          }
+          return [<div hidden={this.state.finish === '1' && progress < 100} className="table-row p-t-1">
             {record.report && !!record.report.isFinish && (
               <IconButton
                 className="m-r-5"
@@ -205,7 +230,7 @@ export default class extends Page {
             )}
           </div>,
           this.state.showPrev && (
-            <div className="prev">
+            <div hidden={Number(this.state.finish) === 0} className="prev">
               {record.prevReport && (
                 <IconButton
                   type="report"
@@ -664,14 +689,15 @@ export default class extends Page {
                 list: [{ key: '0', title: '未完成' }, { key: '1', title: '已完成' }],
                 onChange: item => {
                   if (item.key === finish) {
-                    this.search({ finish: null });
+                    this.search({ finish: null }, false);
                   } else if (item.key === '0') {
-                    this.search({ finish: 0 });
+                    this.search({ finish: '0' }, false);
                   } else if (item.key === '1') {
-                    this.search({ finish: 1 });
+                    this.search({ finish: '1' }, false);
                   } else {
-                    this.search({ finish: null });
+                    this.search({ finish: null }, false);
                   }
+                  this.initData();
                 },
               },
             ]}

+ 23 - 14
front/project/www/routes/exercise/list/page.js

@@ -20,6 +20,26 @@ const LOGIC_ERROR = 'error';
 
 export default class extends Page {
   initState() {
+    this.logics = [{
+      key: LOGIC_NO,
+      title: '按顺序练习',
+    }, {
+      key: LOGIC_PLACE,
+      title: '按考点练习',
+    }, {
+      key: LOGIC_DIFFICULT,
+      title: '按难度练习',
+    }, {
+      key: LOGIC_ERROR,
+      title: '按易错度练习',
+    }];
+    this.rcLogics = [{
+      key: LOGIC_NO,
+      title: '按顺序练习',
+    }, {
+      key: LOGIC_DIFFICULT,
+      title: '按难度练习',
+    }];
     this.columns = [{
       title: '练习册',
       width: 250,
@@ -121,19 +141,7 @@ export default class extends Page {
     return {
       logic: LOGIC_NO,
       logicExtend: '',
-      logics: [{
-        key: LOGIC_NO,
-        title: '按顺序练习',
-      }, {
-        key: LOGIC_PLACE,
-        title: '按考点练习',
-      }, {
-        key: LOGIC_DIFFICULT,
-        title: '按难度练习',
-      }, {
-        key: LOGIC_ERROR,
-        title: '按易错度练习',
-      }],
+      logics: [],
     };
   }
 
@@ -154,7 +162,8 @@ export default class extends Page {
         return row;
       });
       this.inited = true;
-      this.setState({ navs });
+      const logics = tab2 === 'rc' ? this.rcLogics : this.logics;
+      this.setState({ navs, logics });
     });
   }
 

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

@@ -73,12 +73,15 @@
       margin: 3px;
     }
   }
+
   .bottom-info {
     background: #fafafa;
     padding: 50px 0;
+
     .content {
       .other-answer-carousel {
         background: none;
+        padding-top: 0;
       }
     }
   }

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

@@ -1162,9 +1162,7 @@ export default class extends Page {
                   User.needLogin()
                     .then(() => {
                       if (item.type === 'paper') {
-                        if (item.progress === 0) {
-                          Question.startLink('exercise', item);
-                        } else if (item.progress === 100) {
+                        if (item.progress === 0 || item.progress === 100 || !item.report) {
                           Question.startLink('exercise', item);
                         } else {
                           Question.continueLink('exercise', item);

+ 6 - 0
front/project/www/routes/my/course/index.less

@@ -95,6 +95,7 @@
       .continue {
         line-height: 14px;
         margin-bottom: 10px;
+        text-align: left;
 
         .assets {
           vertical-align: top;
@@ -339,6 +340,11 @@
       }
 
       .time-line {
+        .fix-button {
+          position: relative;
+          top: -10px;
+        }
+
         .time-line-item {
 
           padding: 13px 0;

+ 61 - 29
front/project/www/routes/my/course/page.js

@@ -51,7 +51,7 @@ export default class extends Page {
     if (row.papers) {
       row.papers.forEach(paper => {
         if (paper.courseNo) row.paperMap[paper.courseNo] = paper;
-        if (paper.appointment) row.appointmentPaperMap[paper.appointment] = paper;
+        if (paper.courseAppointment) row.appointmentPaperMap[paper.courseAppointment] = paper;
       });
     }
     row.progressMap = {};
@@ -85,10 +85,12 @@ export default class extends Page {
     }
     // 如果已经最新预约结束,则添加一个空记录作为最新预约
     if (row.appointments) {
-      row.appointments.forEach(r => {
+      row.appointments = row.appointments.map(r => {
         r.paper = row.appointmentPaperMap[r.id];
         r.noteList = (row.comments || []).filter(c => c.type === 'note' && c.appointmentId === r.id);
         r.supplyList = (row.comments || []).filter(c => c.type === 'supply' && c.appointmentId === r.id);
+
+        return r;
       });
       // 是否是最后一课时,是否过预约时间
       const last = row.appointments.length - 1;
@@ -101,6 +103,12 @@ export default class extends Page {
           }
         }
       }
+      row.finishAppointment = row.appointments.filter(r => r.id && (new Date(r.endTime).getTime() < new Date().getTime() || r.isFinish));
+    } else {
+      row.finishAppointment = [];
+    }
+    if (!row.teacher) {
+      row.teacher = { realname: '' };
     }
 
     row.days = new Date(row.userEndTime);
@@ -245,14 +253,13 @@ export default class extends Page {
   submitAppointmentComment(data, type) {
     data.type = type;
     if (data.id) {
-      My.editAppointmentComment(data).then(() => {
-        this.refreshDetail(data.recordId);
-      });
-    } else {
-      My.addAppointmentComment(data).then(() => {
+      return My.editAppointmentComment(data).then(() => {
         this.refreshDetail(data.recordId);
       });
     }
+    return My.addAppointmentComment(data).then(() => {
+      this.refreshDetail(data.recordId);
+    });
   }
 
   deleteAppointmentComment(row) {
@@ -457,7 +464,9 @@ export default class extends Page {
           title="上传笔记"
           width={630}
           confirmText="提交"
-          onConfirm={() => this.submitAppointmentComment(note, 'note')}
+          onConfirm={() => this.submitAppointmentComment(note, 'note').then(() => {
+            this.setState({ showUploadNote: false, note: {} });
+          })}
           onCancel={() => this.setState({ showUploadNote: false, note: {} })}
         >
           <textarea
@@ -480,10 +489,11 @@ export default class extends Page {
             </div>
             <FileUpload
               type="none"
+              accept={['image/x-png', 'image/jpeg', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '.zip', '.rar', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel']}
               onDragEnter={() => this.setState({ draging: true })}
               onDragLeave={() => this.setState({ draging: false })}
               onUpload={({ file }) => {
-                Common.upload(file).then(result => {
+                return Common.upload(file).then(result => {
                   note.file = result.url;
                   note.name = file.name;
                   this.setState({ note });
@@ -522,7 +532,9 @@ export default class extends Page {
           title="上传留言"
           width={630}
           confirmText="提交"
-          onConfirm={() => this.submitAppointmentComment(supply, 'supply')}
+          onConfirm={() => this.submitAppointmentComment(supply, 'supply').then(() => {
+            this.setState({ showUploadSupply: false, supply: {} });
+          })}
           onCancel={() => this.setState({ showUploadSupply: false, supply: {} })}
         >
           <textarea
@@ -545,10 +557,11 @@ export default class extends Page {
             </div>
             <FileUpload
               type="none"
+              accept={['image/x-png', 'image/jpeg', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '.zip', '.rar', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel']}
               onDragEnter={() => this.setState({ draging: true })}
               onDragLeave={() => this.setState({ draging: false })}
               onUpload={({ file }) => {
-                Common.upload(file).then(result => {
+                return Common.upload(file).then(result => {
                   supply.file = result.url;
                   supply.name = file.name;
                   this.setState({ supply });
@@ -1201,7 +1214,9 @@ class CourseVs extends Component {
                     tip="Restart"
                     onClick={() => {
                       User.needLogin().then(() => {
-                        Question.restart('preview', text);
+                        return Question.restart(text.paper.id);
+                      }).then(() => {
+                        this.props.refreshDetail();
                       });
                     }}
                   />
@@ -1213,7 +1228,7 @@ class CourseVs extends Component {
                     tip="Report"
                     onClick={() => {
                       User.needLogin().then(() => {
-                        Question.reportLink('preview', text);
+                        Question.reportLink(text);
                       });
                     }}
                   />
@@ -1305,8 +1320,8 @@ class CourseVs extends Component {
       list: this.listMap[this.props.data.course.vsType],
       showTips:
         props.data.commentTips === 0 &&
-        (props.data.appointments.length === Math.ceil(props.data.number / 2) ||
-          props.data.appointments.length === props.data.number) && props.data.number > 1,
+        (props.data.finishAppointment.length === Math.ceil(props.data.number / 2) ||
+          props.data.finishAppointment.length === props.data.number) && props.data.number > 1,
     };
   }
 
@@ -1386,13 +1401,13 @@ class CourseVs extends Component {
             <div className="item">
               <GIcon name="speed-block" active noHover />
               <div className="text">
-                <span>{data.appointments.length}</span>/{data.number}
+                <span>{data.finishAppointment.length}</span>/{data.number}
               </div>
             </div>
             <div className="item">
               <GIcon name="time-block" active noHover />
               <div className="text">
-                <span>{data.appointments.length > 0 ? parseInt(data.totalDays / data.appointments.length, 10) : 0}</span>天/课时
+                <span>{data.finishAppointment.length > 0 ? Math.max(1, parseInt(data.totalDays / data.finishAppointment.length, 10)) : data.totalDays}</span>天/课时
               </div>
             </div>
           </div>
@@ -1506,13 +1521,13 @@ class CourseVs extends Component {
             <div className="item">
               <GIcon name="speed-block" active noHover />
               <div className="text">
-                <span>{data.appointments.length}</span>/{data.number}
+                <span>{data.finishAppointment.length}</span>/{data.number}
               </div>
             </div>
             <div className="item">
               <GIcon name="time-block" active noHover />
               <div className="text">
-                <span>{data.appointments.length > 0 ? parseInt(data.totalDays / data.appointments.length, 10) : 0}</span>天/课时
+                <span>{data.finishAppointment.length > 0 ? Math.max(1, parseInt(data.totalDays / data.finishAppointment.length, 10)) : data.totalDays}</span>天/课时
               </div>
             </div>
           </div>
@@ -1583,13 +1598,13 @@ class CourseVs extends Component {
             <div className="item">
               <GIcon name="speed-block" active noHover />
               <div className="text">
-                <span>{data.appointments.length}</span>/{data.number}
+                <span>{data.finishAppointment.length}</span>/{data.number}
               </div>
             </div>
             <div className="item">
               <GIcon name="time-block" active noHover />
               <div className="text">
-                <span>{data.appointments.length > 0 ? parseInt(data.totalDays / data.appointments.length, 10) : 0}</span>天/课时
+                <span>{data.finishAppointment.length > 0 ? Math.max(1, parseInt(data.totalDays / data.finishAppointment.length, 10)) : data.totalDays}</span>天/课时
               </div>
             </div>
           </div>
@@ -1651,9 +1666,9 @@ class CourseVs extends Component {
 
   renderTable() {
     const { data = {} } = this.props;
-    const { appointments = [] } = data;
+    const { finishAppointment = [] } = data;
     return (
-      <UserTable size="small" columns={this.columns[data.course.vsType]} data={appointments.filter(row => row.id)} />
+      <UserTable size="small" columns={this.columns[data.course.vsType]} data={finishAppointment} />
     );
   }
 }
@@ -1728,6 +1743,8 @@ class TimeLineItem extends Component {
           case 'not':
             return (
               <FileUpload
+                type="image"
+                accept={['image/x-png', 'image/jpeg', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '.zip', '.rar', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel']}
                 onUpload={({ file }) => {
                   return Common.upload(file).then(result => onUploadQuestion(appointment, result.url, file.name));
                 }
@@ -1842,7 +1859,7 @@ class TimeLineItem extends Component {
                         actionList={
                           row.userId ? [{ key: 'edit', label: '编辑' }, { key: 'delete', label: '删除' }] : null
                         }
-                        onAction={key => {
+                        onAction={({ key }) => {
                           switch (key) {
                             case 'edit':
                               onUploadNote(appointment, row);
@@ -1906,7 +1923,7 @@ class TimeLineItem extends Component {
                         actionList={
                           row.userId ? [{ key: 'edit', label: '编辑' }, { key: 'delete', label: '删除' }] : null
                         }
-                        onAction={key => {
+                        onAction={({ key }) => {
                           switch (key) {
                             case 'edit':
                               onUploadSupply(appointment, row);
@@ -1948,10 +1965,25 @@ class TimeLineItem extends Component {
         ];
       case '7':
         return (
-          <ProgressText
-            progress={paper ? formatPercent(paper.report.userNumber, paper.report.questionNumber) : 0}
-            size="small"
-          />
+          <div>
+            <div className="d-i-b"><ProgressText
+              progress={paper && paper.report ? formatPercent(paper.report.userNumber, paper.report.questionNumber) : 0}
+              size="small"
+            />
+            </div>
+            {paper && !paper.report && (
+              <IconButton
+                className="m-l-2 fix-button"
+                type="start"
+                tip="Start"
+                onClick={() => {
+                  User.needLogin().then(() => {
+                    Question.startLink('preview', paper);
+                  });
+                }}
+              />
+            )}
+          </div>
         );
       default:
         return <div />;

+ 15 - 1
front/project/www/routes/my/main/page.js

@@ -96,6 +96,18 @@ class LogItem extends Component {
               data={[row.score]}
             />];
         })}
+        {detail.length === 0 &&
+          <UserTable
+            header={false}
+            size="small"
+            even="odd"
+            columns={[
+              { key: 'type', width: 100 },
+              { key: 'title', width: 310 },
+              { key: 'time', width: 120 },
+            ]}
+            data={[]}
+          />}
         <div className="t-r">
           <Link to="/examination">继续练习></Link>
         </div>
@@ -589,7 +601,9 @@ export default class extends Page {
               name="user-realname"
               active={info.bindReal}
               onClick={() => {
-                this.setState({ showReal: true });
+                if (!info.bindReal) {
+                  this.setState({ showReal: true });
+                }
               }}
             />
             <GIcon

+ 1 - 1
front/project/www/routes/my/report/page.js

@@ -229,7 +229,7 @@ export default class extends Page {
         title: '模考名称',
         fixSort: true,
         render: (text, record) => {
-          return text + (record.paper && record.paper.paperNo > 1 ? String.fromCharCode(64 + record.paper.paperNo - 1) : '');
+          return text + (record.paper && record.paper.paperNo > 1 ? String.fromCharCode(65) : '');
         },
       },
       {

+ 4 - 0
front/project/www/routes/page/ready/index.less

@@ -127,6 +127,10 @@
           }
         }
 
+        .no-result {
+          text-align: center;
+          margin-top: 110px;
+        }
       }
 
       .faq-layout {

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

@@ -184,7 +184,7 @@ export default class extends Page {
           row.area = this.areaMap[row.areaId];
           return row;
         });
-        this.setState({ rooms: result.list });
+        this.setState({ rooms: result.list, roomQuery: true });
       });
   }
 
@@ -397,7 +397,7 @@ export default class extends Page {
   }
 
   renderRoom() {
-    const { rooms = [], isOverseas, keyword, showFinish, showSupple, supple, room } = this.state;
+    const { rooms = [], isOverseas, keyword, showFinish, showSupple, supple, room, roomQuery } = this.state;
     return (
       <div className="query-layout">
         <div className="search-wrapper">
@@ -423,6 +423,9 @@ export default class extends Page {
             </div>
           );
         })}
+        {rooms.length === 0 && roomQuery && <div className="no-result">
+          <span className="t-1 t-s-18 t-13">没有找到相关内容</span>
+        </div>}
         <SuppleModal show={showSupple} defaultData={supple} info={room} onConfirm={() => this.setState({ showSupple: false, showFinish: true })} onCancel={() => this.setState({ showSupple: false })} />
         <SuppleFinishModal show={showFinish} onConfirm={() => this.setState({ showFinish: false })} onCancel={() => this.setState({ showFinish: false })} />
       </div>

+ 3 - 3
front/project/www/routes/paper/process/base/index.js

@@ -214,7 +214,7 @@ export default class extends Component {
     const { step } = this.state;
     const { steps = [], typeset = 'one', type } = question.content;
     return (
-      <div className="block block-content">
+      <div className="block block-content u-s-n">
         {steps.length > 0 && (
           <Navigation
             theme="process"
@@ -224,7 +224,7 @@ export default class extends Component {
           />
         )}
         <div
-          className="text"
+          className="text u-s-n"
           style={{ paddingBottom: typeset === 'one' && type !== 'inline' ? '' : '100px' }}
           dangerouslySetInnerHTML={{ __html: this.formatStrem(steps.length > 0 ? steps[step].stem : question.stem) }}
         />
@@ -242,7 +242,7 @@ export default class extends Component {
         {questions.map((item, index) => {
           return (
             <div>
-              <div className="text m-b-2" dangerouslySetInnerHTML={{ __html: item.description }} />
+              <div className="text m-b-2 u-s-n" dangerouslySetInnerHTML={{ __html: item.description }} />
               <Answer
                 list={item.select}
                 type={type}

+ 10 - 2
front/project/www/routes/paper/process/page.js

@@ -73,6 +73,11 @@ export default class extends Page {
     // type 是获取基础paper的表信息
     // 等同于PaperOrigin
     Question.getPaper(type, id).then(paper => {
+      let { r } = this.state.search;
+      if (!r && paper.reportId > 0) {
+        this.search({ r: paper.reportId }, false);
+        r = paper.reportId;
+      }
       let totalNumber = paper.questionNumber;
       let handler = null;
       if (paper.paperModule === 'examination') {
@@ -87,11 +92,13 @@ export default class extends Page {
           this.stages = result;
           this.relaxProcess = { time: 8 * 60 };
         });
+        if (paper.paperNo > 1) {
+          paper.title += String.fromCharCode(65);
+        }
       } else {
         handler = Promise.resolve();
       }
       this.setState({ paper, totalNumber });
-      const { r } = this.state.search;
       if (r) {
         handler.then(() => {
           this.continue(r);
@@ -169,7 +176,8 @@ export default class extends Page {
             // 乱序显示选项
             questionSetting.questions = [];
             questions.forEach(q => {
-              const order = randomList(q.select.length);
+              // sc从1开始随机
+              const order = randomList(q.select.length, userQuestion.questionType === 'sc' ? 1 : 0);
               q.select = sortListWithOrder(q.select, order);
               questionSetting.questions.push(order);
             });

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

@@ -242,7 +242,7 @@ export default class extends Component {
           请分别找出句子中的主语,谓语和宾语,并做出逻辑关系判断。
         </div>
         <div
-          className="desc"
+          className="desc u-s-n"
           dangerouslySetInnerHTML={{ __html: stem }}
           onClick={e => {
             this.addTarget(e.target);
@@ -323,7 +323,7 @@ export default class extends Component {
           请分别找出句子中的主语,谓语和宾语,并做出逻辑关系判断。
         </div>)}
 
-        <div className="desc" dangerouslySetInnerHTML={{ __html: stem }} />
+        <div className="desc u-s-n" dangerouslySetInnerHTML={{ __html: stem }} />
         <div className="label">主语</div>
         <div className="input">
           <HardInput show={showAnswer} list={userAnswer.subject || []} answer={answer.subject} />
@@ -368,7 +368,7 @@ export default class extends Component {
     switch (analysisTab) {
       case 'chinese':
         content = (
-          <div className="detail-block text-block" dangerouslySetInnerHTML={{ __html: question.chineseContent }} />
+          <div className="detail-block text-block u-s-n" dangerouslySetInnerHTML={{ __html: question.chineseContent }} />
         );
         break;
       case 'qx':

+ 10 - 7
front/project/www/routes/paper/question/detail/index.js

@@ -83,10 +83,13 @@ export default class extends Component {
     const { userQuestion, questionNo = {} } = this.props;
     const { data = {} } = this.state;
     const { ask = {} } = data;
-    if (!ask.originContent || !ask.content || !ask.target) {
-      this.setState({ empty: { ask: { originContent: !ask.originContent, content: !ask.content, target: !ask.target } } });
+    if (!ask.originContent || !ask.content) {
+      this.setState({ empty: { ask: { originContent: !ask.originContent, content: !ask.content } } });
       return Promise.reject();
     }
+    if (!ask.target) {
+      ask.target = AskTarget[0].value;
+    }
     data.ask = {};
     return My.addQuestionAsk(userQuestion.id, ask.target, questionNo.id, ask.originContent, ask.content).then(() => {
       this.setState({ askModal: false, askOkModal: true, data });
@@ -512,7 +515,7 @@ export default class extends Component {
     switch (analysisTab) {
       case 'official':
         content = (
-          <div className="detail-block "><div className="block-text" dangerouslySetInnerHTML={{ __html: question.officialContent }} /></div>
+          <div className="detail-block "><div className="block-text u-s-n" dangerouslySetInnerHTML={{ __html: question.officialContent }} /></div>
         );
         break;
       case 'qx':
@@ -524,12 +527,12 @@ export default class extends Component {
             <Carousel>
               {associations.filter(row => row).map(association => {
                 const { questions = [], type } = association.content || {};
-                return <div className="block-text">
+                return <div className="block-text u-s-n">
                   <div dangerouslySetInnerHTML={{ __html: this.formatOtherStem(association) }} />
                   {questions.map((item, index) => {
                     return (
                       <div>
-                        <div className="text m-b-2" dangerouslySetInnerHTML={{ __html: item.description }} />
+                        <div className="text m-b-2 u-s-n" dangerouslySetInnerHTML={{ __html: item.description }} />
                         <AnswerList
                           answer={(question.answer || { questions: [] }).questions[index]}
                           list={item.select}
@@ -571,7 +574,7 @@ export default class extends Component {
     return questions.map((item, index) => {
       return (
         <div>
-          <div className="text m-b-2" dangerouslySetInnerHTML={{ __html: item.description }} />
+          <div className="text m-b-2 u-s-n" dangerouslySetInnerHTML={{ __html: item.description }} />
           <AnswerList
             show={showAnswer}
             selected={(userQuestion.userAnswer || { questions: [] }).questions[index]}
@@ -616,7 +619,7 @@ export default class extends Component {
           />
         )}
         <div
-          className="text"
+          className="text u-s-n"
           dangerouslySetInnerHTML={{ __html: this.formatStem(steps.length > 0 ? steps[step].stem : question.stem) }}
         />
         {typeset === 'one' && question.questionType !== 'awa' && this.renderAnswer()}

+ 13 - 11
front/project/www/routes/paper/report/page.js

@@ -196,6 +196,7 @@ function lineOption1(title, data, legend, color, yMax = 300) {
       type: 'value',
       min: 0,
       max: Number(yMax),
+      splitNumber: 6,
       axisLabel: { color: '#686872' },
       axisLine: { lineStyle: { color: '#D1D6DF' } },
     },
@@ -571,7 +572,6 @@ export default class extends Page {
                     row.collect = row.collect ? 1 : 0;
                     return row;
                   });
-                  this.refreshExercise(result);
                   break;
                 case 'examination':
                   result = result.map((row) => {
@@ -583,7 +583,6 @@ export default class extends Page {
                     row.collect = row.collect ? 1 : 0;
                     return row;
                   });
-                  this.refreshExamination(result);
                   break;
                 default:
               }
@@ -609,7 +608,10 @@ export default class extends Page {
     this.refreshExercise();
   }
 
-  refreshExamination() {
+  refreshExamination(result) {
+    if (result.paper.paperNo > 1) {
+      result.paper.title += String.fromCharCode(65);
+    }
     const { info = '' } = this.state.search;
     switch (info) {
       case 'score':
@@ -696,7 +698,7 @@ export default class extends Page {
         return this.renderTextbook();
       case 'exercise':
         if (info === 'question') {
-          return this.renderQuestion('exercise');
+          return this.renderBaseQuestion('exercise');
         }
         return this.renderExercise();
       case 'examination':
@@ -941,7 +943,7 @@ export default class extends Page {
           <div className='title'>题目回顾</div>
           <Button className='back' radius onClick={() => {
             linkTo(`/paper/report/${report.id}`);
-          }}>返回练习报告</Button>
+          }}>返回{type === 'examination' ? '模考' : '练习'}报告</Button>
         </div>
       </div>
       <div className='body'>
@@ -1192,13 +1194,13 @@ export default class extends Page {
               <div className="block">
                 <div className="t1">总耗时</div>
                 {subject.verbal && <div className="t1">
-                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(Math.min(subject.verbal.info.userTime, subject.verbal.info.time)).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
+                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.verbal.info.questionNumber === subject.verbal.info.userNumber && subject.verbal.info.time > subject.verbal.info.userTime ? subject.verbal.info.userTime : subject.verbal.info.time).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
                 </div>}
                 {subject.quant && <div className="t1">
-                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(Math.min(subject.quant.info.userTime, subject.quant.info.time)).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
+                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.quant.info.questionNumber === subject.quant.info.userNumber && subject.quant.info.time > subject.quant.info.userTime ? subject.quant.info.userTime : subject.quant.info.time).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
                 </div>}
                 {subject.ir && <div className="t1">
-                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(Math.min(subject.ir.info.userTime, subject.ir.info.time)).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
+                  <div className="t2" dangerouslySetInnerHTML={{ __html: formatSeconds(subject.ir.info.questionNumber === subject.ir.info.userNumber && subject.ir.info.time > subject.ir.info.userTime ? subject.ir.info.userTime : subject.ir.info.time).replace(/([0-9]+)(min|m|hour|h|s)/g, '$1<div class="t3">$2</div>') }} />
                 </div>}
               </div>
               <div className="block">
@@ -1341,7 +1343,7 @@ export default class extends Page {
                   <td>{typeDetail.info.userNumber}</td>
                   <td>{typeDetail.info.userCorrect}</td>
                   <td>
-                    {formatPercent(typeDetail.info.userCorrect, typeDetail.info.userNumber)}
+                    {formatPercent(typeDetail.info.userCorrect, typeDetail.info.questionNumber)}
                   </td>
                   <td>
                     {formatSecond(typeDetail.info.userTime / typeDetail.info.userNumber)}
@@ -1393,9 +1395,9 @@ export default class extends Page {
             <tbody>
               <tr>
                 <td>
-                  {formatPercent(subjectDetail.info.userCorrect, subjectDetail.info.userNumber)}
+                  {formatPercent(subjectDetail.info.userCorrect, subjectDetail.info.questionNumber)}
                   <br />
-                  <span>{subjectDetail.info.userCorrect}题/{subjectDetail.info.userNumber}题</span>
+                  <span>{subjectDetail.info.userCorrect}题/{subjectDetail.info.questionNumber}题</span>
                 </td>
                 <td>
                   {formatSecond(subjectDetail.info.userTime / subjectDetail.info.userNumber)}

+ 5 - 3
front/project/www/routes/question/search/page.js

@@ -196,6 +196,7 @@ export default class extends Page {
         <div className="search-wrapper">
           <input
             value={keyword}
+            placeholder="输入题干内容“According to some critics…”或题号“PREP07-1”"
             onChange={e => this.onChangeSearch(e.target.value)}
             onFocus={() => this.setState({ focus: true })}
             onBlur={() => this.setState({ focus: false })}
@@ -236,15 +237,16 @@ export default class extends Page {
               {searchHistoryList.map((item, index) => {
                 return (
                   <div
-                    className="t-2 t-s-16"
+                    className="t-2 t-s-16 p-r"
                     onClick={() => {
                       // this.onChangeSearch(item, true);
                       this.onSearch(item);
                     }}
                   >
-                    {item}
+                    <span className="nowrap" style={{ width: '95%', overflow: 'hidden', display: 'block' }}>{item}</span>
                     <div
-                      className="f-r t-4 t-s-12 c-p"
+                      className="t-4 t-s-12 c-p p-a"
+                      style={{ top: 0, right: 10 }}
                       onClick={e => {
                         e.stopPropagation();
                         User.removeSearchIndex(index);

+ 4 - 1
front/project/www/routes/textbook/list/page.js

@@ -123,6 +123,9 @@ export default class extends Page {
     }
     return {
       yearList: year,
+      info: {
+        year: new Date().getFullYear().toString(),
+      },
     };
   }
 
@@ -146,7 +149,7 @@ export default class extends Page {
   }
 
   refreshTextbook() {
-    Textbook.listPaper(Object.assign({ year: new Date().getFullYear().toString() }, this.state.search, this.state.info))
+    Textbook.listPaper(Object.assign(this.state.search, this.state.info))
       .then((result) => {
         this.setState({ list: result });
       })

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

@@ -18,8 +18,8 @@ export default class TextbookStore extends BaseStore {
   /**
    * 机经组卷列表
    */
-  listPaper({ page, size, latest, logic, finish }) {
-    return this.apiGet('/textbook/paper/list', { page, size, latest: !!latest, logic, times: finish });
+  listPaper({ year, page, size, latest, logic, finish }) {
+    return this.apiGet('/textbook/paper/list', { year, page, size, latest: !!latest, logic, times: finish });
   }
 
   listYear(year) {

+ 9 - 13
front/src/components/FileUpload/index.js

@@ -84,7 +84,7 @@ class FileUpload extends Component {
             this.FileInput = ref;
           }}
           multiple={false}
-          accept={this.state.type === 'file' ? accept.join(',') : 'image/x-png,image/jpeg'}
+          accept={accept && accept.length > 0 ? accept.join(',') : 'image/x-png,image/jpeg'}
           onChange={e => this.onChange(e)}
           onDragEnter={onDragEnter}
           onDragLeave={onDragLeave}
@@ -92,12 +92,8 @@ class FileUpload extends Component {
         {this.state.type === 'image' ? (
           this.props.value && (!this.state.uploading && !this.props.uploading) ? (
             <img src={this.props.value} style={{ width: '100%', height: '100%' }} />
-          ) : (
-            <Icon type={this.state.uploading || this.props.uploading ? 'loading' : 'upload'} />
-          )
-        ) : (
-          ''
-        )}
+          ) : (<Icon type={this.state.uploading || this.props.uploading ? 'loading' : 'upload'} />)
+        ) : ('')}
         {this.state.type === 'logo' ? (
           <div>
             <Logo className="m-r-1" src={this.props.value} icon={this.props.icon} />
@@ -106,9 +102,7 @@ class FileUpload extends Component {
               上传
             </Button>
           </div>
-        ) : (
-          ''
-        )}
+        ) : ('')}
         {this.state.type === 'file' ? (
           <span>
             <Button disabled={this.props.disabled} className="m-r-1">
@@ -117,9 +111,11 @@ class FileUpload extends Component {
             <div>{this.props.accept && `支持文件格式: ${this.props.accept.join(' ')}`}</div>
             {this.props.hiddenFile ? '' : this.state.fileName}
           </span>
-        ) : (
-          ''
-        )}
+        ) : ('')}
+        {this.state.type === 'none' ? (
+          <span><Icon type={this.state.uploading ? 'loading' : ''} />
+          </span>
+        ) : ('')}
       </div>
     );
   }

+ 3 - 3
front/src/services/Tools.js

@@ -541,13 +541,13 @@ export function getSimpleText(html) {
   return text;
 }
 
-export function randomList(length) {
+export function randomList(length, start = 0) {
   const list = [];
   for (let i = 0; i < length; i += 1) {
     list.push(i);
   }
-  for (let i = 0; i < length; i += 1) {
-    const o = Math.floor(Math.random() * length);
+  for (let i = start; i < length; i += 1) {
+    const o = Math.floor(Math.random() * (length - start)) + start;
     const tmp = list[o];
     list[o] = list[i];
     list[i] = tmp;

+ 7 - 2
server/data/src/main/java/com/qxgmat/data/relation/QuestionNoRelationMapper.java

@@ -41,7 +41,6 @@ public interface QuestionNoRelationMapper {
     List<QuestionNoRelation> searchStemFulltext(
             @Param("keyword") String keyword,
             @Param("questionTypes") String[] questionTypes,
-            @Param("module") String module,
             @Param("structIds") Integer[] structIds,
             @Param("place") String place,
             @Param("difficult") String difficult,
@@ -51,7 +50,6 @@ public interface QuestionNoRelationMapper {
 
     List<QuestionNo> searchNoFulltext(
             @Param("keyword") String keyword,
-            @Param("module") String module,
             @Param("qxCatId") Integer qxCatId
     );
 
@@ -62,6 +60,13 @@ public interface QuestionNoRelationMapper {
             @Param("size") Number size
     );
 
+    List<QuestionNo> randomExaminationRc(
+            @Param("structId") Number structId,
+            @Param("number") Number number,
+            @Param("difficult") String difficult,
+            @Param("filterIds") Collection filterIds
+    );
+
     List<QuestionNo> nextQuestion(
             @Param("structId") Number structId,
             @Param("targetTypes") Collection targetTypes,

+ 36 - 8
server/data/src/main/java/com/qxgmat/data/relation/mapping/QuestionNoRelationMapper.xml

@@ -112,10 +112,8 @@
       and (qn.`title` = #{keyword, jdbcType=VARCHAR} or MATCH (q.`description`) AGAINST (#{keyword, jdbcType=VARCHAR} IN NATURAL LANGUAGE MODE))
     </if>
     <if test="qxCatId != null">
-      and (qn.module='examination' and find_in_set(#{qxCatId}, qn.`module_struct`) = 0)
-    </if>
-    <if test="module != null">
-      and qn.`module` = #{module,jdbcType=VARCHAR}
+      and ((qn.module='examination' and find_in_set(#{qxCatId}, qn.`module_struct`) = 0)
+      or qn.`module` = 'exercise')
     </if>
     <if test="structIds != null">
       and
@@ -127,6 +125,9 @@
     <if test="order != null">
     ${order} ,
     </if>
+    <if test="keyword != null">
+      relation_score Desc,
+    </if>
     qn.`module_struct` ASC, qn.`no` ASC
   </select>
 
@@ -143,11 +144,9 @@
     from `question_no` qn
     where qn.question_id &gt; 0 and qn.`delete_time` is null and
     (MATCH (qn.`title`) AGAINST (#{keyword, jdbcType=VARCHAR} IN NATURAL LANGUAGE MODE) or qn.`title` like #{keywordLike,jdbcType=VARCHAR})
-    <if test="module != null">
-      and qn.`module` = #{module,jdbcType=VARCHAR}
-    </if>
     <if test="qxCatId != null">
-      and (qn.module='examination' and find_in_set(#{qxCatId}, qn.`module_struct`) = 0)
+      and ((qn.module='examination' and find_in_set(#{qxCatId}, qn.`module_struct`) = 0)
+      or qn.`module` = 'exercise')
     </if>
     ORDER BY `relation_score` DESC
   </select>
@@ -183,6 +182,35 @@
   </select>
 
   <!--
+    模考阅读随机出题
+  -->
+  <select id="randomExaminationRc" resultMap="IdMap">
+    select
+    <include refid="Id_Column_List" />
+    from `question_no` qn
+    left join `question` q on q.`id` = qn.`question_id`
+    <if test="difficult != null">
+      and q.`difficult` = #{difficult,jdbcType=VARCHAR}
+    </if>
+    where
+    q.id &gt; 0 and qn.question_id &gt; 0 and qn.`delete_time` is null
+    and qn.`module` = 'examination'
+    <if test="structId != null">
+      and find_in_set(#{structId,jdbcType=VARCHAR}, qn.`module_struct`)
+    </if>
+    <if test="number != null">
+      and qn.`relation_number` = #{number,jdbcType=INTEGER}
+    </if>
+    <if test="filterIds != null and filterIds.size()>0">
+      and qn.`id` NOT IN
+      <foreach collection="filterIds" item="item" index="index" open="(" separator="," close=")">
+        #{item}
+      </foreach>
+    </if>
+    order by RAND()
+    limit 1
+  </select>
+  <!--
     下一题
   -->
   <select id="nextQuestion" resultMap="IdMap">

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

@@ -35,6 +35,9 @@
       </if>
     </if>
     where 1
+    <if test="logic != null">
+      and tp.`logic` = #{logic,jdbcType=VARCHAR}
+    </if>
     <if test="libraryId != null">
       and tp.`library_id` = #{libraryId,jdbcType=VARCHAR}
     </if>

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

@@ -10,15 +10,14 @@ import com.qxgmat.data.constants.enums.QuestionSubject;
 import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.module.CourseModule;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
-import com.qxgmat.data.dao.entity.Course;
-import com.qxgmat.data.dao.entity.PreviewAssign;
-import com.qxgmat.data.dao.entity.PreviewPaper;
-import com.qxgmat.data.dao.entity.User;
+import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.dto.admin.extend.CourseExtendDto;
 import com.qxgmat.dto.admin.extend.UserExtendDto;
 import com.qxgmat.dto.admin.request.PreviewAssignDto;
 import com.qxgmat.dto.admin.request.PreviewPaperDto;
 import com.qxgmat.dto.admin.response.PreviewPaperListDto;
+import com.qxgmat.service.UsersService;
+import com.qxgmat.service.extend.MessageExtendService;
 import com.qxgmat.service.extend.PreviewService;
 import com.qxgmat.service.extend.QuestionFlowService;
 import com.qxgmat.service.inline.*;
@@ -45,6 +44,12 @@ public class PreviewController {
     private CourseService courseService;
 
     @Autowired
+    private UsersService usersService;
+
+    @Autowired
+    private UserCourseAppointmentService userCourseAppointmentService;
+
+    @Autowired
     private PreviewService previewService;
 
     @Autowired
@@ -59,6 +64,9 @@ public class PreviewController {
     @Autowired
     private QuestionFlowService questionFlowService;
 
+    @Autowired
+    private MessageExtendService messageExtendService;
+
     @RequestMapping(value = "/add", method = RequestMethod.POST)
     @ApiOperation(value = "添加预习作业", httpMethod = "POST")
     public Response<PreviewPaper> add(@RequestBody @Validated PreviewPaperDto dto, HttpServletRequest request) {
@@ -132,11 +140,19 @@ public class PreviewController {
     @ApiOperation(value = "添加分配预习作业", httpMethod = "POST")
     public Response<PreviewAssign> addAssign(@RequestBody @Validated PreviewAssignDto dto, HttpServletRequest request) {
         PreviewAssign entity = Transform.dtoToEntity(dto);
+        PreviewAssign in = previewAssignService.getWithAppointment(entity.getCourseAppointment());
+        if (in != null){
+            throw new ParameterException("预习作业已布置");
+        }
         PreviewPaper previewPaper = previewPaperService.get(entity.getPaperId());
         entity.setCourseId(previewPaper.getCourseId());
         entity.setCourseModule(previewPaper.getCourseModule());
         entity.setQuestionNoIds(previewPaper.getQuestionNoIds());
-        previewAssignService.add(entity);
+        in = previewAssignService.add(entity);
+        Course course = courseService.get(in.getCourseId());
+        UserCourseAppointment appointment = userCourseAppointmentService.get(in.getCourseAppointment());
+        User user = usersService.get(appointment.getUserId());
+        messageExtendService.sendPreviewNotice(user, course, in);
         managerLogService.log(request);
         return ResponseHelp.success(entity);
     }

+ 4 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java

@@ -800,6 +800,10 @@ public class UserController {
         List<UserReport> reportList = userReportService.listWithLast(paperIds);
         Transform.combine(pr, reportList, UserCourseAppointmentInfoDto.class, "paperId", "userReport", UserReport.class, "originId", UserReportExtendDto.class);
 
+        Collection courseIds = Transform.getIds(p, UserCourseAppointment.class, "courseId");
+        List<Course> courseList = courseService.select(courseIds);
+        Transform.combine(pr, courseList, UserCourseAppointmentInfoDto.class, "courseId", "course", Course.class, "id", CourseExtendDto.class);
+
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 

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

@@ -393,6 +393,9 @@ public class MyController {
         if (in.getRealPhotoBack() == null || in.getRealPhotoBack().equals("")){
             throw new ParameterException("实名认证流程错误");
         }
+        if (in.getRealName() != null){
+            throw new SystemException("实名认证已完成");
+        }
 
         usersService.edit(User.builder()
                 .id(user.getId())
@@ -1982,7 +1985,7 @@ public class MyController {
         UserCourseAppointment appointment = userCourseAppointmentService.get(entity.getAppointmentId());
         entity.setUserId(user.getId());
         entity.setRecordId(appointment.getRecordId());
-        if (entity.getParentId() > 0){
+        if (entity.getParentId() != null && entity.getParentId() > 0){
             UserCourseAppointmentComment comment = userCourseAppointmentCommentService.get(entity.getParentId());
             if (comment != null){
                 entity.setReply(comment.getContent());

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

@@ -114,6 +114,9 @@ public class QuestionController {
     private UserServiceService userServiceService;
 
     @Autowired
+    private UsersService usersService;
+
+    @Autowired
     private QuestionFlowService questionFlowService;
 
     @Autowired
@@ -127,7 +130,6 @@ public class QuestionController {
             @RequestParam(required = false, defaultValue = "10") int size,
             @RequestParam(required = false) String keyword,
             @RequestParam(required = false) String[] questionTypes,
-            @RequestParam(required = false) String module,
             @RequestParam(required = false) Integer[] structIds,
             @RequestParam(required = false) String place,
             @RequestParam(required = false) String difficult,
@@ -140,7 +142,7 @@ public class QuestionController {
 //        List<ExaminationStruct> structs = examinationStructService.main();
 //        ExaminationStruct qxCatStruct = structs.stream().filter((row)-> row.getExtend().equals(ServiceKey.QX_CAT.key)).findFirst().get();
         ExaminationStruct struct = examinationService.getQxCat();
-        Page<QuestionNoRelation> p = questionNoService.searchStemFulltext(page, size, keyword, questionTypes, module, structIds, place, difficult, struct.getId(), order);
+        Page<QuestionNoRelation> p = questionNoService.searchStemFulltext(page, size, keyword, questionTypes, structIds, place, difficult, struct.getId(), order);
         List<QuestionNoDetailDto> pr = Transform.convert(p, QuestionNoDetailDto.class);
 
         if(user!= null){
@@ -173,7 +175,7 @@ public class QuestionController {
 //        List<ExaminationStruct> structs = examinationStructService.main();
 //        ExaminationStruct qxCatStruct = structs.stream().filter((row)-> row.getExtend().equals(ServiceKey.QX_CAT.key)).findFirst().get();
         ExaminationStruct struct = examinationService.getQxCat();
-        Page<QuestionNo> p = questionNoService.searchNoFulltext(page, size, keyword, module, struct.getId());
+        Page<QuestionNo> p = questionNoService.searchNoFulltext(page, size, keyword, struct.getId());
         List<QuestionNoDto> pr = Transform.convert(p, QuestionNoDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
@@ -299,7 +301,7 @@ public class QuestionController {
                 }
             }
 
-            // 作文、阅读没有第4层
+            // 作文、逻辑没有第4层
             if (children == null || children.size() == 0){
                 // 以下属的paper作为children
                 List<UserExerciseGroupExtendDto> childrenDtos = new ArrayList<>(paperList.size());
@@ -531,13 +533,20 @@ public class QuestionController {
         User user = (User) shiroHelp.getLoginUser();
         ExaminationStruct struct = examinationStructService.get(structId);
         ServiceKey serviceKey = ServiceKey.ValueOf(struct.getExtend());
+        UserService userService = null;
         if (serviceKey == ServiceKey.QX_CAT){
             if (user == null){
                 throw new ParameterException("请先登录");
             }
+            user = usersService.get(user.getId());
             if (!userServiceService.hasService(user.getId(), serviceKey)){
                 throw new ParameterException("请先开通模考");
             }
+            userService = userServiceService.getServiceBase(user.getId(), serviceKey);
+            // 客户端排序
+            if (userService.getIsReset()>0){
+                times = null;
+            }
         }
         PageResult<ExaminationPaper> p = examinationService.list(page, size, structId, user != null ? user.getId():null, serviceKey == ServiceKey.QX_CAT && user != null ? user.getQxCat() : null, times);
 
@@ -565,7 +574,6 @@ public class QuestionController {
 
             if (serviceKey == ServiceKey.QX_CAT && user.getQxCat() > 0){
                 // 获取上一遍模考成绩
-                UserService userService = userServiceService.getServiceBase(user.getId(), serviceKey);
                 if (userService.getIsReset() > 0){
                     List<UserPaper> prevPaperList = userPaperService.listWithCat(user.getId(), ids, user.getQxCat()-1);
                     Transform.combine(pr, prevPaperList, UserExaminationPaperDto.class, "id", "prevPaper", UserPaper.class, "originId", UserPaperBaseExtendDto.class);
@@ -576,7 +584,7 @@ public class QuestionController {
                     // 获取最后一次结果
                     Collection prevPaperIds = Transform.getIds(prevPaperList, UserPaper.class, "id");
                     List<UserReport> prevReportList = userReportService.listWithLastNoReset(prevPaperIds);
-                    Transform.combine(pr, prevReportList, UserExaminationPaperDto.class, "id", "prevReport", UserReport.class, "paperId", UserReportExtendDto.class);
+                    Transform.combine(pr, prevReportList, UserExaminationPaperDto.class, "prevUserPaperId", "prevReport", UserReport.class, "paperId", UserReportExtendDto.class);
                 }
             }
         }
@@ -671,7 +679,7 @@ public class QuestionController {
         }
         UserPaper paper = questionFlowService.paper(user.getId(), PaperOrigin.EXERCISE, paperId);
         PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
-
+        paperDto.setReportId(questionFlowService.needContinueReport(user.getId(), paper));
         return ResponseHelp.success(paperDto);
     }
 
@@ -686,6 +694,7 @@ public class QuestionController {
         }
         UserPaper paper = questionFlowService.paper(user.getId(), PaperOrigin.EXAMINATION, paperId);
         PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
+        paperDto.setReportId(questionFlowService.needContinueReport(user.getId(), paper));
 
         return ResponseHelp.success(paperDto);
     }
@@ -800,7 +809,7 @@ public class QuestionController {
 
     @RequestMapping(value = "/preview/paper", method = RequestMethod.GET)
     @ApiOperation(value = "开始: 预习作业", notes = "提交考试设置", httpMethod = "POST")
-    public Response<PaperBaseDto> startPreview(
+    public Response<PaperBaseDto> detailPreview(
             @RequestParam(required = true) Integer paperId
     )  {
         User user = (User) shiroHelp.getLoginUser();
@@ -810,6 +819,7 @@ public class QuestionController {
         UserPaper paper = questionFlowService.paper(user.getId(), PaperOrigin.PREVIEW, paperId);
 
         PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
+        paperDto.setReportId(questionFlowService.needContinueReport(user.getId(), paper));
 
         return ResponseHelp.success(paperDto);
     }
@@ -842,6 +852,7 @@ public class QuestionController {
         UserPaper paper = questionFlowService.paper(user.getId(), PaperOrigin.TEXTBOOK, paperId);
 
         PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
+        paperDto.setReportId(questionFlowService.needContinueReport(user.getId(), paper));
 
         return ResponseHelp.success(paperDto);
     }
@@ -874,6 +885,7 @@ public class QuestionController {
         UserPaper paper = questionFlowService.paper(user.getId(), PaperOrigin.SENTENCE, paperId);
 
         PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
+        paperDto.setReportId(questionFlowService.needContinueReport(user.getId(), paper));
 
         return ResponseHelp.success(paperDto);
     }
@@ -910,6 +922,7 @@ public class QuestionController {
             throw new ParameterException("试卷不存在");
         }
         PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
+        paperDto.setReportId(questionFlowService.needContinueReport(user.getId(), paper));
 
         return ResponseHelp.success(paperDto);
     }
@@ -947,6 +960,7 @@ public class QuestionController {
             throw new ParameterException("试卷不存在");
         }
         PaperBaseDto paperDto = Transform.convert(paper, PaperBaseDto.class);
+        paperDto.setReportId(questionFlowService.needContinueReport(user.getId(), paper));
 
         return ResponseHelp.success(paperDto);
     }

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

@@ -15,6 +15,8 @@ public class CourseExtendDto {
 
     private String courseModule;
 
+    private String vsType;
+
     public Integer getId() {
         return id;
     }
@@ -54,4 +56,12 @@ public class CourseExtendDto {
     public void setCourseModule(String courseModule) {
         this.courseModule = courseModule;
     }
+
+    public String getVsType() {
+        return vsType;
+    }
+
+    public void setVsType(String vsType) {
+        this.vsType = vsType;
+    }
 }

+ 11 - 4
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserCourseAppointmentInfoDto.java

@@ -2,10 +2,7 @@ package com.qxgmat.dto.admin.response;
 
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.UserCourseAppointment;
-import com.qxgmat.dto.admin.extend.PreviewAssignExtendDto;
-import com.qxgmat.dto.admin.extend.PreviewPaperExtendDto;
-import com.qxgmat.dto.admin.extend.UserPaperExtendDto;
-import com.qxgmat.dto.admin.extend.UserReportExtendDto;
+import com.qxgmat.dto.admin.extend.*;
 
 @Dto(entity = UserCourseAppointment.class)
 public class UserCourseAppointmentInfoDto extends UserCourseAppointment {
@@ -17,6 +14,8 @@ public class UserCourseAppointmentInfoDto extends UserCourseAppointment {
 
     private PreviewAssignExtendDto previewAssign;
 
+    private CourseExtendDto course;
+
     public Integer getPaperId() {
         return paperId;
     }
@@ -48,4 +47,12 @@ public class UserCourseAppointmentInfoDto extends UserCourseAppointment {
     public void setPreviewAssign(PreviewAssignExtendDto previewAssign) {
         this.previewAssign = previewAssign;
     }
+
+    public CourseExtendDto getCourse() {
+        return course;
+    }
+
+    public void setCourse(CourseExtendDto course) {
+        this.course = course;
+    }
 }

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

@@ -15,6 +15,8 @@ public class BasePaperExtendDto {
 
     private Integer courseNo;
 
+    private Integer courseAppointment;
+
     private UserPaperBaseExtendDto paper;
 
     private UserReportExtendDto report;
@@ -66,4 +68,12 @@ public class BasePaperExtendDto {
     public void setCourseNo(Integer courseNo) {
         this.courseNo = courseNo;
     }
+
+    public Integer getCourseAppointment() {
+        return courseAppointment;
+    }
+
+    public void setCourseAppointment(Integer courseAppointment) {
+        this.courseAppointment = courseAppointment;
+    }
 }

+ 28 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/PaperBaseDto.java

@@ -14,6 +14,10 @@ public class PaperBaseDto {
     private Integer times;
     private Integer finishTimes;
     private Integer isAdapt;
+    private Integer paperNo;
+    private Integer qxCat;
+
+    private Integer reportId;
 
     public Integer getQuestionNumber() {
         return questionNumber;
@@ -86,4 +90,28 @@ public class PaperBaseDto {
     public void setFinishTimes(Integer finishTimes) {
         this.finishTimes = finishTimes;
     }
+
+    public Integer getPaperNo() {
+        return paperNo;
+    }
+
+    public void setPaperNo(Integer paperNo) {
+        this.paperNo = paperNo;
+    }
+
+    public Integer getQxCat() {
+        return qxCat;
+    }
+
+    public void setQxCat(Integer qxCat) {
+        this.qxCat = qxCat;
+    }
+
+    public Integer getReportId() {
+        return reportId;
+    }
+
+    public void setReportId(Integer reportId) {
+        this.reportId = reportId;
+    }
 }

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

@@ -42,7 +42,7 @@ public class CourseExtendService {
         // day / 10
         Integer expireDays = course.getExpirePreDays();
 
-        return Math.max(vsNumber / 10, 1) * expireDays;
+        return Math.max(expireDays / 10, 1) * vsNumber;
     }
 
     /**

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

@@ -8,6 +8,7 @@ import com.nuliji.tools.Tools;
 import com.nuliji.tools.Transform;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
+import com.qxgmat.data.constants.enums.QuestionDifficult;
 import com.qxgmat.data.constants.enums.QuestionSubject;
 import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.ServiceKey;
@@ -254,7 +255,7 @@ public class ExaminationService extends AbstractService {
         Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
         List<UserReport> reportList = userReportService.listWithLast(paperIds);
         for(UserReport report: reportList){
-            if(report.getIsFinish() == 0){
+            if(report.getIsFinish() == null || report.getIsFinish() == 0){
                 return false;
             }
         }
@@ -280,15 +281,14 @@ public class ExaminationService extends AbstractService {
         if(!force){
             List<UserReport> reportList = userReportService.listWithLast(paperIds);
             for(UserReport report: reportList){
-                if(report.getIsFinish() == 0){
+                if(report.getIsFinish() == null || report.getIsFinish() == 0){
                     throw new ParameterException("未完成所有");
                 }
             }
         }
         // 增加用户cat计数
         usersService.edit(User.builder().id(userId).qxCat(user.getQxCat() + 1).build());
-        // 在获取start paper时,创建新的paper
-        return userPaperService.reset(paperIds, userId);
+        return true;
     }
 
     public ExaminationStruct getQxCat(){
@@ -455,7 +455,7 @@ public class ExaminationService extends AbstractService {
      */
     public JSONObject initVerbal(Integer structId){
         JSONObject info = new JSONObject();
-        Integer[] rcQ = questionNoService.randomExaminationRc(structId, 4, null);
+        Integer[] rcQ = questionNoService.randomExaminationRc(structId, 4, QuestionDifficult.EASY,null);
         List<QuestionNoRelation> rcRelationList = questionNoService.listWithRelationByIds(rcQ);
         Integer rcLevel = computeNoLevel(rcRelationList);
         Integer targetLevel = 0;
@@ -488,7 +488,26 @@ public class ExaminationService extends AbstractService {
      */
     public JSONObject generateVerbal(Integer structId, Integer level, Map<String, Integer> typeNumbers, Collection ids){
         JSONObject info = new JSONObject();
-        Integer[] rcQ = questionNoService.randomExaminationRc(structId, 3, ids);
+        Random a = new Random();
+        QuestionDifficult difficult = null;
+        if (level<12){
+            difficult = QuestionDifficult.EASY;
+        }else if(level >= 27){
+            difficult = QuestionDifficult.HARD;
+        }else if (level >= 24){
+            if (a.nextInt(2) > 0){
+                difficult = QuestionDifficult.MEDIUM;
+            }else{
+                difficult = QuestionDifficult.HARD;
+            }
+        }else if (level < 15){
+            if (a.nextInt(2) > 0){
+                difficult = QuestionDifficult.MEDIUM;
+            }else{
+                difficult = QuestionDifficult.EASY;
+            }
+        }
+        Integer[] rcQ = questionNoService.randomExaminationRc(structId, 3, difficult, ids);
         List<QuestionNoRelation> rcRelationList = questionNoService.listWithRelationByIds(rcQ);
         Integer rcLevel = computeNoLevel(rcRelationList);
         Collection types = QuestionType.SpecialFromSubject(QuestionSubject.VERBAL);
@@ -686,7 +705,7 @@ public class ExaminationService extends AbstractService {
                 no += 1;
 
                 // 只能还有一个2分
-                if (level == number - no + 1){
+                if (level <= number - no + 1){
                     break;
                 }
             }

+ 45 - 19
server/gateway-api/src/main/java/com/qxgmat/service/extend/ExerciseService.java

@@ -25,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.util.*;
+import java.util.stream.Collectors;
 
 @Service
 public class ExerciseService extends AbstractService {
@@ -167,33 +168,55 @@ public class ExerciseService extends AbstractService {
         List<Integer> tmp = new ArrayList<>(length);
         int min = 0;
         int max = 0;
+        List<Integer> relation = new ArrayList<>();
+        int relationLength = 0;
+        int allQuestionNumber = 0;
         for(QuestionNo question : questionList){
             if (tmp.size() == 0 || min == 0){
                 min = question.getNo();
             }
             tmp.add(question.getId());
             max = question.getNo();
-            if (tmp.size() == length){
-                no += 1;
-                ExercisePaper paper = ExercisePaper.builder()
-                        .logic(logic.key)
-                        .logicExtend(extend)
-                        .questionType(questionType.key)
-                        .structThree(three)
-                        .structFour(four)
-                        .no(no)
-                        .questionNumber(tmp.size())
-                        .status(0)
-                        .questionNoIds(tmp.toArray(new Integer[0])).build();
-                if (logic == ExerciseLogic.NO){
-                    paper.setTitle(exercisePaperService.generateOriginTitle(prefixTitle, min, max));
-                }else{
-                    paper.setTitle(exercisePaperService.generateTitle(prefixTitle, length, paper.getNo(), paper.getQuestionNumber()));
+            if (questionType == QuestionType.RC){
+                // length是篇数,题目数按篇数和
+                if (relation.size() == 0){
+                    relationLength += 1;
+                    for(int id : question.getRelationQuestion()){
+                        relation.add(id);
+                    }
+                    if (relation.size() == 0){
+                        relation.add(question.getId());
+                    }
                 }
-                paper = exercisePaperService.add(paper);
-                list.add(paper);
-                tmp.clear();
+                relation.remove(question.getId());
+                if (relationLength < length || relation.size() > 0) continue;
+                relationLength = 0;
+            }else{
+                if (tmp.size() < length) continue;
+            }
+
+            no += 1;
+            ExercisePaper paper = ExercisePaper.builder()
+                    .logic(logic.key)
+                    .logicExtend(extend)
+                    .questionType(questionType.key)
+                    .structThree(three)
+                    .structFour(four)
+                    .no(no)
+                    .questionNumber(tmp.size())
+                    .status(0)
+                    .questionNoIds(tmp.toArray(new Integer[0])).build();
+            if (logic == ExerciseLogic.NO){
+                paper.setTitle(exercisePaperService.generateOriginTitle(prefixTitle, min, max));
+            }else if (questionType == QuestionType.RC){
+                paper.setTitle(exercisePaperService.generateRcTitle(prefixTitle, allQuestionNumber, paper.getQuestionNumber()));
+                allQuestionNumber += paper.getQuestionNumber();
+            }else{
+                paper.setTitle(exercisePaperService.generateTitle(prefixTitle, length, paper.getNo(), paper.getQuestionNumber()));
             }
+            paper = exercisePaperService.add(paper);
+            list.add(paper);
+            tmp.clear();
         }
         if (tmp.size() > 0){
             no += 1;
@@ -209,6 +232,9 @@ public class ExerciseService extends AbstractService {
                     .questionNoIds(tmp.toArray(new Integer[0])).build();
             if (logic == ExerciseLogic.NO){
                 paper.setTitle(exercisePaperService.generateOriginTitle(prefixTitle, min, max));
+            }else if (questionType == QuestionType.RC) {
+                paper.setTitle(exercisePaperService.generateRcTitle(prefixTitle, allQuestionNumber, paper.getQuestionNumber()));
+                allQuestionNumber += paper.getQuestionNumber();
             }else{
                 paper.setTitle(exercisePaperService.generateTitle(prefixTitle, length, paper.getNo(), paper.getQuestionNumber()));
             }

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

@@ -335,12 +335,11 @@ public class MessageExtendService {
     /**
      * 发送预习作业到期通知:6小时,去除夜间12-7点
      * 课程名称:{courseTitle}
-     * 作业名称:{assignTitle}
      */
     public void sendPreviewNotice(User user, Course course, PreviewAssign previewAssign){
         Map<String, String> map = new HashMap<>();
-        map.put("courseTitle", course.getTitle());
-        map.put("title", previewAssign.getTitle());
+        map.put("title", course.getTitle());
+        map.put("number", String.valueOf(1));
         send(user, MessageCategory.PREVIEW_NOTICE, map, previewAssign.getId());
     }
 

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

@@ -860,7 +860,7 @@ public class OrderFlowService {
                                 .number(info.getIntValue("number"))
                                 .source(RecordSource.GIFT_COURSE.key)
                                 .expireDays(course.getExpireDays())
-                                .useExpireDays(course.getExpirePreDays() / 10)
+                                .useExpireDays(courseExtendService.computeExpire(1, course))
                                 .build();
 
                         InitRecord callback = initRecordCallback.get(ProductType.ValueOf(record.getProductType()));

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

@@ -207,9 +207,8 @@ public class PreviewService extends AbstractService {
         switch(courseModule){
             case VS:
                 // 默认没有?
-                return 0;
-//                UserCourseAppointment appointment = userCourseAppointmentService.get(assign.getCourseAppointment());
-//                return appointment.getRecordId();
+                UserCourseAppointment appointment = userCourseAppointmentService.get(assign.getCourseAppointment());
+                return appointment.getRecordId();
             case ONLINE:
                 UserOrderRecord timeRecord = userOrderRecordService.getByUserAndTime(userId, assign.getCourseId(), assign.getCourseTime());
                 // 有效期延长到n天

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

@@ -160,6 +160,7 @@ public class QuestionFlowService {
             switch(courseModule){
                 case VS:
                     userPaper.setTitle(assign.getTitle());
+                    break;
                 case VIDEO:
                 case ONLINE:
                 default:
@@ -553,6 +554,21 @@ public class QuestionFlowService {
     }
 
     /**
+     * 获取未完成试卷
+     * @param userId
+     * @param paper
+     * @return
+     */
+    public Integer needContinueReport(Integer userId, UserPaper paper){
+        UserReport userReport = userReportService.getLastByPaper(userId, paper.getId());
+        if (userReport == null) return 0;
+        if (userReport.getDetail() != null && userReport.getFinishTime() != null){
+            return 0;
+        }
+        return userReport.getId();
+    }
+
+    /**
      * 开始新一轮做题
      * @param userId
      * @param origin
@@ -578,10 +594,13 @@ public class QuestionFlowService {
             }else{
                 paper = userPaperService.add(paper);
             }
-        }else if(setting == null){
-            // 对于长难句这种没有设置开始页的,读取最后一次
+        }else{
             userReport = userReportService.getLastByPaper(userId, paper.getId());
+            if (userReport != null && userReport.getFinishTime() != null){
+                userReport = null;
+            }
         }
+
         if (userReport == null){
             userReport = userReportService.newByPaper(paper, setting);
             userReport.setTimes(paper.getTimes() + 1);
@@ -637,7 +656,7 @@ public class QuestionFlowService {
      */
     @Transactional
     public UserQuestion next(Integer userId, Integer userReportId){
-        UserQuestion userQuestion = userQuestionService.getLastByReport(userId, userReportId);
+        UserQuestion userQuestion = userQuestionService.getLastByReportWithAuto(userId, userReportId);
         // 查找未完成的questionId
         if (userQuestion==null || userQuestion.getUserTime() >0 || userQuestion.getIsAuto() == 1){
             // 创建新的question
@@ -693,6 +712,7 @@ public class QuestionFlowService {
         if (userQuestion.getUserAnswer()!=null){
             userReportService.accumulation(userQuestion);
         }
+        userReportService.edit(UserReport.builder().id(userReport.getId()).build());
 
         // 自动finish
         if (userReport.getQuestionNumber().equals(userReport.getUserNumber())){
@@ -945,7 +965,7 @@ public class QuestionFlowService {
         // 根据设置出题
         Integer questionNoId = 0;
         if (paper.getIsAdapt() > 0){
-            List<String> questionTypes = QuestionType.SpecialFromSubject(subject);
+            List<String> questionTypes = QuestionType.FromSubject(subject);
             List<UserQuestion> userQuestionList = userQuestionService.listByReportAndType(report.getUserId(), report.getId(), questionTypes);
             Collection ids = Transform.getIds(userQuestionList, UserQuestion.class, "questionNoId");
 
@@ -1075,7 +1095,7 @@ public class QuestionFlowService {
             // 计算上阶段的做题情况
             long correct = subQuestionList.stream().filter((x)->questionNoIds.contains(x.getQuestionNoId()) && x.getIsCorrect() > 0).count();
             // 下一阶段
-            Integer level = examinationService.quantNextLevel(info.getInteger("level"), examinationService.quantBaseLevel[step], correct, step);
+            Integer level = examinationService.quantNextLevel(info.getInteger("level"), examinationService.quantBaseLevel[nextStep], correct, nextStep);
             Map<String, Integer> typeNumber = examinationService.statTypeNumber(subQuestionList);
             info = examinationService.generateQuant(paper.getStructThree(), level, typeNumber, ids, nextStep);
             steps.add(info);
@@ -1119,7 +1139,7 @@ public class QuestionFlowService {
                     }else{
                         number = 3;
                     }
-                    Integer[] questions = questionNoService.randomExaminationRc(paper.getStructThree(), number, rc.values());
+                    Integer[] questions = questionNoService.randomExaminationRc(paper.getStructThree(), number, null, rc.values());
                     // 写入后续题目关系
                     Integer position = subnumber;
                     for(int q: questions){
@@ -1781,6 +1801,7 @@ public class QuestionFlowService {
                 type.put("difficult", new JSONObject());
                 JSONObject initTypeInfo = new JSONObject();
                 // 初始化题型基础信息
+                initTypeInfo.put("questionNumber", 0);
                 initTypeInfo.put("userNumber", 0);
                 initTypeInfo.put("userTime", 0);
                 initTypeInfo.put("userCorrect", 0);
@@ -1855,9 +1876,9 @@ public class QuestionFlowService {
             if (userQuestion.getIsAuto()==0) {
                 difficult.put("userNumber", difficult.getInteger("userNumber") + 1);
                 difficult.put("userCorrect", difficult.getInteger("userCorrect") + userQuestion.getIsCorrect());
-                difficult.put("totalNumber", difficult.getInteger("totalNumber") + relation.getTotalNumber() + 1);
-                difficult.put("totalCorrect", difficult.getInteger("totalCorrect") + relation.getTotalCorrect() + userQuestion.getIsCorrect());
             }
+            difficult.put("totalNumber", difficult.getInteger("totalNumber") + relation.getTotalNumber() + 1);
+            difficult.put("totalCorrect", difficult.getInteger("totalCorrect") + relation.getTotalCorrect() + userQuestion.getIsCorrect());
 
             difficult = difficultSubjectMap.getJSONObject(difficultKey);
             if (difficult == null){
@@ -1872,9 +1893,9 @@ public class QuestionFlowService {
             if (userQuestion.getIsAuto()==0){
                 difficult.put("userNumber", difficult.getInteger("userNumber") + 1);
                 difficult.put("userCorrect", difficult.getInteger("userCorrect") + userQuestion.getIsCorrect());
-                difficult.put("totalNumber", difficult.getInteger("totalNumber") + relation.getTotalNumber() + 1);
-                difficult.put("totalCorrect", difficult.getInteger("totalCorrect") + relation.getTotalCorrect() + userQuestion.getIsCorrect());
             }
+            difficult.put("totalNumber", difficult.getInteger("totalNumber") + relation.getTotalNumber() + 1);
+            difficult.put("totalCorrect", difficult.getInteger("totalCorrect") + relation.getTotalCorrect() + userQuestion.getIsCorrect());
 
             // 基础数据
             if (userQuestion.getIsAuto()==0) {
@@ -1882,6 +1903,7 @@ public class QuestionFlowService {
                 typeInfo.put("userTime", typeInfo.getIntValue("userTime") + userQuestion.getUserTime());
                 typeInfo.put("userCorrect", typeInfo.getIntValue("userCorrect") + userQuestion.getIsCorrect());
             }
+            typeInfo.put("questionNumber", typeInfo.getInteger("questionNumber") + 1);
 
             if (userQuestion.getIsCorrect() > 0){
                 typeInfo.put("correctTime", typeInfo.getIntValue("correctTime")+userQuestion.getUserTime());
@@ -1921,13 +1943,13 @@ public class QuestionFlowService {
             JSONObject type = typeMap.getJSONObject(key);
             JSONObject typeInfo = type.getJSONObject("info");
             typeInfo.put("avgDiffCorrect", toolsService.avgDiffScore(typeInfo.getFloat("diffCorrect"), typeInfo.getIntValue("userCorrect")));
-            typeInfo.put("avgDiffIncorrect", toolsService.avgDiffScore(typeInfo.getFloat("diffIncorrect"), typeInfo.getIntValue("userNumber") - typeInfo.getIntValue("userCorrect")));
+            typeInfo.put("avgDiffIncorrect", toolsService.avgDiffScore(typeInfo.getFloat("diffIncorrect"), typeInfo.getIntValue("questionNumber") - typeInfo.getIntValue("userCorrect")));
         }
         for (String key : subjectMap.keySet()) {
             JSONObject subject = subjectMap.getJSONObject(key);
             JSONObject subjectInfo = subject.getJSONObject("info");
             subjectInfo.put("avgDiffCorrect", toolsService.avgDiffScore(subjectInfo.getFloat("diffCorrect"), subjectInfo.getIntValue("userCorrect")));
-            subjectInfo.put("avgDiffIncorrect", toolsService.avgDiffScore(subjectInfo.getFloat("diffIncorrect"), subjectInfo.getIntValue("userNumber") - subjectInfo.getIntValue("userCorrect")));
+            subjectInfo.put("avgDiffIncorrect", toolsService.avgDiffScore(subjectInfo.getFloat("diffIncorrect"), subjectInfo.getIntValue("questionNumber") - subjectInfo.getIntValue("userCorrect")));
         }
 
         // 学科得分计算
@@ -1937,17 +1959,17 @@ public class QuestionFlowService {
         JSONObject quantSubject = subjectMap.getJSONObject(QuestionSubject.QUANT.key);
         if (quantSubject != null){
             JSONObject quantInfo = quantSubject.getJSONObject("info");
-            quantScore = toolsService.quantScore(quantInfo.getIntValue("number"), quantInfo.getIntValue("userNumber"), quantInfo.getFloatValue("difficultScore"), quantInfo.getInteger("userCorrect"));
+            quantScore = toolsService.quantScore(quantInfo.getIntValue("questionNumber"), quantInfo.getIntValue("userNumber"), quantInfo.getFloatValue("difficultScore"), quantInfo.getInteger("userCorrect"));
         }
         JSONObject verbalSubject = subjectMap.getJSONObject(QuestionSubject.VERBAL.key);
         if (verbalSubject != null){
             JSONObject verbalInfo = verbalSubject.getJSONObject("info");
-            verbalScore = toolsService.verbalScore(verbalInfo.getIntValue("number"), verbalInfo.getIntValue("userNumber"), verbalInfo.getFloatValue("difficultScore"), verbalInfo.getInteger("userCorrect"));
+            verbalScore = toolsService.verbalScore(verbalInfo.getIntValue("questionNumber"), verbalInfo.getIntValue("userNumber"), verbalInfo.getFloatValue("difficultScore"), verbalInfo.getInteger("userCorrect"));
         }
         JSONObject irSubject = subjectMap.getJSONObject(QuestionSubject.IR.key);
         if (irSubject != null){
             JSONObject irInfo = irSubject.getJSONObject("info");
-            irScore = toolsService.irScore(irInfo.getIntValue("number"), irInfo.getIntValue("userNumber"), irInfo.getInteger("difficultScore"), irInfo.getInteger("userCorrect"));
+            irScore = toolsService.irScore(irInfo.getIntValue("questionNumber"), irInfo.getIntValue("userNumber"), irInfo.getInteger("difficultScore"), irInfo.getInteger("userCorrect"));
         }
 
         Rank rank = toolsService.totalScore(quantScore, verbalScore);
@@ -1956,6 +1978,7 @@ public class QuestionFlowService {
         score.put("quantScore", quantScore);
         score.put("quantRank", rank.getQuantRank());
         score.put("verbalScore", verbalScore);
+        score.put("verbalRank", rank.getVerbalRank());
         score.put("irScore", irScore);
         score.put("irRank", rank.getIrRank());
 

+ 4 - 3
server/gateway-api/src/main/java/com/qxgmat/service/extend/TextbookService.java

@@ -306,19 +306,20 @@ public class TextbookService {
         // 获取最后一个paper
         TextbookPaper paper = textbookPaperService.getLastByLibrary(logic, question.getLibraryId());
         Integer no = 0;
-        if (paper == null || paper.getQuestionNumber().equals(paperLength)){
+        if (paper == null || paper.getQuestionNumber().equals(paperLength)) {
             if (paper != null){
                 no = paper.getNo();
             }
             paper = textbookPaperService.add(TextbookPaper.builder()
-                    .logic(SentenceLogic.NO.key)
+                    .logic(logic.key)
                     .year(question.getYear())
                     .libraryId(question.getLibraryId())
                     .title(textbookPaperService.generateTitle(prefixTitle, paperLength, no, 1))
                     .no(no+1)
                     .questionNumber(1)
                     .status(1)
-                    .questionNoIds(new Integer[]{question.getId()}).build()
+                    .questionTIds(new Integer[]{question.getId()})
+                    .questionNoIds(new Integer[]{question.getQuestionNoId()}).build()
             );
         }else{
             // 加入

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

@@ -254,7 +254,7 @@ public class ToolsService {
      * @return
      */
     public long quantScore(Integer number, Integer userNumber, float difficultScore, Integer correctNumber){
-        long score = Math.round(17 + 19.45 * correctNumber / number + 0.72 * difficultScore / userNumber);
+        long score = Math.round(17 + 19.45 * correctNumber / userNumber + 0.72 * difficultScore / userNumber);
         if (score > scoreMax) return scoreMax;
         if (score < scoreMin) return scoreMin;
         return score;
@@ -272,7 +272,7 @@ public class ToolsService {
 //        return verbalA * correctNumber / number + verbalB * difficultScore / userNumber;
 //    }
     /**
-     * Verbal分数计算:23.59 + 平均正确率*29.6+平均难度*1.72
+     * Verbal分数计算:23.59 + 平均正确率*39.6+平均难度*1.72
      * @param number
      * @param userNumber
      * @param difficultScore
@@ -280,7 +280,7 @@ public class ToolsService {
      * @return
      */
     public long verbalScore(Integer number, Integer userNumber, float difficultScore, Integer correctNumber){
-        long score = Math.round(23.59 + 29.6 * correctNumber / number + 1.72 * difficultScore / userNumber);
+        long score = Math.round(-23.59 + 39.6 * correctNumber / userNumber + 1.72 * difficultScore / userNumber);
         if (score > scoreMax) return scoreMax;
         if (score < scoreMin) return scoreMin;
         return score;

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

@@ -88,6 +88,10 @@ public class ExercisePaperService extends AbstractService {
         return String.format("%s(%d-%d)", prefixTitle, (no - 1) * length + 1, (no - 1) * length + questionNumber);
     }
 
+    public String generateRcTitle(String prefixTitle, Integer lastMaxNumber, Integer questionNumber){
+        return String.format("%s(%d-%d)", prefixTitle, lastMaxNumber + 1, lastMaxNumber + questionNumber);
+    }
+
     public String generateOriginTitle(String prefixTitle, Integer min, Integer max){
         return String.format("%s(%d-%d)", prefixTitle, min, max);
     }

+ 14 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewAssignService.java

@@ -146,6 +146,20 @@ public class PreviewAssignService extends AbstractService {
 
     /**
      * 用户查询1v1相对的预约作业
+     * @param appointmentId
+     * @return
+     */
+    public PreviewAssign getWithAppointment(Integer appointmentId){
+        Example example = new Example(PreviewAssign.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("courseAppointment", appointmentId)
+        );
+        return one(previewAssignMapper, example);
+    }
+
+    /**
+     * 用户查询1v1相对的预约作业
      * @param appointmentIds
      * @return
      */

+ 0 - 14
server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewPaperService.java

@@ -2,7 +2,6 @@ package com.qxgmat.service.inline;
 
 import com.github.pagehelper.Page;
 import com.nuliji.tools.AbstractService;
-import com.nuliji.tools.PageResult;
 import com.nuliji.tools.Tools;
 import com.nuliji.tools.Transform;
 import com.nuliji.tools.exception.ParameterException;
@@ -10,29 +9,16 @@ import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
 import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.module.CourseModule;
-import com.qxgmat.data.constants.enums.module.PaperModule;
-import com.qxgmat.data.constants.enums.module.PaperOrigin;
-import com.qxgmat.data.constants.enums.module.QuestionModule;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
-import com.qxgmat.data.constants.enums.status.PreviewStatus;
 import com.qxgmat.data.dao.PreviewPaperMapper;
 import com.qxgmat.data.dao.entity.PreviewPaper;
-import com.qxgmat.data.dao.entity.UserPaper;
-import com.qxgmat.data.dao.entity.UserReport;
 import com.qxgmat.data.relation.PreviewPaperRelationMapper;
-import com.qxgmat.data.relation.UserPaperRelationMapper;
-import com.qxgmat.data.relation.UserReportRelationMapper;
-import com.qxgmat.data.relation.entity.UserPreviewPaperRelation;
-import com.qxgmat.service.UserPaperService;
-import com.qxgmat.service.extend.ToolsService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.util.*;
-import java.util.stream.Collectors;
 
 @Service
 public class PreviewPaperService extends AbstractService {

+ 11 - 25
server/gateway-api/src/main/java/com/qxgmat/service/inline/QuestionNoService.java

@@ -7,6 +7,7 @@ import com.nuliji.tools.Transform;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
+import com.qxgmat.data.constants.enums.QuestionDifficult;
 import com.qxgmat.data.constants.enums.module.StructModule;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.QuestionNoMapper;
@@ -108,9 +109,9 @@ public class QuestionNoService extends AbstractService {
      * @param keyword
      * @return
      */
-    public Page<QuestionNoRelation> searchStemFulltext(int page, int size, String keyword, String[] questionTypes, String module, Integer[] structIds, String place, String difficult, Integer qxCatId, String order){
+    public Page<QuestionNoRelation> searchStemFulltext(int page, int size, String keyword, String[] questionTypes, Integer[] structIds, String place, String difficult, Integer qxCatId, String order){
         Page<QuestionNoRelation> p = page(()->{
-            questionNoRelationMapper.searchStemFulltext(keyword, questionTypes, module, structIds, place, difficult, qxCatId, order);
+            questionNoRelationMapper.searchStemFulltext(keyword, questionTypes, structIds, place, difficult, qxCatId, order);
         }, page, size);
 
         Collection ids = Transform.getIds(p, QuestionNo.class, "id");
@@ -128,9 +129,9 @@ public class QuestionNoService extends AbstractService {
      * @param keyword
      * @return
      */
-    public Page<QuestionNo> searchNoFulltext(int page, int size, String keyword, String module, Integer qxCatId){
+    public Page<QuestionNo> searchNoFulltext(int page, int size, String keyword, Integer qxCatId){
         Page<QuestionNo> p = page(()->{
-            questionNoRelationMapper.searchNoFulltext(keyword, module, qxCatId);
+            questionNoRelationMapper.searchNoFulltext(keyword, qxCatId);
         }, page, size);
 
         Collection ids = Transform.getIds(p, QuestionNo.class, "id");
@@ -347,32 +348,17 @@ public class QuestionNoService extends AbstractService {
      * 随机查找模考阅读题
      * @param structId
      * @param number
+     * @param difficult
      * @param filterIds
      * @return
      */
-    public Integer[] randomExaminationRc(Integer structId, Integer number, Collection filterIds){
-        Example example = new Example(QuestionNo.class);
-        example.and(
-                example.createCriteria()
-                        .andGreaterThan("questionId", 0)
-                .andCondition(String.format(formatSet, structId, "module_struct"))
-                .andEqualTo("relationNumber", number)
-        );
-        if (filterIds != null && filterIds.size() > 0){
-            example.and(
-                    example.createCriteria()
-                            .andNotIn("id", filterIds)
-            );
-        }
-        example.and(
-                example.createCriteria()
-                        .andIsNull("deleteTime")
-        );
-        example.setOrderByClause("RAND()");
-        QuestionNo questionNo = one(questionNoMapper, example);
-        if (questionNo == null){
+    public Integer[] randomExaminationRc(Integer structId, Integer number, QuestionDifficult difficult, Collection filterIds){
+        List<QuestionNo> questionNoList = questionNoRelationMapper.randomExaminationRc(structId, number, difficult != null ? difficult.key : null, filterIds);
+        if (questionNoList == null || questionNoList.size() == 0) {
             throw new ParameterException("阅读题查找失败");
         }
+        Integer id = questionNoList.get(0).getId();
+        QuestionNo questionNo = get(id);
         return Arrays.stream(questionNo.getRelationQuestion()).boxed().toArray(Integer[]::new);
     }
 

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

@@ -94,6 +94,7 @@ public class UserOrderCheckoutService extends AbstractService {
                 example.createCriteria()
                         .andEqualTo("userId", userId)
                         .andEqualTo("orderId", orderId)
+                        .andEqualTo("isSelect", 1)
         );
         return update(userOrderCheckoutMapper, example, checkout) > 0;
     }

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

@@ -116,6 +116,11 @@ public class AsyncTask {
             }
             exerciseService.switchPaper(struct.getParentId(), struct.getId(), ExerciseLogic.DIFFICULT, allDifficultIds);
 
+            if (struct.getExtend().equals(QuestionType.RC.key)){
+                // 阅读没有考点和难易度组卷
+                continue;
+            }
+
             // 按考点组卷
             Map<String, List<QuestionNoRelation>> placeMap = new HashMap<>();
             for(QuestionNoRelation relation:list){
@@ -197,6 +202,10 @@ public class AsyncTask {
             if (struct.getLevel()!=4){
                 continue;
             }
+            if (struct.getExtend().equals(QuestionType.RC.key)){
+                // 阅读没有考点和难易度组卷
+                continue;
+            }
             String prefixTitle = String.format("%s%s", struct.getTitleEn(), struct.getTitleZh());
             QuestionType questionType = QuestionType.ValueOf(struct.getExtend());
             List<QuestionNoRelation> list = questionNoService.listWithRelationByStruct(StructModule.EXERCISE, struct.getId());

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java

@@ -229,7 +229,7 @@ public class ScheduledTask {
     }
 
     // 每小时判断发送预习作业消息:发送预习作业到期通知:6小时,去除夜间12-7点
-    @Scheduled(cron="0 0 * * * *")
+//    @Scheduled(cron="0 0 * * * *")
     public void autoSendPreviewMessage(){
         // 只考虑assign设定到期时间的作业
         Date now = new Date();