Browse Source

feat(server): 题目添加

Go 6 years ago
parent
commit
a1da4813d9
84 changed files with 2066 additions and 507 deletions
  1. 61 7
      front/project/admin/components/Association/index.js
  2. 39 30
      front/project/admin/components/QuestionNoList/index.js
  3. 3 0
      front/project/admin/components/QuestionNoList/index.less
  4. 1 1
      front/project/admin/components/SimilarQuestionNo/index.js
  5. 5 2
      front/project/admin/routes/setting/index/page.js
  6. 4 4
      front/project/admin/routes/setting/rank/page.js
  7. 3 0
      front/project/admin/routes/setting/service/page.js
  8. 8 5
      front/project/admin/routes/setting/struct/page.js
  9. 7 9
      front/project/admin/routes/subject/exercise/page.js
  10. 2 1
      front/project/admin/routes/subject/index.js
  11. 5 4
      front/project/admin/routes/subject/preview/page.js
  12. 6 20
      front/project/admin/routes/subject/previewDetail/page.js
  13. 106 15
      front/project/admin/routes/subject/question/page.js
  14. 8 5
      front/project/admin/routes/subject/sentence/page.js
  15. 2 4
      front/project/admin/routes/subject/sentenceQuestion/page.js
  16. 6 9
      front/project/admin/routes/subject/textbook/page.js
  17. 15 0
      front/project/admin/routes/subject/textbookLibrary/index.js
  18. 3 0
      front/project/admin/routes/subject/textbookLibrary/index.less
  19. 213 0
      front/project/admin/routes/subject/textbookLibrary/page.js
  20. 43 34
      front/project/admin/routes/subject/textbookQuestion/page.js
  21. 1 1
      front/project/admin/routes/system/manager/list/page.js
  22. 5 5
      front/project/admin/routes/user/ask/page.js
  23. 18 18
      front/project/admin/routes/user/feedback/page.js
  24. 2 1
      front/project/admin/stores/index.js
  25. 4 0
      front/project/admin/stores/sentence.js
  26. 24 4
      front/project/admin/stores/textbook.js
  27. 1 1
      front/src/components/DragList/index.js
  28. 0 1
      front/src/containers/Page.js
  29. 8 7
      front/src/services/Api.js
  30. 14 11
      front/src/services/Tools.js
  31. 6 0
      front/src/style/adminLeft.less
  32. 7 0
      server/data/src/main/java/com/qxgmat/data/dao/UserOrderMapper.java
  33. 5 4
      server/data/src/main/java/com/qxgmat/data/dao/entity/ManagerRole.java
  34. 25 25
      server/data/src/main/java/com/qxgmat/data/dao/entity/Pay.java
  35. 74 4
      server/data/src/main/java/com/qxgmat/data/dao/entity/QuestionNo.java
  36. 263 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookLibrary.java
  37. 140 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookLibraryHistory.java
  38. 16 16
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookPaper.java
  39. 16 69
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookQuestion.java
  40. 63 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrder.java
  41. 5 5
      server/data/src/main/java/com/qxgmat/data/dao/mapping/PayMapper.xml
  42. 4 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/QuestionNoMapper.xml
  43. 16 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookLibraryHistoryMapper.xml
  44. 15 0
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookLibraryMapper.xml
  45. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookPaperMapper.xml
  46. 2 4
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookQuestionMapper.xml
  47. 10 0
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderMapper.xml
  48. 3 5
      server/data/src/main/resources/mybatis-generator.xml
  49. 1 1
      server/dependencyDefine.gradle
  50. 1 0
      server/gateway-api/src/main/java/com/qxgmat/Application.java
  51. 7 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/ExerciseController.java
  52. 5 2
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/QuestionController.java
  53. 18 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SentenceController.java
  54. 102 15
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/TextbookController.java
  55. 7 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  56. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  57. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/QuestionNoExtendDto.java
  58. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/QuestionDto.java
  59. 14 4
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/QuestionNoDto.java
  60. 5 5
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/QuestionNoSearchDto.java
  61. 17 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/TextbookLibraryDto.java
  62. 57 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/TextbookLibraryHistoryDto.java
  63. 6 2
      server/gateway-api/src/main/java/com/qxgmat/service/ExercisePaperService.java
  64. 3 2
      server/gateway-api/src/main/java/com/qxgmat/service/SentencePaperService.java
  65. 48 77
      server/gateway-api/src/main/java/com/qxgmat/service/TextbookPaperService.java
  66. 5 1
      server/gateway-api/src/main/java/com/qxgmat/service/UsersService.java
  67. 180 0
      server/gateway-api/src/main/java/com/qxgmat/service/extend/TextbookService.java
  68. 25 6
      server/gateway-api/src/main/java/com/qxgmat/service/inline/QuestionNoService.java
  69. 21 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/SentenceQuestionService.java
  70. 18 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryHistoryService.java
  71. 71 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryService.java
  72. 77 4
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookQuestionService.java
  73. 0 7
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserClassService.java
  74. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserFeedbackErrorService.java
  75. 8 3
      server/gateway-api/src/main/java/com/qxgmat/task/AsyncTask.java
  76. 4 3
      server/gateway-api/src/main/java/com/qxgmat/util/shiro/ManagerRealm.java
  77. 1 1
      server/gateway-api/src/main/profile/dev/application-runtime.yml
  78. 13 13
      server/tools/src/main/java/com/nuliji/tools/PageMessage.java
  79. 13 13
      server/tools/src/main/java/com/nuliji/tools/mybatis/handler/IntegerArrayHandler.java
  80. 6 1
      server/tools/src/main/java/com/nuliji/tools/mybatis/handler/IntegerArrayWithJsonHandler.java
  81. 1 1
      server/tools/src/main/java/com/nuliji/tools/mybatis/handler/JsonArrayHandler.java
  82. 1 1
      server/tools/src/main/java/com/nuliji/tools/mybatis/handler/JsonObjectHandler.java
  83. 2 1
      server/tools/src/main/java/com/nuliji/tools/mybatis/handler/StringArrayHandler.java
  84. 8 4
      server/tools/src/main/java/com/nuliji/tools/mybatis/handler/StringArrayWithJsonHandler.java

+ 61 - 7
front/project/admin/components/Association/index.js

@@ -2,13 +2,26 @@ import React, { Component } from 'react';
 import { Form, Modal, Alert } from 'antd';
 import './index.less';
 import Select from '@src/components/Select';
+import { getMap, generateSearch } from '@src/services/Tools';
 import QuestionNoList from '../QuestionNoList';
+import { Question } from '../../stores/question';
+import { Sentence } from '../../stores/sentence';
 
 class Association extends Component {
   constructor(props) {
     super(props);
-    this.state = { ids: this.props.ids, nos: this.props.nos, show: !!props.modal, loading: false, err: '' };
+    this.state = { ids: this.props.ids, show: !!props.modal, loading: false, err: '' };
     this.questionNos = [];
+
+    generateSearch(this.props.field, { mode: 'multiple' }, this, (search) => {
+      return this.searchQuestion(search);
+    }, (row) => {
+      return {
+        title: row.title,
+        value: row.id,
+      };
+    }, this.props.ids, null);
+    this.listQuestion(this.props.ids, 'ids');
   }
 
   onConfirm() {
@@ -38,22 +51,63 @@ class Association extends Component {
     if (this.props.onCancel) this.props.onCancel();
   }
 
+  listQuestion(values, field = 'ids') {
+    if (!values || values.length === 0) {
+      this.setState({ questionNos: [] });
+      return;
+    }
+    let handler;
+    switch (this.props.module) {
+      case 'exercise':
+        // 查找练习题目
+        handler = Question.listNo({ [field]: values, module: this.props.module });
+        break;
+      case 'sentence':
+        handler = Sentence.listQuestion({ [field]: values });
+        break;
+      default:
+        return;
+    }
+    this.setState({ loading: true });
+    handler.then(result => {
+      const map = getMap(result, 'id');
+      const questionNos = values.map(row => map[row]).filter(row => row);
+      this.setState({ questionNos, loading: false });
+    });
+  }
+
+  searchQuestion(params) {
+    let handler;
+    switch (this.props.module) {
+      case 'exercise':
+        // 查找练习题目
+        handler = Question.searchNo(params);
+        break;
+      case 'sentence':
+        handler = Sentence.searchQuestion(params);
+        break;
+      default:
+        handler = Promise.reject(new Error('module is error'));
+    }
+    return handler;
+  }
+
   renderForm() {
     const { getFieldDecorator, setFieldsValue } = this.props.form;
     const { field = 'questionNoIds' } = this.props;
     return <Form>
       <Form.Item>
         {getFieldDecorator(field)(
-          <Select mode='tags' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} onChange={(values) => {
-            this.setState({ nos: values });
+          <Select mode='multiple' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} {...this.state[field]} onChange={(values) => {
+            // this.setState({ nos: values });
+            this.listQuestion(values, 'ids');
           }} />,
         )}
       </Form.Item>
-      <QuestionNoList module={this.props.module} loading={false} ids={this.state.ids} nos={this.state.nos} onChange={(questionNos) => {
-        this.questionNos = questionNos;
+      <QuestionNoList loading={this.state.loading} questionNos={this.state.questionNos} onChange={(nos) => {
+        this.questionNos = nos;
         getFieldDecorator(field);
-        const nos = questionNos.map(row => row.no);
-        setFieldsValue({ [field]: nos });
+        setFieldsValue({ [field]: nos.map(row => row.id) });
       }} />
     </Form>;
   }

+ 39 - 30
front/project/admin/components/QuestionNoList/index.js

@@ -2,22 +2,26 @@ import React, { Component } from 'react';
 import { Icon, List, Avatar, Typography } from 'antd';
 import './index.less';
 import DragList from '@src/components/DragList';
-import { getMap } from '@src/services/Tools';
-import { Question } from '../../stores/question';
 
 export default class QuestionNoList extends Component {
   constructor(props) {
     super(props);
     this.state = { loading: 0 };
-    this.searchQuestion(this.props.ids, 'ids');
+    // this.searchQuestion(this.props.ids, 'ids');
+    this.setState({ questionNos: this.props.questionNos });
   }
 
+  // componentWillReceiveProps(nextProps) {
+  //   if (this.props.ids !== nextProps.ids) {
+  //     this.searchQuestion(nextProps.ids, 'ids');
+  //   } else if (this.props.nos !== nextProps.nos) {
+  //     this.searchQuestion(nextProps.nos, 'nos');
+  //   }
+  // }
+
   componentWillReceiveProps(nextProps) {
-    if (this.props.ids !== nextProps.ids) {
-      this.searchQuestion(nextProps.ids, 'ids');
-    } else if (this.props.nos !== nextProps.nos) {
-      this.searchQuestion(nextProps.nos, 'nos');
-    }
+    const { loading } = this.state;
+    this.setState({ questionNos: nextProps.questionNos, loading: loading + 1 });
   }
 
   deleteQuestion(index) {
@@ -32,46 +36,51 @@ export default class QuestionNoList extends Component {
     const tmp = questionNos[oldIndex];
     questionNos[oldIndex] = questionNos[newIndex];
     questionNos[newIndex] = tmp;
-    console.log(questionNos);
     this.setState({ questionNos, loading: loading + 1 });
     this.props.onChange(questionNos);
   }
 
-  searchQuestion(values, field = 'nos') {
-    // console.log('search', values, field);
-    if (!values || values.length === 0) {
-      this.setState({ questionNos: [] });
-      return;
-    }
-    // 查找练习题目
-    Question.listNo({ [field]: values, module: this.props.module }).then(result => {
-      const { loading } = this.state;
-      const map = getMap(result, 'no');
-      const questionNos = values.map(no => map[no]).filter(row => row);
-      this.setState({ questionNos, loading: loading + 1 });
-      this.props.onChange(questionNos);
-    });
-  }
+  // searchQuestion(values, field = 'nos') {
+  //   // console.log('search', values, field);
+  //   if (!values || values.length === 0) {
+  //     this.setState({ questionNos: [] });
+  //     return;
+  //   }
+  //   // 查找练习题目
+  //   Question.listNo({ [field]: values, module: this.props.module }).then(result => {
+  //     const { loading } = this.state;
+  //     const map = getMap(result, 'no');
+  //     const questionNos = values.map(no => map[no]).filter(row => row);
+  //     this.setState({ questionNos, loading: loading + 1 });
+  //     this.props.onChange(questionNos);
+  //   });
+  // }
 
   render() {
     return <DragList
       key={this.state.loading}
-      loading={this.props.loading}
+      loading={!!this.props.loading}
       dataSource={this.state.questionNos || []}
       handle={'.icon'}
       onMove={(oldIndex, newIndex) => {
         this.orderQuestion(oldIndex, newIndex);
       }}
-      renderItem={(item, index) => (
-        <List.Item actions={[<Icon type='delete' onClick={() => {
+      type={this.props.type}
+      renderItem={(item, index) => {
+        if (this.props.render) {
+          return this.props.render(item, 'icon', () => {
+            this.deleteQuestion(index);
+          });
+        }
+        return <List.Item actions={[<Icon type='delete' onClick={() => {
           this.deleteQuestion(index);
         }} />, <Icon type='bars' className='icon' />]}>
           <List.Item.Meta
             avatar={<Avatar alt={index + 1} size='small' >{index + 1}</Avatar>}
-            title={item.no}
+            title={item.title}
             description={<Typography.Text ellipsis disabled>{item.description}</Typography.Text>}
           />
-        </List.Item>
-      )} />;
+        </List.Item>;
+      }} />;
   }
 }

+ 3 - 0
front/project/admin/components/QuestionNoList/index.less

@@ -0,0 +1,3 @@
+.drag.inline {
+  display: inline-block;
+}

+ 1 - 1
front/project/admin/components/SimilarQuestionNo/index.js

@@ -46,7 +46,7 @@ class SimilarQuestionNo extends Component {
             this.setState({ questionNo: value ? row : null });
           }} /></Col>
           <Col span={4} offset={1}>
-            {row.no}
+            {row.title}
           </Col>
           <Col span={16} offset={1}>
             {row.question.description}

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

@@ -191,8 +191,8 @@ export default class extends Page {
                     showUploadList={false}
                     beforeUpload={(file) => System.uploadImage(file).then((result) => {
                       setFieldsValue({ [`class.${index}.image`]: result });
+                      return Promise.reject();
                     })}
-                    onChange={this.handleChange}
                   >
                     {image ? <img src={image} alt="avatar" /> : <div>
                       <Icon type={this.state.loading ? 'loading' : 'plus'} />
@@ -257,8 +257,8 @@ export default class extends Page {
                     showUploadList={false}
                     beforeUpload={(file) => System.uploadImage(file).then((result) => {
                       setFieldsValue({ [`claactivityss.${index}.image`]: result });
+                      return Promise.reject();
                     })}
-                    onChange={this.handleChange}
                   >
                     {image ? <img src={image} alt="avatar" /> : <div>
                       <Icon type={this.state.loading ? 'loading' : 'plus'} />
@@ -323,6 +323,7 @@ export default class extends Page {
                     showUploadList={false}
                     beforeUpload={(file) => System.uploadImage(file).then((result) => {
                       setFieldsValue({ [`evaluation.${index}.avatar`]: result });
+                      return Promise.reject();
                     })}
                   >
                     {avatar ? <img src={avatar} alt="avatar" /> : <div>
@@ -404,6 +405,7 @@ export default class extends Page {
               beforeUpload={(file) => {
                 System.uploadImage(file).then((result) => {
                   setFieldsValue({ 'contact.wechatImage': result });
+                  return Promise.reject();
                 });
               }
               }
@@ -422,6 +424,7 @@ export default class extends Page {
               showUploadList={false}
               beforeUpload={(file) => System.uploadImage(file).then((result) => {
                 setFieldsValue({ 'contact.weiboImage': result });
+                return Promise.reject();
               })}
             >
               {weiboImage ? <img src={weiboImage} alt="avatar" /> : <div>

+ 4 - 4
front/project/admin/routes/setting/rank/page.js

@@ -116,10 +116,10 @@ export default class extends Page {
   delAction() {
     const { selectedKeys } = this.state;
     asyncDelConfirm('删除确认', '是否删除选中数据?', () => {
-      return Promise.all(selectedKeys.map(row => System.delRank({ id: row })));
-    }).then(() => {
-      asyncSMessage('操作成功!');
-      this.refresh();
+      return Promise.all(selectedKeys.map(row => System.delRank({ id: row }))).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
     });
   }
 

+ 3 - 0
front/project/admin/routes/setting/service/page.js

@@ -178,6 +178,7 @@ export default class extends Page {
               showUploadList={false}
               beforeUpload={(file) => System.uploadImage(file).then((result) => {
                 setFieldsValue({ 'qx_cat.image': result });
+                return Promise.reject();
               })}
             >
               {image ? <img src={image} alt="avatar" /> : <div>
@@ -207,6 +208,7 @@ export default class extends Page {
               showUploadList={false}
               beforeUpload={(file) => System.uploadImage(file).then((result) => {
                 setFieldsValue({ 'textbook.image': result });
+                return Promise.reject();
               })}
             >
               {image ? <img src={image} alt="avatar" /> : <div>
@@ -301,6 +303,7 @@ export default class extends Page {
             showUploadList={false}
             beforeUpload={(file) => System.uploadImage(file).then((result) => {
               setFieldsValue({ 'vip.image': result });
+              return Promise.reject();
             })}
           >
             {image ? <img src={image} alt="avatar" /> : <div>

+ 8 - 5
front/project/admin/routes/setting/struct/page.js

@@ -373,13 +373,16 @@ export default class extends Page {
   deleteAction() {
     const { tab, checkedKeys } = this.state;
     asyncDelConfirm('删除确认', '是否删除选中节点?', () => {
+      let handler;
       if (tab === 'exercise') {
-        return Promise.all(checkedKeys.map(row => Examination.delStruct({ id: row })));
+        handler = Promise.all(checkedKeys.map(row => Examination.delStruct({ id: row })));
+      } else {
+        handler = Promise.all(checkedKeys.map(row => Examination.delStruct({ id: row })));
       }
-      return Promise.all(checkedKeys.map(row => Examination.delStruct({ id: row })));
-    }).then(() => {
-      asyncSMessage('删除成功!');
-      this.refresh();
+      return handler.then(() => {
+        asyncSMessage('删除成功!');
+        this.refresh();
+      });
     });
   }
 

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

@@ -167,7 +167,6 @@ export default class extends Page {
           )}
           {(
             <a onClick={() => {
-              // this.setState({ detail: { title: '题源联想', ids: record.question.associationContent, field: 'associationContent', module: 'exercise', modal: true, show: true } });
               asyncGet(() => import('../../../components/Association'),
                 { title: '题源联想', ids: record.question.associationContent, field: 'associationContent', module: 'exercise', modal: true },
                 (data) => {
@@ -177,7 +176,7 @@ export default class extends Page {
                     asyncSMessage('修改成功!');
                   });
                 });
-            }}>编辑</a>
+            }}>题源联想</a>
           )}
         </div>;
       },
@@ -228,7 +227,7 @@ export default class extends Page {
       return Question.searchNo(search);
     }, (row) => {
       return {
-        title: row.no,
+        title: row.title,
         value: row.id,
       };
     }, this.state.search.questionNoId ? Number(this.state.search.questionNoId) : null, null);
@@ -243,7 +242,7 @@ export default class extends Page {
       data.endTime = data.time[1] || '';
     }
     Exercise.listQuestion(data).then(result => {
-      this.setTableData(result.list, result.total || 1);
+      this.setTableData(result.list, result.total);
     });
   }
 
@@ -259,10 +258,10 @@ export default class extends Page {
   deleteAction() {
     const { selectedRows } = this.state;
     asyncDelConfirm('删除确认', '是否删除选中题目?', () => {
-      return Promise.all(selectedRows.map(row => Question.del({ id: row.question_id })));
-    }).then(() => {
-      asyncSMessage('删除成功!');
-      this.refresh();
+      return Promise.all(selectedRows.map(row => Question.del({ id: row.question_id }))).then(() => {
+        asyncSMessage('删除成功!');
+        this.refresh();
+      });
     });
   }
 
@@ -293,7 +292,6 @@ export default class extends Page {
         onSelect={(keys, rows) => this.tableSelect(keys, rows)}
         selectedKeys={this.state.selectedKeys}
       />
-      {/* {this.state.detail && <Association {...this.state.detail} />} */}
     </Block>;
   }
 }

+ 2 - 1
front/project/admin/routes/subject/index.js

@@ -2,6 +2,7 @@ import question from './question';
 import exercise from './exercise';
 import examination from './examination';
 import textbook from './textbook';
+import textbookLibrary from './textbookLibrary';
 import textbookQuestion from './textbookQuestion';
 import sentence from './sentence';
 import preview from './preview';
@@ -9,4 +10,4 @@ import previewDetail from './previewDetail';
 import sentenceArticle from './sentenceArticle';
 import sentenceQuestion from './sentenceQuestion';
 
-export default [question, exercise, examination, textbook, textbookQuestion, sentence, preview, previewDetail, sentenceArticle, sentenceQuestion];
+export default [question, exercise, examination, textbook, textbookLibrary, textbookQuestion, sentence, preview, previewDetail, sentenceArticle, sentenceQuestion];

+ 5 - 4
front/project/admin/routes/subject/preview/page.js

@@ -58,6 +58,7 @@ export default class extends Page {
           showUploadList={false}
           beforeUpload={(file) => System.uploadImage(file).then((result) => {
             asyncSMessage(result);
+            return Promise.reject();
           })}
         >
           <Button>{item.name}</Button>
@@ -140,10 +141,10 @@ export default class extends Page {
   deleteAction() {
     const { selectedKeys } = this.state;
     asyncDelConfirm('删除确认', '是否删除选中作业?', () => {
-      return Promise.all(selectedKeys.map(row => Preview.delStruct({ id: row })));
-    }).then(() => {
-      asyncSMessage('删除成功!');
-      this.refresh();
+      return Promise.all(selectedKeys.map(row => Preview.delStruct({ id: row }))).then(() => {
+        asyncSMessage('删除成功!');
+        this.refresh();
+      });
     });
   }
 

+ 6 - 20
front/project/admin/routes/subject/previewDetail/page.js

@@ -5,13 +5,13 @@ import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import Select from '@src/components/Select';
 // import FileUpload from '@src/components/FileUpload';
-import { formatFormError, generateSearch, getMap } from '@src/services/Tools';
+import { formatFormError, generateSearch } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
 // import { PreviewMode } from '../../../../Constant';
 import QuestionNoList from '../../../components/QuestionNoList';
 import { Preview } from '../../../stores/preview';
 import { User } from '../../../stores/user';
-import { Question } from '../../../stores/question';
+// import { Question } from '../../../stores/question';
 import { Exercise } from '../../../stores/exercise';
 import config from './index';
 
@@ -98,20 +98,6 @@ export default class extends Page {
     this.props.form.setFieldsValue({ questionNos: questionNos.map(row => row.no) });
   }
 
-  searchQuestion(values, field = 'nos') {
-    if (values.length === 0) {
-      this.setState({ questionNos: [] });
-      return;
-    }
-    // 查找练习题目
-    Question.listNo({ [field]: values, module: 'exercise' }).then(result => {
-      const map = getMap(result, 'no');
-      const questionNos = values.map(no => map[no]).filter(row => row);
-      this.setState({ questionNos });
-      this.props.form.setFieldsValue({ questionNos: questionNos.map(row => row.no) });
-    });
-  }
-
   renderBase() {
     const { getFieldDecorator } = this.props.form;
     return <Block>
@@ -159,7 +145,8 @@ export default class extends Page {
               { required: true, message: '请选择作业题' },
             ],
           })(
-            <Select mode='tags' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} onChange={(values) => {
+            <Select mode='multiple' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} {...this.state.questionNos} onChange={(values) => {
+              // todo 查找
               this.setState({ questionNos: values });
             }} />,
           )}
@@ -172,10 +159,9 @@ export default class extends Page {
     const { getFieldDecorator, setFieldsValue } = this.props.form;
     return <Block>
       <h1>题目预览</h1>
-      <QuestionNoList module='exercise' loading={false} ids={this.state.questionNoIds} nos={this.state.questionNos} onChange={(questionNos) => {
-        this.questionNos = questionNos;
+      <QuestionNoList loading={false} questionNos={this.state.questionNos} onChange={(nos) => {
         getFieldDecorator('questionNos');
-        setFieldsValue({ questionNos: questionNos.map(row => row.no) });
+        setFieldsValue({ questionNos: nos.map(row => row.id) });
       }} />
     </Block>;
   }

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

@@ -6,7 +6,7 @@ import Editor from '@src/components/Editor';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import Select from '@src/components/Select';
-import { getMap, formatFormError, formatTreeData } from '@src/services/Tools';
+import { getMap, formatFormError, formatTreeData, generateSearch } from '@src/services/Tools';
 import { asyncSMessage, asyncGet } from '@src/services/AsyncTools';
 import { QuestionType, QuestionDifficult, QuestionStyleType, QuestionRadioDirection } from '../../../../Constant';
 import QuestionNoList from '../../../components/QuestionNoList';
@@ -22,7 +22,6 @@ export default class extends Page {
     super(props);
     this.placeList = [];
     this.placeSetting = null;
-    this.associationContent = [];
     this.uuid = [];
     this.examinationStructMap = {};
     this.exerciseStructMap = {};
@@ -31,6 +30,7 @@ export default class extends Page {
   init() {
     Promise.all([
       Exercise.allStruct().then(result => {
+        result = result.filter(row => row.isExamination);
         this.exerciseStructMap = getMap(result, 'id');
         return { value: 'exercise', key: 'exercise', label: '练习', title: '练习', children: formatTreeData(result.map(row => { row.title = `${row.titleZh}/${row.titleEn}`; return row; }), 'id', 'title', 'parentId') };
       }),
@@ -54,7 +54,7 @@ export default class extends Page {
         return result;
       });
     } else {
-      handler = Promise.resolve({ content: { number: 1, type: 'single', typeset: 'one', questions: [], steps: [] } });
+      handler = Promise.resolve({ content: { number: 1, type: 'single', typeset: 'one', questions: [], steps: [] }, associationContent: [1], questionNoIds: [1], questionType: 'rc' });
     }
     handler.then(result => {
       this.uuid[0] = -1;
@@ -89,16 +89,37 @@ export default class extends Page {
 
       form.getFieldDecorator('content.step');
       form.getFieldDecorator('content.number');
+      form.getFieldDecorator('content.table.row');
+      form.getFieldDecorator('content.table.col');
+      form.getFieldDecorator('stem');
+      form.getFieldDecorator('difficult');
+      form.getFieldDecorator('questionType');
+      form.getFieldDecorator('place');
+      form.getFieldDecorator('id');
+      form.getFieldDecorator('questionNoIds');
+      form.getFieldDecorator('relationQuestion');
       form.setFieldsValue(result);
       return result;
     })
       .then((result) => {
-        this.setState({ associationContentIds: result.associationContent || [] });
+        this.setState({ associationContentQuestionNos: [], realtionQuestionNos: [] });
         if (result.questionNoIds && result.questionNoIds.length > 0) {
-          return Question.listNo({ ids: result.questionNoIds }).then(list => {
+          Question.listNo({ ids: result.questionNoIds }).then(list => {
             this.setState({ questionNos: list });
+            return list;
+          }).then(rr => {
+            // 阅读关联题目: 只有一个id
+            if (rr.length === 1 && rr[0].relationQuestion && rr[0].relationQuestion.length > 0) {
+              this.bindRelationQuestion(rr[0].relationQuestion);
+            } else {
+              this.bindRelationQuestion();
+            }
+            return null;
           });
+        } else {
+          this.bindRelationQuestion();
         }
+        this.bindAssociationContent(result.associationContent);
         return null;
       });
   }
@@ -137,7 +158,7 @@ export default class extends Page {
         data.questionId = data.id || 0;
         delete data.id;
         data.no = data.questionNo;
-        data.no = `${nodeString}-${data.no}`;
+        data.title = `${nodeString}-${data.no}`;
         delete data.questionNo;
         Question.addNo(data).then((result) => {
           const { questionNos = [] } = this.state;
@@ -246,7 +267,7 @@ export default class extends Page {
       if (!err) {
         const data = form.getFieldsValue(fields);
         let handler;
-        data.associationContent = this.state.associationContent.map(row => row.id);
+        data.associationContent = this.state.associationContentQuestionNos.map(row => row.id);
         data.description = data.stem.replace(/<[^>]+>/g, '');
         data.content.questions = data.content.questions.map(row => {
           row.select = row.select.filter(r => r);
@@ -257,6 +278,7 @@ export default class extends Page {
           if (row.double) row.double = row.double.filter(r => r);
           return row;
         });
+        data.relationQuestion = this.state.realtionQuestionNos.map(row => row.id);
         if (data.id) {
           handler = Question.add(data);
         } else {
@@ -290,6 +312,49 @@ export default class extends Page {
       });
   }
 
+  bindAssociationContent(associationContent) {
+    generateSearch('associationContent', { mode: 'multiple' }, this, (search) => {
+      return this.searchQuestion(search);
+    }, (row) => {
+      return {
+        title: row.title,
+        value: row.id,
+      };
+    }, associationContent, null);
+    if (associationContent) this.listQuestion(associationContent, 'ids', 'associationContentQuestionNos');
+  }
+
+  bindRelationQuestion(relationQuestion) {
+    generateSearch('relationQuestion', { mode: 'multiple' }, this, (search) => {
+      return this.searchQuestion(search);
+    }, (row) => {
+      return {
+        title: row.title,
+        value: row.id,
+      };
+    }, relationQuestion, null);
+    if (relationQuestion) this.listQuestion(relationQuestion, 'ids', 'realtionQuestionNos');
+  }
+
+  listQuestion(values, field, targetField) {
+    if (!values || values.length === 0) {
+      this.setState({ [targetField]: [] });
+      return;
+    }
+    console.log(targetField, 'loading');
+    this.setState({ [`${targetField}Loading`]: true });
+    Question.listNo({ [field]: values }).then(result => {
+      const map = getMap(result, 'id');
+      console.log(targetField, 'finish');
+      const questionNos = values.map(row => map[row]).filter(row => row);
+      this.setState({ [targetField]: questionNos, [`${targetField}Loading`]: false });
+    });
+  }
+
+  searchQuestion(params) {
+    return Question.searchNo(params);
+  }
+
   renderBase() {
     const { getFieldDecorator } = this.props.form;
     return <Block flex>
@@ -426,7 +491,7 @@ export default class extends Page {
             return <Tag key={index} closable onClose={() => {
               this.removeNo(no.id);
             }}>
-              {no.no}
+              {no.title}
             </Tag>;
           })}
           <Row>
@@ -447,7 +512,7 @@ export default class extends Page {
                   rules: [{
                     required: true, message: '输入编号',
                   }],
-                })(<Input placeholder='题目id' onClick={(value) => {
+                })(<InputNumber placeholder='题目id' onClick={(value) => {
                   this.setState({ questionNo: value });
                 }} />)}
               </Form.Item>
@@ -469,7 +534,9 @@ export default class extends Page {
   }
 
   renderAttr() {
-    const { getFieldDecorator } = this.props.form;
+    const { getFieldDecorator, getFieldValue, setFieldsValue } = this.props.form;
+    const { questionNos = [] } = this.state;
+    const isRc = getFieldValue('questionType') === 'rc';
     return <Block flex>
       <h1>题目属性</h1>
       <Form>
@@ -502,6 +569,29 @@ export default class extends Page {
             <Select select={QuestionDifficult} placeholder='请选择难度' />,
           )}
         </Form.Item>
+        {/* 阅读题,并且只有一个id才能关联 */}
+        {isRc && questionNos.length === 1 && <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='关联题目'>
+          {getFieldDecorator('relationQuestion')(
+            <Select mode='multiple' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} {...this.state.relationQuestion} onChange={(values) => {
+              this.listQuestion(values, 'ids', 'relationQuestionNos');
+              // this.setState({ relationQuestion: values });
+            }} />,
+          )}
+          <QuestionNoList type='inline' loading={this.state.relationQuestionNosLoading} questionNos={this.state.relationQuestionNos} onChange={(nos) => {
+            getFieldDecorator('relationQuestion');
+            setFieldsValue({ relationQuestion: nos.map(row => row.id) });
+            this.setState({ relationQuestionNos: nos });
+          }}
+            render={(row, dragClass, close) => {
+              return <Tag className={dragClass} closable onClose={() => {
+                close();
+              }}>
+                {row.title}
+              </Tag>;
+            }}
+          />
+        </Form.Item>}
+
       </Form>
     </Block>;
   }
@@ -767,15 +857,16 @@ export default class extends Page {
       <Form>
         <Form.Item>
           {getFieldDecorator('associationContent')(
-            <Select mode='tags' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} onChange={(values) => {
-              this.setState({ associationContent: values });
+            <Select mode='multiple' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} {...this.state.associationContent} onChange={(values) => {
+              this.listQuestion(values, 'ids', 'associationContentQuestionNos');
+              // this.setState({ associationContent: values });
             }} />,
           )}
         </Form.Item>
-        <QuestionNoList loading={false} ids={this.state.associationContentIds} nos={this.state.associationContent} onChange={(questionNos) => {
-          this.associationContent = questionNos;
+        <QuestionNoList loading={this.state.associationContentQuestionNosLoading} questionNos={this.state.associationContentQuestionNos} onChange={(nos) => {
           getFieldDecorator('associationContent');
-          setFieldsValue({ associationContent: questionNos.map(row => row.no) });
+          setFieldsValue({ associationContent: nos.map(row => row.id) });
+          this.setState({ associationContentQuestionNos: nos });
         }} />
       </Form>
     </Block>;

+ 8 - 5
front/project/admin/routes/subject/sentence/page.js

@@ -207,13 +207,16 @@ export default class extends Page {
     const item = this.structMap[this.state.search.chapter];
     if (!item) return;
     asyncDelConfirm('删除确认', '是否删除选中?', () => {
+      let handler;
       if (item.exercise) {
-        return Promise.all(selectedKeys.map(row => Sentence.delQuestion({ id: row })));
+        handler = Promise.all(selectedKeys.map(row => Sentence.delQuestion({ id: row })));
+      } else {
+        handler = Promise.all(selectedKeys.map(row => Sentence.delArticle({ id: row })));
       }
-      return Promise.all(selectedKeys.map(row => Sentence.delArticle({ id: row })));
-    }).then(() => {
-      asyncSMessage('删除成功!');
-      this.refresh();
+      return handler.then(() => {
+        asyncSMessage('删除成功!');
+        this.refresh();
+      });
     });
   }
 

+ 2 - 4
front/project/admin/routes/subject/sentenceQuestion/page.js

@@ -75,14 +75,12 @@ export default class extends Page {
 
   renderTitle() {
     const { id } = this.params;
-    const { data } = this.state;
     const { getFieldDecorator } = this.props.form;
     return <Block>
       <Form>
         {getFieldDecorator('id')(<input hidden />)}
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题目序号' help='设定后,不允许修改'>
-          {id && (data.isSystem ? '是' : '否')}
-          {!id && getFieldDecorator('no', {
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题目序号'>
+          {getFieldDecorator('no', {
             rules: [
               { required: true, message: '请输入序号' },
               // {

+ 6 - 9
front/project/admin/routes/subject/textbook/page.js

@@ -10,11 +10,9 @@ import TableLayout from '@src/layouts/TableLayout';
 import { getMap, bindSearch, formatDate } from '@src/services/Tools';
 // import { asyncSMessage } from '@src/services/AsyncTools';
 import { TextbookType } from '../../../../Constant';
-import { Exercise } from '../../../stores/exercise';
 // import { System } from '../../../stores/system';
-import { Question } from '../../../stores/question';
+import { Textbook } from '../../../stores/textbook';
 // import { Slient } from '../../../stores/slient';
-// import Association from '../../../components/Association';
 
 const TextbookTypeMap = getMap(TextbookType, 'value', 'label');
 
@@ -70,7 +68,7 @@ export default class extends Page {
       title: '练习册',
       dataIndex: 'paper',
       render: (text) => {
-        return text.title;
+        return (text || {}).text.title;
       },
     }, {
       title: '题目ID',
@@ -96,7 +94,7 @@ export default class extends Page {
 
   init() {
     bindSearch(filterForm, 'paperId', this, (search) => {
-      return Exercise.listPaper(search);
+      return Textbook.listPaper(search);
     }, (row) => {
       return {
         title: row.title,
@@ -104,10 +102,10 @@ export default class extends Page {
       };
     }, this.state.search.paperId ? Number(this.state.search.paperId) : null, null);
     bindSearch(filterForm, 'questionNoId', this, (search) => {
-      return Question.searchNo(search);
+      return Textbook.searchQuestion(search);
     }, (row) => {
       return {
-        title: row.no,
+        title: row.title,
         value: row.id,
       };
     }, this.state.search.questionNoId ? Number(this.state.search.questionNoId) : null, null);
@@ -116,7 +114,7 @@ export default class extends Page {
   initData() {
     const { search } = this.state;
     const data = Object.assign({}, search);
-    Exercise.listQuestion(data).then(result => {
+    Textbook.listQuestion(data).then(result => {
       this.setTableData(result.list, result.total || 1);
     });
   }
@@ -145,7 +143,6 @@ export default class extends Page {
         onSelect={(keys, rows) => this.tableSelect(keys, rows)}
         selectedKeys={this.state.selectedKeys}
       />
-      {/* {this.state.detail && <Association {...this.state.detail} />} */}
     </Block>;
   }
 }

+ 15 - 0
front/project/admin/routes/subject/textbookLibrary/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/subject/textbook/library',
+  key: 'subject-textbook/library',
+  title: '换库列表',
+  needLogin: true,
+  module,
+  group,
+  index: true,
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/admin/routes/subject/textbookLibrary/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#subject-textbook-library {}

+ 213 - 0
front/project/admin/routes/subject/textbookLibrary/page.js

@@ -0,0 +1,213 @@
+import React from 'react';
+import { Button, Form, Modal, DatePicker, Input, Upload } from 'antd';
+import { Link } from 'react-router-dom';
+import moment from 'moment';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+// import FilterLayout from '@src/layouts/FilterLayout';
+// import ActionLayout from '@src/layouts/ActionLayout';
+import TableLayout from '@src/layouts/TableLayout';
+// import { getMap, bindSearch, formatDate } from '@src/services/Tools';
+import { asyncDelConfirm, asyncSMessage } from '@src/services/AsyncTools';
+// import { TextbookType } from '../../../../Constant';
+import { Textbook } from '../../../stores/textbook';
+import { System } from '../../../stores/system';
+// import { Question } from '../../../stores/question';
+// import { Slient } from '../../../stores/slient';
+
+export default class extends Page {
+  constructor(props) {
+    super(props);
+    this.actionList = [{
+      key: 'add',
+      name: '新建',
+      render: (item) => {
+        return <Button onClick={() => {
+          linkTo('/subject/textbook/question');
+        }}>{item.name}</Button>;
+      },
+    }];
+    this.columns = [{
+      title: '库头',
+      dataIndex: 'startDate',
+    }, {
+      title: '库尾',
+      dataIndex: 'endDate',
+    }, {
+      title: '发布次数',
+      dataIndex: 'historyNumber',
+      render: (text, record) => {
+        return text ? <a onClick={() => {
+          this.loadHistory(record);
+        }}>{text}</a> : 0;
+      },
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          {(
+            <a onClick={() => {
+              this.setState({ post: record });
+              this.props.form.getFieldDecorator('post.libraryId');
+              this.props.form.setFieldsValue({ 'post.libraryId': record.id });
+            }}>发布机经</a>
+          )}
+          {(
+            <Link to={`/subject/textbook/question?libraryId=${record.id}`}>录入机经</Link>
+          )}
+        </div>;
+      },
+    }];
+  }
+
+  initData() {
+    const { search } = this.state;
+    const data = Object.assign({}, search);
+    Textbook.listLibrary(data).then(result => {
+      this.setTableData(result.list, result.total || 1);
+    });
+  }
+
+  loadHistory(record) {
+    Textbook.listHistory({ libraryId: record.id }).then(result => {
+      this.setState({ history: result.list });
+    });
+  }
+
+  submit() {
+    this.props.form.validateFields(['add'], (err, fieldsValue) => {
+      if (err) {
+        return;
+      }
+      const data = fieldsValue.add;
+      data.startDate = data.startDate.format('YYYY-MM-DD');
+      asyncDelConfirm('新增确认', '是否添加新的换库?', () => {
+        return Textbook.addLibrary(data).then(() => {
+          asyncSMessage('添加成功!');
+          this.refresh();
+        });
+      });
+    });
+  }
+
+  submitPost() {
+    this.props.form.validateFields(['post'], (err, fieldsValue) => {
+      if (err) {
+        return;
+      }
+      const data = fieldsValue.post;
+      Textbook.postHistory(data).then(() => {
+        asyncSMessage('发布成功!');
+        this.close('post', false);
+        this.refresh();
+      });
+    });
+  }
+
+  renderView() {
+    const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
+
+    const quant = getFieldValue('post.quant') || null;
+    const ir = getFieldValue('post.ir') || null;
+    const rc = getFieldValue('post.rc') || null;
+    return <Block flex>
+      <Form layout="inline">
+        <Form.Item>
+          {getFieldDecorator('add.startDate', {
+            initialValue: moment(new Date(), 'YYYY-mm-dd'),
+          })(
+            <DatePicker />,
+          )}
+        </Form.Item>
+        <Form.Item><Button type='primary' onClick={() => {
+          this.submit();
+        }}>新增换库</Button></Form.Item>
+      </Form>
+      <TableLayout
+        columns={this.columns}
+        list={this.state.list}
+        pagination={this.state.page}
+        loading={this.props.core.loading}
+        onChange={(pagination, filters, sorter) => this.tableChange(pagination, filters, sorter)}
+        onSelect={(keys, rows) => this.tableSelect(keys, rows)}
+        selectedKeys={this.state.selectedKeys}
+      />
+      {this.state.history && <Modal visible closable title='发布历史' onCancel={() => {
+        this.close(false, 'history');
+      }} onOk={() => {
+        this.close(false, 'history');
+      }}>
+        {this.state.history.map(row => {
+          return <p>{row.content}</p>;
+        })}
+      </Modal>}
+      {this.state.post && <Modal visible closable title='发布机经' onCancel={() => {
+        this.close(false, 'post');
+      }} onOk={() => {
+        this.submitPost();
+      }}>
+        <Form>
+          {getFieldDecorator('post.libraryId')}
+          <Form.Item label='请上传机经文件,并点击发布'>
+            {getFieldDecorator('post.content')(
+              <Input.TextArea placeholder='更新日志' />,
+            )}
+          </Form.Item>
+          <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 15 }} label='数学机经'>
+            {getFieldDecorator('post.quant')(
+              <Upload
+                listType="text"
+                showUploadList={false}
+                beforeUpload={(file) => System.uploadImage(file).then((result) => {
+                  setFieldsValue({ 'post.quant': result });
+                  return Promise.reject();
+                })}
+                onChange={this.handleChange}
+              >
+                {<div>
+                  <Button>上传附件</Button> {quant && '已上传'}
+                </div>}
+              </Upload>,
+            )}
+          </Form.Item>
+          <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 15 }} label='阅读机经'>
+            {getFieldDecorator('post.rc')(
+              <Upload
+                listType="text"
+                showUploadList={false}
+                beforeUpload={(file) => System.uploadImage(file).then((result) => {
+                  setFieldsValue({ 'post.rc': result });
+                  return Promise.reject();
+                })}
+                onChange={this.handleChange}
+              >
+                {<div>
+                  <Button>上传附件</Button> {rc && '已上传'}
+                </div>}
+              </Upload>,
+            )}
+          </Form.Item>
+          <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 15 }} label='逻辑机经'>
+            {getFieldDecorator('post.ir')(
+              <Upload
+                listType="text"
+                showUploadList={false}
+                beforeUpload={(file) => System.uploadImage(file).then((result) => {
+                  setFieldsValue({ 'post.ir': result });
+                  return Promise.reject();
+                })}
+                onChange={this.handleChange}
+              >
+                {<div>
+                  <Button>上传附件</Button> {ir && '已上传'}
+                </div>}
+              </Upload>,
+            )}
+          </Form.Item>
+        </Form>
+      </Modal>}
+    </Block>;
+  }
+}

+ 43 - 34
front/project/admin/routes/subject/textbookQuestion/page.js

@@ -1,5 +1,5 @@
 import React from 'react';
-import { Form, Input, Tabs, Row, Col, Button, DatePicker, List, Icon, Checkbox } from 'antd';
+import { Form, Input, Tabs, Row, Col, Button, List, Icon, Checkbox } from 'antd';
 import './index.less';
 import DragList from '@src/components/DragList';
 import Editor from '@src/components/Editor';
@@ -10,7 +10,6 @@ import Select from '@src/components/Select';
 import { formatFormError, generateSearch, getMap } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
 import { TextbookType, QuestionStyleType } from '../../../../Constant';
-import { Preview } from '../../../stores/preview';
 import { Textbook } from '../../../stores/textbook';
 import { System } from '../../../stores/system';
 import config from './index';
@@ -33,14 +32,21 @@ export default class extends Page {
     }
   }
 
+  init() {
+  }
+
   initData() {
     const { id } = this.params;
     const { form } = this.props;
     let handler;
     if (id) {
-      handler = Preview.get({ id });
+      handler = Textbook.getQuestion({ id });
     } else {
-      handler = Promise.resolve({ question: { content: { number: 1, type: 'single', typeset: 'one', questions: [], steps: [] } } });
+      let { libraryId } = this.state.search;
+      libraryId = Number(libraryId || 0);
+      handler = Textbook.getNextQuestion({ libraryId }).then(result => {
+        return { libraryId: libraryId || '', no: result, question: { content: { number: 1, type: 'single', typeset: 'one', questions: [], steps: [] } } };
+      });
     }
     handler
       .then(result => {
@@ -80,15 +86,22 @@ export default class extends Page {
         form.getFieldDecorator('question.content.number');
         form.getFieldDecorator('question.content.type');
         form.getFieldDecorator('question.content.typeset');
+        form.getFieldDecorator('libraryId');
+        form.getFieldDecorator('no');
+        form.getFieldDecorator('question.questionType');
+        form.getFieldDecorator('question.place');
+        form.getFieldDecorator('question.id');
+        form.getFieldDecorator('id');
         form.setFieldsValue(result);
-        generateSearch('setId', {}, this, (search) => {
-          return System.listRank(search);
+
+        generateSearch('libraryId', {}, this, (search) => {
+          return Textbook.listLibrary(search);
         }, (row) => {
           return {
-            title: row.title,
+            title: `${row.startDate} - ${row.endDate || '至今'}`,
             value: row.id,
           };
-        }, result.setId, null);
+        }, result.libraryId, null);
         this.setState({ questionNoIds });
       });
   }
@@ -100,12 +113,18 @@ export default class extends Page {
     } else {
       handler = System.getPlace();
     }
-    handler.then(result => {
+    return handler.then(result => {
       this.placeSetting = result;
       this.placeList = result[type] || [];
     });
   }
 
+  refreshNo(libraryId) {
+    return Textbook.getNextQuestion({ libraryId }).then(result => {
+      this.props.form.setFieldsValue({ no: result });
+    });
+  }
+
   removeQuestion(index, k) {
     const { form } = this.props;
     const keys = form.getFieldValue(`question.content.questions[${index}].keys`);
@@ -168,38 +187,37 @@ export default class extends Page {
   }
 
   renderAttr() {
+    const { id } = this.params;
     const { getFieldDecorator } = this.props.form;
     return <Block flex>
       <h1>基本信息</h1>
       <Form>
         {getFieldDecorator('id')(<input hidden />)}
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='起止时间'>
-          {getFieldDecorator('time', {
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题型' help='设定后,不允许修改'>
+          {getFieldDecorator('question.questionType', {
             rules: [
-              { required: true, message: '请输入起止时间' },
+              { required: true, message: '请选择题型' },
             ],
           })(
-            <DatePicker.RangePicker />,
+            <Select disabled={id} select={TextbookType} placeholder='请选择题型' onChange={(v) => {
+              this.refreshPlace(v);
+            }} />,
           )}
         </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='换库表'>
-          {getFieldDecorator('setId', {
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='换库表' help='设定后,不允许修改'>
+          {getFieldDecorator('libraryId', {
             rules: [
               { required: true, message: '请选择换库表' },
             ],
           })(
-            <Select {...this.state.setId} placeholder='请选择换库表' />,
+            <Select disabled={id} {...this.state.libraryId} placeholder='请选择换库表' onChange={(v) => {
+              this.refreshNo(v);
+            }} />,
           )}
         </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题型'>
-          {getFieldDecorator('question.questionType', {
-            rules: [
-              { required: true, message: '请选择题型' },
-            ],
-          })(
-            <Select select={TextbookType} placeholder='请选择题型' onChange={(v) => {
-              this.refreshPlace(v);
-            }} />,
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题目序号'>
+          {getFieldDecorator('no')(
+            <Input disabled />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='考点'>
@@ -213,15 +231,6 @@ export default class extends Page {
             }} />,
           )}
         </Form.Item>
-        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题目id'>
-          {getFieldDecorator('title', {
-            rules: [
-              { required: true, message: '请输入题目id' },
-            ],
-          })(
-            <Input placeholder='请输入题目id' />,
-          )}
-        </Form.Item>
       </Form>
     </Block>;
   }

+ 1 - 1
front/project/admin/routes/system/manager/list/page.js

@@ -85,7 +85,7 @@ export default class extends Page {
   delAction() {
     const { selectedKeys } = this.state;
     asyncDelConfirm('删除确认', '是否删除选中账号?', () => {
-      Promise.all(selectedKeys.map(row => System.delManager({ id: row }))).then(() => {
+      return Promise.all(selectedKeys.map(row => System.delManager({ id: row }))).then(() => {
         asyncSMessage('删除成功!');
         this.refresh();
       });

+ 5 - 5
front/project/admin/routes/user/ask/page.js

@@ -139,7 +139,7 @@ export default class extends Page {
       return Question.searchNo(search);
     }, (row) => {
       return {
-        title: row.no,
+        title: row.title,
         value: row.id,
       };
     }, this.state.search.questionNoId ? Number(this.state.search.questionNoId) : [], null);
@@ -154,10 +154,10 @@ export default class extends Page {
   ignoreAction() {
     const { selectedKeys } = this.state;
     asyncDelConfirm('忽略确认', '是否忽略选中提问?', () => {
-      return Promise.all(selectedKeys.map(row => User.editAsk({ id: row, answerStatus: 2 })));
-    }).then(() => {
-      asyncSMessage('操作成功!');
-      this.refresh();
+      return Promise.all(selectedKeys.map(row => User.editAsk({ id: row, answerStatus: 2 }))).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
     });
   }
 

+ 18 - 18
front/project/admin/routes/user/feedback/page.js

@@ -107,42 +107,42 @@ export default class extends Page {
   handleDetail() {
     const { detail } = this.state;
     asyncDelConfirm('处理确认', '是否处理选中记录?', () => {
-      return User.editFeedbackError({ id: detail.id, status: 1 });
-    }).then(() => {
-      asyncSMessage('操作成功!');
-      this.setState({ detail: null });
-      this.refresh();
+      return User.editFeedbackError({ id: detail.id, status: 1 }).then(() => {
+        asyncSMessage('操作成功!');
+        this.setState({ detail: null });
+        this.refresh();
+      });
     });
   }
 
   ignoreDetail() {
     const { detail } = this.state;
     asyncDelConfirm('忽略确认', '是否忽略选中记录?', () => {
-      return User.editFeedbackError({ id: detail.id, status: 2 });
-    }).then(() => {
-      asyncSMessage('操作成功!');
-      this.setState({ detail: null });
-      this.refresh();
+      return User.editFeedbackError({ id: detail.id, status: 2 }).then(() => {
+        asyncSMessage('操作成功!');
+        this.setState({ detail: null });
+        this.refresh();
+      });
     });
   }
 
   handleAction() {
     const { selectedKeys } = this.state;
     asyncDelConfirm('处理确认', '是否处理选中记录?', () => {
-      return Promise.all(selectedKeys.map(row => User.editFeedbackError({ id: row, status: 1 })));
-    }).then(() => {
-      asyncSMessage('操作成功!');
-      this.refresh();
+      return Promise.all(selectedKeys.map(row => User.editFeedbackError({ id: row, status: 1 }))).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
     });
   }
 
   ignoreAction() {
     const { selectedKeys } = this.state;
     asyncDelConfirm('忽略确认', '是否忽略选中记录?', () => {
-      return Promise.all(selectedKeys.map(row => User.editFeedbackError({ id: row, status: 2 })));
-    }).then(() => {
-      asyncSMessage('操作成功!');
-      this.refresh();
+      return Promise.all(selectedKeys.map(row => User.editFeedbackError({ id: row, status: 2 }))).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
     });
   }
 

+ 2 - 1
front/project/admin/stores/index.js

@@ -2,10 +2,11 @@ import { System } from './system';
 import { Examination } from './examination';
 import { Exercise } from './exercise';
 import { Preview } from './preview';
+import { Textbook } from './textbook';
 import { User } from './user';
 import { Sentence } from './sentence';
 import { Question } from './question';
 import { Class } from './class';
 import { Slient } from './slient';
 
-export default [System, Examination, Exercise, Preview, User, Sentence, Question, Class, Slient];
+export default [System, Examination, Exercise, Preview, Textbook, User, Sentence, Question, Class, Slient];

+ 4 - 0
front/project/admin/stores/sentence.js

@@ -29,6 +29,10 @@ export default class SentenceStore extends BaseStore {
     return this.apiDel('/sentence/article/delete', params);
   }
 
+  searchQuestion(params) {
+    return this.apiGet('/sentence/question/search', params);
+  }
+
   listQuestion(params) {
     return this.apiGet('/sentence/question/list', params);
   }

+ 24 - 4
front/project/admin/stores/textbook.js

@@ -1,10 +1,22 @@
 import BaseStore from '@src/stores/base';
 
 export default class TextbookStore extends BaseStore {
+  listPaper(params) {
+    return this.apiGet('/textbook/paper/list', params);
+  }
+
+  searchQuestion(params) {
+    return this.apiGet('/textbook/question/search', params);
+  }
+
   listQuestion(params) {
     return this.apiGet('/textbook/question/list', params);
   }
 
+  getNextQuestion(params) {
+    return this.apiGet('/textbook/question/next', params);
+  }
+
   getQuestion(params) {
     return this.apiGet('/textbook/question/detail', params);
   }
@@ -21,12 +33,20 @@ export default class TextbookStore extends BaseStore {
     return this.apiDel('/textbook/question/delete', params);
   }
 
-  searchNo(params) {
-    return this.apiPost('/textbook/search/no', params);
+  listLibrary(params) {
+    return this.apiGet('/textbook/library/list', params);
   }
 
-  listPaper(params) {
-    return this.apiGet('/textbook/paper/list', params);
+  addLibrary(params) {
+    return this.apiPost('/textbook/library/add', params);
+  }
+
+  listHistory(params) {
+    return this.apiGet('/textbook/library/history/list', params);
+  }
+
+  postHistory(params) {
+    return this.apiPost('/textbook/library/history/post', params);
   }
 }
 

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

@@ -55,7 +55,7 @@ export default class DragSortingTable extends React.Component {
             if (ref) this.sortableDecorator(ref);
           }}>
           {this.props.dataSource.map((item, index) => {
-            return <div className='drag'>{this.props.renderItem(item, index)}</div>;
+            return <div className={`drag ${this.props.type}`}>{this.props.renderItem(item, index)}</div>;
           })}
         </div></Spin>
     );

+ 0 - 1
front/src/containers/Page.js

@@ -135,7 +135,6 @@ export default class extends Component {
   }
 
   refresh() {
-    this.restart();
     this.refreshQuery(this.state.search);
   }
 

+ 8 - 7
front/src/services/Api.js

@@ -37,13 +37,14 @@ export default class {
     return new Promise((resolve, reject) => {
       const request = Request(option.method, `${this.path}${url}`).accept('application/json');
       if (option.method === GET || option.method === DELETE) {
-        Object.keys(data).forEach(i => {
-          if (data[i] instanceof Object && data[i].length > 0) {
-            request.field(`${i}[]`, data[i]);
-            data[`${i}[]`] = data[i];
-            delete data[i];
-          }
-        });
+        // php后台需要[]来访问数组
+        // Object.keys(data).forEach(i => {
+        //   if (data[i] instanceof Object && data[i].length > 0) {
+        //     request.field(`${i}[]`, data[i]);
+        //     data[`${i}[]`] = data[i];
+        //     delete data[i];
+        //   }
+        // });
         request.query(data);
       }
       if (option.method !== GET && option.type !== FORM) {

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

@@ -239,9 +239,9 @@ export function formatDate(time, format = 'YYYY-MM-DD HH:mm:ss') {
     'q+': Math.floor((date.getMonth() + 3) / 3),
     S: date.getMilliseconds(),
   };
-  if (/(y+)/.test(format)) format = format.replace(RegExp.$1, `${date.getFullYear()}`.substr(4 - RegExp.$1.length));
+  if (/(Y+)/.test(format)) format = format.replace(RegExp.$1, `${date.getFullYear()}`.substr(4 - RegExp.$1.length));
   Object.keys(o).forEach(k => {
-    if (new RegExp(`('${k}')`).test(format)) {
+    if (new RegExp(`(${k})`).test(format)) {
       format = format.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : `00${o[k]}`.substr(`${o[k]}`.length));
     }
   });
@@ -326,7 +326,7 @@ export function bindTags(targetList, field, render, def, notFound) {
   });
 }
 
-export function bindSearch(targetList, field, Component, listFunc, render, def, notFound) {
+export function bindSearch(targetList, field, Component, listFunc, render, def, notFound = null) {
   let index = -1;
   targetList.forEach((row, i) => {
     if (row.key === field) index = i;
@@ -336,6 +336,7 @@ export function bindSearch(targetList, field, Component, listFunc, render, def,
   const searchFunc = (data) => {
     Component[key] += 1;
     const fetchId = Component[key];
+    targetList[index].loading = true;
     Component.setState({ fetching: true });
     listFunc(data).then(result => {
       if (fetchId !== Component[key]) {
@@ -345,6 +346,7 @@ export function bindSearch(targetList, field, Component, listFunc, render, def,
       targetList[index].select = (result.list || result || []).map(row => {
         return render(row);
       });
+      targetList[index].loading = false;
       Component.setState({ fetching: false });
     });
   };
@@ -359,7 +361,7 @@ export function bindSearch(targetList, field, Component, listFunc, render, def,
   };
   targetList[index] = Object.assign(targetList[index], item);
   if (def) {
-    if (targetList[index].type === 'multiple') {
+    if (targetList[index].type === 'multiple' || targetList[index].mode === 'multiple') {
       searchFunc({ ids: def, page: 1, number: def.length });
     } else {
       searchFunc({ ids: [def], page: 1, number: 1 });
@@ -369,7 +371,7 @@ export function bindSearch(targetList, field, Component, listFunc, render, def,
   }
 }
 
-export function generateSearch(field, prop, Component, listFunc, render, def, notFound) {
+export function generateSearch(field, props, Component, listFunc, render, def, notFound = null) {
   const key = `lastFetchId${field}`;
   if (!Component[key]) Component[key] = 0;
   let item = {
@@ -378,10 +380,12 @@ export function generateSearch(field, prop, Component, listFunc, render, def, no
     filterOption: false,
     notFoundContent: notFound,
   };
+  item = Object.assign(props || {}, item);
   const searchFunc = (data) => {
     Component[key] += 1;
     const fetchId = Component[key];
-    Component.setState({ fetching: true });
+    item.loading = true;
+    Component.setState({ [field]: item, fetching: true });
     listFunc(data).then(result => {
       if (fetchId !== Component[key]) {
         // for fetch callback order
@@ -390,22 +394,21 @@ export function generateSearch(field, prop, Component, listFunc, render, def, no
       item.select = result.list.map(row => {
         return render(row);
       });
-      Component.setState({ [field]: prop, fetching: false });
+      item.loading = false;
+      Component.setState({ [field]: item, fetching: false });
     });
   };
   item.onSearch = (keyword) => {
     searchFunc({ page: 1, number: 5, keyword });
   };
-  item = Object.assign(prop || {}, item);
   if (def) {
-    if (item.type === 'multiple') {
+    if (item.mode === 'multiple' || item.type === 'multiple') {
       searchFunc({ ids: def, page: 1, number: def.length });
     } else {
       searchFunc({ ids: [def], page: 1, number: 1 });
     }
-    searchFunc({ ids: def, page: 1, number: def.length });
   } else {
     item.onSearch();
   }
-  Component.setState({ [field]: prop });
+  Component.setState({ [field]: item });
 }

+ 6 - 0
front/src/style/adminLeft.less

@@ -28,6 +28,12 @@ body,
     margin-bottom: 20px;
   }
 
+  .table-button {
+    a {
+      margin: 0 2px;
+    }
+  }
+
   .admin {
     .ant-layout-sider-collapsed {
       #logo {

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

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

+ 5 - 4
server/data/src/main/java/com/qxgmat/data/dao/entity/ManagerRole.java

@@ -2,6 +2,7 @@ package com.qxgmat.data.dao.entity;
 
 import java.io.Serializable;
 import java.util.Date;
+import java.util.List;
 import javax.persistence.*;
 
 @Table(name = "manager_role")
@@ -21,7 +22,7 @@ public class ManagerRole implements Serializable {
      * 权限列表:json
      */
     @Column(name = "`permission_list`")
-    private String[] permissionList;
+    private List permissionList;
 
     @Column(name = "`create_time`")
     private Date createTime;
@@ -68,7 +69,7 @@ public class ManagerRole implements Serializable {
      *
      * @return permission_list - 权限列表:json
      */
-    public String[] getPermissionList() {
+    public List getPermissionList() {
         return permissionList;
     }
 
@@ -77,7 +78,7 @@ public class ManagerRole implements Serializable {
      *
      * @param permissionList 权限列表:json
      */
-    public void setPermissionList(String[] permissionList) {
+    public void setPermissionList(List permissionList) {
         this.permissionList = permissionList;
     }
 
@@ -158,7 +159,7 @@ public class ManagerRole implements Serializable {
          *
          * @param permissionList 权限列表:json
          */
-        public Builder permissionList(String[] permissionList) {
+        public Builder permissionList(List permissionList) {
             obj.setPermissionList(permissionList);
             return this;
         }

+ 25 - 25
server/data/src/main/java/com/qxgmat/data/dao/entity/Pay.java

@@ -55,12 +55,6 @@ public class Pay implements Serializable {
     private String module;
 
     /**
-     * 模块扩展信息
-     */
-    @Column(name = "`module_extend`")
-    private String moduleExtend;
-
-    /**
      * 商品标题
      */
     @Column(name = "`subject`")
@@ -115,6 +109,12 @@ public class Pay implements Serializable {
     private Date updateTime;
 
     /**
+     * 模块扩展信息
+     */
+    @Column(name = "`module_extend`")
+    private String moduleExtend;
+
+    /**
      * 支付传递的扩展参数
      */
     @Column(name = "`pay_info`")
@@ -269,24 +269,6 @@ public class Pay implements Serializable {
     }
 
     /**
-     * 获取模块扩展信息
-     *
-     * @return module_extend - 模块扩展信息
-     */
-    public String getModuleExtend() {
-        return moduleExtend;
-    }
-
-    /**
-     * 设置模块扩展信息
-     *
-     * @param moduleExtend 模块扩展信息
-     */
-    public void setModuleExtend(String moduleExtend) {
-        this.moduleExtend = moduleExtend;
-    }
-
-    /**
      * 获取商品标题
      *
      * @return subject - 商品标题
@@ -449,6 +431,24 @@ public class Pay implements Serializable {
     }
 
     /**
+     * 获取模块扩展信息
+     *
+     * @return module_extend - 模块扩展信息
+     */
+    public String getModuleExtend() {
+        return moduleExtend;
+    }
+
+    /**
+     * 设置模块扩展信息
+     *
+     * @param moduleExtend 模块扩展信息
+     */
+    public void setModuleExtend(String moduleExtend) {
+        this.moduleExtend = moduleExtend;
+    }
+
+    /**
      * 获取支付传递的扩展参数
      *
      * @return pay_info - 支付传递的扩展参数
@@ -498,7 +498,6 @@ public class Pay implements Serializable {
         sb.append(", clientIp=").append(clientIp);
         sb.append(", money=").append(money);
         sb.append(", module=").append(module);
-        sb.append(", moduleExtend=").append(moduleExtend);
         sb.append(", subject=").append(subject);
         sb.append(", body=").append(body);
         sb.append(", transactionNo=").append(transactionNo);
@@ -508,6 +507,7 @@ public class Pay implements Serializable {
         sb.append(", payType=").append(payType);
         sb.append(", createTime=").append(createTime);
         sb.append(", updateTime=").append(updateTime);
+        sb.append(", moduleExtend=").append(moduleExtend);
         sb.append(", payInfo=").append(payInfo);
         sb.append(", resultInfo=").append(resultInfo);
         sb.append("]");

+ 74 - 4
server/data/src/main/java/com/qxgmat/data/dao/entity/QuestionNo.java

@@ -24,6 +24,12 @@ public class QuestionNo implements Serializable {
     private Integer questionId;
 
     /**
+     * 序号:人工
+     */
+    @Column(name = "`no`")
+    private Integer no;
+
+    /**
      * 模块:examination, exercise, sentence, textbook
      */
     @Column(name = "`module`")
@@ -33,7 +39,7 @@ public class QuestionNo implements Serializable {
      * 对应模块结构信息,逗号分隔
      */
     @Column(name = "`module_struct`")
-    private Integer[] moduleStruct;
+    private int[] moduleStruct;
 
     /**
      * 总作答时间
@@ -56,6 +62,12 @@ public class QuestionNo implements Serializable {
     @Column(name = "`delete_time`")
     private Date deleteTime;
 
+    /**
+     * 关联题目列表
+     */
+    @Column(name = "`relation_question`")
+    private int[] relationQuestion;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -109,6 +121,24 @@ public class QuestionNo implements Serializable {
     }
 
     /**
+     * 获取序号:人工
+     *
+     * @return no - 序号:人工
+     */
+    public Integer getNo() {
+        return no;
+    }
+
+    /**
+     * 设置序号:人工
+     *
+     * @param no 序号:人工
+     */
+    public void setNo(Integer no) {
+        this.no = no;
+    }
+
+    /**
      * 获取模块:examination, exercise, sentence, textbook
      *
      * @return module - 模块:examination, exercise, sentence, textbook
@@ -131,7 +161,7 @@ public class QuestionNo implements Serializable {
      *
      * @return module_struct - 对应模块结构信息,逗号分隔
      */
-    public Integer[] getModuleStruct() {
+    public int[] getModuleStruct() {
         return moduleStruct;
     }
 
@@ -140,7 +170,7 @@ public class QuestionNo implements Serializable {
      *
      * @param moduleStruct 对应模块结构信息,逗号分隔
      */
-    public void setModuleStruct(Integer[] moduleStruct) {
+    public void setModuleStruct(int[] moduleStruct) {
         this.moduleStruct = moduleStruct;
     }
 
@@ -212,6 +242,24 @@ public class QuestionNo implements Serializable {
         this.deleteTime = deleteTime;
     }
 
+    /**
+     * 获取关联题目列表
+     *
+     * @return relation_question - 关联题目列表
+     */
+    public int[] getRelationQuestion() {
+        return relationQuestion;
+    }
+
+    /**
+     * 设置关联题目列表
+     *
+     * @param relationQuestion 关联题目列表
+     */
+    public void setRelationQuestion(int[] relationQuestion) {
+        this.relationQuestion = relationQuestion;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -221,12 +269,14 @@ public class QuestionNo implements Serializable {
         sb.append(", id=").append(id);
         sb.append(", title=").append(title);
         sb.append(", questionId=").append(questionId);
+        sb.append(", no=").append(no);
         sb.append(", module=").append(module);
         sb.append(", moduleStruct=").append(moduleStruct);
         sb.append(", totalTime=").append(totalTime);
         sb.append(", totalNumber=").append(totalNumber);
         sb.append(", totalCorrect=").append(totalCorrect);
         sb.append(", deleteTime=").append(deleteTime);
+        sb.append(", relationQuestion=").append(relationQuestion);
         sb.append("]");
         return sb.toString();
     }
@@ -271,6 +321,16 @@ public class QuestionNo implements Serializable {
         }
 
         /**
+         * 设置序号:人工
+         *
+         * @param no 序号:人工
+         */
+        public Builder no(Integer no) {
+            obj.setNo(no);
+            return this;
+        }
+
+        /**
          * 设置模块:examination, exercise, sentence, textbook
          *
          * @param module 模块:examination, exercise, sentence, textbook
@@ -285,7 +345,7 @@ public class QuestionNo implements Serializable {
          *
          * @param moduleStruct 对应模块结构信息,逗号分隔
          */
-        public Builder moduleStruct(Integer[] moduleStruct) {
+        public Builder moduleStruct(int[] moduleStruct) {
             obj.setModuleStruct(moduleStruct);
             return this;
         }
@@ -328,6 +388,16 @@ public class QuestionNo implements Serializable {
             return this;
         }
 
+        /**
+         * 设置关联题目列表
+         *
+         * @param relationQuestion 关联题目列表
+         */
+        public Builder relationQuestion(int[] relationQuestion) {
+            obj.setRelationQuestion(relationQuestion);
+            return this;
+        }
+
         public QuestionNo build() {
             return this.obj;
         }

+ 263 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookLibrary.java

@@ -1,6 +1,7 @@
 package com.qxgmat.data.dao.entity;
 
 import java.io.Serializable;
+import java.util.Date;
 import javax.persistence.*;
 
 @Table(name = "textbook_library")
@@ -10,6 +11,48 @@ public class TextbookLibrary implements Serializable {
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
 
+    /**
+     * 库头
+     */
+    @Column(name = "`start_date`")
+    private String startDate;
+
+    /**
+     * 库尾
+     */
+    @Column(name = "`end_date`")
+    private String endDate;
+
+    /**
+     * 数学
+     */
+    @Column(name = "`quant`")
+    private String quant;
+
+    /**
+     * 综合逻辑
+     */
+    @Column(name = "`ir`")
+    private String ir;
+
+    /**
+     * 阅读
+     */
+    @Column(name = "`rc`")
+    private String rc;
+
+    /**
+     * 更新次数
+     */
+    @Column(name = "`history_number`")
+    private Integer historyNumber;
+
+    @Column(name = "`create_time`")
+    private Date createTime;
+
+    @Column(name = "`update_time`")
+    private Date updateTime;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -26,6 +69,142 @@ public class TextbookLibrary implements Serializable {
         this.id = id;
     }
 
+    /**
+     * 获取库头
+     *
+     * @return start_date - 库头
+     */
+    public String getStartDate() {
+        return startDate;
+    }
+
+    /**
+     * 设置库头
+     *
+     * @param startDate 库头
+     */
+    public void setStartDate(String startDate) {
+        this.startDate = startDate;
+    }
+
+    /**
+     * 获取库尾
+     *
+     * @return end_date - 库尾
+     */
+    public String getEndDate() {
+        return endDate;
+    }
+
+    /**
+     * 设置库尾
+     *
+     * @param endDate 库尾
+     */
+    public void setEndDate(String endDate) {
+        this.endDate = endDate;
+    }
+
+    /**
+     * 获取数学
+     *
+     * @return quant - 数学
+     */
+    public String getQuant() {
+        return quant;
+    }
+
+    /**
+     * 设置数学
+     *
+     * @param quant 数学
+     */
+    public void setQuant(String quant) {
+        this.quant = quant;
+    }
+
+    /**
+     * 获取综合逻辑
+     *
+     * @return ir - 综合逻辑
+     */
+    public String getIr() {
+        return ir;
+    }
+
+    /**
+     * 设置综合逻辑
+     *
+     * @param ir 综合逻辑
+     */
+    public void setIr(String ir) {
+        this.ir = ir;
+    }
+
+    /**
+     * 获取阅读
+     *
+     * @return rc - 阅读
+     */
+    public String getRc() {
+        return rc;
+    }
+
+    /**
+     * 设置阅读
+     *
+     * @param rc 阅读
+     */
+    public void setRc(String rc) {
+        this.rc = rc;
+    }
+
+    /**
+     * 获取更新次数
+     *
+     * @return history_number - 更新次数
+     */
+    public Integer getHistoryNumber() {
+        return historyNumber;
+    }
+
+    /**
+     * 设置更新次数
+     *
+     * @param historyNumber 更新次数
+     */
+    public void setHistoryNumber(Integer historyNumber) {
+        this.historyNumber = historyNumber;
+    }
+
+    /**
+     * @return create_time
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * @param createTime
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * @return update_time
+     */
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    /**
+     * @param updateTime
+     */
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -33,6 +212,14 @@ public class TextbookLibrary implements Serializable {
         sb.append(" [");
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
+        sb.append(", startDate=").append(startDate);
+        sb.append(", endDate=").append(endDate);
+        sb.append(", quant=").append(quant);
+        sb.append(", ir=").append(ir);
+        sb.append(", rc=").append(rc);
+        sb.append(", historyNumber=").append(historyNumber);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
         sb.append("]");
         return sb.toString();
     }
@@ -56,6 +243,82 @@ public class TextbookLibrary implements Serializable {
             return this;
         }
 
+        /**
+         * 设置库头
+         *
+         * @param startDate 库头
+         */
+        public Builder startDate(String startDate) {
+            obj.setStartDate(startDate);
+            return this;
+        }
+
+        /**
+         * 设置库尾
+         *
+         * @param endDate 库尾
+         */
+        public Builder endDate(String endDate) {
+            obj.setEndDate(endDate);
+            return this;
+        }
+
+        /**
+         * 设置数学
+         *
+         * @param quant 数学
+         */
+        public Builder quant(String quant) {
+            obj.setQuant(quant);
+            return this;
+        }
+
+        /**
+         * 设置综合逻辑
+         *
+         * @param ir 综合逻辑
+         */
+        public Builder ir(String ir) {
+            obj.setIr(ir);
+            return this;
+        }
+
+        /**
+         * 设置阅读
+         *
+         * @param rc 阅读
+         */
+        public Builder rc(String rc) {
+            obj.setRc(rc);
+            return this;
+        }
+
+        /**
+         * 设置更新次数
+         *
+         * @param historyNumber 更新次数
+         */
+        public Builder historyNumber(Integer historyNumber) {
+            obj.setHistoryNumber(historyNumber);
+            return this;
+        }
+
+        /**
+         * @param createTime
+         */
+        public Builder createTime(Date createTime) {
+            obj.setCreateTime(createTime);
+            return this;
+        }
+
+        /**
+         * @param updateTime
+         */
+        public Builder updateTime(Date updateTime) {
+            obj.setUpdateTime(updateTime);
+            return this;
+        }
+
         public TextbookLibrary build() {
             return this.obj;
         }

+ 140 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookLibraryHistory.java

@@ -17,9 +17,33 @@ public class TextbookLibraryHistory implements Serializable {
     @Column(name = "`library_id`")
     private Integer libraryId;
 
+    /**
+     * 数学
+     */
+    @Column(name = "`quant`")
+    private String quant;
+
+    /**
+     * 阅读
+     */
+    @Column(name = "`rc`")
+    private String rc;
+
+    /**
+     * 综合推理
+     */
+    @Column(name = "`ir`")
+    private String ir;
+
     @Column(name = "`create_time`")
     private Date createTime;
 
+    /**
+     * 更新日志
+     */
+    @Column(name = "`content`")
+    private String content;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -55,6 +79,60 @@ public class TextbookLibraryHistory implements Serializable {
     }
 
     /**
+     * 获取数学
+     *
+     * @return quant - 数学
+     */
+    public String getQuant() {
+        return quant;
+    }
+
+    /**
+     * 设置数学
+     *
+     * @param quant 数学
+     */
+    public void setQuant(String quant) {
+        this.quant = quant;
+    }
+
+    /**
+     * 获取阅读
+     *
+     * @return rc - 阅读
+     */
+    public String getRc() {
+        return rc;
+    }
+
+    /**
+     * 设置阅读
+     *
+     * @param rc 阅读
+     */
+    public void setRc(String rc) {
+        this.rc = rc;
+    }
+
+    /**
+     * 获取综合推理
+     *
+     * @return ir - 综合推理
+     */
+    public String getIr() {
+        return ir;
+    }
+
+    /**
+     * 设置综合推理
+     *
+     * @param ir 综合推理
+     */
+    public void setIr(String ir) {
+        this.ir = ir;
+    }
+
+    /**
      * @return create_time
      */
     public Date getCreateTime() {
@@ -68,6 +146,24 @@ public class TextbookLibraryHistory implements Serializable {
         this.createTime = createTime;
     }
 
+    /**
+     * 获取更新日志
+     *
+     * @return content - 更新日志
+     */
+    public String getContent() {
+        return content;
+    }
+
+    /**
+     * 设置更新日志
+     *
+     * @param content 更新日志
+     */
+    public void setContent(String content) {
+        this.content = content;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -76,7 +172,11 @@ public class TextbookLibraryHistory implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", libraryId=").append(libraryId);
+        sb.append(", quant=").append(quant);
+        sb.append(", rc=").append(rc);
+        sb.append(", ir=").append(ir);
         sb.append(", createTime=").append(createTime);
+        sb.append(", content=").append(content);
         sb.append("]");
         return sb.toString();
     }
@@ -111,6 +211,36 @@ public class TextbookLibraryHistory implements Serializable {
         }
 
         /**
+         * 设置数学
+         *
+         * @param quant 数学
+         */
+        public Builder quant(String quant) {
+            obj.setQuant(quant);
+            return this;
+        }
+
+        /**
+         * 设置阅读
+         *
+         * @param rc 阅读
+         */
+        public Builder rc(String rc) {
+            obj.setRc(rc);
+            return this;
+        }
+
+        /**
+         * 设置综合推理
+         *
+         * @param ir 综合推理
+         */
+        public Builder ir(String ir) {
+            obj.setIr(ir);
+            return this;
+        }
+
+        /**
          * @param createTime
          */
         public Builder createTime(Date createTime) {
@@ -118,6 +248,16 @@ public class TextbookLibraryHistory implements Serializable {
             return this;
         }
 
+        /**
+         * 设置更新日志
+         *
+         * @param content 更新日志
+         */
+        public Builder content(String content) {
+            obj.setContent(content);
+            return this;
+        }
+
         public TextbookLibraryHistory build() {
             return this.obj;
         }

+ 16 - 16
server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookPaper.java

@@ -12,10 +12,10 @@ public class TextbookPaper implements Serializable {
     private Integer id;
 
     /**
-     * 年份
+     * 所属时间
      */
-    @Column(name = "`year`")
-    private Integer year;
+    @Column(name = "`time`")
+    private String time;
 
     /**
      * 标题
@@ -76,21 +76,21 @@ public class TextbookPaper implements Serializable {
     }
 
     /**
-     * 获取年份
+     * 获取所属时间
      *
-     * @return year - 年份
+     * @return time - 所属时间
      */
-    public Integer getYear() {
-        return year;
+    public String getTime() {
+        return time;
     }
 
     /**
-     * 设置年份
+     * 设置所属时间
      *
-     * @param year 年份
+     * @param time 所属时间
      */
-    public void setYear(Integer year) {
-        this.year = year;
+    public void setTime(String time) {
+        this.time = time;
     }
 
     /**
@@ -236,7 +236,7 @@ public class TextbookPaper implements Serializable {
         sb.append(" [");
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
-        sb.append(", year=").append(year);
+        sb.append(", time=").append(time);
         sb.append(", title=").append(title);
         sb.append(", logic=").append(logic);
         sb.append(", no=").append(no);
@@ -269,12 +269,12 @@ public class TextbookPaper implements Serializable {
         }
 
         /**
-         * 设置年份
+         * 设置所属时间
          *
-         * @param year 年份
+         * @param time 所属时间
          */
-        public Builder year(Integer year) {
-            obj.setYear(year);
+        public Builder time(String time) {
+            obj.setTime(time);
             return this;
         }
 

+ 16 - 69
server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookQuestion.java

@@ -1,7 +1,6 @@
 package com.qxgmat.data.dao.entity;
 
 import java.io.Serializable;
-import java.util.Date;
 import javax.persistence.*;
 
 @Table(name = "textbook_question")
@@ -60,16 +59,10 @@ public class TextbookQuestion implements Serializable {
     private Integer totalCorrect;
 
     /**
-     * 自定义题目:0为基础题,组卷,1自定义题,不组卷
+     * 所属时间
      */
-    @Column(name = "`is_custom`")
-    private Integer isCustom;
-
-    @Column(name = "`start_time`")
-    private Date startTime;
-
-    @Column(name = "`end_time`")
-    private Date endTime;
+    @Column(name = "`time`")
+    private String time;
 
     private static final long serialVersionUID = 1L;
 
@@ -232,49 +225,21 @@ public class TextbookQuestion implements Serializable {
     }
 
     /**
-     * 获取自定义题目:0为基础题,组卷,1自定义题,不组卷
+     * 获取所属时间
      *
-     * @return is_custom - 自定义题目:0为基础题,组卷,1自定义题,不组卷
+     * @return time - 所属时间
      */
-    public Integer getIsCustom() {
-        return isCustom;
+    public String getTime() {
+        return time;
     }
 
     /**
-     * 设置自定义题目:0为基础题,组卷,1自定义题,不组卷
+     * 设置所属时间
      *
-     * @param isCustom 自定义题目:0为基础题,组卷,1自定义题,不组卷
-     */
-    public void setIsCustom(Integer isCustom) {
-        this.isCustom = isCustom;
-    }
-
-    /**
-     * @return start_time
-     */
-    public Date getStartTime() {
-        return startTime;
-    }
-
-    /**
-     * @param startTime
-     */
-    public void setStartTime(Date startTime) {
-        this.startTime = startTime;
-    }
-
-    /**
-     * @return end_time
+     * @param time 所属时间
      */
-    public Date getEndTime() {
-        return endTime;
-    }
-
-    /**
-     * @param endTime
-     */
-    public void setEndTime(Date endTime) {
-        this.endTime = endTime;
+    public void setTime(String time) {
+        this.time = time;
     }
 
     @Override
@@ -292,9 +257,7 @@ public class TextbookQuestion implements Serializable {
         sb.append(", totalTime=").append(totalTime);
         sb.append(", totalNumber=").append(totalNumber);
         sb.append(", totalCorrect=").append(totalCorrect);
-        sb.append(", isCustom=").append(isCustom);
-        sb.append(", startTime=").append(startTime);
-        sb.append(", endTime=").append(endTime);
+        sb.append(", time=").append(time);
         sb.append("]");
         return sb.toString();
     }
@@ -399,28 +362,12 @@ public class TextbookQuestion implements Serializable {
         }
 
         /**
-         * 设置自定义题目:0为基础题,组卷,1自定义题,不组卷
+         * 设置所属时间
          *
-         * @param isCustom 自定义题目:0为基础题,组卷,1自定义题,不组卷
-         */
-        public Builder isCustom(Integer isCustom) {
-            obj.setIsCustom(isCustom);
-            return this;
-        }
-
-        /**
-         * @param startTime
-         */
-        public Builder startTime(Date startTime) {
-            obj.setStartTime(startTime);
-            return this;
-        }
-
-        /**
-         * @param endTime
+         * @param time 所属时间
          */
-        public Builder endTime(Date endTime) {
-            obj.setEndTime(endTime);
+        public Builder time(String time) {
+            obj.setTime(time);
             return this;
         }
 

+ 63 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrder.java

@@ -0,0 +1,63 @@
+package com.qxgmat.data.dao.entity;
+
+import java.io.Serializable;
+import javax.persistence.*;
+
+@Table(name = "user_order")
+public class UserOrder implements Serializable {
+    @Id
+    @Column(name = "`id`")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * @return id
+     */
+    public Integer getId() {
+        return id;
+    }
+
+    /**
+     * @param id
+     */
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append("]");
+        return sb.toString();
+    }
+
+    public static UserOrder.Builder builder() {
+        return new UserOrder.Builder();
+    }
+
+    public static class Builder {
+        private UserOrder obj;
+
+        public Builder() {
+            this.obj = new UserOrder();
+        }
+
+        /**
+         * @param id
+         */
+        public Builder id(Integer id) {
+            obj.setId(id);
+            return this;
+        }
+
+        public UserOrder build() {
+            return this.obj;
+        }
+    }
+}

+ 5 - 5
server/data/src/main/java/com/qxgmat/data/dao/mapping/PayMapper.xml

@@ -13,7 +13,6 @@
     <result column="client_ip" jdbcType="VARCHAR" property="clientIp" />
     <result column="money" jdbcType="DECIMAL" property="money" />
     <result column="module" jdbcType="VARCHAR" property="module" />
-    <result column="module_extend" jdbcType="VARCHAR" property="moduleExtend" />
     <result column="subject" jdbcType="VARCHAR" property="subject" />
     <result column="body" jdbcType="VARCHAR" property="body" />
     <result column="transaction_no" jdbcType="VARCHAR" property="transactionNo" />
@@ -28,6 +27,7 @@
     <!--
       WARNING - @mbg.generated
     -->
+    <result column="module_extend" jdbcType="LONGVARCHAR" property="moduleExtend" />
     <result column="pay_info" jdbcType="LONGVARCHAR" property="payInfo" />
     <result column="result_info" jdbcType="LONGVARCHAR" property="resultInfo" />
   </resultMap>
@@ -35,14 +35,14 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `no`, `user_id`, `channel`, `pid`, `client_ip`, `money`, `module`, `module_extend`, 
-    `subject`, `body`, `transaction_no`, `currency`, `trade_status`, `pay_time`, `pay_type`, 
-    `create_time`, `update_time`
+    `id`, `no`, `user_id`, `channel`, `pid`, `client_ip`, `money`, `module`, `subject`, 
+    `body`, `transaction_no`, `currency`, `trade_status`, `pay_time`, `pay_type`, `create_time`, 
+    `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `pay_info`, `result_info`
+    `module_extend`, `pay_info`, `result_info`
   </sql>
 </mapper>

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

@@ -8,18 +8,20 @@
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="question_id" jdbcType="INTEGER" property="questionId" />
+    <result column="no" jdbcType="INTEGER" property="no" />
     <result column="module" jdbcType="VARCHAR" property="module" />
     <result column="module_struct" jdbcType="VARCHAR" property="moduleStruct" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayHandler" />
     <result column="total_time" jdbcType="INTEGER" property="totalTime" />
     <result column="total_number" jdbcType="INTEGER" property="totalNumber" />
     <result column="total_correct" jdbcType="INTEGER" property="totalCorrect" />
     <result column="delete_time" jdbcType="TIMESTAMP" property="deleteTime" />
+    <result column="relation_question" jdbcType="VARCHAR" property="relationQuestion" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayHandler" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title`, `question_id`, `module`, `module_struct`, `total_time`, `total_number`, 
-    `total_correct`, `delete_time`
+    `id`, `title`, `question_id`, `no`, `module`, `module_struct`, `total_time`, `total_number`, 
+    `total_correct`, `delete_time`, `relation_question`
   </sql>
 </mapper>

+ 16 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookLibraryHistoryMapper.xml

@@ -7,12 +7,27 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="library_id" jdbcType="INTEGER" property="libraryId" />
+    <result column="quant" jdbcType="VARCHAR" property="quant" />
+    <result column="rc" jdbcType="VARCHAR" property="rc" />
+    <result column="ir" jdbcType="VARCHAR" property="ir" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
+  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.TextbookLibraryHistory">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <result column="content" jdbcType="LONGVARCHAR" property="content" />
+  </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `library_id`, `create_time`
+    `id`, `library_id`, `quant`, `rc`, `ir`, `create_time`
+  </sql>
+  <sql id="Blob_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `content`
   </sql>
 </mapper>

+ 15 - 0
server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookLibraryMapper.xml

@@ -6,5 +6,20 @@
       WARNING - @mbg.generated
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
+    <result column="start_date" jdbcType="VARCHAR" property="startDate" />
+    <result column="end_date" jdbcType="VARCHAR" property="endDate" />
+    <result column="quant" jdbcType="VARCHAR" property="quant" />
+    <result column="ir" jdbcType="VARCHAR" property="ir" />
+    <result column="rc" jdbcType="VARCHAR" property="rc" />
+    <result column="history_number" jdbcType="INTEGER" property="historyNumber" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
   </resultMap>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `id`, `start_date`, `end_date`, `quant`, `ir`, `rc`, `history_number`, `create_time`, 
+    `update_time`
+  </sql>
 </mapper>

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

@@ -6,7 +6,7 @@
       WARNING - @mbg.generated
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
-    <result column="year" jdbcType="INTEGER" property="year" />
+    <result column="time" jdbcType="VARCHAR" property="time" />
     <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="logic" jdbcType="VARCHAR" property="logic" />
     <result column="no" jdbcType="INTEGER" property="no" />
@@ -20,7 +20,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `year`, `title`, `logic`, `no`, `library_id`, `question_no_ids`, `question_number`, 
+    `id`, `time`, `title`, `logic`, `no`, `library_id`, `question_no_ids`, `question_number`, 
     `create_time`, `status`
   </sql>
 </mapper>

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

@@ -14,15 +14,13 @@
     <result column="total_time" jdbcType="INTEGER" property="totalTime" />
     <result column="total_number" jdbcType="INTEGER" property="totalNumber" />
     <result column="total_correct" jdbcType="INTEGER" property="totalCorrect" />
-    <result column="is_custom" jdbcType="INTEGER" property="isCustom" />
-    <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
-    <result column="end_time" jdbcType="TIMESTAMP" property="endTime" />
+    <result column="time" jdbcType="VARCHAR" property="time" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
     `id`, `title`, `no`, `library_id`, `question_id`, `question_no_id`, `total_time`, 
-    `total_number`, `total_correct`, `is_custom`, `start_time`, `end_time`
+    `total_number`, `total_correct`, `time`
   </sql>
 </mapper>

+ 10 - 0
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderMapper.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qxgmat.data.dao.UserOrderMapper">
+  <resultMap id="BaseResultMap" type="com.qxgmat.data.dao.entity.UserOrder">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+  </resultMap>
+</mapper>

+ 3 - 5
server/data/src/main/resources/mybatis-generator.xml

@@ -96,7 +96,7 @@
         </table>
         <table schema="qianxing" tableName="manager_role" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
-            <columnOverride column="permission_list" javaType="String[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.StringArrayWithJsonHandler"/>
+            <columnOverride column="permission_list" javaType="java.util.List" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.StringArrayWithJsonHandler"/>
         </table>
         <table schema="qianxing" tableName="exercise_paper" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
@@ -104,7 +104,6 @@
         </table>
         <table schema="qianxing" tableName="examination_paper" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
-            <columnOverride column="question_no_ids" javaType="Integer[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler"/>
         </table>
         <table schema="qianxing" tableName="sentence_paper" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
@@ -133,7 +132,8 @@
         </table>
         <table schema="qianxing" tableName="question_no" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
-            <columnOverride column="module_struct" javaType="Integer[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayHandler"/>
+            <columnOverride column="module_struct" javaType="int[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayHandler"/>
+            <columnOverride column="relation_question" javaType="int[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayHandler"/>
         </table>
         <table schema="qianxing" tableName="user_paper" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
@@ -143,14 +143,12 @@
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
             <columnOverride column="detail" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler"/>
             <columnOverride column="question_no_ids" javaType="Integer[]" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayWithJsonHandler"/>
-            <columnOverride column="detail" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler"/>
             <columnOverride column="score" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler"/>
             <columnOverride column="setting" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler"/>
         </table>
         <table schema="qianxing" tableName="user_question" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" delimitAllColumns="true">
             <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
             <columnOverride column="user_answer" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler"/>
-            <columnOverride column="answer" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler"/>
             <columnOverride column="setting" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler"/>
             <columnOverride column="detail" javaType="com.alibaba.fastjson.JSONObject" jdbcType="VARCHAR" typeHandler="com.nuliji.tools.mybatis.handler.JsonObjectHandler"/>
         </table>

+ 1 - 1
server/dependencyDefine.gradle

@@ -28,7 +28,7 @@ ext.libraries = [
         "mybatis":"org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.4",
         "mybatis-mapper": "tk.mybatis:mapper-spring-boot-starter:2.1.5",
         "mybatis-generator": "org.mybatis.generator:mybatis-generator-core:1.3.7",
-        "mybatis-mapping-generator": "tk.mybatis:mapper-generator:1.0.0",
+        "mybatis-mapping-generator": "tk.mybatis:mapper-generator:1.1.5",
         "mybatis-generator-plugin":"com.itfsw:mybatis-generator-plugin:1.3.2",
         "mybatis-mysql": "mysql:mysql-connector-java",
         // 通过文件配置page,不使用starter

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

@@ -33,3 +33,4 @@ public class Application {
 // 订单结构分析:服务、课程、数据都是商品
 // 购物车,订单
 // 自动组卷:练习,长难句,作文练习,单题成卷
+// 长难句:整体页数

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

@@ -99,8 +99,14 @@ public class ExerciseController {
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false, defaultValue = "") String keyword,
+            @RequestParam(required = false) Integer[] ids,
             HttpSession session) {
-        Page<ExercisePaper> p = exercisePaperService.select(page, size, keyword);
+        Page<ExercisePaper> p;
+        if (ids != null && ids.length > 0){
+            p = exercisePaperService.select(ids);
+        }else{
+            p = exercisePaperService.select(page, size, keyword);
+        }
         List<ExercisePaperListDto> pr = Transform.convert(p, ExercisePaperListDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());

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

@@ -71,7 +71,8 @@ public class QuestionController {
         entity = questionService.add(entity);
         // 更新编号绑定
         questionNoService.bindQuestion(dto.getQuestionNoIds(), entity.getId());
-
+        // 更新编号关联
+        questionNoService.relationQuestion(dto.getRelationQuestion());
         managerLogService.log(request);
         return ResponseHelp.success(entity);
     }
@@ -83,6 +84,8 @@ public class QuestionController {
         entity = questionService.edit(entity);
         // 更新编号绑定
         questionNoService.bindQuestion(dto.getQuestionNoIds(), entity.getId());
+        // 更新编号关联
+        questionNoService.relationQuestion(dto.getRelationQuestion());
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -153,7 +156,7 @@ public class QuestionController {
     @RequestMapping(value = "/search/no", method = RequestMethod.POST)
     @ApiOperation(value = "搜索题目编号列表:题目编号搜索", httpMethod = "POST")
     public Response<PageMessage<QuestionNoExtendDto>> searchNo(@RequestBody @Validated QuestionNoSearchDto dto, HttpServletRequest request) {
-        PageResult<QuestionNoRelation> p = questionNoService.searchNo(dto.getPage(), dto.getSize(), dto.getNo(), dto.getModule());
+        PageResult<QuestionNoRelation> p = questionNoService.searchNo(dto.getPage(), dto.getSize(), dto.getKeyword(), dto.getModule());
         List<QuestionNoExtendDto> pr = Transform.convert(p, QuestionNoExtendDto.class);
 
         return ResponseHelp.success(pr, dto.getPage(), dto.getSize(), p.getTotal());

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

@@ -141,6 +141,24 @@ public class SentenceController {
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
+    @RequestMapping(value = "/question/search", method = RequestMethod.GET)
+    @ApiOperation(value = "搜索长难句题目列表", httpMethod = "GET")
+    public Response<PageMessage<SentenceQuestionListDto>> listQuestion(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer[] ids,
+            HttpSession session) {
+        Page<SentenceQuestion> p;
+        if (ids != null && ids.length > 0){
+            p = sentenceQuestionService.select(ids);
+        }else{
+            p = sentenceQuestionService.searchAdmin(page, size, keyword);
+        }
+        List<SentenceQuestionListDto> pr = Transform.convert(p, SentenceQuestionListDto.class);
+        return ResponseHelp.success(pr, page, size, p.getTotal());
+    }
+
     @RequestMapping(value = "/paper/auto", method = RequestMethod.POST)
     @ApiOperation(value = "自动组卷", httpMethod = "POST")
     public Response<Boolean> autoPaper(@RequestBody @Validated ExerciseStructDto dto, HttpServletRequest request) {

+ 102 - 15
server/gateway-api/src/main/java/com/qxgmat/controller/admin/TextbookController.java

@@ -15,12 +15,15 @@ import com.qxgmat.dto.admin.extend.QuestionExtendDto;
 import com.qxgmat.dto.admin.extend.QuestionNoExtendDto;
 import com.qxgmat.dto.admin.request.ExerciseStructDto;
 import com.qxgmat.dto.admin.request.QuestionNoSearchDto;
+import com.qxgmat.dto.admin.request.TextbookLibraryDto;
+import com.qxgmat.dto.admin.request.TextbookLibraryHistoryDto;
 import com.qxgmat.dto.admin.response.ExercisePaperListDto;
 import com.qxgmat.dto.admin.response.ExerciseQuestionListDto;
 import com.qxgmat.dto.admin.response.TextbookPaperListDto;
 import com.qxgmat.dto.admin.response.TextbookQuestionListDto;
 import com.qxgmat.service.ExercisePaperService;
 import com.qxgmat.service.TextbookPaperService;
+import com.qxgmat.service.extend.TextbookService;
 import com.qxgmat.service.inline.*;
 import com.qxgmat.task.AsyncTask;
 import io.swagger.annotations.Api;
@@ -50,13 +53,22 @@ public class TextbookController {
     private TextbookPaperService textbookPaperService;
 
     @Autowired
+    private TextbookLibraryService textbookLibraryService;
+
+    @Autowired
+    private TextbookLibraryHistoryService textbookLibraryHistoryService;
+
+    @Autowired
+    private TextbookService textbookService;
+
+    @Autowired
     private AsyncTask asyncTask;
 
     @RequestMapping(value = "/question/add", method = RequestMethod.POST)
     @ApiOperation(value = "添加机经题目", httpMethod = "POST")
     public Response<TextbookQuestion> addQuestion(@RequestBody @Validated TextbookQuestionRelation dto, HttpServletRequest request) {
 //        SentenceQuestion entity = Transform.dtoToEntity(dto);
-        TextbookQuestion entity = textbookPaperService.addQuestion(dto);
+        TextbookQuestion entity = textbookService.addQuestion(dto);
         managerLogService.log(request);
         return ResponseHelp.success(entity);
     }
@@ -64,7 +76,7 @@ public class TextbookController {
     @ApiOperation(value = "修改机经题目", httpMethod = "PUT")
     public Response<Boolean> editQuestion(@RequestBody @Validated TextbookQuestionRelation dto, HttpServletRequest request) {
 //        SentenceQuestion entity = Transform.dtoToEntity(dto);
-        TextbookQuestion entity = textbookPaperService.editQuestion(dto);
+        TextbookQuestion entity = textbookService.editQuestion(dto);
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -84,6 +96,53 @@ public class TextbookController {
         return ResponseHelp.success(relation);
     }
 
+    @RequestMapping(value = "/question/next", method = RequestMethod.GET)
+    @ApiOperation(value = "获取机经题目下一题序号", httpMethod = "GET")
+    public Response<Integer> nextQuestion(@RequestParam int libraryId, HttpSession session) {
+        TextbookQuestion entity = textbookQuestionService.lastByLibrary(libraryId);
+        Integer no = 1;
+        if (entity != null){
+            no += entity.getNo();
+        }
+        return ResponseHelp.success(no);
+    }
+
+    @RequestMapping(value = "/paper/list", method = RequestMethod.GET)
+    @ApiOperation(value = "组卷列表", httpMethod = "GET")
+    public Response<PageMessage<TextbookPaper>> listPaper(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer[] ids,
+            HttpSession session) {
+        Page<TextbookPaper> p;
+        if (ids != null && ids.length > 0){
+            p = textbookPaperService.select(ids);
+        }else{
+            p = textbookPaperService.listAdmin(page, size, keyword);
+        }
+        return ResponseHelp.success(p, page, size, p.getTotal());
+    }
+
+    @RequestMapping(value = "/question/search", method = RequestMethod.GET)
+    @ApiOperation(value = "试题列表", httpMethod = "GET")
+    public Response<PageMessage<TextbookQuestionListDto>> searchQuestion(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer[] ids,
+            HttpSession session) {
+        Page<TextbookQuestion> p;
+        if (ids != null && ids.length > 0){
+            p = textbookQuestionService.select(ids);
+        }else{
+            p = textbookQuestionService.searchAdmin(page, size, keyword);
+        }
+        List<TextbookQuestionListDto> pr = Transform.convert(p, TextbookQuestionListDto.class);
+
+        return ResponseHelp.success(pr, page, size, p.getTotal());
+    }
+
     @RequestMapping(value = "/question/list", method = RequestMethod.GET)
     @ApiOperation(value = "试题列表", httpMethod = "GET")
     public Response<PageMessage<TextbookQuestionListDto>> listQuestion(
@@ -101,25 +160,53 @@ public class TextbookController {
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
-    @RequestMapping(value = "/search/no", method = RequestMethod.POST)
-    @ApiOperation(value = "搜索题目编号列表:题目编号搜索", httpMethod = "POST")
-    public Response<PageMessage<TextbookQuestionListDto>> searchNo(@RequestBody @Validated QuestionNoSearchDto dto, HttpServletRequest request) {
-        PageResult<TextbookQuestionRelation> p = textbookQuestionService.searchNo(dto.getPage(), dto.getSize(), dto.getNo());
-        List<TextbookQuestionListDto> pr = Transform.convert(p, TextbookQuestionListDto.class);
+    @RequestMapping(value = "/library/add", method = RequestMethod.POST)
+    @ApiOperation(value = "发布新换库信息", httpMethod = "POST")
+    public Response<Boolean> addLibrary(@RequestBody @Validated TextbookLibraryDto dto, HttpServletRequest request) {
+        TextbookLibrary entity = Transform.dtoToEntity(dto);
+        textbookService.replace(entity);
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
+    }
 
-        return ResponseHelp.success(pr, dto.getPage(), dto.getSize(), p.getTotal());
+    @RequestMapping(value = "/library/list", method = RequestMethod.GET)
+    @ApiOperation(value = "换库表", httpMethod = "GET")
+    public Response<PageMessage<TextbookLibrary>> listLibrary(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer[] ids,
+            HttpSession session) {
+        Page<TextbookLibrary> p;
+        if (ids != null && ids.length > 0){
+            p = textbookLibraryService.select(ids);
+        }else{
+            p = textbookLibraryService.listAdmin(page, size, keyword);
+        }
+
+        return ResponseHelp.success(p, page, size, p.getTotal());
     }
 
-    @RequestMapping(value = "/paper/list", method = RequestMethod.GET)
-    @ApiOperation(value = "练习册列表", httpMethod = "GET")
-    public Response<PageMessage<TextbookPaperListDto>> listPaper(
+    @RequestMapping(value = "/library/history/list", method = RequestMethod.GET)
+    @ApiOperation(value = "换库表", httpMethod = "GET")
+    public Response<PageMessage<TextbookLibraryHistory>> listLibraryHistory(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
-            @RequestParam(required = false, defaultValue = "") String keyword,
+            @RequestParam(required = true) Integer libraryId,
             HttpSession session) {
-        Page<TextbookPaper> p = textbookPaperService.select(page, size, keyword);
-        List<TextbookPaperListDto> pr = Transform.convert(p, TextbookPaperListDto.class);
+        Page<TextbookLibraryHistory> p = textbookLibraryHistoryService.listByLibrary(page, size, libraryId);
 
-        return ResponseHelp.success(pr, page, size, p.getTotal());
+        return ResponseHelp.success(p, page, size, p.getTotal());
+    }
+
+    @RequestMapping(value = "/library/history/post", method = RequestMethod.POST)
+    @ApiOperation(value = "发布新换库信息", httpMethod = "POST")
+    public Response<Boolean> postHistory(@RequestBody @Validated TextbookLibraryHistoryDto dto, HttpServletRequest request) {
+        TextbookLibraryHistory entity = Transform.dtoToEntity(dto);
+        textbookLibraryService.addHistory(entity);
+        // 发送机经到用户邮箱
+        asyncTask.postTextbookToEmail();
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
     }
 }

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

@@ -103,11 +103,17 @@ public class UserController {
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false, defaultValue = "") String keyword,
+            @RequestParam(required = false) Integer[] ids,
             @RequestParam(required = false) Boolean real,
             @RequestParam(required = false) String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<User> p = usersService.select(page, size, keyword, real, order, DirectionStatus.ValueOf(direction));
+        Page<User> p;
+        if (ids != null && ids.length > 0){
+            p = usersService.select(ids);
+        }else{
+            p = usersService.select(page, size, keyword, real, order, DirectionStatus.ValueOf(direction));
+        }
         List<UserListDto> pr = Transform.convert(p, UserListDto.class);
 
         Collection userIds = Transform.getIds(p, User.class, "id");

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

@@ -59,7 +59,7 @@ public class CourseController {
         List<UserPay> pays = userPayService.listUnUse(user.getId(), PayModule.CLASS);
         Collection ids = Transform.getIds(userClassList, UserClass.class, "courseId");
         for(UserPay pay : pays){
-            Integer courseId = Integer.valueOf(pay.getModuleExtend());
+            Integer courseId = Integer.valueOf(0);
             if (!ids.contains(courseId)){
                 UserClassDetailDto dto = new UserClassDetailDto();
                 dto.setCourseId(courseId);

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

@@ -9,6 +9,8 @@ import javax.validation.constraints.NotEmpty;
 public class QuestionNoExtendDto {
     private Integer id;
 
+    private String title;
+
     /**
      * 模块:examination, exercise, sentence
      */
@@ -29,6 +31,8 @@ public class QuestionNoExtendDto {
 
     private Integer questionId;
 
+    private Integer[] relationQuestion;
+
     private QuestionExtendDto question;
 
     public String getModule() {
@@ -78,4 +82,20 @@ public class QuestionNoExtendDto {
     public void setQuestionId(Integer questionId) {
         this.questionId = questionId;
     }
+
+    public Integer[] getRelationQuestion() {
+        return relationQuestion;
+    }
+
+    public void setRelationQuestion(Integer[] relationQuestion) {
+        this.relationQuestion = relationQuestion;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
 }

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

@@ -41,6 +41,8 @@ public class QuestionDto {
 
     private JSONObject style;
 
+    private int[] relationQuestion;
+
     public Integer getId() {
         return id;
     }
@@ -152,4 +154,12 @@ public class QuestionDto {
     public void setAnswer(JSONObject answer) {
         this.answer = answer;
     }
+
+    public int[] getRelationQuestion() {
+        return relationQuestion;
+    }
+
+    public void setRelationQuestion(int[] relationQuestion) {
+        this.relationQuestion = relationQuestion;
+    }
 }

+ 14 - 4
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/QuestionNoDto.java

@@ -7,20 +7,22 @@ import com.qxgmat.data.dao.entity.QuestionNo;
 public class QuestionNoDto {
     private Integer id;
 
+    private String title;
+
     /**
      * 题目id
      */
     private Integer questionId;
 
     /**
-     * 模块:examination, exercise,sentence
+     * 模块:examination, exercise
      */
     private String module;
 
     /**
      * 人工id
      */
-    private String no;
+    private Integer no;
 
     /**
      * 对应模块结构信息,逗号分隔
@@ -51,11 +53,11 @@ public class QuestionNoDto {
         this.module = module;
     }
 
-    public String getNo() {
+    public Integer getNo() {
         return no;
     }
 
-    public void setNo(String no) {
+    public void setNo(Integer no) {
         this.no = no;
     }
 
@@ -66,4 +68,12 @@ public class QuestionNoDto {
     public void setModuleStruct(Integer[] moduleStruct) {
         this.moduleStruct = moduleStruct;
     }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
 }

+ 5 - 5
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/QuestionNoSearchDto.java

@@ -12,7 +12,7 @@ public class QuestionNoSearchDto {
 
     private int size = 20;
 
-    private String no;
+    private String keyword;
 
     private String[] nos;
 
@@ -70,11 +70,11 @@ public class QuestionNoSearchDto {
         this.size = size;
     }
 
-    public String getNo() {
-        return no;
+    public String getKeyword() {
+        return keyword;
     }
 
-    public void setNo(String no) {
-        this.no = no;
+    public void setKeyword(String keyword) {
+        this.keyword = keyword;
     }
 }

+ 17 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/TextbookLibraryDto.java

@@ -0,0 +1,17 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.TextbookLibrary;
+
+@Dto(entity = TextbookLibrary.class)
+public class TextbookLibraryDto {
+    private String startDate;
+
+    public String getStartDate() {
+        return startDate;
+    }
+
+    public void setStartDate(String startDate) {
+        this.startDate = startDate;
+    }
+}

+ 57 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/TextbookLibraryHistoryDto.java

@@ -0,0 +1,57 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.TextbookLibraryHistory;
+
+@Dto(entity = TextbookLibraryHistory.class)
+public class TextbookLibraryHistoryDto {
+    private Integer libraryId;
+
+    private String content;
+
+    private String quant = "";
+
+    private String ir = "";
+
+    private String rc = "";
+
+    public Integer getLibraryId() {
+        return libraryId;
+    }
+
+    public void setLibraryId(Integer libraryId) {
+        this.libraryId = libraryId;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getQuant() {
+        return quant;
+    }
+
+    public void setQuant(String quant) {
+        this.quant = quant;
+    }
+
+    public String getIr() {
+        return ir;
+    }
+
+    public void setIr(String ir) {
+        this.ir = ir;
+    }
+
+    public String getRc() {
+        return rc;
+    }
+
+    public void setRc(String rc) {
+        this.rc = rc;
+    }
+}

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

@@ -149,14 +149,18 @@ public class ExercisePaperService extends AbstractService {
                 example.createCriteria()
                         .andEqualTo("status", 1)
         );
-        if (keyword != null)
+        if(keyword != null)
             example.and(
                     example.createCriteria()
-                            .andLike("title", keyword)
+                            .andLike("title", "%"+keyword+"%")
             );
         return select(exercisePaperMapper, example, page, pageSize);
     }
 
+    public Page<ExercisePaper> select(Integer[] ids){
+        return page(()-> select(exercisePaperMapper, ids), 1, ids.length);
+    }
+
     public List<ExercisePaper> select(Collection ids){
         return select(exercisePaperMapper, ids);
     }

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

@@ -62,6 +62,9 @@ public class SentencePaperService extends AbstractService {
         // 绑定关系
         relation.setQuestionId(question.getId());
 
+        // 生成title
+        relation.setTitle("CNJ-"+relation.getNo());
+
         SentenceQuestion sentenceQuestion =  sentenceQuestionService.add(relation);
 
         // 绑定关系
@@ -70,8 +73,6 @@ public class SentencePaperService extends AbstractService {
         if (sentenceQuestion.getIsTrail() > 0){
             addQuestionToPaperWithTrail(sentenceQuestion);
         }
-
-        sentenceQuestionService.edit(sentenceQuestion);
         return sentenceQuestion;
     }
 

+ 48 - 77
server/gateway-api/src/main/java/com/qxgmat/service/TextbookPaperService.java

@@ -12,6 +12,7 @@ import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.SentenceQuestionRelation;
 import com.qxgmat.data.relation.entity.TextbookQuestionRelation;
 import com.qxgmat.service.inline.QuestionService;
+import com.qxgmat.service.inline.TextbookLibraryService;
 import com.qxgmat.service.inline.TextbookQuestionService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -20,6 +21,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
+import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -30,9 +32,6 @@ public class TextbookPaperService extends AbstractService {
     final static String formatSet = "FIND_IN_SET('%d', question_no_ids)";
     final static String formatTitle = "";
 
-    @Value("${paper.textbookLength}")
-    private Integer paperLength;
-
     @Resource
     private TextbookPaperMapper textbookPaperMapper;
 
@@ -42,46 +41,54 @@ public class TextbookPaperService extends AbstractService {
     @Resource
     private TextbookQuestionService textbookQuestionService;
 
+    @Resource
+    private TextbookLibraryService textbookLibraryService;
+
+
     /**
-     * 添加长难句题目:加入题库,关联题目,并关联长难句paper
-     * @param relation
-     * @return
+     * 更新所有组卷title
+     * @param libraryId
+     * @param prefixTitle
      */
-    @Transactional
-    public TextbookQuestion addQuestion(TextbookQuestionRelation relation){
-        Question question = relation.getQuestion();
-        question = questionService.add(question);
-        // 绑定关系
-        relation.setQuestionId(question.getId());
-
-        TextbookQuestion textbookQuestion =  textbookQuestionService.add(relation);
-
-        // 绑定关系
-        if (question.getQuestionType().equals(TextbookLogic.DS.key)){
-            addQuestionToPaper(textbookQuestion, TextbookLogic.DS);
-            addQuestionToPaper(textbookQuestion, TextbookLogic.DS_PS);
-        }else if (question.getQuestionType().equals(TextbookLogic.PS.key)){
-            addQuestionToPaper(textbookQuestion, TextbookLogic.PS);
-            addQuestionToPaper(textbookQuestion, TextbookLogic.DS_PS);
+    public void updateTitle(Integer libraryId, String prefixTitle, Integer length){
+        Example example = new Example(TextbookLibrary.class);
+        example.and(
+                example.createCriteria()
+                .andEqualTo("libraryId", libraryId)
+        );
+        List<TextbookPaper> paperList = select(textbookPaperMapper, example);
+        for(TextbookPaper paper : paperList){
+            paper.setTitle(generateTitle(prefixTitle, length, paper.getNo(), paper.getQuestionNumber()));
+            edit(paper);
         }
-
-        textbookQuestionService.edit(textbookQuestion);
-        return textbookQuestion;
     }
 
     /**
-     * 更新机经题目:更新题库
-     * @param relation
+     * 生成题目id
+     * @param prefixTitle
+     * @param no
+     * @param questionNumber
      * @return
      */
-    @Transactional
-    public TextbookQuestion editQuestion(TextbookQuestionRelation relation){
-        Question question = relation.getQuestion();
-        question = questionService.edit(question);
-
-        TextbookQuestion textbookQuestion = textbookQuestionService.edit(relation);
+    public String generateTitle(String prefixTitle, Integer length, Integer no, Integer questionNumber){
+        return String.format("%s#%d~%d", prefixTitle, (no - 1) * length + 1, (no - 1) * length + questionNumber);
+    }
 
-        return textbookQuestion;
+    /**
+     * 管理后台查询列表
+     * @param page
+     * @param keyword
+     * @return
+     */
+    public Page<TextbookPaper> listAdmin(int page, int size, String keyword){
+        Example example = new Example(TextbookLibrary.class);
+        if(keyword != null)
+            example.and(
+                    example.createCriteria()
+                            .orLike("title", "%"+keyword+"%")
+            );
+        example.orderBy("id").desc();
+        return page(()->select(textbookPaperMapper, example), page, size);
     }
 
     /**
@@ -100,47 +107,7 @@ public class TextbookPaperService extends AbstractService {
         return select(textbookPaperMapper, example);
     }
 
-    private void addQuestionToPaper(TextbookQuestion question, TextbookLogic logic){
-        // 获取最后一个paper
-        TextbookPaper paper = getLastBySet(logic, question.getLibraryId());
-        Integer no = 0;
-        if (paper == null || paper.getQuestionNumber().equals(paperLength)){
-            if (paper != null){
-                no = paper.getNo();
-            }
-            Calendar calendar = Calendar.getInstance();
-            paper = add(TextbookPaper.builder()
-                    .logic(SentenceLogic.NO.key)
-                    .title(String.format(formatTitle, no))
-                    .year(calendar.get(Calendar.YEAR))
-                    .libraryId(question.getLibraryId())
-                    .no(no+1)
-                    .questionNumber(1)
-                    .status(1)
-                    .questionNoIds(new Integer[]{question.getId()}).build()
-            );
-        }else{
-            // 加入
-            List<Integer> list = Arrays.stream(paper.getQuestionNoIds()).collect(Collectors.toList());
-            list.add(question.getId());
-            paper.setQuestionNumber(paper.getQuestionNumber() + 1);
-            paper.setQuestionNoIds(list.toArray(new Integer[0]));
-            paper = edit(paper);
-        }
-    }
-
-    private void removeQuestionFromPaper(TextbookQuestion question){
-        List<TextbookPaper> paperList = listByQuestion(question);
-        for(TextbookPaper paper : paperList){
-            paper.setQuestionNumber(paper.getQuestionNumber() - 1);
-            List<Integer> list = Arrays.stream(paper.getQuestionNoIds()).collect(Collectors.toList());
-            list.remove(question.getId());
-            paper.setQuestionNoIds(list.toArray(new Integer[0]));
-            edit(paper);
-        }
-    }
-
-    private List<TextbookPaper> listByQuestion(TextbookQuestion question){
+    public List<TextbookPaper> listByQuestion(TextbookQuestion question){
         Example example = new Example(SentencePaper.class);
         example.and(
                 example.createCriteria()
@@ -149,7 +116,7 @@ public class TextbookPaperService extends AbstractService {
         return select(textbookPaperMapper, example);
     }
 
-    private TextbookPaper getLastBySet(TextbookLogic logic, Integer setId){
+    public TextbookPaper getLastBySet(TextbookLogic logic, Integer setId){
         Example example = new Example(TextbookPaper.class);
         example.and(
                 example.createCriteria()
@@ -210,11 +177,15 @@ public class TextbookPaperService extends AbstractService {
         if (keyword != null)
             example.and(
                     example.createCriteria()
-                            .andLike("title", keyword)
+                            .andLike("title", "%"+keyword+"%")
             );
         return select(textbookPaperMapper, example, page, pageSize);
     }
 
+    public Page<TextbookPaper> select(Integer[] ids){
+        return page(()-> select(textbookPaperMapper, ids), 1, ids.length);
+    }
+
     public List<TextbookPaper> select(Collection ids){
         return select(textbookPaperMapper, ids);
     }

+ 5 - 1
server/gateway-api/src/main/java/com/qxgmat/service/UsersService.java

@@ -506,7 +506,7 @@ public class UsersService extends AbstractService {
     }
     public Page<User> select(int page, int pageSize, String keyword, Boolean real, String order, DirectionStatus direction){
         Example example = new Example(User.class);
-        if(!keyword.isEmpty())
+        if(keyword != null)
             example.and(
                     example.createCriteria()
                             .orLike("id", "%"+keyword+"%")
@@ -532,6 +532,10 @@ public class UsersService extends AbstractService {
         return select(userMapper, example, page, pageSize);
     }
 
+    public Page<User> select(Integer[] ids){
+        return page(()->select(userMapper, ids), 1, ids.length);
+    }
+
 
     public List<User> select(Collection ids){
         return select(userMapper, ids);

+ 180 - 0
server/gateway-api/src/main/java/com/qxgmat/service/extend/TextbookService.java

@@ -0,0 +1,180 @@
+package com.qxgmat.service.extend;
+
+import com.nuliji.tools.mybatis.Example;
+import com.qxgmat.data.constants.enums.logic.SentenceLogic;
+import com.qxgmat.data.constants.enums.logic.TextbookLogic;
+import com.qxgmat.data.dao.TextbookPaperMapper;
+import com.qxgmat.data.dao.entity.Question;
+import com.qxgmat.data.dao.entity.TextbookLibrary;
+import com.qxgmat.data.dao.entity.TextbookPaper;
+import com.qxgmat.data.dao.entity.TextbookQuestion;
+import com.qxgmat.data.relation.entity.TextbookQuestionRelation;
+import com.qxgmat.service.TextbookPaperService;
+import com.qxgmat.service.inline.QuestionService;
+import com.qxgmat.service.inline.TextbookLibraryService;
+import com.qxgmat.service.inline.TextbookQuestionService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class TextbookService {
+    SimpleDateFormat stringToDate = new SimpleDateFormat("yyyy-MM-dd");
+    SimpleDateFormat dateToString = new SimpleDateFormat("yyyy年MM月dd日");
+    SimpleDateFormat dateToDay = new SimpleDateFormat("dd日");
+
+    @Value("${paper.textbookLength}")
+    private Integer paperLength;
+
+    @Resource
+    private TextbookPaperService textbookPaperService;
+
+    @Resource
+    private QuestionService questionService;
+
+    @Resource
+    private TextbookQuestionService textbookQuestionService;
+
+    @Resource
+    private TextbookLibraryService textbookLibraryService;
+
+
+
+    /**
+     * 添加新一期的换库
+     * @param entity
+     * @return
+     */
+    @Transactional
+    public TextbookLibrary replace(TextbookLibrary entity){
+        TextbookLibrary last = textbookLibraryService.getLast();
+
+        if (last != null){
+            // 将最后一期设定库尾
+            last.setEndDate(entity.getStartDate());
+            textbookLibraryService.edit(last);
+
+            String prefixTitle = generatePrefixTitle(last);
+
+            textbookPaperService.updateTitle(last.getId(), prefixTitle, paperLength);
+            textbookQuestionService.updateTitle(last.getId(), prefixTitle);
+        }
+        return textbookLibraryService.add(entity);
+    }
+    /**
+     * 添加长难句题目:加入题库,关联题目,并关联长难句paper
+     * @param relation
+     * @return
+     */
+    @Transactional
+    public TextbookQuestion addQuestion(TextbookQuestionRelation relation){
+        Question question = relation.getQuestion();
+        question = questionService.add(question);
+        // 绑定关系
+        relation.setQuestionId(question.getId());
+
+        TextbookLibrary library = textbookLibraryService.get(relation.getLibraryId());
+        String prefixTitle = generatePrefixTitle(library);
+
+        relation.setTitle(textbookQuestionService.generateTitle(prefixTitle, relation.getNo()));
+        // 保存年份
+        relation.setTime(library.getStartDate().substring(0,3));
+
+        TextbookQuestion textbookQuestion =  textbookQuestionService.add(relation);
+
+        // 绑定关系
+        if (question.getQuestionType().equals(TextbookLogic.DS.key)){
+            addQuestionToPaper(library, textbookQuestion, TextbookLogic.DS);
+            addQuestionToPaper(library, textbookQuestion, TextbookLogic.DS_PS);
+        }else if (question.getQuestionType().equals(TextbookLogic.PS.key)){
+            addQuestionToPaper(library, textbookQuestion, TextbookLogic.PS);
+            addQuestionToPaper(library, textbookQuestion, TextbookLogic.DS_PS);
+        }
+
+        textbookQuestionService.edit(textbookQuestion);
+        return textbookQuestion;
+    }
+
+    /**
+     * 更新机经题目:更新题库
+     * @param relation
+     * @return
+     */
+    @Transactional
+    public TextbookQuestion editQuestion(TextbookQuestionRelation relation){
+        Question question = relation.getQuestion();
+        question = questionService.edit(question);
+
+        TextbookQuestion textbookQuestion = textbookQuestionService.edit(relation);
+
+        return textbookQuestion;
+    }
+
+    private void addQuestionToPaper(TextbookLibrary library, TextbookQuestion question, TextbookLogic logic){
+        String prefixTitle = generatePrefixTitle(library);
+        // 获取最后一个paper
+        TextbookPaper paper = textbookPaperService.getLastBySet(logic, question.getLibraryId());
+        Integer no = 0;
+        if (paper == null || paper.getQuestionNumber().equals(paperLength)){
+            if (paper != null){
+                no = paper.getNo();
+            }
+            paper = textbookPaperService.add(TextbookPaper.builder()
+                    .logic(SentenceLogic.NO.key)
+                    .time(question.getTime())
+                    .libraryId(question.getLibraryId())
+                    .title(textbookPaperService.generateTitle(prefixTitle, paperLength, no, 1))
+                    .no(no+1)
+                    .questionNumber(1)
+                    .status(1)
+                    .questionNoIds(new Integer[]{question.getId()}).build()
+            );
+        }else{
+            // 加入
+            List<Integer> list = Arrays.stream(paper.getQuestionNoIds()).collect(Collectors.toList());
+            list.add(question.getId());
+            paper.setQuestionNumber(paper.getQuestionNumber() + 1);
+            paper.setQuestionNoIds(list.toArray(new Integer[0]));
+            paper.setTitle(textbookPaperService.generateTitle(prefixTitle, paperLength, paper.getNo(), paper.getQuestionNumber()));
+            paper = textbookPaperService.edit(paper);
+        }
+    }
+
+    private void removeQuestionFromPaper(TextbookQuestion question){
+        List<TextbookPaper> paperList = textbookPaperService.listByQuestion(question);
+        for(TextbookPaper paper : paperList){
+            paper.setQuestionNumber(paper.getQuestionNumber() - 1);
+            List<Integer> list = Arrays.stream(paper.getQuestionNoIds()).collect(Collectors.toList());
+            list.remove(question.getId());
+            paper.setQuestionNoIds(list.toArray(new Integer[0]));
+            textbookPaperService.edit(paper);
+        }
+    }
+
+    private String generatePrefixTitle(TextbookLibrary library){
+        try {
+            String startDate = dateToString.format(stringToDate.parse(library.getStartDate()));
+            String endDate;
+            if (library.getEndDate().isEmpty()){
+                // 至今
+                endDate = "now";
+            }else if(library.getStartDate().substring(0,6).equals(library.getEndDate().substring(0,6))){
+                // 同月
+                endDate = dateToDay.format(stringToDate.parse(library.getEndDate()));
+            }else{
+                endDate = dateToString.format(stringToDate.parse(library.getEndDate()));
+            }
+            return String.format("%s-%s", startDate, endDate);
+        } catch (ParseException e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+}

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

@@ -72,16 +72,18 @@ public class QuestionNoService extends AbstractService {
      * 根据题目编号搜索相似题目
      * @param page
      * @param size
-     * @param no
+     * @param keyword
      * @param module
      * @return
      */
-    public PageResult<QuestionNoRelation> searchNo(int page, int size, String no, String module){
+    public PageResult<QuestionNoRelation> searchNo(int page, int size, String keyword, String module){
+        logger.info("SearchNo: {}, {}", page, size);
         Example example = new Example(QuestionNo.class);
-        example.and(
-                example.createCriteria()
-                        .andLike("no", no)
-        );
+        if(keyword != null)
+            example.and(
+                    example.createCriteria()
+                            .andLike("title", "%"+keyword+"%")
+            );
         if (module != null)
             example.and(
                     example.createCriteria()
@@ -89,6 +91,7 @@ public class QuestionNoService extends AbstractService {
             );
         example.orderBy("id").asc();
         Page<QuestionNo> p = page(()->select(questionNoMapper, example), page, size);
+        logger.info("SearchNo result: {}", p.toString());
         return new PageResult<>(relation(p), p.getTotal());
     }
 
@@ -181,6 +184,22 @@ public class QuestionNoService extends AbstractService {
     }
 
     /**
+     * 设定编号关联关系
+     * @param ids
+     * @return
+     */
+    public Boolean relationQuestion(int[] ids){
+        if (ids.length == 0) return false;
+        Example example = new Example(QuestionNo.class);
+        example.and(
+                example.createCriteria()
+                        .andIn("id", Arrays.stream(ids).boxed().collect(Collectors.toList()))
+        );
+        int result = update(questionNoMapper, example, QuestionNo.builder().relationQuestion(ids).deleteTime(null).build());
+        return result > 0;
+    }
+
+    /**
      * 根据题目获取总试卷统计信息
      * @param questionNoList
      * @return

+ 21 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/SentenceQuestionService.java

@@ -36,6 +36,23 @@ public class SentenceQuestionService extends AbstractService {
     private SentenceQuestionRelationMapper sentenceQuestionRelationMapper;
 
     /**
+     * 后台搜索长难句
+     * @param page
+     * @param size
+     * @param keyword
+     * @return
+     */
+    public Page<SentenceQuestion> searchAdmin(int page, int size, String keyword){
+        Example example = new Example(SentenceQuestion.class);
+        if(keyword != null)
+            example.and(
+                    example.createCriteria()
+                    .andLike("title", "%"+keyword+"%")
+            );
+        return page(() -> select(sentenceQuestionMapper, example), page, size);
+    }
+
+    /**
      * 根据题目编号id列表获取关联题目
      * @param ids
      * @return
@@ -195,6 +212,10 @@ public class SentenceQuestionService extends AbstractService {
         return select(sentenceQuestionMapper, page, pageSize);
     }
 
+    public Page<SentenceQuestion> select(Integer[] ids){
+        return page(()-> select(sentenceQuestionMapper, ids), 1, ids.length);
+    }
+
     public List<SentenceQuestion> select(Collection ids){
         return select(sentenceQuestionMapper, ids);
     }

+ 18 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryHistoryService.java

@@ -4,6 +4,7 @@ import com.github.pagehelper.Page;
 import com.nuliji.tools.AbstractService;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
+import com.nuliji.tools.mybatis.Example;
 import com.qxgmat.data.dao.TextbookLibraryHistoryMapper;
 import com.qxgmat.data.dao.TextbookLibraryMapper;
 import com.qxgmat.data.dao.entity.TextbookLibrary;
@@ -23,6 +24,23 @@ public class TextbookLibraryHistoryService extends AbstractService {
     @Resource
     private TextbookLibraryHistoryMapper textbookLibraryHistoryMapper;
 
+    /**
+     * 获取换库历史
+     * @param page
+     * @param size
+     * @param libraryId
+     * @return
+     */
+    public Page<TextbookLibraryHistory> listByLibrary(int page, int size, Integer libraryId){
+        Example example = new Example(TextbookLibraryHistory.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("libraryId", libraryId)
+        );
+        example.orderBy("id").desc();
+        return page(()->select(textbookLibraryHistoryMapper, example), page, size);
+    }
+
     public TextbookLibraryHistory add(TextbookLibraryHistory ad){
         int result = insert(textbookLibraryHistoryMapper, ad);
         ad = one(textbookLibraryHistoryMapper, ad.getId());

+ 71 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryService.java

@@ -9,11 +9,14 @@ import com.qxgmat.data.dao.AdMapper;
 import com.qxgmat.data.dao.TextbookLibraryMapper;
 import com.qxgmat.data.dao.entity.Ad;
 import com.qxgmat.data.dao.entity.TextbookLibrary;
+import com.qxgmat.data.dao.entity.TextbookLibraryHistory;
 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.text.SimpleDateFormat;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
@@ -25,6 +28,70 @@ public class TextbookLibraryService extends AbstractService {
     @Resource
     private TextbookLibraryMapper textbookLibraryMapper;
 
+    @Resource
+    private TextbookLibraryHistoryService textbookLibraryHistoryService;
+
+    /**
+     * 后台查看机经换库表
+     * @param page
+     * @param size
+     * @param keyword
+     * @return
+     */
+    public Page<TextbookLibrary> listAdmin(int page, int size, String keyword){
+        Example example = new Example(TextbookLibrary.class);
+        if(keyword != null)
+            example.and(
+                    example.createCriteria()
+                    .orLike("startDate", "%"+keyword+"%")
+                    .orLike("endDate", "%"+keyword+"%")
+            );
+        example.orderBy("id").desc();
+        return page(()->select(textbookLibraryMapper, example), page, size);
+    }
+
+    /**
+     * 获取最后一期
+     * @return
+     */
+    @Transactional
+    public TextbookLibrary getLast(){
+        Example example = new Example(TextbookLibrary.class);
+        example.orderBy("id").desc();
+        return one(textbookLibraryMapper, example);
+    }
+
+    /**
+     * 添加一次发布
+     * @param history
+     * @return
+     */
+    @Transactional
+    public TextbookLibrary addHistory(TextbookLibraryHistory history){
+        Example example = new Example(TextbookLibrary.class);
+        example.and(
+                example.createCriteria()
+                .andEqualTo("id", history.getLibraryId())
+        );
+        example.orderBy("id").desc();
+        TextbookLibrary last = one(textbookLibraryMapper, example);
+        if (last == null){
+            throw new ParameterException("机经不存在");
+        }
+        history = textbookLibraryHistoryService.add(history);
+        if (!history.getQuant().isEmpty()){
+            last.setQuant(history.getQuant());
+        }
+        if (!history.getIr().isEmpty()){
+            last.setIr(history.getIr());
+        }
+        if (!history.getRc().isEmpty()){
+            last.setRc(history.getRc());
+        }
+        last.setHistoryNumber(last.getHistoryNumber() + 1);
+        return edit(last);
+    }
+
     public TextbookLibrary add(TextbookLibrary ad){
         int result = insert(textbookLibraryMapper, ad);
         ad = one(textbookLibraryMapper, ad.getId());
@@ -65,6 +132,10 @@ public class TextbookLibraryService extends AbstractService {
         return select(textbookLibraryMapper, page, pageSize);
     }
 
+    public Page<TextbookLibrary> select(Integer[] ids){
+        return page(()-> select(textbookLibraryMapper, ids), 1, ids.length);
+    }
+
     public List<TextbookLibrary> select(Collection ids){
         return select(textbookLibraryMapper, ids);
     }

+ 77 - 4
server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookQuestionService.java

@@ -10,6 +10,7 @@ import com.nuliji.tools.mybatis.Example;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.TextbookQuestionMapper;
 import com.qxgmat.data.dao.entity.Question;
+import com.qxgmat.data.dao.entity.TextbookLibrary;
 import com.qxgmat.data.dao.entity.TextbookQuestion;
 import com.qxgmat.data.dao.entity.UserQuestion;
 import com.qxgmat.data.inline.PaperStat;
@@ -20,12 +21,17 @@ import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.stream.Collectors;
 
 @Service
 public class TextbookQuestionService extends AbstractService {
     private static final Logger logger = LoggerFactory.getLogger(TextbookQuestionService.class);
+    SimpleDateFormat stringToDate = new SimpleDateFormat("yyyy-MM-dd");
+    SimpleDateFormat dateToString = new SimpleDateFormat("yyyy年MM月dd日");
+    SimpleDateFormat dateToDay = new SimpleDateFormat("dd日");
 
     @Resource
     private TextbookQuestionMapper textbookQuestionMapper;
@@ -69,18 +75,81 @@ public class TextbookQuestionService extends AbstractService {
     }
 
     /**
-     * 根据题目id获取题目列表
+     * 更新所有题目及组卷title
+     * @param libraryId
+     * @param prefixTitle
+     */
+    public void updateTitle(Integer libraryId, String prefixTitle) {
+        Example example = new Example(TextbookLibrary.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("libraryId", libraryId)
+        );
+        List<TextbookQuestion> questionList = select(textbookQuestionMapper, example);
+        for(TextbookQuestion question : questionList){
+            question.setTitle(generateTitle(prefixTitle, question.getNo()));
+            edit(question);
+        }
+    }
+
+    /**
+     * 生成题目id
+     * @param prefixTitle
+     * @param no
+     * @return
+     */
+    public String generateTitle(String prefixTitle, Integer no) {
+        return String.format("%s#%d", prefixTitle, no);
+    }
+
+    /**
+     * 管理后台查询列表
      * @param page
      * @param size
-     * @param no
+     * @param keyword
      * @return
      */
-    public PageResult<TextbookQuestionRelation> searchNo(int page, int size, String no){
+    public Page<TextbookQuestion> searchAdmin(int page, int size, String keyword){
+        // 分割#号
+        Example example = new Example(TextbookQuestion.class);
+        if(keyword != null)
+            example.and(
+                    example.createCriteria()
+                            .andLike("title", "%"+keyword+"%")
+            );
+        example.orderBy("id").desc();
+        return page(()->select(textbookQuestionMapper, example), page, size);
+    }
+
+    /**
+     * 获取换库表中的最后一题
+     * @param libraryId
+     * @return
+     */
+    public TextbookQuestion lastByLibrary(Integer libraryId){
         Example example = new Example(TextbookQuestion.class);
         example.and(
                 example.createCriteria()
-                        .andLike("title", no)
+                .andEqualTo("libraryId", libraryId)
         );
+        example.orderBy("no").desc();
+        return one(textbookQuestionMapper, example);
+    }
+
+    /**
+     * 根据题目id获取题目列表
+     * @param page
+     * @param size
+     * @param keyword
+     * @return
+     */
+    public PageResult<TextbookQuestionRelation> searchNo(int page, int size, String keyword){
+        Example example = new Example(TextbookQuestion.class);
+        if(keyword != null)
+            example.and(
+                    example.createCriteria()
+                            .andLike("title", "%"+keyword+"%")
+            );
         example.orderBy("id").asc();
         Page<TextbookQuestion> p = page(()->select(textbookQuestionMapper, example), page, size);
         return new PageResult<>(relation(p), p.getTotal());
@@ -232,6 +301,10 @@ public class TextbookQuestionService extends AbstractService {
         return select(textbookQuestionMapper, page, pageSize);
     }
 
+    public Page<TextbookQuestion> select(Integer[] ids){
+        return page(()-> select(textbookQuestionMapper, ids), 1, ids.length);
+    }
+
     public List<TextbookQuestion> select(Collection ids){
         return select(textbookQuestionMapper, ids);
     }

+ 0 - 7
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserClassService.java

@@ -24,13 +24,6 @@ public class UserClassService extends AbstractService {
     @Resource
     private UserPayService userPayService;
 
-    // 开通服务
-    public boolean used(UserPay pay){
-        // todo 分析时长
-        add(UserClass.builder().userId(pay.getUserId()).courseId(Integer.valueOf(pay.getModuleExtend())).build());
-        return true;
-    }
-
     /**
      * 合并用户信息,将old转移至new
      * @param oldUserId

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

@@ -40,7 +40,7 @@ public class UserFeedbackErrorService extends AbstractService {
                     example.createCriteria()
                             .andEqualTo("status", status.index)
             );
-        if(!keyword.isEmpty())
+        if(keyword != null)
             example.and(
                     example.createCriteria()
                             .andLike("title", "%"+keyword+"%")

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

@@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
 public class AsyncTask {
     private static final Logger logger = LoggerFactory.getLogger(AsyncTask.class);
 
-    @Async("autoExercisePaper")
+    @Async
     public void autoExercisePaper() {
         logger.info("自动练习组卷:顺序,考点,难易度");
         long start = System.currentTimeMillis();
@@ -17,13 +17,18 @@ public class AsyncTask {
         logger.info("完成任务一,耗时:" + (end - start) + "毫秒");
     }
 
-    @Async("autoExercisePaperError")
+    @Async
     public void autoExercisePaperError() {
         logger.info("自动练习:难易度组卷,全局难易度判断");
     }
 
-    @Async("autoSentencePaper")
+    @Async
     public void autoSentencePaper() {
         logger.info("自动长难句:对于试用卷进行自动生成");
     }
+
+    @Async
+    public void postTextbookToEmail(){
+        logger.info("发布机经:发送到订阅用户邮箱");
+    }
 }

+ 4 - 3
server/gateway-api/src/main/java/com/qxgmat/util/shiro/ManagerRealm.java

@@ -17,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 /**
  * Created by GaoJie on 2017/11/3.
@@ -51,9 +52,9 @@ public class ManagerRealm extends AuthorizingRealm {
         sa.addRoles(roleAuthorization);
 
         if (role != null){
-            String[] permissionList = role.getPermissionList();
-            if (permissionList.length > 0){
-                sa.addStringPermissions(Arrays.asList(permissionList));
+            List permissionList = role.getPermissionList();
+            if (permissionList.size() > 0){
+                sa.addStringPermissions(permissionList);
             }
         }
         return sa;

+ 1 - 1
server/gateway-api/src/main/profile/dev/application-runtime.yml

@@ -81,7 +81,7 @@ spring:
         # 连接池最大阻塞等待时间(使用负值表示没有限制)
         max-wait: 10000
     # 连接超时时间(毫秒)
-    timeout: 1000
+    timeout: 5000
 
 upload:
   local_path: ./upload/

+ 13 - 13
server/tools/src/main/java/com/nuliji/tools/PageMessage.java

@@ -21,19 +21,19 @@ public class PageMessage<T> implements Serializable {
     private int size = 0;
 
     @ApiModelProperty(value = "记录总数", required = true)
-    private long count = 0;
+    private long total = 0;
 
-    public PageMessage(List<T> list, int page, int size, int count) {
+    public PageMessage(List<T> list, int page, int size, int total) {
         this.list = list;
         this.page = page;
         this.size = size;
-        this.count = (long) count;
+        this.total = (long) total;
     }
-    public PageMessage(List<T> list, int page, int size, long count) {
+    public PageMessage(List<T> list, int page, int size, long total) {
         this.list = list;
         this.page = page;
         this.size = size;
-        this.count = count;
+        this.total = total;
     }
 
     public int getPage() {
@@ -52,14 +52,6 @@ public class PageMessage<T> implements Serializable {
         this.size = size;
     }
 
-    public long getCount() {
-        return count;
-    }
-
-    public void setCount(long count) {
-        this.count = count;
-    }
-
     public List<T> getList() {
         return list;
     }
@@ -67,4 +59,12 @@ public class PageMessage<T> implements Serializable {
     public void setList(List<T> list) {
         this.list = list;
     }
+
+    public long getTotal() {
+        return total;
+    }
+
+    public void setTotal(long total) {
+        this.total = total;
+    }
 }

+ 13 - 13
server/tools/src/main/java/com/nuliji/tools/mybatis/handler/IntegerArrayHandler.java

@@ -13,12 +13,12 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-@MappedTypes(Integer[].class)
-@MappedJdbcTypes({JdbcType.LONGVARCHAR, JdbcType.VARCHAR})
-public class IntegerArrayHandler extends BaseTypeHandler<Integer[]> {
+@MappedTypes(int[].class)
+@MappedJdbcTypes(value = {JdbcType.VARCHAR}, includeNullJdbcType = true)
+public class IntegerArrayHandler extends BaseTypeHandler<int[]> {
 
     @Override
-    public void setNonNullParameter(PreparedStatement ps, int i, Integer[] parameter, JdbcType jdbcType) throws SQLException {
+    public void setNonNullParameter(PreparedStatement ps, int i, int[] parameter, JdbcType jdbcType) throws SQLException {
         List<String> list = new ArrayList<>();
         for (Integer item : parameter) {
             list.add(String.valueOf(item));
@@ -27,30 +27,30 @@ public class IntegerArrayHandler extends BaseTypeHandler<Integer[]> {
     }
 
     @Override
-    public Integer[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
+    public int[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
         String str = rs.getString(columnName);
         if (rs.wasNull())
             return null;
-
-        return Arrays.stream(str.split(",")).map(Integer::valueOf).toArray(Integer[]::new);
+        if (str.isEmpty()) return new int[0];
+        return Arrays.stream(str.split(",")).mapToInt(Integer::valueOf).toArray();
     }
 
     @Override
-    public Integer[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+    public int[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
         String str = rs.getString(columnIndex);
         if (rs.wasNull())
             return null;
-
-        return Arrays.stream(str.split(",")).map(Integer::valueOf).toArray(Integer[]::new);
+        if (str.isEmpty()) return new int[0];
+        return Arrays.stream(str.split(",")).mapToInt(Integer::valueOf).toArray();
     }
 
     @Override
-    public Integer[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+    public int[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
         String str = cs.getString(columnIndex);
         if (cs.wasNull())
             return null;
-
-        return Arrays.stream(str.split(",")).map(Integer::valueOf).toArray(Integer[]::new);
+        if (str.isEmpty()) return new int[0];
+        return Arrays.stream(str.split(",")).mapToInt(Integer::valueOf).toArray();
     }
 }
 

+ 6 - 1
server/tools/src/main/java/com/nuliji/tools/mybatis/handler/IntegerArrayWithJsonHandler.java

@@ -5,10 +5,15 @@ import org.apache.ibatis.type.JdbcType;
 import org.apache.ibatis.type.MappedJdbcTypes;
 import org.apache.ibatis.type.MappedTypes;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
 @MappedTypes(Integer[].class)
-@MappedJdbcTypes({JdbcType.LONGVARCHAR, JdbcType.VARCHAR})
+@MappedJdbcTypes(value = {JdbcType.LONGVARCHAR, JdbcType.VARCHAR},includeNullJdbcType = true)
 public class IntegerArrayWithJsonHandler extends NativeJsonHandler<Integer[]> {
     public IntegerArrayWithJsonHandler() {
         super(Integer[].class);
+//        super((Class<List<Integer>>) new ArrayList<Integer>().getClass());
     }
 }

+ 1 - 1
server/tools/src/main/java/com/nuliji/tools/mybatis/handler/JsonArrayHandler.java

@@ -16,7 +16,7 @@ import java.sql.SQLException;
  * @description 用以mysql中json格式的字段,进行转换的自定义转换器,转换为实体类的JSONObject属性
  */
 @MappedTypes(JSONArray.class)
-@MappedJdbcTypes({JdbcType.LONGVARCHAR, JdbcType.VARCHAR})
+@MappedJdbcTypes(value = {JdbcType.LONGVARCHAR, JdbcType.VARCHAR}, includeNullJdbcType = true)
 public class JsonArrayHandler extends BaseTypeHandler<JSONArray> {
 
     public JsonArrayHandler(){

+ 1 - 1
server/tools/src/main/java/com/nuliji/tools/mybatis/handler/JsonObjectHandler.java

@@ -12,7 +12,7 @@ import java.sql.*;
  * @description 用以mysql中json格式的字段,进行转换的自定义转换器,转换为实体类的JSONObject属性
  */
 @MappedTypes(JSONObject.class)
-@MappedJdbcTypes({JdbcType.LONGVARCHAR, JdbcType.VARCHAR})
+@MappedJdbcTypes(value = {JdbcType.LONGVARCHAR, JdbcType.VARCHAR}, includeNullJdbcType = true)
 public class JsonObjectHandler extends BaseTypeHandler<JSONObject> {
 
     public JsonObjectHandler(){

+ 2 - 1
server/tools/src/main/java/com/nuliji/tools/mybatis/handler/StringArrayHandler.java

@@ -1,6 +1,7 @@
 package com.nuliji.tools.mybatis.handler;
 
 
+import com.google.common.base.Strings;
 import org.apache.ibatis.type.BaseTypeHandler;
 import org.apache.ibatis.type.JdbcType;
 import org.apache.ibatis.type.MappedJdbcTypes;
@@ -12,7 +13,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 
 @MappedTypes(String[].class)
-@MappedJdbcTypes({JdbcType.LONGVARCHAR, JdbcType.VARCHAR})
+@MappedJdbcTypes(value = {JdbcType.VARCHAR}, includeNullJdbcType = true)
 public class StringArrayHandler extends BaseTypeHandler<String[]> {
     @Override
     public void setNonNullParameter(PreparedStatement ps, int i, String[] parameter, JdbcType jdbcType) throws SQLException {

+ 8 - 4
server/tools/src/main/java/com/nuliji/tools/mybatis/handler/StringArrayWithJsonHandler.java

@@ -5,10 +5,14 @@ import org.apache.ibatis.type.JdbcType;
 import org.apache.ibatis.type.MappedJdbcTypes;
 import org.apache.ibatis.type.MappedTypes;
 
-@MappedTypes(String[].class)
-@MappedJdbcTypes({JdbcType.LONGVARCHAR, JdbcType.VARCHAR})
-public class StringArrayWithJsonHandler extends NativeJsonHandler<String[]> {
+import java.util.ArrayList;
+import java.util.List;
+
+@MappedTypes(List.class)
+@MappedJdbcTypes(value = {JdbcType.LONGVARCHAR, JdbcType.VARCHAR}, includeNullJdbcType = true)
+public class StringArrayWithJsonHandler extends NativeJsonHandler<List> {
     public StringArrayWithJsonHandler() {
-        super(String[].class);
+        super(List.class);
+//        super((Class<List<String>>) new ArrayList<String>().getClass());
     }
 }