Browse Source

feat(server): 调整后台操作

Go 5 years ago
parent
commit
30734dc4ba
79 changed files with 2552 additions and 389 deletions
  1. 5 4
      front/project/Constant.js
  2. 9 5
      front/project/admin/routes/course/ask/page.js
  3. 26 5
      front/project/admin/routes/course/askDetail/page.js
  4. 4 1
      front/project/admin/routes/course/data/page.js
  5. 114 88
      front/project/admin/routes/course/dataDetail/page.js
  6. 16 0
      front/project/admin/routes/course/dataHistory/index.js
  7. 3 0
      front/project/admin/routes/course/dataHistory/index.less
  8. 229 0
      front/project/admin/routes/course/dataHistory/page.js
  9. 45 9
      front/project/admin/routes/course/detail/page.js
  10. 5 2
      front/project/admin/routes/course/experience/page.js
  11. 47 0
      front/project/admin/routes/course/experienceDetail/page.js
  12. 143 1
      front/project/admin/routes/course/invoice/page.js
  13. 12 6
      front/project/admin/routes/course/list/page.js
  14. 45 22
      front/project/admin/routes/course/student/page.js
  15. 3 2
      front/project/admin/routes/course/studyDetail/page.js
  16. 21 1
      front/project/admin/routes/course/vsDetail/page.js
  17. 4 1
      front/project/admin/routes/setting/comment/page.js
  18. 4 3
      front/project/admin/routes/setting/faq/page.js
  19. 1 1
      front/project/admin/routes/user/ask/page.js
  20. 27 6
      front/project/admin/routes/user/askDetail/page.js
  21. 68 11
      front/project/admin/routes/user/feedback/page.js
  22. 1 1
      front/project/admin/routes/user/preview/page.js
  23. 4 0
      front/project/admin/stores/course.js
  24. 6 0
      front/project/admin/stores/exercise.js
  25. 12 0
      front/project/admin/stores/user.js
  26. 10 4
      server/data/src/main/java/com/qxgmat/data/constants/enums/ServiceKey.java
  27. 2 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/user/ServiceSource.java
  28. 154 14
      server/data/src/main/java/com/qxgmat/data/dao/entity/Course.java
  29. 140 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/CourseData.java
  30. 47 47
      server/data/src/main/java/com/qxgmat/data/dao/entity/CourseDataHistory.java
  31. 175 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/CourseExperience.java
  32. 70 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/CourseNo.java
  33. 70 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/CourseTeacher.java
  34. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/ExerciseStruct.java
  35. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserAskCourse.java
  36. 26 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserInvoice.java
  37. 111 40
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrder.java
  38. 70 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrderRecord.java
  39. 3 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseDataHistoryMapper.xml
  40. 6 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseDataMapper.xml
  41. 7 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseExperienceMapper.xml
  42. 8 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseMapper.xml
  43. 4 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseNoMapper.xml
  44. 14 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseTeacherMapper.xml
  45. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/ExerciseStructMapper.xml
  46. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserAskCourseMapper.xml
  47. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserInvoiceMapper.xml
  48. 5 14
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderMapper.xml
  49. 5 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderRecordMapper.xml
  50. 1 0
      server/data/src/main/java/com/qxgmat/data/relation/UserAskQuestionRelationMapper.java
  51. 20 0
      server/data/src/main/java/com/qxgmat/data/relation/UserFeedbackErrorRelationMapper.java
  52. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserAskCourseRelationMapper.xml
  53. 3 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserAskQuestionRelationMapper.xml
  54. 46 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserFeedbackErrorRelationMapper.xml
  55. 22 4
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java
  56. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/PreviewController.java
  57. 6 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/QuestionController.java
  58. 124 8
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  59. 45 9
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  60. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/UserExtendDto.java
  61. 49 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/UserOrderExtendDto.java
  62. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/CourseDto.java
  63. 40 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/CourseNoDto.java
  64. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/CourseTeacherDto.java
  65. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserAskCourseDto.java
  66. 0 32
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserAskOrderDto.java
  67. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserAskQuestionDto.java
  68. 37 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserInvoiceDto.java
  69. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/CourseListDto.java
  70. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserAskQuestionDetailDto.java
  71. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserAskQuestionListDto.java
  72. 15 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserInvoiceListDto.java
  73. 24 0
      server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java
  74. 0 3
      server/gateway-api/src/main/java/com/qxgmat/service/extend/TradeService.java
  75. 4 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ManagerRoleService.java
  76. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/PreviewPaperService.java
  77. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserAskQuestionService.java
  78. 17 20
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserFeedbackErrorService.java
  79. 106 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserInvoiceService.java

File diff suppressed because it is too large
+ 5 - 4
front/project/Constant.js


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

@@ -12,7 +12,6 @@ import { AskStatus, SwitchSelect, MoneyRange } from '../../../../Constant';
 import { User } from '../../../stores/user';
 import { Exercise } from '../../../stores/exercise';
 import { Course } from '../../../stores/course';
-import user from '../../user';
 
 const AskStatusMap = getMap(AskStatus, 'value', 'label');
 const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
@@ -96,10 +95,15 @@ export default class extends Page {
       dataIndex: 'user.nickname',
     }, {
       title: '承诺时间',
-      dataIndex: 'user.askTime',
+      dataIndex: 'askTime',
       render: (text, record) => {
-        const cost = (new Date().getTime() - new Date(record.createTime).getTime()) / 1000;
-        return user.askTime ? formatSeconds(user.askTime - cost) : '-';
+        const end = new Date(record.answerTime) || new Date();
+        const cost = (end.getTime() - new Date(record.createTime).getTime()) / 1000;
+        if (text) {
+          if (text - cost > 0) return `${formatSeconds(text - cost)}/${formatSeconds(text)}`;
+          return `0/${formatSeconds(text)}`;
+        }
+        return '-';
       },
     }, {
       title: '回答状态',
@@ -170,7 +174,7 @@ export default class extends Page {
   ignoreAction() {
     const { selectedKeys } = this.state;
     asyncDelConfirm('忽略确认', '是否忽略选中提问?', () => {
-      return Promise.all(selectedKeys.map(row => User.editAsk({ id: row, answerStatus: 2 }))).then(() => {
+      return Promise.all(selectedKeys.map(row => User.editAsk({ id: row, ignoreStatus: 1 }))).then(() => {
         asyncSMessage('操作成功!');
         this.refresh();
       });

+ 26 - 5
front/project/admin/routes/course/askDetail/page.js

@@ -37,11 +37,12 @@ export default class extends Page {
     }
     handler
       .then(result => {
+        result.ignoreStatus = result.answerStatus === 2;
         const { getFieldDecorator, setFieldsValue } = this.props.form;
         getFieldDecorator('id');
         getFieldDecorator('answer');
         getFieldDecorator('showStatus');
-        setFieldsValue({ id: result.id, answer: result.answer, showStatus: result.showStatus });
+        setFieldsValue({ id: result.id, answer: result.answer });
         this.setState({ data: result });
       });
   }
@@ -74,6 +75,8 @@ export default class extends Page {
     form.validateFields((err) => {
       if (!err) {
         const data = form.getFieldsValue();
+        data.showStatus = data.showStatus ? 1 : 0;
+        data.ignoreStatus = data.ignoreStatus ? 1 : 0;
         data.other = this.state.data.others.map(row => row.id);
         Course.editAsk(data).then(() => {
           asyncSMessage('保存成功');
@@ -101,7 +104,8 @@ export default class extends Page {
   }
 
   renderAsk() {
-    const { data } = this.state;
+    const { getFieldDecorator, getFieldValue } = this.props.form;
+    const { data, editContent } = this.state;
     const { user = {}, createTime, courseNo, position, originContent, content } = data;
     return <Block>
       <h1>提问信息</h1>
@@ -119,7 +123,15 @@ export default class extends Page {
           {originContent}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='提问详情'>
-          {content}
+          {!editContent && content}
+          {getFieldDecorator('content', {
+          })(
+            editContent ? <Input.TextArea placeholder='输入内容' /> : <input hidden />,
+          )}
+          <Switch checked={editContent} checkedChildren='编辑模式' unCheckedChildren='关闭' onChange={(value) => {
+            data.content = getFieldValue('content');
+            this.setState({ editContent: value, data });
+          }} />
         </Form.Item>
       </Form>
     </Block>;
@@ -138,8 +150,8 @@ export default class extends Page {
         renderItem={(item) => (
           <List.Item actions={[<Icon type='bars' className='icon' />, <Typography.Text copyable={{ text: `${UserUrl}/course/ask?askId=${item.id}` }} />]}>
             <Row style={{ width: '100%' }}>
-              <Col span={11}>问题:{item.content}</Col>
-              <Col span={11} offset={1}>答复:{item.answer}</Col>
+              <Col span={11}>问题:<span dangerouslySetInnerHTML={{ __html: item.content }} /></Col>
+              <Col span={11} offset={1}>答复:<span dangerouslySetInnerHTML={{ __html: item.answer }} /></Col>
             </Row>
           </List.Item>
         )}
@@ -173,6 +185,15 @@ export default class extends Page {
               )}
             </Form.Item>
           </Col>
+          <Col span={12}>
+            <Form.Item labelCol={{ span: 12 }} wrapperCol={{ span: 10 }} label='是否忽略'>
+              {getFieldDecorator('ignoreStatus', {
+                valuePropName: 'checked',
+              })(
+                <Switch checkedChildren='on' unCheckedChildren='off' />,
+              )}
+            </Form.Item>
+          </Col>
         </Row>
       </Form>
     </Block>;

+ 4 - 1
front/project/admin/routes/course/data/page.js

@@ -89,10 +89,13 @@ export default class extends Page {
           {(
             <Link to={`/course/data/detail/${record.id}`}>编辑</Link>
           )}
+          {(
+            <Link to={`/course/data/history/${record.id}`}>更新历史</Link>
+          )}
         </div>;
       },
     }];
-    Exercise.courseStruct().then((result) => {
+    Exercise.dataStruct().then((result) => {
       const list = result.map(row => { row.title = `${row.titleZh}`; row.value = row.id; return row; });
       this.filterForm[0].tree = formatTreeData(list, 'id', 'title', 'parentId');
       this.exerciseMap = getMap(result.map(row => {

+ 114 - 88
front/project/admin/routes/course/dataDetail/page.js

@@ -7,8 +7,8 @@ import Block from '@src/components/Block';
 import Radio from '@src/components/Radio';
 import TreeSelect from '@src/components/TreeSelect';
 import EditTableCell from '@src/components/EditTableCell';
-import ActionLayout from '@src/layouts/ActionLayout';
-import TableLayout from '@src/layouts/TableLayout';
+// import ActionLayout from '@src/layouts/ActionLayout';
+// import TableLayout from '@src/layouts/TableLayout';
 import { formatFormError, formatTreeData, getMap, formatDate } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
 import { SwitchSelect, DataType } from '../../../../Constant';
@@ -25,35 +25,41 @@ export default class extends Page {
   init() {
     this.exerciseMap = {};
     this.actionList = [{
-      key: 'add',
+      key: 'addHistory',
       type: 'primary',
-      name: '上传pdf文件',
-      render: (item) => {
-        return <Upload
-          showUploadList={false}
-          beforeUpload={(file) => System.uploadImage(file).then((result) => {
-            return Course.addDataHistory({ resource: result.url });
-          }).then(() => {
-            this.refreshHistory();
-          })}
-        >
-          <Button>{item.name}</Button>
-        </Upload>;
-      },
+      name: '新增版本',
+    }];
+    this.itemList = [{
+      key: 'id',
+      type: 'hidden',
+    }, {
+      key: 'time',
+      type: 'date',
+      name: '更新时间',
+    }, {
+      key: 'position',
+      type: 'input',
+      name: '更新位置',
+    }, {
+      key: 'originContent',
+      type: 'input',
+      name: '原内容',
+    }, {
+      key: 'content',
+      type: 'input',
+      name: '更改为',
+    }, {
+      key: 'version',
+      type: 'input',
+      name: '更新至',
     }];
     this.columns = [{
-      title: '上传时间',
-      dataIndex: 'createTime',
+      title: '更新时间',
+      dataIndex: 'time',
       render: (text) => {
         return formatDate(text);
       },
     }, {
-      title: '文件地址',
-      dataIndex: 'resource',
-      render: (text) => {
-        return <a href={text} target='_blank'>下载</a>;
-      },
-    }, {
       title: '版本名称',
       dataIndex: 'version',
       render: (text, record) => {
@@ -62,31 +68,36 @@ export default class extends Page {
         }} />;
       },
     }, {
-      title: '变更页数',
-      dataIndex: 'changePage',
-      render: (text, record) => {
-        return <EditTableCell value={text} onChange={(v) => {
-          this.changeHistory('changePage', record.id, v);
-        }} />;
-      },
+      title: '位置',
+      dataIndex: 'position',
     }, {
-      title: '原',
+      title: '原内容',
       dataIndex: 'originContent',
-      render: (text, record) => {
-        return <EditTableCell value={text} onChange={(v) => {
-          this.changeHistory('originContent', record.id, v);
-        }} />;
-      },
     }, {
       title: '更正为',
       dataIndex: 'content',
+    }, {
+      title: '更新至',
+      dataIndex: 'version',
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
       render: (text, record) => {
-        return <EditTableCell value={text} onChange={(v) => {
-          this.changeHistory('content', record.id, v);
-        }} />;
+        return <div className="table-button">
+          {(
+            <a onClick={() => {
+              this.changeHistory(record);
+            }}>编辑</a>
+          )}
+          {(
+            <a onClick={() => {
+              this.deleteHistory(record);
+            }}>删除</a>
+          )}
+        </div>;
       },
     }];
-    Exercise.courseStruct().then((result) => {
+    Exercise.dataStruct().then((result) => {
       const list = result.map(row => { row.title = `${row.titleZh}`; row.value = row.id; return row; });
       const tree = formatTreeData(list, 'id', 'title', 'parentId');
       this.exerciseMap = getMap(result.map(row => {
@@ -110,40 +121,12 @@ export default class extends Page {
     handler
       .then(result => {
         const { setFieldsValue } = this.props.form;
-        result.structId = `${result.structId}`;
-        // getFieldDecorator('id');
-        // getFieldDecorator('structId');
-        // getFieldDecorator('dataType');
-        // getFieldDecorator('isNovice');
-        // getFieldDecorator('isOriginal');
-        // getFieldDecorator('title');
-        // getFieldDecorator('price');
-        // getFieldDecorator('pages');
-        // getFieldDecorator('content');
-        // getFieldDecorator('content');
-        // getFieldDecorator('authorContent');
-        // getFieldDecorator('methondContent');
+        result.structId = `${result.structId || ' '}`;
         setFieldsValue(result);
         this.setState({ data: result });
-        if (result.dataType === 'electron') {
-          this.refreshHistory();
-        }
       });
   }
 
-  refreshHistory() {
-    const { id } = this.params;
-    Course.listDataHistory({ dataId: id }).then(result => {
-      this.setState({ list: result.list, total: result.total, history: true });
-    });
-  }
-
-  changeHistory(field, id, value) {
-    Course.editDataHistory({ id, [field]: value }).then(() => {
-      this.refreshHistory();
-    });
-  }
-
   submit() {
     const { form } = this.props;
     form.validateFields((err) => {
@@ -285,27 +268,71 @@ export default class extends Page {
             <Input placeholder='请输入' />,
           )}
         </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='试用页码'>
+          <Row>
+            <Col span={3}>
+              <Form.Item>
+                {getFieldDecorator('trailStart')(
+                  <InputNumber min={1} precision={0} formatter={(v) => parseInt(v, 10) || 1} />,
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={1}>至</Col>
+            <Col span={3}>
+              <Form.Item>
+                {getFieldDecorator('trailEnd')(
+                  <InputNumber min={1} precision={0} formatter={(v) => parseInt(v, 10) || 1} />,
+                )}
+              </Form.Item>
+            </Col>
+          </Row>
+        </Form.Item>
       </Form>
     </Block>;
   }
 
-  renderHistory() {
+  renderFile() {
+    const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
+    const resource = getFieldValue('resource');
+    const trailResource = getFieldValue('trailResource');
     return <Block>
-      <h1>资料版本</h1>
-      <ActionLayout
-        itemList={this.actionList}
-        selectedKeys={this.state.selectedKeys}
-        onAction={key => this.onAction(key)}
-      />
-      <TableLayout
-        columns={this.columns}
-        list={this.state.list}
-        pagination={false}
-        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}
-      />
+      <h1>资料文件</h1>
+      <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='正式版本'>
+        {resource && <a href={resource} target="_blank">访问</a>}
+        {getFieldDecorator('resource', {
+          rules: [
+            { required: true, message: '上传文件' },
+          ],
+        })(
+          <Upload
+            showUploadList={false}
+            beforeUpload={(file) => System.uploadImage(file).then((result) => {
+              setFieldsValue({ resource: result.url });
+              return Promise.reject();
+            })}
+          >
+            <Button>上传文件</Button>
+          </Upload>,
+        )}
+      </Form.Item>
+      <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='试用版本'>
+        {trailResource && <a href={trailResource} target="_blank">访问</a>}
+        {getFieldDecorator('trailResource', {
+          rules: [
+            { required: true, message: '上传文件' },
+          ],
+        })(
+          <Upload
+            showUploadList={false}
+            beforeUpload={(file) => System.uploadImage(file).then((result) => {
+              setFieldsValue({ trailResource: result.url });
+              return Promise.reject();
+            })}
+          >
+            <Button>上传文件</Button>
+          </Upload>,
+        )}
+      </Form.Item>
     </Block>;
   }
 
@@ -352,11 +379,10 @@ export default class extends Page {
   }
 
   renderView() {
-    const { history } = this.state;
     return <div flex>
       {this.renderBase()}
       {this.renderInfo()}
-      {history && this.renderHistory()}
+      {this.renderFile()}
       {this.renderContent()}
       {this.renderAuthor()}
       {this.renderMethod()}

+ 16 - 0
front/project/admin/routes/course/dataHistory/index.js

@@ -0,0 +1,16 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/course/data/history',
+  matchPath: '/course/data/history/:id',
+  key: 'course-data-history',
+  title: '资料上传',
+  needLogin: true,
+  module,
+  group,
+  showKey: 'course-data',
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/admin/routes/course/dataHistory/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#course-data-history {}

+ 229 - 0
front/project/admin/routes/course/dataHistory/page.js

@@ -0,0 +1,229 @@
+import React from 'react';
+import { Form, Button, Row, Col, Upload } from 'antd';
+import './index.less';
+// import Editor from '@src/components/Editor';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+// import Radio from '@src/components/Radio';
+// import TreeSelect from '@src/components/TreeSelect';
+// import EditTableCell from '@src/components/EditTableCell';
+import ActionLayout from '@src/layouts/ActionLayout';
+import TableLayout from '@src/layouts/TableLayout';
+import { formatDate } from '@src/services/Tools';
+import { asyncSMessage, asyncForm, asyncDelConfirm } from '@src/services/AsyncTools';
+// import { SwitchSelect, DataType } from '../../../../Constant';
+// import { User } from '../../../stores/user';
+// import { Exercise } from '../../../stores/exercise';
+import { Course } from '../../../stores/course';
+import { System } from '../../../stores/system';
+
+export default class extends Page {
+  initState() {
+    return { history: false };
+  }
+
+  init() {
+    this.exerciseMap = {};
+    this.actionList = [{
+      key: 'addHistory',
+      type: 'primary',
+      name: '新增版本',
+    }];
+    this.itemList = [{
+      key: 'id',
+      type: 'hidden',
+    }, {
+      key: 'time',
+      type: 'date',
+      name: '更新时间',
+    }, {
+      key: 'position',
+      type: 'input',
+      name: '更新位置',
+    }, {
+      key: 'originContent',
+      type: 'input',
+      name: '原内容',
+    }, {
+      key: 'content',
+      type: 'input',
+      name: '更改为',
+    }, {
+      key: 'version',
+      type: 'input',
+      name: '更新至',
+    }];
+    this.columns = [{
+      title: '更新时间',
+      dataIndex: 'time',
+      render: (text) => {
+        return formatDate(text);
+      },
+    }, {
+      title: '版本名称',
+      dataIndex: 'version',
+    }, {
+      title: '位置',
+      dataIndex: 'position',
+    }, {
+      title: '原内容',
+      dataIndex: 'originContent',
+    }, {
+      title: '更正为',
+      dataIndex: 'content',
+    }, {
+      title: '更新至',
+      dataIndex: 'version',
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          {(
+            <a onClick={() => {
+              this.changeHistory(record);
+            }}>编辑</a>
+          )}
+          {(
+            <a onClick={() => {
+              this.deleteHistory(record);
+            }}>删除</a>
+          )}
+        </div>;
+      },
+    }];
+  }
+
+  initData() {
+    const { id } = this.params;
+    let handler;
+    if (id) {
+      handler = Course.getData({ id });
+    } else {
+      handler = Promise.resolve({ structId: 0 });
+    }
+    handler
+      .then(result => {
+        const { setFieldsValue } = this.props.form;
+        setFieldsValue(result);
+        this.setState({ data: result });
+        this.refreshHistory();
+      });
+  }
+
+  refreshHistory() {
+    const { id } = this.params;
+    Course.listDataHistory({ dataId: id }).then(result => {
+      this.setState({ list: result.list, total: result.total, history: true });
+    });
+  }
+
+  addHistory() {
+    asyncForm('创建', this.itemList, {}, data => {
+      return Course.addDataHistory(data).then(() => {
+        asyncSMessage('添加成功!');
+        this.refreshHistory();
+      });
+    });
+  }
+
+  changeHistory(record) {
+    asyncForm('修改', this.itemList, record, data => {
+      return Course.editDataHistory(data).then(() => {
+        asyncSMessage('修改成功!');
+        this.refreshHistory();
+      });
+    });
+  }
+
+  deleteHistory(record) {
+    asyncDelConfirm('删除确认', '是否删除选中记录?', () => {
+      return Course.delDataHistory(record).then(() => {
+        asyncSMessage('删除成功!');
+        this.refreshHistory();
+      });
+    });
+  }
+
+  renderFile() {
+    const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
+    const resource = getFieldValue('resource');
+    const trailResource = getFieldValue('trailResource');
+    return <Block>
+      <h1>资料文件</h1>
+      <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='正式版本'>
+        {resource && <a href={resource} target="_blank">访问</a>}
+        {getFieldDecorator('resource', {
+          rules: [
+            { required: true, message: '上传文件' },
+          ],
+        })(
+          <Upload
+            showUploadList={false}
+            beforeUpload={(file) => System.uploadImage(file).then((result) => {
+              setFieldsValue({ resource: result.url });
+              return Promise.reject();
+            })}
+          >
+            <Button>上传文件</Button>
+          </Upload>,
+        )}
+      </Form.Item>
+      <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='试用版本'>
+        {trailResource && <a href={trailResource} target="_blank">访问</a>}
+        {getFieldDecorator('trailResource', {
+          rules: [
+            { required: true, message: '上传文件' },
+          ],
+        })(
+          <Upload
+            showUploadList={false}
+            beforeUpload={(file) => System.uploadImage(file).then((result) => {
+              setFieldsValue({ trailResource: result.url });
+              return Promise.reject();
+            })}
+          >
+            <Button>上传文件</Button>
+          </Upload>,
+        )}
+      </Form.Item>
+    </Block>;
+  }
+
+  renderHistory() {
+    return <Block>
+      <h1>资料版本</h1>
+      <ActionLayout
+        itemList={this.actionList}
+        selectedKeys={this.state.selectedKeys}
+        onAction={key => this.onAction(key)}
+      />
+      <TableLayout
+        columns={this.columns}
+        list={this.state.list}
+        pagination={false}
+        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}
+      />
+    </Block>;
+  }
+
+  renderView() {
+    const { history } = this.state;
+    return <div flex>
+      {this.renderBase()}
+      {this.renderFile()}
+      {history && this.renderHistory()}
+
+      <Row type="flex" justify="center">
+        <Col>
+          <Button type="primary" onClick={() => {
+            this.submit();
+          }}>保存</Button>
+        </Col>
+      </Row>
+    </div>;
+  }
+}

+ 45 - 9
front/project/admin/routes/course/detail/page.js

@@ -1,12 +1,13 @@
 import React from 'react';
 import { Link } from 'react-router-dom';
 import moment from 'moment';
-import { Form, Input, Button, Row, Col, InputNumber, Upload, DatePicker } from 'antd';
+import { Form, Input, Button, Row, Col, InputNumber, Upload, DatePicker, Checkbox } from 'antd';
 import './index.less';
 import Editor from '@src/components/Editor';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import TreeSelect from '@src/components/TreeSelect';
+import Select from '@src/components/Select';
 import EditTableCell from '@src/components/EditTableCell';
 import Radio from '@src/components/Radio';
 import ActionLayout from '@src/layouts/ActionLayout';
@@ -14,7 +15,7 @@ import TableLayout from '@src/layouts/TableLayout';
 // import FileUpload from '@src/components/FileUpload';
 import { formatFormError, formatSeconds, formatTreeData, getMap } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
-import { CrowdList, CourseStatus } from '../../../../Constant';
+import { CrowdList, CourseStatus, CourseVideoType } from '../../../../Constant';
 import { Course } from '../../../stores/course';
 import { System } from '../../../stores/system';
 import { Exercise } from '../../../stores/exercise';
@@ -66,12 +67,28 @@ export default class extends Page {
         return <a href={text} target='_blank'>访问</a>;
       },
     }, {
-      title: '试用区间',
-      dataIndex: 'originContent',
+      title: '试用视频',
+      dataIndex: 'isTrail',
       render: (text, record) => {
-        return <EditTableCell value={text} onChange={(v) => {
-          this.changeNo('originContent', record.id, v);
-        }} />;
+        return <div>
+          <Checkbox checked={!!text} onChange={(v) => {
+            this.changeNo('isTrail', record.id, v.target.checked ? 1 : 0);
+          }} />
+          {text > 0 && <InputNumber value={record.startTrail} onChange={(v) => {
+            this.changeNo('startTrail', record.id, v);
+          }} />}
+          {text > 0 && <InputNumber value={record.endTrail} onChange={(v) => {
+            this.changeNo('endTrail', record.id, v);
+          }} />}
+          {text > 0 && <Upload
+            showUploadList={false}
+            beforeUpload={(file) => System.uploadVideo(file).then((result) => {
+              return this.changeNo('trailResource', record.id, result.url);
+            })}
+          >
+            <Button>上传视频{record.trailResource ? '(已上传)' : ''}</Button>
+          </Upload>}
+        </div>;
       },
     }, {
       title: '操作',
@@ -115,8 +132,18 @@ export default class extends Page {
     }, {
       title: '状态',
       dataIndex: 'status',
-      render: (text) => {
-        return CourseStatusMap[text] || text;
+      render: (text, record) => {
+        let status = 0;
+        const start = new Date(record.startTime);
+        const end = new Date(record.endTime);
+        if (new Date().getTime() > start.getTime()) {
+          status = 1;
+          if (new Date().getTime() > end.getTime()) {
+            status = 2;
+          }
+        }
+
+        return CourseStatusMap[status] || status;
       },
     }];
 
@@ -247,6 +274,15 @@ export default class extends Page {
             <TreeSelect treeData={exercise} />,
           )}
         </Form.Item>}
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='类型'>
+          {getFieldDecorator('videoType', {
+            rules: [
+              { required: true, message: '请选择' },
+            ],
+          })(
+            <Select select={CourseVideoType} />,
+          )}
+        </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='适合人群'>
           {getFieldDecorator('crowd', {
             rules: [

+ 5 - 2
front/project/admin/routes/course/experience/page.js

@@ -55,12 +55,15 @@ export default class extends Page {
         return (text || {}).nickname;
       },
     }, {
-      title: '录入时间',
-      dataIndex: 'createTime',
+      title: '更新时间',
+      dataIndex: 'updateTime',
       render: (text) => {
         return formatDate(text);
       },
     }, {
+      title: '阅读数',
+      dataIndex: 'viewNumber',
+    }, {
       title: '收藏数',
       dataIndex: 'collectNumber',
     }, {

+ 47 - 0
front/project/admin/routes/course/experienceDetail/page.js

@@ -8,6 +8,7 @@ import Select from '@src/components/Select';
 // import FileUpload from '@src/components/FileUpload';
 import { formatFormError, generateSearch } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
+import { PrepareStatus, PrepareExaminationTime, ExperienceScore, ExperiencePercent } from '../../../../Constant';
 import { Course } from '../../../stores/course';
 import { User } from '../../../stores/user';
 
@@ -80,6 +81,52 @@ export default class extends Page {
             <Select {...this.state.userId} placeholder='请选择作者' />,
           )}
         </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='作者信息'>
+          <Row>
+            <Col span={6}>
+              {getFieldDecorator('prepareStatus', {
+                rules: [{
+                  required: true, message: '请选择',
+                }],
+              })(
+                <Select select={PrepareStatus} placeholder='身份' />,
+              )}
+            </Col>
+            <Col span={6}>
+              {getFieldDecorator('prepareExaminationTime', {
+                rules: [{
+                  required: true, message: '请选择',
+                }],
+              })(
+                <Select select={PrepareExaminationTime} placeholder='备考周期' />,
+              )}
+            </Col>
+            <Col span={6}>
+              {getFieldDecorator('experienceScore', {
+                rules: [{
+                  required: true, message: '请选择',
+                }],
+              })(
+                <Select select={ExperienceScore} placeholder='分手成绩' />,
+              )}
+            </Col>
+            <Col span={6}>
+              {getFieldDecorator('experiencePercent', {
+                rules: [{
+                  required: true, message: '请选择',
+                }],
+              })(
+                <Select select={ExperiencePercent} placeholder='提分范围' />,
+              )}
+            </Col>
+          </Row>
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='问卷链接'>
+          {getFieldDecorator('link', {
+          })(
+            <Input placeholder='输入内容' />,
+          )}
+        </Form.Item>
         <Form.Item label='正文'>
           {getFieldDecorator('content', {
           })(

+ 143 - 1
front/project/admin/routes/course/invoice/page.js

@@ -1,12 +1,154 @@
 import React from 'react';
 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, formatDate, bindSearch } from '@src/services/Tools';
+import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
+import { SwitchSelect, InvoiceType } from '../../../../Constant';
+import { User } from '../../../stores/user';
+
+const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
+const InvoiceTypeMap = getMap(InvoiceType, 'value', 'label');
 
 export default class extends Page {
   init() {
+    this.exerciseMap = {};
+    this.filterForm = [
+      {
+        key: 'userId',
+        type: 'tree',
+        allowClear: true,
+        tree: [],
+        name: '用户',
+        placeholder: '用户',
+      }, {
+        key: 'isDownload',
+        type: 'select',
+        allowClear: true,
+        name: '下载状态',
+        select: SwitchSelect,
+      }, {
+        key: 'isFinish',
+        type: 'select',
+        allowClear: true,
+        name: '开票状态',
+        select: SwitchSelect,
+      },
+    ];
+    this.actionList = [{
+      key: 'download',
+      name: '下载',
+    }, {
+      key: 'finish',
+      name: '批量开票',
+    }];
+    this.columns = [{
+      title: '申请时间',
+      dataIndex: 'createTime',
+      render: (text) => {
+        return formatDate(text);
+      },
+    }, {
+      title: '申请用户',
+      dataIndex: 'userId',
+      render: (text, record) => {
+        return `${record.user.nickname}`;
+      },
+    }, {
+      title: '发票金额',
+      dataIndex: 'order.invoiceMoney',
+    }, {
+      title: '抬头类型',
+      dataIndex: 'invoiceType',
+      render: (text) => {
+        return InvoiceTypeMap[text] || text;
+      },
+    }, {
+      title: '抬头',
+      dataIndex: 'title',
+    }, {
+      title: '纳税人识别号',
+      dataIndex: 'identity',
+    }, {
+      title: '邮箱',
+      dataIndex: 'user.email',
+    }, {
+      title: '开票状态',
+      dataIndex: 'isFinish',
+      render: (text) => {
+        return SwitchSelectMap[text ? 1 : 0];
+      },
+    }, {
+      title: '下载状态',
+      dataIndex: 'isDownload',
+      render: (text) => {
+        return SwitchSelectMap[text ? 1 : 0];
+      },
+    }];
+
+    bindSearch(this.filterForm, 'userId', this, (search) => {
+      return User.list(search);
+    }, (row) => {
+      return {
+        title: `${row.nickname}(${row.mobile})`,
+        value: row.id,
+      };
+    }, this.state.search.userId ? Number(this.state.search.userId) : [], null);
+  }
+
+  initData() {
+    User.listInvoice(this.state.search).then(result => {
+      this.setTableData(result.list, result.total);
+    });
+  }
+
+  downloadAction() {
+    const { selectedKeys } = this.state;
+    asyncDelConfirm('下载确认', '是否下载选中记录?', () => {
+      return Promise.all(selectedKeys.map(row => User.editInvoice({ id: row, isFinish: 1 }))).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
+    });
+  }
+
+  finishAction() {
+    const { selectedKeys } = this.state;
+    asyncDelConfirm('开票确认', '是否开票选中记录?', () => {
+      return User.editInvoice({ ids: selectedKeys }).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
+    });
   }
 
   renderView() {
-    return <div />;
+    return <Block flex>
+      {<FilterLayout
+        show
+        itemList={this.filterForm}
+        data={this.state.search}
+        onChange={data => {
+          this.search(data);
+        }} />}
+      <ActionLayout
+        itemList={this.actionList}
+        selectedKeys={this.state.selectedKeys}
+        onAction={key => this.onAction(key)}
+      />
+      <TableLayout
+        select
+        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}
+      />
+    </Block>;
   }
 }

+ 12 - 6
front/project/admin/routes/course/list/page.js

@@ -9,12 +9,13 @@ import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
 import { formatTreeData, getMap } from '@src/services/Tools';
 import { asyncSMessage } from '@src/services/AsyncTools';
-import { CourseModule } from '../../../../Constant';
+import { CourseModule, CourseVideoType } from '../../../../Constant';
 import { System } from '../../../stores/system';
 import { Course } from '../../../stores/course';
 import { Exercise } from '../../../stores/exercise';
 
 const CourseModuleMap = getMap(CourseModule, 'value', 'label');
+const CourseVideoTypeMap = getMap(CourseVideoType, 'value', 'label');
 
 export default class extends Page {
   init() {
@@ -28,11 +29,11 @@ export default class extends Page {
         name: '学科',
         placeholder: '标题或正文',
       }, {
-        key: 'type',
+        key: 'courseModule',
         type: 'select',
-        name: '类',
+        name: '课程种类',
         allowClear: true,
-        select: [],
+        select: CourseModule,
         placeholder: '请输入',
       },
     ];
@@ -75,7 +76,10 @@ export default class extends Page {
       },
     }, {
       title: '类型',
-      dataIndex: 'type',
+      dataIndex: 'videoType',
+      render: (text) => {
+        return CourseVideoTypeMap[text] || text;
+      },
     }, {
       title: '课程名称',
       dataIndex: 'title',
@@ -88,6 +92,9 @@ export default class extends Page {
     }, {
       title: '购买数量(含套餐)',
       dataIndex: 'saleNumber',
+      render: (text, record) => {
+        return text + record.packageSaleNumber;
+      },
     }, {
       title: '操作',
       dataIndex: 'handler',
@@ -163,7 +170,6 @@ export default class extends Page {
         onAction={key => this.onAction(key)}
       />
       <TableLayout
-        select
         columns={this.columns}
         list={this.state.list}
         pagination={this.state.page}

+ 45 - 22
front/project/admin/routes/course/student/page.js

@@ -8,16 +8,24 @@ import ActionLayout from '@src/layouts/ActionLayout';
 import TableLayout from '@src/layouts/TableLayout';
 import { formatDate, bindSearch, getMap } from '@src/services/Tools';
 import { asyncSMessage, asyncForm } from '@src/services/AsyncTools';
-import { CourseSource, CourseStatus } from '../../../../Constant';
+import { CourseSource, CourseStatus, CourseVsType } from '../../../../Constant';
 import { Course } from '../../../stores/course';
 import { User } from '../../../stores/user';
 
 const CourseSourceMap = getMap(CourseSource, 'value', 'label');
 const CourseStatusMap = getMap(CourseStatus, 'value', 'label');
+const CourseVsTypeMap = getMap(CourseVsType, 'value', 'label');
 
 export default class extends Page {
   init() {
     this.onlineAction = [{
+      key: 'info',
+      name: '课程',
+      render: () => {
+        const { data } = this.state;
+        return `${data.title}`;
+      },
+    }, {
       key: 'addOnlineStudent',
       name: '添加学员',
     }];
@@ -82,6 +90,13 @@ export default class extends Page {
     }];
 
     this.vsAction = [{
+      key: 'info',
+      name: '课程',
+      render: () => {
+        const { data } = this.state;
+        return `${data.title}(${CourseVsTypeMap[data.vsType]})`;
+      },
+    }, {
       key: 'addVsStudent',
       name: '添加学员',
     }];
@@ -95,10 +110,10 @@ export default class extends Page {
       select: [],
       number: true,
       placeholder: '请输入',
-    }, {
-      key: 'time',
-      type: 'daterange',
-      name: '有效期',
+      // }, {
+      //   key: 'time',
+      //   type: 'daterange',
+      //   name: '有效期',
     }, {
       key: 'teacherId',
       type: 'select',
@@ -141,16 +156,16 @@ export default class extends Page {
         }
         return CourseStatusMap[status] || status;
       },
-    }, {
-      title: '操作',
-      dataIndex: 'handler',
-      render: (text, record) => {
-        return <div className="table-button">
-          {<a onClick={() => {
-            this.changeVs(record);
-          }}>修改</a>}
-        </div>;
-      },
+      // }, {
+      //   title: '操作',
+      //   dataIndex: 'handler',
+      //   render: (text, record) => {
+      //     return <div className="table-button">
+      //       {<a onClick={() => {
+      //         this.changeVs(record);
+      //       }}>修改</a>}
+      //     </div>;
+      //   },
     }];
   }
 
@@ -253,9 +268,12 @@ export default class extends Page {
 
   changeVs(record) {
     const { id } = this.params;
+    const { data } = this.state;
     record.time = [moment(record.useStartTime), moment(record.useEndTime)];
-    asyncForm('修改', this.vsList, record, data => {
-      return Course.editStudentVs(Object.assign({ courseId: id }, data)).then(() => {
+    this.vsList[4].type = data.vsType === 'novice' ? 'hidden' : 'number';
+    asyncForm('修改', this.vsList, record, info => {
+      // ([info.useStartTime, info.useEndTime] = info.time);
+      return Course.editStudentVs(Object.assign({ courseId: id }, info)).then(() => {
         asyncSMessage('添加成功!');
         this.refresh();
       });
@@ -281,9 +299,15 @@ export default class extends Page {
 
   addVsStudentAction() {
     const { id } = this.params;
-    asyncForm('添加', this.vsList, {}, data => {
-      ([data.useStartTime, data.useEndTime] = data.time);
-      return Course.addStudentVs(Object.assign({ courseId: id }, data)).then(() => {
+    const { data } = this.state;
+    this.vsList[4].type = data.vsType === 'novice' ? 'hidden' : 'number';
+    asyncForm('添加', this.vsList, {}, info => {
+      if (data.vsType === 'novice') {
+        // 写死:新手每次1课时
+        info.vsNumber = 1;
+      }
+      // ([info.useStartTime, info.useEndTime] = info.time);
+      return Course.addStudentVs(Object.assign({ courseId: id }, info)).then(() => {
         asyncSMessage('添加成功!');
         this.refresh();
       });
@@ -334,9 +358,8 @@ export default class extends Page {
   }
 
   renderVs() {
-    const { data } = this.state;
     return <Block flex>
-      {data.vsType === 'novice' && <ActionLayout
+      {<ActionLayout
         itemList={this.vsAction}
         selectedKeys={this.state.selectedKeys}
         onAction={key => this.onAction(key)}

+ 3 - 2
front/project/admin/routes/course/studyDetail/page.js

@@ -58,6 +58,7 @@ export default class extends Page {
     }, {
       key: 'timerange',
       type: 'daterange',
+      showTime: true,
       name: '上课时间',
     }, {
       key: 'channel',
@@ -75,7 +76,7 @@ export default class extends Page {
       title: '上课时间',
       dataIndex: 'time',
       render: (text, record) => {
-        return <DatePicker.RangePicker value={[record.startTime, record.endTime]} onChange={(value) => {
+        return <DatePicker.RangePicker showTime value={[record.startTime, record.endTime]} onChange={(value) => {
           this.changeAppointment(record.id, { startTime: value[0], endTime: value[1] });
         }} />;
       },
@@ -290,7 +291,7 @@ export default class extends Page {
             >
               <Button>上传文档{(this.state.supplyInfo || {}).url ? '(已上传)' : ''}</Button>
             </Upload></Col>
-            <Col span={1} offset={10}><Button type='primary' onClick={() => {
+            <Col span={1} offset={8}><Button type='primary' onClick={() => {
               if (!this.state.supplyInfo || !this.state.supplyInfo.url) return;
               let { supplyList = [] } = this.state.supply;
               supplyList = supplyList || [];

+ 21 - 1
front/project/admin/routes/course/vsDetail/page.js

@@ -186,7 +186,7 @@ export default class extends Page {
             <Editor placeholder='输入内容' />,
           )}
         </Form.Item>
-        <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='微信头像'>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label='微信头像'>
           {getFieldDecorator('wechatAvatar')(
             <Upload
               listType="picture-card"
@@ -237,6 +237,21 @@ export default class extends Page {
                   this.changeTeacher('wechat', index, e.target.value);
                 }} />
               </Form.Item>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label='头像'>
+                <Upload
+                  listType="picture-card"
+                  showUploadList={false}
+                  beforeUpload={(file) => System.uploadImage(file).then((result) => {
+                    this.changeTeacher('avatar', index, result.url);
+                    return Promise.reject();
+                  })}
+                >
+                  {row.avatar ? <img src={row.avatar} alt="avatar" /> : <div>
+                    <Icon type={this.state.loading ? 'loading' : 'plus'} />
+                    <div className="ant-upload-text">Upload</div>
+                  </div>}
+                </Upload>
+              </Form.Item>
               <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label='二维码'>
                 <Upload
                   listType="picture-card"
@@ -252,6 +267,11 @@ export default class extends Page {
                   </div>}
                 </Upload>
               </Form.Item>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label='简介'>
+                <Input.TextArea placeholder='请输入简介' value={row.description} onChange={(e) => {
+                  this.changeTeacher('description', index, e.target.value);
+                }} />
+              </Form.Item>
               <Button onClick={() => {
                 let handler = null;
                 if (row.id) {

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

@@ -103,7 +103,10 @@ export default class extends Page {
         title: '用户',
         dataIndex: 'user',
         render: (text, record) => {
-          return text ? text.nickname : record.nickname;
+          let extend = '';
+          if (record.isSystem) extend = '系统创建';
+          else if (!record.userId) extend = '未注册';
+          return `${text.nickname || record.nickname}${extend ? `(${extend})` : ''}`;
         },
       }, {
         title: '时间',

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

@@ -121,9 +121,10 @@ export default class extends Page {
         title: '提问者',
         dataIndex: 'user',
         render: (text, record) => {
-          if (record.isSystem) return '系统创建';
-          if (!record.userId) return '未注册';
-          return text ? text.nickname : '';
+          let extend = '';
+          if (record.isSystem) extend = '系统创建';
+          else if (!record.userId) extend = '未注册';
+          return `${text.nickname || record.nickname}${extend ? `(${extend})` : ''}`;
         },
       },
       {

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

@@ -157,7 +157,7 @@ export default class extends Page {
   ignoreAction() {
     const { selectedKeys } = this.state;
     asyncDelConfirm('忽略确认', '是否忽略选中提问?', () => {
-      return Promise.all(selectedKeys.map(row => Question.editAsk({ id: row, answerStatus: 2 }))).then(() => {
+      return Promise.all(selectedKeys.map(row => Question.editAsk({ id: row, ignoreStatus: 1 }))).then(() => {
         asyncSMessage('操作成功!');
         this.refresh();
       });

+ 27 - 6
front/project/admin/routes/user/askDetail/page.js

@@ -1,7 +1,7 @@
 import React from 'react';
 import { Form, Button, Row, Col, List, Icon, Switch, Typography, Input } from 'antd';
 import './index.less';
-// import Editor from '@src/components/Editor';
+import Editor from '@src/components/Editor';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
 import DragList from '@src/components/DragList';
@@ -32,6 +32,7 @@ export default class extends Page {
     }
     handler
       .then(result => {
+        result.ignoreStatus = result.answerStatus === 2;
         const { getFieldDecorator, setFieldsValue } = this.props.form;
         getFieldDecorator('id');
         getFieldDecorator('answer');
@@ -69,6 +70,8 @@ export default class extends Page {
     form.validateFields((err) => {
       if (!err) {
         const data = form.getFieldsValue();
+        data.showStatus = data.showStatus ? 1 : 0;
+        data.ignoreStatus = data.ignoreStatus ? 1 : 0;
         data.other = this.state.data.others.map(row => row.id);
         Question.editAsk(data).then(() => {
           asyncSMessage('保存成功');
@@ -96,7 +99,8 @@ export default class extends Page {
   }
 
   renderAsk() {
-    const { data } = this.state;
+    const { getFieldDecorator, getFieldValue } = this.props.form;
+    const { data, editContent } = this.state;
     const { user = {}, createTime, target, originContent, content } = data;
     return <Block>
       <h1>提问信息</h1>
@@ -114,7 +118,15 @@ export default class extends Page {
           {originContent}
         </Form.Item>
         <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='提问详情'>
-          {content}
+          {!editContent && content}
+          {getFieldDecorator('content', {
+          })(
+            editContent ? <Input.TextArea placeholder='输入内容' /> : <input hidden />,
+          )}
+          <Switch checked={editContent} checkedChildren='编辑模式' unCheckedChildren='关闭' onChange={(value) => {
+            data.content = getFieldValue('content');
+            this.setState({ editContent: value, data });
+          }} />
         </Form.Item>
       </Form>
     </Block>;
@@ -133,8 +145,8 @@ export default class extends Page {
         renderItem={(item) => (
           <List.Item actions={[<Icon type='bars' className='icon' />, <Typography.Text copyable={{ text: `${UserUrl}/paper/question/${this.state.data.userQuestionId}?askId=${item.id}` }} />]}>
             <Row style={{ width: '100%' }}>
-              <Col span={11}>问题:{item.content}</Col>
-              <Col span={11} offset={1}>答复:{item.answer}</Col>
+              <Col span={11}>问题:<span dangerouslySetInnerHTML={{ __html: item.content }} /></Col>
+              <Col span={11} offset={1}>答复:<span dangerouslySetInnerHTML={{ __html: item.answer }} /></Col>
             </Row>
           </List.Item>
         )}
@@ -149,7 +161,7 @@ export default class extends Page {
         <Form.Item label='教师回复'>
           {getFieldDecorator('answer', {
           })(
-            <Input.TextArea placeholder='输入内容' />,
+            <Editor placeholder='输入内容' />,
           )}
         </Form.Item>
         <Row type="flex" justify="center">
@@ -168,6 +180,15 @@ export default class extends Page {
               )}
             </Form.Item>
           </Col>
+          <Col span={12}>
+            <Form.Item labelCol={{ span: 12 }} wrapperCol={{ span: 10 }} label='是否忽略'>
+              {getFieldDecorator('ignoreStatus', {
+                valuePropName: 'checked',
+              })(
+                <Switch checkedChildren='on' unCheckedChildren='off' />,
+              )}
+            </Form.Item>
+          </Col>
         </Row>
       </Form>
     </Block>;

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

@@ -1,24 +1,31 @@
 import React from 'react';
 import { Modal, Button } from 'antd';
+import { Link } from 'react-router-dom';
 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, formatDate } from '@src/services/Tools';
+import { getMap, formatDate, bindSearch } from '@src/services/Tools';
 import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
-import { FeedbackStatus, FeedbackModule } from '../../../../Constant';
+import { FeedbackStatus, FeedbackModule, MoneyRange, AskTarget } from '../../../../Constant';
 import { User } from '../../../stores/user';
 
 const FeedbackStatusMap = getMap(FeedbackStatus, 'value', 'label');
 const FeedbackModuleMap = getMap(FeedbackModule, 'value', 'label');
+const AskTargetMap = getMap(AskTarget, 'value', 'label');
 export default class extends Page {
   init() {
     this.actionList = [{
       key: 'handle',
       type: 'danger',
-      name: '批量处理',
+      name: '批量修改',
+      needSelect: 1,
+    }, {
+      key: 'nohandle',
+      type: 'danger',
+      name: '批量不修改',
       needSelect: 1,
     }, {
       key: 'ignore',
@@ -41,11 +48,25 @@ export default class extends Page {
       number: true,
       select: FeedbackStatus,
     }, {
-      key: 'title',
+      key: 'userId',
+      type: 'select',
+      name: '用户',
+      allowClear: true,
+      select: [],
+      number: true,
+      placeholder: '请输入',
+    }, {
+      key: 'money',
+      type: 'select',
+      allowClear: true,
+      name: '消费金额',
+      select: MoneyRange,
+      number: true,
+    }, {
+      key: 'keyword',
       type: 'input',
-      name: '材料名称',
       allowClear: true,
-      placeholder: '输入名称搜索',
+      name: '勘误名称',
     }];
     this.columns = [
       {
@@ -67,7 +88,11 @@ export default class extends Page {
         dataIndex: 'user.nickname',
       },
       {
-        title: '材料名称',
+        title: '消费金额',
+        dataIndex: 'user.totalMoney',
+      },
+      {
+        title: '勘误对象',
         dataIndex: 'title',
       }, {
         title: '处理状态',
@@ -89,6 +114,14 @@ export default class extends Page {
         },
       },
     ];
+    bindSearch(this.filterForm, 'userId', this, (search) => {
+      return User.list(search);
+    }, (row) => {
+      return {
+        title: `${row.nickname}(${row.mobile})`,
+        value: row.id,
+      };
+    }, this.state.search.userId ? Number(this.state.search.userId) : null, null);
   }
 
   initData() {
@@ -116,6 +149,17 @@ export default class extends Page {
     });
   }
 
+  nohandleDetail() {
+    const { detail } = this.state;
+    asyncDelConfirm('不处理确认', '是否不处理选中记录?', () => {
+      return User.editFeedbackError({ id: detail.id, status: 3 }).then(() => {
+        asyncSMessage('操作成功!');
+        this.setState({ detail: null });
+        this.refresh();
+      });
+    });
+  }
+
   ignoreDetail() {
     const { detail } = this.state;
     asyncDelConfirm('忽略确认', '是否忽略选中记录?', () => {
@@ -129,7 +173,7 @@ export default class extends Page {
 
   handleAction() {
     const { selectedKeys } = this.state;
-    asyncDelConfirm('处理确认', '是否处理选中记录?', () => {
+    asyncDelConfirm('修改确认', '是否修改选中记录?', () => {
       return Promise.all(selectedKeys.map(row => User.editFeedbackError({ id: row, status: 1 }))).then(() => {
         asyncSMessage('操作成功!');
         this.refresh();
@@ -137,6 +181,16 @@ export default class extends Page {
     });
   }
 
+  nohandleAction() {
+    const { selectedKeys } = this.state;
+    asyncDelConfirm('不修改确认', '是否不修改选中记录?', () => {
+      return Promise.all(selectedKeys.map(row => User.editFeedbackError({ id: row, status: 3 }))).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
+    });
+  }
+
   ignoreAction() {
     const { selectedKeys } = this.state;
     asyncDelConfirm('忽略确认', '是否忽略选中记录?', () => {
@@ -175,13 +229,16 @@ export default class extends Page {
         this.setState({ detail: null });
       }}>
         <p>类型:{FeedbackModuleMap[this.state.detail.module]}</p>
-        <p>材料名称:{this.state.detail.title}</p>
-        {this.state.detail.position.length > 1 && <p>错误位置:{this.state.detail.position[0]}页,{this.state.detail.position[1]}行</p>}
+        {this.state.detail.module === 'data' && <p>勘误对象:{<Link to={`/course/data/detail/${this.state.detail.moduleId}`}>{this.state.detail.title}</Link>}</p>}
+        {this.state.detail.module === 'question' && <p>勘误对象:{<Link to={`/subject/question/${this.state.detail.moduleId}`}>{this.state.detail.title}-{AskTargetMap[this.state.detail.position[0]]}</Link>}</p>}
+        {this.state.detail.position.length > 1 && <p>错误位置:{this.state.detail.position[0]}页,{this.state.detail.position[1]}行{this.state.detail.position[2] ? `题号:${this.state.detail.position[2]}` : ''}</p>}
         <p>错误内容:{this.state.detail.originContent}</p>
         <p>应修改为:{this.state.detail.content}</p>
         {!this.state.detail.status && <p><Button type="primary" onClick={() => {
           this.handleDetail();
-        }}>已处理</Button><Button type="ghost" onClick={() => {
+        }}>确认修改</Button><Button type="primary" onClick={() => {
+          this.nohandleDetail();
+        }}>无需修改</Button><Button type="ghost" onClick={() => {
           this.ignoreDetail();
         }}>忽略</Button></p>}
       </Modal>}

+ 1 - 1
front/project/admin/routes/user/preview/page.js

@@ -43,7 +43,7 @@ export default class extends Page {
         placeholder: '请选择',
       },
       {
-        key: 'user_id',
+        key: 'userId',
         type: 'select',
         name: '用户',
         allowClear: true,

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

@@ -81,6 +81,10 @@ export default class CourseStore extends BaseStore {
     return this.apiPut('/course/data/history/edit', params);
   }
 
+  delDataHistory(params) {
+    return this.apiDel('/course/data/history/delete', params);
+  }
+
   listExperience(params) {
     return this.apiGet('/course/experience/list', params);
   }

+ 6 - 0
front/project/admin/stores/exercise.js

@@ -7,6 +7,12 @@ export default class ExerciseStore extends BaseStore {
     });
   }
 
+  dataStruct() {
+    return this.allStruct().then((result) => {
+      return result.filter(row => row.isData);
+    });
+  }
+
   allStruct() {
     return this.apiGet('/exercise/struct/all');
   }

+ 12 - 0
front/project/admin/stores/user.js

@@ -93,6 +93,18 @@ export default class UserStore extends BaseStore {
     return this.apiDel('/user/course/appointment/delete', params);
   }
 
+  listInvoice(params) {
+    return this.apiGet('/user/invoice/list', params);
+  }
+
+  finishInvoice(params) {
+    return this.apiPut('/user/invoice/finish', params);
+  }
+
+  downloadInvoice(params) {
+    return this.apiPut('/user/invoice/download', params);
+  }
+
   listOrder(params) {
     return this.apiGet('/user/order/list', params);
   }

+ 10 - 4
server/data/src/main/java/com/qxgmat/data/constants/enums/ServiceKey.java

@@ -1,14 +1,20 @@
 package com.qxgmat.data.constants.enums;
 
 public enum ServiceKey {
-    VIP("vip"), // 收藏和错题处的组卷、导出;笔记导出功能;部分解析只有VIP可以看;下载模考报告; 解锁完整版模考报告;“提问开放”期间有提问权限
-    TEXTBOOK("textbook"),
-    QX_CAT("qx_cat"), // 6个月内可以考2次
+    VIP("vip", 0, 0), // 收藏和错题处的组卷、导出;笔记导出功能;部分解析只有VIP可以看;下载模考报告; 解锁完整版模考报告;“提问开放”期间有提问权限
+    TEXTBOOK("textbook", 0, 0),
+    QX_CAT("qx_cat", 0, 0), // 6个月内可以考2次
 
     ;
     public String key;
-    private ServiceKey(String key){
+
+    public Integer expireTime;
+
+    public Integer useExpireTime;
+    private ServiceKey(String key, Integer expireTime, Integer userExpireTime){
         this.key = key;
+        this.expireTime = expireTime;
+        this.useExpireTime = userExpireTime;
     }
 
     public static ServiceKey ValueOf(String name){

+ 2 - 1
server/data/src/main/java/com/qxgmat/data/constants/enums/user/ServiceSource.java

@@ -6,7 +6,8 @@ public enum ServiceSource {
     ONLINE("online"),
     REAL("real"),
     INVITE("invite"),
-    GIFT("gift"),
+    GIFT_COURSE("gift_course"),
+    GIFT_ACTIVITY("gift_activity"),
 
     ;
     public String key;

+ 154 - 14
server/data/src/main/java/com/qxgmat/data/dao/entity/Course.java

@@ -37,6 +37,12 @@ public class Course implements Serializable {
     private String vsType;
 
     /**
+     * 视频课程类型
+     */
+    @Column(name = "`video_type`")
+    private String videoType;
+
+    /**
      * 从struct上继承extend
      */
     @Column(name = "`extend`")
@@ -79,12 +85,24 @@ public class Course implements Serializable {
     private Integer maxNumber;
 
     /**
-     * 有效天数
+     * 1v1课时有效天数
      */
     @Column(name = "`expire_days`")
     private Integer expireDays;
 
     /**
+     * 视频课程开通有效时长
+     */
+    @Column(name = "`expire_time`")
+    private Integer expireTime;
+
+    /**
+     * 使用有效时长
+     */
+    @Column(name = "`use_expire_time`")
+    private Integer useExpireTime;
+
+    /**
      * 微信头像
      */
     @Column(name = "`wechat_avatar`")
@@ -97,11 +115,17 @@ public class Course implements Serializable {
     private Integer trailNumber;
 
     /**
-     * 购买数量
+     * 销售数量
      */
     @Column(name = "`sale_number`")
     private Integer saleNumber;
 
+    /**
+     * 套餐销售数量
+     */
+    @Column(name = "`package_sale_number`")
+    private Integer packageSaleNumber;
+
     @Column(name = "`create_time`")
     private Date createTime;
 
@@ -263,6 +287,24 @@ public class Course implements Serializable {
     }
 
     /**
+     * 获取视频课程类型
+     *
+     * @return video_type - 视频课程类型
+     */
+    public String getVideoType() {
+        return videoType;
+    }
+
+    /**
+     * 设置视频课程类型
+     *
+     * @param videoType 视频课程类型
+     */
+    public void setVideoType(String videoType) {
+        this.videoType = videoType;
+    }
+
+    /**
      * 获取从struct上继承extend
      *
      * @return extend - 从struct上继承extend
@@ -389,24 +431,60 @@ public class Course implements Serializable {
     }
 
     /**
-     * 获取有效天数
+     * 获取1v1课时有效天数
      *
-     * @return expire_days - 有效天数
+     * @return expire_days - 1v1课时有效天数
      */
     public Integer getExpireDays() {
         return expireDays;
     }
 
     /**
-     * 设置有效天数
+     * 设置1v1课时有效天数
      *
-     * @param expireDays 有效天数
+     * @param expireDays 1v1课时有效天数
      */
     public void setExpireDays(Integer expireDays) {
         this.expireDays = expireDays;
     }
 
     /**
+     * 获取视频课程开通有效时长
+     *
+     * @return expire_time - 视频课程开通有效时长
+     */
+    public Integer getExpireTime() {
+        return expireTime;
+    }
+
+    /**
+     * 设置视频课程开通有效时长
+     *
+     * @param expireTime 视频课程开通有效时长
+     */
+    public void setExpireTime(Integer expireTime) {
+        this.expireTime = expireTime;
+    }
+
+    /**
+     * 获取使用有效时长
+     *
+     * @return use_expire_time - 使用有效时长
+     */
+    public Integer getUseExpireTime() {
+        return useExpireTime;
+    }
+
+    /**
+     * 设置使用有效时长
+     *
+     * @param useExpireTime 使用有效时长
+     */
+    public void setUseExpireTime(Integer useExpireTime) {
+        this.useExpireTime = useExpireTime;
+    }
+
+    /**
      * 获取微信头像
      *
      * @return wechat_avatar - 微信头像
@@ -443,24 +521,42 @@ public class Course implements Serializable {
     }
 
     /**
-     * 获取购买数量
+     * 获取销售数量
      *
-     * @return sale_number - 购买数量
+     * @return sale_number - 销售数量
      */
     public Integer getSaleNumber() {
         return saleNumber;
     }
 
     /**
-     * 设置购买数量
+     * 设置销售数量
      *
-     * @param saleNumber 购买数量
+     * @param saleNumber 销售数量
      */
     public void setSaleNumber(Integer saleNumber) {
         this.saleNumber = saleNumber;
     }
 
     /**
+     * 获取套餐销售数量
+     *
+     * @return package_sale_number - 套餐销售数量
+     */
+    public Integer getPackageSaleNumber() {
+        return packageSaleNumber;
+    }
+
+    /**
+     * 设置套餐销售数量
+     *
+     * @param packageSaleNumber 套餐销售数量
+     */
+    public void setPackageSaleNumber(Integer packageSaleNumber) {
+        this.packageSaleNumber = packageSaleNumber;
+    }
+
+    /**
      * @return create_time
      */
     public Date getCreateTime() {
@@ -697,6 +793,7 @@ public class Course implements Serializable {
         sb.append(", parentStructId=").append(parentStructId);
         sb.append(", courseModule=").append(courseModule);
         sb.append(", vsType=").append(vsType);
+        sb.append(", videoType=").append(videoType);
         sb.append(", extend=").append(extend);
         sb.append(", title=").append(title);
         sb.append(", crowd=").append(crowd);
@@ -705,9 +802,12 @@ public class Course implements Serializable {
         sb.append(", minNumber=").append(minNumber);
         sb.append(", maxNumber=").append(maxNumber);
         sb.append(", expireDays=").append(expireDays);
+        sb.append(", expireTime=").append(expireTime);
+        sb.append(", useExpireTime=").append(useExpireTime);
         sb.append(", wechatAvatar=").append(wechatAvatar);
         sb.append(", trailNumber=").append(trailNumber);
         sb.append(", saleNumber=").append(saleNumber);
+        sb.append(", packageSaleNumber=").append(packageSaleNumber);
         sb.append(", createTime=").append(createTime);
         sb.append(", updateTime=").append(updateTime);
         sb.append(", teacherContent=").append(teacherContent);
@@ -785,6 +885,16 @@ public class Course implements Serializable {
         }
 
         /**
+         * 设置视频课程类型
+         *
+         * @param videoType 视频课程类型
+         */
+        public Builder videoType(String videoType) {
+            obj.setVideoType(videoType);
+            return this;
+        }
+
+        /**
          * 设置从struct上继承extend
          *
          * @param extend 从struct上继承extend
@@ -875,9 +985,9 @@ public class Course implements Serializable {
         }
 
         /**
-         * 设置有效天数
+         * 设置1v1课时有效天数
          *
-         * @param expireDays 有效天数
+         * @param expireDays 1v1课时有效天数
          */
         public Builder expireDays(Integer expireDays) {
             obj.setExpireDays(expireDays);
@@ -885,6 +995,26 @@ public class Course implements Serializable {
         }
 
         /**
+         * 设置视频课程开通有效时长
+         *
+         * @param expireTime 视频课程开通有效时长
+         */
+        public Builder expireTime(Integer expireTime) {
+            obj.setExpireTime(expireTime);
+            return this;
+        }
+
+        /**
+         * 设置使用有效时长
+         *
+         * @param useExpireTime 使用有效时长
+         */
+        public Builder useExpireTime(Integer useExpireTime) {
+            obj.setUseExpireTime(useExpireTime);
+            return this;
+        }
+
+        /**
          * 设置微信头像
          *
          * @param wechatAvatar 微信头像
@@ -905,9 +1035,9 @@ public class Course implements Serializable {
         }
 
         /**
-         * 设置购买数量
+         * 设置销售数量
          *
-         * @param saleNumber 购买数量
+         * @param saleNumber 销售数量
          */
         public Builder saleNumber(Integer saleNumber) {
             obj.setSaleNumber(saleNumber);
@@ -915,6 +1045,16 @@ public class Course implements Serializable {
         }
 
         /**
+         * 设置套餐销售数量
+         *
+         * @param packageSaleNumber 套餐销售数量
+         */
+        public Builder packageSaleNumber(Integer packageSaleNumber) {
+            obj.setPackageSaleNumber(packageSaleNumber);
+            return this;
+        }
+
+        /**
          * @param createTime
          */
         public Builder createTime(Date createTime) {

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

@@ -73,6 +73,30 @@ public class CourseData implements Serializable {
     private String cover;
 
     /**
+     * 资料
+     */
+    @Column(name = "`resource`")
+    private String resource;
+
+    /**
+     * 试用资料
+     */
+    @Column(name = "`trail_resource`")
+    private String trailResource;
+
+    /**
+     * 试用开始
+     */
+    @Column(name = "`trail_start`")
+    private Integer trailStart;
+
+    /**
+     * 试用结束
+     */
+    @Column(name = "`trail_end`")
+    private Integer trailEnd;
+
+    /**
      * 查看人数
      */
     @Column(name = "`view_number`")
@@ -311,6 +335,78 @@ public class CourseData implements Serializable {
     }
 
     /**
+     * 获取资料
+     *
+     * @return resource - 资料
+     */
+    public String getResource() {
+        return resource;
+    }
+
+    /**
+     * 设置资料
+     *
+     * @param resource 资料
+     */
+    public void setResource(String resource) {
+        this.resource = resource;
+    }
+
+    /**
+     * 获取试用资料
+     *
+     * @return trail_resource - 试用资料
+     */
+    public String getTrailResource() {
+        return trailResource;
+    }
+
+    /**
+     * 设置试用资料
+     *
+     * @param trailResource 试用资料
+     */
+    public void setTrailResource(String trailResource) {
+        this.trailResource = trailResource;
+    }
+
+    /**
+     * 获取试用开始
+     *
+     * @return trail_start - 试用开始
+     */
+    public Integer getTrailStart() {
+        return trailStart;
+    }
+
+    /**
+     * 设置试用开始
+     *
+     * @param trailStart 试用开始
+     */
+    public void setTrailStart(Integer trailStart) {
+        this.trailStart = trailStart;
+    }
+
+    /**
+     * 获取试用结束
+     *
+     * @return trail_end - 试用结束
+     */
+    public Integer getTrailEnd() {
+        return trailEnd;
+    }
+
+    /**
+     * 设置试用结束
+     *
+     * @param trailEnd 试用结束
+     */
+    public void setTrailEnd(Integer trailEnd) {
+        this.trailEnd = trailEnd;
+    }
+
+    /**
      * 获取查看人数
      *
      * @return view_number - 查看人数
@@ -463,6 +559,10 @@ public class CourseData implements Serializable {
         sb.append(", pages=").append(pages);
         sb.append(", link=").append(link);
         sb.append(", cover=").append(cover);
+        sb.append(", resource=").append(resource);
+        sb.append(", trailResource=").append(trailResource);
+        sb.append(", trailStart=").append(trailStart);
+        sb.append(", trailEnd=").append(trailEnd);
         sb.append(", viewNumber=").append(viewNumber);
         sb.append(", saleNumber=").append(saleNumber);
         sb.append(", createTime=").append(createTime);
@@ -595,6 +695,46 @@ public class CourseData implements Serializable {
         }
 
         /**
+         * 设置资料
+         *
+         * @param resource 资料
+         */
+        public Builder resource(String resource) {
+            obj.setResource(resource);
+            return this;
+        }
+
+        /**
+         * 设置试用资料
+         *
+         * @param trailResource 试用资料
+         */
+        public Builder trailResource(String trailResource) {
+            obj.setTrailResource(trailResource);
+            return this;
+        }
+
+        /**
+         * 设置试用开始
+         *
+         * @param trailStart 试用开始
+         */
+        public Builder trailStart(Integer trailStart) {
+            obj.setTrailStart(trailStart);
+            return this;
+        }
+
+        /**
+         * 设置试用结束
+         *
+         * @param trailEnd 试用结束
+         */
+        public Builder trailEnd(Integer trailEnd) {
+            obj.setTrailEnd(trailEnd);
+            return this;
+        }
+
+        /**
          * 设置查看人数
          *
          * @param viewNumber 查看人数

+ 47 - 47
server/data/src/main/java/com/qxgmat/data/dao/entity/CourseDataHistory.java

@@ -18,22 +18,22 @@ public class CourseDataHistory implements Serializable {
     private Integer dataId;
 
     /**
-     * 存储地址
-     */
-    @Column(name = "`resource`")
-    private String resource;
-
-    /**
      * 版本名称
      */
     @Column(name = "`version`")
     private String version;
 
     /**
-     * 变更页数
+     * 更新时间
+     */
+    @Column(name = "`time`")
+    private Date time;
+
+    /**
+     * 更新位置
      */
-    @Column(name = "`change_page`")
-    private String changePage;
+    @Column(name = "`position`")
+    private String position;
 
     @Column(name = "`create_time`")
     private Date createTime;
@@ -85,57 +85,57 @@ public class CourseDataHistory implements Serializable {
     }
 
     /**
-     * 获取存储地址
+     * 获取版本名称
      *
-     * @return resource - 存储地址
+     * @return version - 版本名称
      */
-    public String getResource() {
-        return resource;
+    public String getVersion() {
+        return version;
     }
 
     /**
-     * 设置存储地址
+     * 设置版本名称
      *
-     * @param resource 存储地址
+     * @param version 版本名称
      */
-    public void setResource(String resource) {
-        this.resource = resource;
+    public void setVersion(String version) {
+        this.version = version;
     }
 
     /**
-     * 获取版本名称
+     * 获取更新时间
      *
-     * @return version - 版本名称
+     * @return time - 更新时间
      */
-    public String getVersion() {
-        return version;
+    public Date getTime() {
+        return time;
     }
 
     /**
-     * 设置版本名称
+     * 设置更新时间
      *
-     * @param version 版本名称
+     * @param time 更新时间
      */
-    public void setVersion(String version) {
-        this.version = version;
+    public void setTime(Date time) {
+        this.time = time;
     }
 
     /**
-     * 获取变更页数
+     * 获取更新位置
      *
-     * @return change_page - 变更页数
+     * @return position - 更新位置
      */
-    public String getChangePage() {
-        return changePage;
+    public String getPosition() {
+        return position;
     }
 
     /**
-     * 设置变更页数
+     * 设置更新位置
      *
-     * @param changePage 变更页数
+     * @param position 更新位置
      */
-    public void setChangePage(String changePage) {
-        this.changePage = changePage;
+    public void setPosition(String position) {
+        this.position = position;
     }
 
     /**
@@ -196,9 +196,9 @@ public class CourseDataHistory implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", dataId=").append(dataId);
-        sb.append(", resource=").append(resource);
         sb.append(", version=").append(version);
-        sb.append(", changePage=").append(changePage);
+        sb.append(", time=").append(time);
+        sb.append(", position=").append(position);
         sb.append(", createTime=").append(createTime);
         sb.append(", originContent=").append(originContent);
         sb.append(", content=").append(content);
@@ -236,32 +236,32 @@ public class CourseDataHistory implements Serializable {
         }
 
         /**
-         * 设置存储地址
+         * 设置版本名称
          *
-         * @param resource 存储地址
+         * @param version 版本名称
          */
-        public Builder resource(String resource) {
-            obj.setResource(resource);
+        public Builder version(String version) {
+            obj.setVersion(version);
             return this;
         }
 
         /**
-         * 设置版本名称
+         * 设置更新时间
          *
-         * @param version 版本名称
+         * @param time 更新时间
          */
-        public Builder version(String version) {
-            obj.setVersion(version);
+        public Builder time(Date time) {
+            obj.setTime(time);
             return this;
         }
 
         /**
-         * 设置变更页数
+         * 设置更新位置
          *
-         * @param changePage 变更页数
+         * @param position 更新位置
          */
-        public Builder changePage(String changePage) {
-            obj.setChangePage(changePage);
+        public Builder position(String position) {
+            obj.setPosition(position);
             return this;
         }
 

+ 175 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/CourseExperience.java

@@ -24,6 +24,36 @@ public class CourseExperience implements Serializable {
     private String title;
 
     /**
+     * 问卷链接
+     */
+    @Column(name = "`link`")
+    private String link;
+
+    /**
+     * 备考身份
+     */
+    @Column(name = "`prepare_status`")
+    private String prepareStatus;
+
+    /**
+     * 备考周期
+     */
+    @Column(name = "`prepare_examination_time`")
+    private String prepareExaminationTime;
+
+    /**
+     * 分手成绩
+     */
+    @Column(name = "`experience_score`")
+    private String experienceScore;
+
+    /**
+     * 提分幅度
+     */
+    @Column(name = "`experience_percent`")
+    private String experiencePercent;
+
+    /**
      * 阅读量
      */
     @Column(name = "`view_number`")
@@ -100,6 +130,96 @@ public class CourseExperience implements Serializable {
     }
 
     /**
+     * 获取问卷链接
+     *
+     * @return link - 问卷链接
+     */
+    public String getLink() {
+        return link;
+    }
+
+    /**
+     * 设置问卷链接
+     *
+     * @param link 问卷链接
+     */
+    public void setLink(String link) {
+        this.link = link;
+    }
+
+    /**
+     * 获取备考身份
+     *
+     * @return prepare_status - 备考身份
+     */
+    public String getPrepareStatus() {
+        return prepareStatus;
+    }
+
+    /**
+     * 设置备考身份
+     *
+     * @param prepareStatus 备考身份
+     */
+    public void setPrepareStatus(String prepareStatus) {
+        this.prepareStatus = prepareStatus;
+    }
+
+    /**
+     * 获取备考周期
+     *
+     * @return prepare_examination_time - 备考周期
+     */
+    public String getPrepareExaminationTime() {
+        return prepareExaminationTime;
+    }
+
+    /**
+     * 设置备考周期
+     *
+     * @param prepareExaminationTime 备考周期
+     */
+    public void setPrepareExaminationTime(String prepareExaminationTime) {
+        this.prepareExaminationTime = prepareExaminationTime;
+    }
+
+    /**
+     * 获取分手成绩
+     *
+     * @return experience_score - 分手成绩
+     */
+    public String getExperienceScore() {
+        return experienceScore;
+    }
+
+    /**
+     * 设置分手成绩
+     *
+     * @param experienceScore 分手成绩
+     */
+    public void setExperienceScore(String experienceScore) {
+        this.experienceScore = experienceScore;
+    }
+
+    /**
+     * 获取提分幅度
+     *
+     * @return experience_percent - 提分幅度
+     */
+    public String getExperiencePercent() {
+        return experiencePercent;
+    }
+
+    /**
+     * 设置提分幅度
+     *
+     * @param experiencePercent 提分幅度
+     */
+    public void setExperiencePercent(String experiencePercent) {
+        this.experiencePercent = experiencePercent;
+    }
+
+    /**
      * 获取阅读量
      *
      * @return view_number - 阅读量
@@ -190,6 +310,11 @@ public class CourseExperience implements Serializable {
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
         sb.append(", title=").append(title);
+        sb.append(", link=").append(link);
+        sb.append(", prepareStatus=").append(prepareStatus);
+        sb.append(", prepareExaminationTime=").append(prepareExaminationTime);
+        sb.append(", experienceScore=").append(experienceScore);
+        sb.append(", experiencePercent=").append(experiencePercent);
         sb.append(", viewNumber=").append(viewNumber);
         sb.append(", collectNumber=").append(collectNumber);
         sb.append(", createTime=").append(createTime);
@@ -239,6 +364,56 @@ public class CourseExperience implements Serializable {
         }
 
         /**
+         * 设置问卷链接
+         *
+         * @param link 问卷链接
+         */
+        public Builder link(String link) {
+            obj.setLink(link);
+            return this;
+        }
+
+        /**
+         * 设置备考身份
+         *
+         * @param prepareStatus 备考身份
+         */
+        public Builder prepareStatus(String prepareStatus) {
+            obj.setPrepareStatus(prepareStatus);
+            return this;
+        }
+
+        /**
+         * 设置备考周期
+         *
+         * @param prepareExaminationTime 备考周期
+         */
+        public Builder prepareExaminationTime(String prepareExaminationTime) {
+            obj.setPrepareExaminationTime(prepareExaminationTime);
+            return this;
+        }
+
+        /**
+         * 设置分手成绩
+         *
+         * @param experienceScore 分手成绩
+         */
+        public Builder experienceScore(String experienceScore) {
+            obj.setExperienceScore(experienceScore);
+            return this;
+        }
+
+        /**
+         * 设置提分幅度
+         *
+         * @param experiencePercent 提分幅度
+         */
+        public Builder experiencePercent(String experiencePercent) {
+            obj.setExperiencePercent(experiencePercent);
+            return this;
+        }
+
+        /**
          * 设置阅读量
          *
          * @param viewNumber 阅读量

+ 70 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/CourseNo.java

@@ -42,6 +42,12 @@ public class CourseNo implements Serializable {
     private Integer time;
 
     /**
+     * 是否试用
+     */
+    @Column(name = "`is_trail`")
+    private Integer isTrail;
+
+    /**
      * 试用区间:开始
      */
     @Column(name = "`start_trail`")
@@ -53,6 +59,12 @@ public class CourseNo implements Serializable {
     @Column(name = "`end_trail`")
     private Integer endTrail;
 
+    /**
+     * 试用视频
+     */
+    @Column(name = "`trail_resource`")
+    private String trailResource;
+
     @Column(name = "`create_time`")
     private Date createTime;
 
@@ -169,6 +181,24 @@ public class CourseNo implements Serializable {
     }
 
     /**
+     * 获取是否试用
+     *
+     * @return is_trail - 是否试用
+     */
+    public Integer getIsTrail() {
+        return isTrail;
+    }
+
+    /**
+     * 设置是否试用
+     *
+     * @param isTrail 是否试用
+     */
+    public void setIsTrail(Integer isTrail) {
+        this.isTrail = isTrail;
+    }
+
+    /**
      * 获取试用区间:开始
      *
      * @return start_trail - 试用区间:开始
@@ -205,6 +235,24 @@ public class CourseNo implements Serializable {
     }
 
     /**
+     * 获取试用视频
+     *
+     * @return trail_resource - 试用视频
+     */
+    public String getTrailResource() {
+        return trailResource;
+    }
+
+    /**
+     * 设置试用视频
+     *
+     * @param trailResource 试用视频
+     */
+    public void setTrailResource(String trailResource) {
+        this.trailResource = trailResource;
+    }
+
+    /**
      * @return create_time
      */
     public Date getCreateTime() {
@@ -248,8 +296,10 @@ public class CourseNo implements Serializable {
         sb.append(", title=").append(title);
         sb.append(", resource=").append(resource);
         sb.append(", time=").append(time);
+        sb.append(", isTrail=").append(isTrail);
         sb.append(", startTrail=").append(startTrail);
         sb.append(", endTrail=").append(endTrail);
+        sb.append(", trailResource=").append(trailResource);
         sb.append(", createTime=").append(createTime);
         sb.append(", content=").append(content);
         sb.append("]");
@@ -326,6 +376,16 @@ public class CourseNo implements Serializable {
         }
 
         /**
+         * 设置是否试用
+         *
+         * @param isTrail 是否试用
+         */
+        public Builder isTrail(Integer isTrail) {
+            obj.setIsTrail(isTrail);
+            return this;
+        }
+
+        /**
          * 设置试用区间:开始
          *
          * @param startTrail 试用区间:开始
@@ -346,6 +406,16 @@ public class CourseNo implements Serializable {
         }
 
         /**
+         * 设置试用视频
+         *
+         * @param trailResource 试用视频
+         */
+        public Builder trailResource(String trailResource) {
+            obj.setTrailResource(trailResource);
+            return this;
+        }
+
+        /**
          * @param createTime
          */
         public Builder createTime(Date createTime) {

+ 70 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/CourseTeacher.java

@@ -24,6 +24,12 @@ public class CourseTeacher implements Serializable {
     private String realname;
 
     /**
+     * 头像
+     */
+    @Column(name = "`avatar`")
+    private String avatar;
+
+    /**
      * 微信号
      */
     @Column(name = "`wechat`")
@@ -38,6 +44,12 @@ public class CourseTeacher implements Serializable {
     @Column(name = "`create_time`")
     private Date createTime;
 
+    /**
+     * 描述
+     */
+    @Column(name = "`description`")
+    private String description;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -91,6 +103,24 @@ public class CourseTeacher implements Serializable {
     }
 
     /**
+     * 获取头像
+     *
+     * @return avatar - 头像
+     */
+    public String getAvatar() {
+        return avatar;
+    }
+
+    /**
+     * 设置头像
+     *
+     * @param avatar 头像
+     */
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    /**
      * 获取微信号
      *
      * @return wechat - 微信号
@@ -140,6 +170,24 @@ public class CourseTeacher implements Serializable {
         this.createTime = createTime;
     }
 
+    /**
+     * 获取描述
+     *
+     * @return description - 描述
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * 设置描述
+     *
+     * @param description 描述
+     */
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -149,9 +197,11 @@ public class CourseTeacher implements Serializable {
         sb.append(", id=").append(id);
         sb.append(", courseId=").append(courseId);
         sb.append(", realname=").append(realname);
+        sb.append(", avatar=").append(avatar);
         sb.append(", wechat=").append(wechat);
         sb.append(", qr=").append(qr);
         sb.append(", createTime=").append(createTime);
+        sb.append(", description=").append(description);
         sb.append("]");
         return sb.toString();
     }
@@ -196,6 +246,16 @@ public class CourseTeacher implements Serializable {
         }
 
         /**
+         * 设置头像
+         *
+         * @param avatar 头像
+         */
+        public Builder avatar(String avatar) {
+            obj.setAvatar(avatar);
+            return this;
+        }
+
+        /**
          * 设置微信号
          *
          * @param wechat 微信号
@@ -223,6 +283,16 @@ public class CourseTeacher implements Serializable {
             return this;
         }
 
+        /**
+         * 设置描述
+         *
+         * @param description 描述
+         */
+        public Builder description(String description) {
+            obj.setDescription(description);
+            return this;
+        }
+
         public CourseTeacher build() {
             return this.obj;
         }

+ 35 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/ExerciseStruct.java

@@ -53,6 +53,12 @@ public class ExerciseStruct implements Serializable {
     private Integer isCourse;
 
     /**
+     * 是否是资料
+     */
+    @Column(name = "`is_data`")
+    private Integer isData;
+
+    /**
      * 是否是长难句:每一层都继承
      */
     @Column(name = "`is_sentence`")
@@ -216,6 +222,24 @@ public class ExerciseStruct implements Serializable {
     }
 
     /**
+     * 获取是否是资料
+     *
+     * @return is_data - 是否是资料
+     */
+    public Integer getIsData() {
+        return isData;
+    }
+
+    /**
+     * 设置是否是资料
+     *
+     * @param isData 是否是资料
+     */
+    public void setIsData(Integer isData) {
+        this.isData = isData;
+    }
+
+    /**
      * 获取是否是长难句:每一层都继承
      *
      * @return is_sentence - 是否是长难句:每一层都继承
@@ -297,6 +321,7 @@ public class ExerciseStruct implements Serializable {
         sb.append(", level=").append(level);
         sb.append(", questionStatus=").append(questionStatus);
         sb.append(", isCourse=").append(isCourse);
+        sb.append(", isData=").append(isData);
         sb.append(", isSentence=").append(isSentence);
         sb.append(", isExamination=").append(isExamination);
         sb.append(", extend=").append(extend);
@@ -395,6 +420,16 @@ public class ExerciseStruct implements Serializable {
         }
 
         /**
+         * 设置是否是资料
+         *
+         * @param isData 是否是资料
+         */
+        public Builder isData(Integer isData) {
+            obj.setIsData(isData);
+            return this;
+        }
+
+        /**
          * 设置是否是长难句:每一层都继承
          *
          * @param isSentence 是否是长难句:每一层都继承

+ 35 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserAskCourse.java

@@ -30,6 +30,12 @@ public class UserAskCourse implements Serializable {
     private Integer courseNoId;
 
     /**
+     * 记录id
+     */
+    @Column(name = "`record_id`")
+    private Integer recordId;
+
+    /**
      * 位置
      */
     @Column(name = "`position`")
@@ -154,6 +160,24 @@ public class UserAskCourse implements Serializable {
     }
 
     /**
+     * 获取记录id
+     *
+     * @return record_id - 记录id
+     */
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    /**
+     * 设置记录id
+     *
+     * @param recordId 记录id
+     */
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+
+    /**
      * 获取位置
      *
      * @return position - 位置
@@ -335,6 +359,7 @@ public class UserAskCourse implements Serializable {
         sb.append(", userId=").append(userId);
         sb.append(", courseId=").append(courseId);
         sb.append(", courseNoId=").append(courseNoId);
+        sb.append(", recordId=").append(recordId);
         sb.append(", position=").append(position);
         sb.append(", answerStatus=").append(answerStatus);
         sb.append(", managerId=").append(managerId);
@@ -399,6 +424,16 @@ public class UserAskCourse implements Serializable {
         }
 
         /**
+         * 设置记录id
+         *
+         * @param recordId 记录id
+         */
+        public Builder recordId(Integer recordId) {
+            obj.setRecordId(recordId);
+            return this;
+        }
+
+        /**
          * 设置位置
          *
          * @param position 位置

+ 26 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserInvoice.java

@@ -56,6 +56,9 @@ public class UserInvoice implements Serializable {
     @Column(name = "`create_time`")
     private Date createTime;
 
+    @Column(name = "`update_time`")
+    private Date updateTime;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -212,6 +215,20 @@ public class UserInvoice implements Serializable {
         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();
@@ -227,6 +244,7 @@ public class UserInvoice implements Serializable {
         sb.append(", isDownload=").append(isDownload);
         sb.append(", isFinish=").append(isFinish);
         sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
         sb.append("]");
         return sb.toString();
     }
@@ -328,6 +346,14 @@ public class UserInvoice implements Serializable {
             return this;
         }
 
+        /**
+         * @param updateTime
+         */
+        public Builder updateTime(Date updateTime) {
+            obj.setUpdateTime(updateTime);
+            return this;
+        }
+
         public UserInvoice build() {
             return this.obj;
         }

+ 111 - 40
server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrder.java

@@ -1,6 +1,7 @@
 package com.qxgmat.data.dao.entity;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.util.Date;
 import javax.persistence.*;
 
@@ -18,19 +19,31 @@ public class UserOrder implements Serializable {
     private Integer userId;
 
     /**
-     * 模块
+     * 包含的订单类型:json
      */
-    @Column(name = "`module`")
-    private String module;
+    @Column(name = "`product_types`")
+    private String productTypes;
 
-    @Column(name = "`create_time`")
-    private Date createTime;
+    /**
+     * 支付方式
+     */
+    @Column(name = "`pay_method`")
+    private String payMethod;
+
+    /**
+     * 支付费用
+     */
+    @Column(name = "`money`")
+    private BigDecimal money;
 
     /**
-     * 模块扩展信息: json
+     * 开票费用
      */
-    @Column(name = "`module_extend`")
-    private String moduleExtend;
+    @Column(name = "`invoice_money`")
+    private BigDecimal invoiceMoney;
+
+    @Column(name = "`create_time`")
+    private Date createTime;
 
     private static final long serialVersionUID = 1L;
 
@@ -67,53 +80,89 @@ public class UserOrder implements Serializable {
     }
 
     /**
-     * 获取模块
+     * 获取包含的订单类型:json
      *
-     * @return module - 模块
+     * @return product_types - 包含的订单类型:json
      */
-    public String getModule() {
-        return module;
+    public String getProductTypes() {
+        return productTypes;
     }
 
     /**
-     * 设置模块
+     * 设置包含的订单类型:json
      *
-     * @param module 模块
+     * @param productTypes 包含的订单类型:json
      */
-    public void setModule(String module) {
-        this.module = module;
+    public void setProductTypes(String productTypes) {
+        this.productTypes = productTypes;
     }
 
     /**
-     * @return create_time
+     * 获取支付方式
+     *
+     * @return pay_method - 支付方式
      */
-    public Date getCreateTime() {
-        return createTime;
+    public String getPayMethod() {
+        return payMethod;
     }
 
     /**
-     * @param createTime
+     * 设置支付方式
+     *
+     * @param payMethod 支付方式
      */
-    public void setCreateTime(Date createTime) {
-        this.createTime = createTime;
+    public void setPayMethod(String payMethod) {
+        this.payMethod = payMethod;
+    }
+
+    /**
+     * 获取支付费用
+     *
+     * @return money - 支付费用
+     */
+    public BigDecimal getMoney() {
+        return money;
+    }
+
+    /**
+     * 设置支付费用
+     *
+     * @param money 支付费用
+     */
+    public void setMoney(BigDecimal money) {
+        this.money = money;
     }
 
     /**
-     * 获取模块扩展信息: json
+     * 获取开票费用
      *
-     * @return module_extend - 模块扩展信息: json
+     * @return invoice_money - 开票费用
      */
-    public String getModuleExtend() {
-        return moduleExtend;
+    public BigDecimal getInvoiceMoney() {
+        return invoiceMoney;
     }
 
     /**
-     * 设置模块扩展信息: json
+     * 设置开票费用
      *
-     * @param moduleExtend 模块扩展信息: json
+     * @param invoiceMoney 开票费用
      */
-    public void setModuleExtend(String moduleExtend) {
-        this.moduleExtend = moduleExtend;
+    public void setInvoiceMoney(BigDecimal invoiceMoney) {
+        this.invoiceMoney = invoiceMoney;
+    }
+
+    /**
+     * @return create_time
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * @param createTime
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
     }
 
     @Override
@@ -124,9 +173,11 @@ public class UserOrder implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
-        sb.append(", module=").append(module);
+        sb.append(", productTypes=").append(productTypes);
+        sb.append(", payMethod=").append(payMethod);
+        sb.append(", money=").append(money);
+        sb.append(", invoiceMoney=").append(invoiceMoney);
         sb.append(", createTime=").append(createTime);
-        sb.append(", moduleExtend=").append(moduleExtend);
         sb.append("]");
         return sb.toString();
     }
@@ -161,22 +212,42 @@ public class UserOrder implements Serializable {
         }
 
         /**
-         * 设置模块
+         * 设置包含的订单类型:json
+         *
+         * @param productTypes 包含的订单类型:json
+         */
+        public Builder productTypes(String productTypes) {
+            obj.setProductTypes(productTypes);
+            return this;
+        }
+
+        /**
+         * 设置支付方式
+         *
+         * @param payMethod 支付方式
+         */
+        public Builder payMethod(String payMethod) {
+            obj.setPayMethod(payMethod);
+            return this;
+        }
+
+        /**
+         * 设置支付费用
          *
-         * @param module 模块
+         * @param money 支付费用
          */
-        public Builder module(String module) {
-            obj.setModule(module);
+        public Builder money(BigDecimal money) {
+            obj.setMoney(money);
             return this;
         }
 
         /**
-         * 设置模块扩展信息: json
+         * 设置开票费用
          *
-         * @param moduleExtend 模块扩展信息: json
+         * @param invoiceMoney 开票费用
          */
-        public Builder moduleExtend(String moduleExtend) {
-            obj.setModuleExtend(moduleExtend);
+        public Builder invoiceMoney(BigDecimal invoiceMoney) {
+            obj.setInvoiceMoney(invoiceMoney);
             return this;
         }
 

+ 70 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrderRecord.java

@@ -24,6 +24,12 @@ public class UserOrderRecord implements Serializable {
     private Integer orderId;
 
     /**
+     * 套餐id
+     */
+    @Column(name = "`parent_record_id`")
+    private Integer parentRecordId;
+
+    /**
      * 产品类型
      */
     @Column(name = "`product_type`")
@@ -66,6 +72,12 @@ public class UserOrderRecord implements Serializable {
     private Integer vsNumber;
 
     /**
+     * 回复时常
+     */
+    @Column(name = "`ask_time`")
+    private Integer askTime;
+
+    /**
      * 开通开始时间
      */
     @Column(name = "`start_time`")
@@ -157,6 +169,24 @@ public class UserOrderRecord implements Serializable {
     }
 
     /**
+     * 获取套餐id
+     *
+     * @return parent_record_id - 套餐id
+     */
+    public Integer getParentRecordId() {
+        return parentRecordId;
+    }
+
+    /**
+     * 设置套餐id
+     *
+     * @param parentRecordId 套餐id
+     */
+    public void setParentRecordId(Integer parentRecordId) {
+        this.parentRecordId = parentRecordId;
+    }
+
+    /**
      * 获取产品类型
      *
      * @return product_type - 产品类型
@@ -283,6 +313,24 @@ public class UserOrderRecord implements Serializable {
     }
 
     /**
+     * 获取回复时常
+     *
+     * @return ask_time - 回复时常
+     */
+    public Integer getAskTime() {
+        return askTime;
+    }
+
+    /**
+     * 设置回复时常
+     *
+     * @param askTime 回复时常
+     */
+    public void setAskTime(Integer askTime) {
+        this.askTime = askTime;
+    }
+
+    /**
      * 获取开通开始时间
      *
      * @return start_time - 开通开始时间
@@ -413,6 +461,7 @@ public class UserOrderRecord implements Serializable {
         sb.append(", id=").append(id);
         sb.append(", userId=").append(userId);
         sb.append(", orderId=").append(orderId);
+        sb.append(", parentRecordId=").append(parentRecordId);
         sb.append(", productType=").append(productType);
         sb.append(", productId=").append(productId);
         sb.append(", service=").append(service);
@@ -420,6 +469,7 @@ public class UserOrderRecord implements Serializable {
         sb.append(", source=").append(source);
         sb.append(", teacherId=").append(teacherId);
         sb.append(", vsNumber=").append(vsNumber);
+        sb.append(", askTime=").append(askTime);
         sb.append(", startTime=").append(startTime);
         sb.append(", endTime=").append(endTime);
         sb.append(", useStartTime=").append(useStartTime);
@@ -471,6 +521,16 @@ public class UserOrderRecord implements Serializable {
         }
 
         /**
+         * 设置套餐id
+         *
+         * @param parentRecordId 套餐id
+         */
+        public Builder parentRecordId(Integer parentRecordId) {
+            obj.setParentRecordId(parentRecordId);
+            return this;
+        }
+
+        /**
          * 设置产品类型
          *
          * @param productType 产品类型
@@ -541,6 +601,16 @@ public class UserOrderRecord implements Serializable {
         }
 
         /**
+         * 设置回复时常
+         *
+         * @param askTime 回复时常
+         */
+        public Builder askTime(Integer askTime) {
+            obj.setAskTime(askTime);
+            return this;
+        }
+
+        /**
          * 设置开通开始时间
          *
          * @param startTime 开通开始时间

+ 3 - 3
server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseDataHistoryMapper.xml

@@ -7,9 +7,9 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="data_id" jdbcType="INTEGER" property="dataId" />
-    <result column="resource" jdbcType="VARCHAR" property="resource" />
     <result column="version" jdbcType="VARCHAR" property="version" />
-    <result column="change_page" jdbcType="VARCHAR" property="changePage" />
+    <result column="time" jdbcType="TIMESTAMP" property="time" />
+    <result column="position" jdbcType="VARCHAR" property="position" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.CourseDataHistory">
@@ -23,7 +23,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `data_id`, `resource`, `version`, `change_page`, `create_time`
+    `id`, `data_id`, `version`, `time`, `position`, `create_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 6 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseDataMapper.xml

@@ -16,6 +16,10 @@
     <result column="pages" jdbcType="INTEGER" property="pages" />
     <result column="link" jdbcType="VARCHAR" property="link" />
     <result column="cover" jdbcType="VARCHAR" property="cover" />
+    <result column="resource" jdbcType="VARCHAR" property="resource" />
+    <result column="trail_resource" jdbcType="VARCHAR" property="trailResource" />
+    <result column="trail_start" jdbcType="INTEGER" property="trailStart" />
+    <result column="trail_end" jdbcType="INTEGER" property="trailEnd" />
     <result column="view_number" jdbcType="INTEGER" property="viewNumber" />
     <result column="sale_number" jdbcType="INTEGER" property="saleNumber" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
@@ -35,7 +39,8 @@
       WARNING - @mbg.generated
     -->
     `id`, `title`, `struct_id`, `parent_struct_id`, `data_type`, `is_novice`, `is_original`, 
-    `price`, `pages`, `link`, `cover`, `view_number`, `sale_number`, `create_time`, `update_time`
+    `price`, `pages`, `link`, `cover`, `resource`, `trail_resource`, `trail_start`, `trail_end`, 
+    `view_number`, `sale_number`, `create_time`, `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 7 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseExperienceMapper.xml

@@ -8,6 +8,11 @@
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="title" jdbcType="VARCHAR" property="title" />
+    <result column="link" jdbcType="VARCHAR" property="link" />
+    <result column="prepare_status" jdbcType="VARCHAR" property="prepareStatus" />
+    <result column="prepare_examination_time" jdbcType="VARCHAR" property="prepareExaminationTime" />
+    <result column="experience_score" jdbcType="VARCHAR" property="experienceScore" />
+    <result column="experience_percent" jdbcType="VARCHAR" property="experiencePercent" />
     <result column="view_number" jdbcType="INTEGER" property="viewNumber" />
     <result column="collect_number" jdbcType="INTEGER" property="collectNumber" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
@@ -23,7 +28,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `title`, `view_number`, `collect_number`, `create_time`, `update_time`
+    `id`, `user_id`, `title`, `link`, `prepare_status`, `prepare_examination_time`, `experience_score`, 
+    `experience_percent`, `view_number`, `collect_number`, `create_time`, `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 8 - 3
server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseMapper.xml

@@ -10,6 +10,7 @@
     <result column="parent_struct_id" jdbcType="INTEGER" property="parentStructId" />
     <result column="course_module" jdbcType="VARCHAR" property="courseModule" />
     <result column="vs_type" jdbcType="VARCHAR" property="vsType" />
+    <result column="video_type" jdbcType="VARCHAR" property="videoType" />
     <result column="extend" jdbcType="VARCHAR" property="extend" />
     <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="crowd" jdbcType="VARCHAR" property="crowd" />
@@ -18,9 +19,12 @@
     <result column="min_number" jdbcType="INTEGER" property="minNumber" />
     <result column="max_number" jdbcType="INTEGER" property="maxNumber" />
     <result column="expire_days" jdbcType="INTEGER" property="expireDays" />
+    <result column="expire_time" jdbcType="INTEGER" property="expireTime" />
+    <result column="use_expire_time" jdbcType="INTEGER" property="useExpireTime" />
     <result column="wechat_avatar" jdbcType="VARCHAR" property="wechatAvatar" />
     <result column="trail_number" jdbcType="INTEGER" property="trailNumber" />
     <result column="sale_number" jdbcType="INTEGER" property="saleNumber" />
+    <result column="package_sale_number" jdbcType="INTEGER" property="packageSaleNumber" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
   </resultMap>
@@ -44,9 +48,10 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `struct_id`, `parent_struct_id`, `course_module`, `vs_type`, `extend`, `title`, 
-    `crowd`, `price`, `teacher`, `min_number`, `max_number`, `expire_days`, `wechat_avatar`, 
-    `trail_number`, `sale_number`, `create_time`, `update_time`
+    `id`, `struct_id`, `parent_struct_id`, `course_module`, `vs_type`, `video_type`, 
+    `extend`, `title`, `crowd`, `price`, `teacher`, `min_number`, `max_number`, `expire_days`, 
+    `expire_time`, `use_expire_time`, `wechat_avatar`, `trail_number`, `sale_number`, 
+    `package_sale_number`, `create_time`, `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -11,8 +11,10 @@
     <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="resource" jdbcType="VARCHAR" property="resource" />
     <result column="time" jdbcType="INTEGER" property="time" />
+    <result column="is_trail" jdbcType="INTEGER" property="isTrail" />
     <result column="start_trail" jdbcType="INTEGER" property="startTrail" />
     <result column="end_trail" jdbcType="INTEGER" property="endTrail" />
+    <result column="trail_resource" jdbcType="VARCHAR" property="trailResource" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.CourseNo">
@@ -25,8 +27,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `course_id`, `no`, `title`, `resource`, `time`, `start_trail`, `end_trail`, 
-    `create_time`
+    `id`, `course_id`, `no`, `title`, `resource`, `time`, `is_trail`, `start_trail`, 
+    `end_trail`, `trail_resource`, `create_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 14 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseTeacherMapper.xml

@@ -8,14 +8,27 @@
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="course_id" jdbcType="INTEGER" property="courseId" />
     <result column="realname" jdbcType="VARCHAR" property="realname" />
+    <result column="avatar" jdbcType="VARCHAR" property="avatar" />
     <result column="wechat" jdbcType="VARCHAR" property="wechat" />
     <result column="qr" jdbcType="VARCHAR" property="qr" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
+  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.CourseTeacher">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <result column="description" jdbcType="LONGVARCHAR" property="description" />
+  </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `course_id`, `realname`, `wechat`, `qr`, `create_time`
+    `id`, `course_id`, `realname`, `avatar`, `wechat`, `qr`, `create_time`
+  </sql>
+  <sql id="Blob_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `description`
   </sql>
 </mapper>

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

@@ -13,6 +13,7 @@
     <result column="level" jdbcType="INTEGER" property="level" />
     <result column="question_status" jdbcType="INTEGER" property="questionStatus" />
     <result column="is_course" jdbcType="INTEGER" property="isCourse" />
+    <result column="is_data" jdbcType="INTEGER" property="isData" />
     <result column="is_sentence" jdbcType="INTEGER" property="isSentence" />
     <result column="is_examination" jdbcType="INTEGER" property="isExamination" />
     <result column="extend" jdbcType="VARCHAR" property="extend" />
@@ -28,7 +29,7 @@
       WARNING - @mbg.generated
     -->
     `id`, `title_zh`, `title_en`, `parent_id`, `order`, `level`, `question_status`, `is_course`, 
-    `is_sentence`, `is_examination`, `extend`
+    `is_data`, `is_sentence`, `is_examination`, `extend`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -9,6 +9,7 @@
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="course_id" jdbcType="INTEGER" property="courseId" />
     <result column="course_no_id" jdbcType="INTEGER" property="courseNoId" />
+    <result column="record_id" jdbcType="INTEGER" property="recordId" />
     <result column="position" jdbcType="VARCHAR" property="position" />
     <result column="answer_status" jdbcType="INTEGER" property="answerStatus" />
     <result column="manager_id" jdbcType="INTEGER" property="managerId" />
@@ -29,8 +30,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `course_id`, `course_no_id`, `position`, `answer_status`, `manager_id`, 
-    `show_status`, `answer_time`, `order`, `create_time`, `update_time`
+    `id`, `user_id`, `course_id`, `course_no_id`, `record_id`, `position`, `answer_status`, 
+    `manager_id`, `show_status`, `answer_time`, `order`, `create_time`, `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -14,12 +14,13 @@
     <result column="is_download" jdbcType="INTEGER" property="isDownload" />
     <result column="is_finish" jdbcType="INTEGER" property="isFinish" />
     <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`, `user_id`, `order_id`, `invoice_type`, `title`, `identity`, `is_download`, 
-    `is_finish`, `create_time`
+    `is_finish`, `create_time`, `update_time`
   </sql>
 </mapper>

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

@@ -7,25 +7,16 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
-    <result column="module" jdbcType="VARCHAR" property="module" />
+    <result column="product_types" jdbcType="VARCHAR" property="productTypes" />
+    <result column="pay_method" jdbcType="VARCHAR" property="payMethod" />
+    <result column="money" jdbcType="DECIMAL" property="money" />
+    <result column="invoice_money" jdbcType="DECIMAL" property="invoiceMoney" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
-  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.UserOrder">
-    <!--
-      WARNING - @mbg.generated
-    -->
-    <result column="module_extend" jdbcType="LONGVARCHAR" property="moduleExtend" />
-  </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `module`, `create_time`
-  </sql>
-  <sql id="Blob_Column_List">
-    <!--
-      WARNING - @mbg.generated
-    -->
-    `module_extend`
+    `id`, `user_id`, `product_types`, `pay_method`, `money`, `invoice_money`, `create_time`
   </sql>
 </mapper>

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

@@ -8,6 +8,7 @@
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="user_id" jdbcType="INTEGER" property="userId" />
     <result column="order_id" jdbcType="INTEGER" property="orderId" />
+    <result column="parent_record_id" jdbcType="INTEGER" property="parentRecordId" />
     <result column="product_type" jdbcType="VARCHAR" property="productType" />
     <result column="product_id" jdbcType="INTEGER" property="productId" />
     <result column="service" jdbcType="VARCHAR" property="service" />
@@ -15,6 +16,7 @@
     <result column="source" jdbcType="VARCHAR" property="source" />
     <result column="teacher_id" jdbcType="INTEGER" property="teacherId" />
     <result column="vs_number" jdbcType="INTEGER" property="vsNumber" />
+    <result column="ask_time" jdbcType="INTEGER" property="askTime" />
     <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
     <result column="end_time" jdbcType="TIMESTAMP" property="endTime" />
     <result column="use_start_time" jdbcType="TIMESTAMP" property="useStartTime" />
@@ -27,8 +29,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `order_id`, `product_type`, `product_id`, `service`, `param`, `source`, 
-    `teacher_id`, `vs_number`, `start_time`, `end_time`, `use_start_time`, `use_end_time`, 
-    `is_used`, `is_stop`, `create_time`
+    `id`, `user_id`, `order_id`, `parent_record_id`, `product_type`, `product_id`, `service`, 
+    `param`, `source`, `teacher_id`, `vs_number`, `ask_time`, `start_time`, `end_time`, 
+    `use_start_time`, `use_end_time`, `is_used`, `is_stop`, `create_time`
   </sql>
 </mapper>

+ 1 - 0
server/data/src/main/java/com/qxgmat/data/relation/UserAskQuestionRelationMapper.java

@@ -11,6 +11,7 @@ import java.util.List;
 public interface UserAskQuestionRelationMapper {
     List<UserAskQuestion> listWithUser(
             @Param("questionType") String questionType,
+            @Param("module") String module,
             @Param("userId") Number userId,
             @Param("questionNoId") Number questionNoId,
             @Param("target") String target,

+ 20 - 0
server/data/src/main/java/com/qxgmat/data/relation/UserFeedbackErrorRelationMapper.java

@@ -0,0 +1,20 @@
+package com.qxgmat.data.relation;
+
+import com.qxgmat.data.dao.entity.UserAskQuestion;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * Created by gaojie on 2017/11/9.
+ */
+public interface UserFeedbackErrorRelationMapper {
+    List<UserAskQuestion> listAdmin(
+            @Param("module") String module,
+            @Param("status") Integer status,
+            @Param("userId") Integer userId,
+            @Param("keyword") String keyword,
+            @Param("min") Integer min,
+            @Param("max") Integer max
+    );
+}

+ 1 - 1
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserAskCourseRelationMapper.xml

@@ -20,7 +20,7 @@
     <trim prefix="set" suffixOverrides=",">
       `order`#{flag}1
     </trim>
-    WHERE `question_id` = #{questionId,jdbcType=VARCHAR} and `order` #{direction} #{order,jdbcType=INT}
+    WHERE `course_id` = #{courseId,jdbcType=VARCHAR} and `order` #{direction} #{order,jdbcType=INT}
   </update>
 
   <!--用户提问列表-->

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

@@ -53,6 +53,9 @@
     <if test="showStatus != null">
       and ua.`show_status` = #{showStatus,jdbcType=INT}
     </if>
+    <if test="questionModule != null">
+      and ua.`questionModule` =#{questionModule,jdbcType=VARCHAR}
+    </if>
     <if test="questionType != null">
       and q.`questionType` =#{questionType,jdbcType=VARCHAR}
     </if>

+ 46 - 0
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserFeedbackErrorRelationMapper.xml

@@ -0,0 +1,46 @@
+<?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.relation.UserFeedbackErrorRelationMapper">
+  <resultMap id="IdMap" type="com.qxgmat.data.dao.entity.UserFeedbackError">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+  </resultMap>
+  <sql id="Id_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    ua.`id`
+  </sql>
+
+  <!--用户提问列表-->
+  <select id="listAdmin" resultMap="IdMap">
+    <bind name="keywordLike" value="'%' + keyword + '%'" />
+    select
+    <include refid="Id_Column_List" />
+    from `user_feedback_error` uf
+    left join `user` u on u.`id` = ua.`user_id`
+      <if test="userId != null">
+        and ua.`user_id` = #{userId,jdbcType=VARCHAR}
+      </if>
+      <if test="max != null">
+        and u.`total_money` &lt; ${max}
+      </if>
+      <if test="min != null">
+        and u.`total_money` &gt; ${min}
+      </if>
+    where
+    u.`id` &gt; 0
+    <if test="keyword != null">
+      and uf.`title` like #{keywordLike,jdbcType=VARCHAR}
+    </if>
+    <if test="module != null">
+      and uf.`module` = #{module,jdbcType=VARCHAR}
+    </if>
+    <if test="status != null">
+      and uf.`status` = #{status,jdbcType=INT}
+    </if>
+    order by ${order} ${direction}
+  </select>
+</mapper>

+ 22 - 4
server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java

@@ -19,6 +19,7 @@ import com.qxgmat.dto.admin.response.*;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.ManagerService;
 import com.qxgmat.service.UsersService;
+import com.qxgmat.service.extend.CourseExtendService;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -81,6 +82,9 @@ public class CourseController {
     @Autowired
     private UserOrderRecordService userOrderRecordService;
 
+    @Autowired
+    private CourseExtendService courseExtendService;
+
 
     @RequestMapping(value = "/add", method = RequestMethod.POST)
     @ApiOperation(value = "添加课程", httpMethod = "POST")
@@ -246,6 +250,14 @@ public class CourseController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/data/history/delete", method = RequestMethod.DELETE)
+    @ApiOperation(value = "删除资料历史", httpMethod = "DELETE")
+    public Response<Boolean> deleteDataHistory(@RequestParam int id, HttpServletRequest request) {
+        courseDataHistoryService.delete(id);
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/data/history/list", method = RequestMethod.GET)
     @ApiOperation(value = "资料历史列表", httpMethod = "GET")
     public Response<PageMessage<CourseDataHistory>> listDataHistory(
@@ -522,12 +534,16 @@ public class CourseController {
     @ApiOperation(value = "添加vs学员", httpMethod = "POST")
     private Response<Boolean> addStudentVs(@RequestBody @Validated UserCourseRecordDto dto){
         UserOrderRecord entity = Transform.dtoToEntity(dto);
+        Date startTime = new Date();
+        Date endTime = courseExtendService.computeExpire(startTime, dto.getVsNumber(), dto.getCourseId());
         entity.setProductType(ProductType.COURSE.key);
         entity.setOrderId(0);
         entity.setProductId(dto.getCourseId());
         entity.setTeacherId(dto.getTeacherId());
         entity.setSource("offline");
         entity.setIsUsed(1);
+        entity.setStartTime(startTime);
+        entity.setEndTime(endTime);
         userOrderRecordService.add(entity);
         return ResponseHelp.success(true);
     }
@@ -536,10 +552,9 @@ public class CourseController {
     @ApiOperation(value = "修改vs学员", httpMethod = "PUT")
     private Response<Boolean> editStudentVs(@RequestBody @Validated UserCourseRecordDto dto){
         UserOrderRecord entity = Transform.dtoToEntity(dto);
-        entity.setProductType(ProductType.COURSE.key);
-        entity.setOrderId(0);
-        entity.setProductId(dto.getCourseId());
-        entity.setTeacherId(dto.getTeacherId());
+        UserOrderRecord in = userOrderRecordService.get(entity.getId());
+        Date endTime = courseExtendService.computeExpire(in.getStartTime(), dto.getVsNumber(), dto.getCourseId());
+        entity.setEndTime(endTime);
         userOrderRecordService.edit(entity);
         return ResponseHelp.success(true);
     }
@@ -628,6 +643,9 @@ public class CourseController {
             Manager manager = shiroHelp.getLoginManager();
             entity.setManagerId(manager.getId());
         }
+        if (dto.getIgnoreStatus() != null && dto.getIgnoreStatus() > 0){
+            entity.setAnswerStatus(AskStatus.IGNORE.index);
+        }
 
         entity = userAskCourseService.edit(entity);
 

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

@@ -118,7 +118,7 @@ public class PreviewController {
             @RequestParam(required = false) Integer category,
             @RequestParam(required = false) Integer status,
             HttpSession session) {
-        Page<PreviewPaper> p = previewPaperService.listByAdmin(page, size, category, PreviewStatus.ValueOf(status));
+        Page<PreviewPaper> p = previewPaperService.listAdmin(page, size, category, PreviewStatus.ValueOf(status));
         List<PreviewListDto> pr = Transform.convert(p, PreviewListDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());

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

@@ -180,6 +180,10 @@ public class QuestionController {
             entity.setManagerId(manager.getId());
         }
 
+        if (dto.getIgnoreStatus() != null && dto.getIgnoreStatus() > 0){
+            entity.setAnswerStatus(AskStatus.IGNORE.index);
+        }
+
         entity = userAskQuestionService.edit(entity);
 
         if (dto.getOther() !=null && dto.getOther().length > 0){
@@ -224,6 +228,7 @@ public class QuestionController {
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) String questionType,
+            @RequestParam(required = false) String questionModule,
             @RequestParam(required = false) Number userId,
             @RequestParam(required = false) Number questionNoId,
             @RequestParam(required = false) String target,
@@ -233,7 +238,7 @@ public class QuestionController {
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<UserAskQuestion> p = userAskQuestionService.listWithUser(page, size, questionType, userId, questionNoId, AskTarget.ValueOf(target), AskStatus.ValueOf(answerStatus), showStatus, MoneyRange.ValueOf(moneyRang), order, DirectionStatus.ValueOf(direction));
+        Page<UserAskQuestion> p = userAskQuestionService.listWithUser(page, size, questionType, questionModule, userId, questionNoId, AskTarget.ValueOf(target), AskStatus.ValueOf(answerStatus), showStatus, MoneyRange.ValueOf(moneyRang), order, DirectionStatus.ValueOf(direction));
         List<UserAskQuestionListDto> pr = Transform.convert(p, UserAskQuestionListDto.class);
 
         // 绑定题目

+ 124 - 8
server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java

@@ -7,11 +7,13 @@ import com.qxgmat.data.constants.enums.module.FeedbackModule;
 import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.status.FeedbackStatus;
+import com.qxgmat.data.constants.enums.user.MoneyRange;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.UserPreviewPaperRelation;
 import com.qxgmat.dto.admin.extend.*;
 import com.qxgmat.dto.admin.request.UserCourseAppointmentDto;
 import com.qxgmat.dto.admin.request.UserFeedbackErrorDto;
+import com.qxgmat.dto.admin.request.UserInvoiceDto;
 import com.qxgmat.dto.admin.request.UserServiceRecordDto;
 import com.qxgmat.dto.admin.response.*;
 import com.qxgmat.help.ShiroHelp;
@@ -23,6 +25,11 @@ import com.qxgmat.service.UsersService;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -30,8 +37,11 @@ import org.springframework.http.MediaType;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
+import java.io.*;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
@@ -90,6 +100,9 @@ public class UserController {
     @Autowired
     private UserOrderRecordService userOrderRecordService;
 
+    @Autowired
+    private UserInvoiceService userInvoiceService;
+
 //    @RequestMapping(value = "/add", method = RequestMethod.POST)
 //    @ApiOperation(value = "添加用户信息", httpMethod = "POST")
 //    public Response<User> add(@RequestBody @Validated UserDto dto, HttpServletRequest request) {
@@ -267,10 +280,12 @@ public class UserController {
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false) String module,
+            @RequestParam(required = false) Integer userId,
             @RequestParam(required = false) Integer status,
             @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer moneyRang,
             HttpSession session) {
-        Page<UserFeedbackError> p = userFeedbackErrorService.listAdmin(page, size, FeedbackModule.ValueOf(module), FeedbackStatus.ValueOf(status), keyword);
+        Page<UserFeedbackError> p = userFeedbackErrorService.listAdmin(page, size, FeedbackModule.ValueOf(module), FeedbackStatus.ValueOf(status), keyword, userId, MoneyRange.ValueOf(moneyRang));
         List<UserFeedbackErrorInfoDto> pr = Transform.convert(p, UserFeedbackErrorInfoDto.class);
 
         // 绑定用户
@@ -357,13 +372,13 @@ public class UserController {
         return ResponseHelp.success(true);
     }
 
-//    @RequestMapping(value = "/course/appointment/delete", method = RequestMethod.DELETE)
-//    @ApiOperation(value = "删除课程预约", httpMethod = "DELETE")
-//    public Response<Boolean> deleteCourseAppointment(@RequestParam int id, HttpServletRequest request) {
-//        userCourseAppointmentService.delete(id);
-//        managerLogService.log(request);
-//        return ResponseHelp.success(true);
-//    }
+    @RequestMapping(value = "/course/appointment/delete", method = RequestMethod.DELETE)
+    @ApiOperation(value = "删除课程预约", httpMethod = "DELETE")
+    public Response<Boolean> deleteCourseAppointment(@RequestParam int id, HttpServletRequest request) {
+        userCourseAppointmentService.delete(id);
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
+    }
 
     @RequestMapping(value = "/course/appointment/detail", method = RequestMethod.GET)
     @ApiOperation(value = "获取课程预约", httpMethod = "GET")
@@ -401,6 +416,107 @@ public class UserController {
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
+    @RequestMapping(value = "/invoice/finish", method = RequestMethod.PUT)
+    @ApiOperation(value = "开发票", httpMethod = "PUT")
+    public Response<Boolean> finishData(
+            @RequestParam(required = false) Integer[] ids,
+            HttpServletRequest request) {
+        userInvoiceService.finish(ids);
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/invoice/download", method = RequestMethod.PUT)
+    @ApiOperation(value = "下载资料", httpMethod = "PUT")
+    public Response<Boolean> downloadData(
+            @RequestParam(required = false) Integer[] ids,
+            HttpServletRequest request, HttpServletResponse response) throws IOException {
+        managerLogService.log(request);
+        List<UserInvoice> p = userInvoiceService.select(ids);
+        String columnNames[]={"ID","项目名","销售人","负责人","所用技术","备注"};//列名
+        String filename = "发票";
+        //生成一个Excel文件
+        // 创建excel工作簿
+        Workbook wb = new HSSFWorkbook();
+        // 创建第一个sheet(页),并命名
+        Sheet sheet = wb.createSheet();
+        // 创建第一行
+        Row row = sheet.createRow(0);
+        //设置列名
+        for(int i=0;i<columnNames.length;i++){
+            Cell cell = row.createCell(i);
+            cell.setCellValue(columnNames[i]);
+        }
+        int i = 0;
+        for(UserInvoice invoice : p){
+            i+=1;
+            Row r = sheet.createRow(i);
+            Cell cell = r.createCell(0);
+            cell.setCellValue(invoice.getId());
+        }
+        //同理可以设置数据行
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        try {
+            wb.write(os);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        byte[] content = os.toByteArray();
+        InputStream is = new ByteArrayInputStream(content);
+        // 设置response参数,可以打开下载页面
+        response.reset();
+        response.setContentType("application/vnd.ms-excel;charset=utf-8");
+        response.setHeader("Content-Disposition", "attachment;filename="+ new String((filename + ".xls").getBytes(), "iso-8859-1"));
+        ServletOutputStream out = response.getOutputStream();
+        BufferedInputStream bis = null;
+        BufferedOutputStream bos = null;
+        try {
+            bis = new BufferedInputStream(is);
+            bos = new BufferedOutputStream(out);
+            byte[] buff = new byte[2048];
+            int bytesRead;
+            // Simple read/write loop.
+            while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
+                bos.write(buff, 0, bytesRead);
+            }
+        } catch (final IOException e) {
+            throw e;
+        } finally {
+            if (bis != null)
+                bis.close();
+            if (bos != null)
+                bos.close();
+        }
+        return null;
+    }
+
+    @RequestMapping(value = "/invoice/list", method = RequestMethod.GET)
+    @ApiOperation(value = "发票列表", httpMethod = "GET")
+    public Response<PageMessage<UserInvoiceListDto>> listData(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) Integer userId,
+            @RequestParam(required = false) Boolean isFinish,
+            @RequestParam(required = false) Boolean isDownload,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
+            HttpSession session) {
+        Page<UserInvoice> p = userInvoiceService.listAdmin(page, size, userId, isDownload, isFinish);
+        List<UserInvoiceListDto> pr = Transform.convert(p, UserInvoiceListDto.class);
+
+        // 绑定用户
+        Collection userIds = Transform.getIds(p, UserInvoice.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Transform.combine(pr, userList, UserInvoiceListDto.class, "userId", "user", User.class, "id", UserExtendDto.class);
+
+        // 绑定订单
+        Collection orderIds = Transform.getIds(p, UserInvoice.class, "orderId");
+        List<UserOrder> orderList = userOrderService.select(orderIds);
+        Transform.combine(pr, orderList, UserInvoiceListDto.class, "orderId", "order", UserOrder.class, "id", UserOrderExtendDto.class);
+
+        return ResponseHelp.success(pr, page, size, p.getTotal());
+    }
+
     @RequestMapping(value = "/valid/mobile", method = RequestMethod.GET)
     @ApiOperation(value = "验证手机号", notes="查询手机对应账号", httpMethod = "GET")
     public Response<Boolean> validMobile(

+ 45 - 9
server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java

@@ -156,17 +156,16 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
-    @RequestMapping(value = "/real", produces = MediaType.IMAGE_JPEG_VALUE, method = RequestMethod.POST)
+    @RequestMapping(value = "/real/front", produces = MediaType.IMAGE_JPEG_VALUE, method = RequestMethod.POST)
     @ApiOperation(value = "实名认证", notes = "保存用户实名信息", httpMethod = "POST")
-    public Response<UserRealDto> real(@RequestParam("front") MultipartFile front, @RequestParam("back") MultipartFile back) throws IOException {
-        if (front.isEmpty() || back.isEmpty()) {
+    public Response<UserRealDto> realFront(@RequestParam("file") MultipartFile file) throws IOException {
+        if (file.isEmpty()) {
             throw new ParameterException("上传文件为空");
         }
         User user = (User) shiroHelp.getLoginUser();
         UserRealDto dto = new UserRealDto();
 
-        aiHelp.orcIdcardBack(back.getBytes());
-        Map<String, String> map = aiHelp.orcIdcardFront(front.getBytes());
+        Map<String, String> map = aiHelp.orcIdcardFront(file.getBytes());
         dto.setName(map.get("name"));
         dto.setAddress(map.get("address"));
         dto.setIdentity(map.get("identity"));
@@ -175,17 +174,40 @@ public class MyController {
         String backName = UUID.randomUUID().toString();
         try {
             File frontDest = new File(localPath + File.separator+frontName);
-            front.transferTo(frontDest);
+            file.transferTo(frontDest);
             dto.setPhotoFront(webUrl+frontName);
-            File backDest = new File(localPath + File.separator+backName);
-            back.transferTo(backDest);
-            dto.setPhotoBack(webUrl+backName);
             usersService.edit(User.builder()
                     .id(user.getId())
                     .realAddress(dto.getAddress())
                     .realName(dto.getName())
                     .realIdentity(dto.getIdentity())
                     .realPhotoFront(dto.getPhotoFront())
+                    .build());
+            // todo 180天vip
+            return ResponseHelp.success(dto);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return ResponseHelp.exception(new SystemException("图片上传失败"));
+        }
+    }
+
+    @RequestMapping(value = "/real/back", produces = MediaType.IMAGE_JPEG_VALUE, method = RequestMethod.POST)
+    @ApiOperation(value = "实名认证", notes = "保存用户实名信息", httpMethod = "POST")
+    public Response<UserRealDto> realBack(@RequestParam("file") MultipartFile file) throws IOException {
+        if (file.isEmpty()) {
+            throw new ParameterException("上传文件为空");
+        }
+        User user = (User) shiroHelp.getLoginUser();
+        UserRealDto dto = new UserRealDto();
+
+        aiHelp.orcIdcardBack(file.getBytes());
+        String backName = UUID.randomUUID().toString();
+        try {
+            File backDest = new File(localPath + File.separator+backName);
+            file.transferTo(backDest);
+            dto.setPhotoBack(webUrl+backName);
+            usersService.edit(User.builder()
+                    .id(user.getId())
                     .realPhotoBack(dto.getPhotoBack())
                     .build());
             // todo 180天vip
@@ -196,6 +218,20 @@ public class MyController {
         }
     }
 
+    @RequestMapping(value = "/real/finish", produces = MediaType.IMAGE_JPEG_VALUE, method = RequestMethod.POST)
+    @ApiOperation(value = "实名认证", notes = "保存用户实名信息", httpMethod = "POST")
+    public Response<UserRealDto> realFinish() {
+        User user = (User) shiroHelp.getLoginUser();
+        UserRealDto dto = new UserRealDto();
+
+        usersService.edit(User.builder()
+                .id(user.getId())
+                .realStatus(1)
+                .build());
+        // todo 180天vip
+        return ResponseHelp.success(dto);
+    }
+
     @RequestMapping(value = "/message", method = RequestMethod.GET)
     @ApiOperation(value = "用户站内信", notes = "用户消息列表", httpMethod = "GET")
     public Response<PageMessage<UserMessage>> message(

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

@@ -11,6 +11,8 @@ public class UserExtendDto {
 
     private String mobile;
 
+    private String email;
+
     private Integer totalMoney;
 
     public Integer getId() {
@@ -44,4 +46,12 @@ public class UserExtendDto {
     public void setTotalMoney(Integer totalMoney) {
         this.totalMoney = totalMoney;
     }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
 }

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

@@ -0,0 +1,49 @@
+package com.qxgmat.dto.admin.extend;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserOrder;
+
+import java.math.BigDecimal;
+
+@Dto(entity = UserOrder.class)
+public class UserOrderExtendDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private BigDecimal money;
+
+    private BigDecimal invoiceMoney;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public BigDecimal getMoney() {
+        return money;
+    }
+
+    public void setMoney(BigDecimal money) {
+        this.money = money;
+    }
+
+    public BigDecimal getInvoiceMoney() {
+        return invoiceMoney;
+    }
+
+    public void setInvoiceMoney(BigDecimal invoiceMoney) {
+        this.invoiceMoney = invoiceMoney;
+    }
+}

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

@@ -17,6 +17,10 @@ public class CourseDto {
 
     private String courseModule;
 
+    private String vsType;
+
+    private String videoType;
+
     private BigDecimal price;
 
     public Integer getId() {
@@ -66,4 +70,20 @@ public class CourseDto {
     public void setCourseModule(String courseModule) {
         this.courseModule = courseModule;
     }
+
+    public String getVsType() {
+        return vsType;
+    }
+
+    public void setVsType(String vsType) {
+        this.vsType = vsType;
+    }
+
+    public String getVideoType() {
+        return videoType;
+    }
+
+    public void setVideoType(String videoType) {
+        this.videoType = videoType;
+    }
 }

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

@@ -21,6 +21,14 @@ public class CourseNoDto {
 
     private String content;
 
+    private Integer isTrail;
+
+    private Integer startTrail;
+
+    private Integer endTrail;
+
+    private String trailResource;
+
     public Integer getId() {
         return id;
     }
@@ -76,4 +84,36 @@ public class CourseNoDto {
     public void setTime(Integer time) {
         this.time = time;
     }
+
+    public Integer getIsTrail() {
+        return isTrail;
+    }
+
+    public void setIsTrail(Integer isTrail) {
+        this.isTrail = isTrail;
+    }
+
+    public String getTrailResource() {
+        return trailResource;
+    }
+
+    public void setTrailResource(String trailResource) {
+        this.trailResource = trailResource;
+    }
+
+    public Integer getStartTrail() {
+        return startTrail;
+    }
+
+    public void setStartTrail(Integer startTrail) {
+        this.startTrail = startTrail;
+    }
+
+    public Integer getEndTrail() {
+        return endTrail;
+    }
+
+    public void setEndTrail(Integer endTrail) {
+        this.endTrail = endTrail;
+    }
 }

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

@@ -11,12 +11,16 @@ public class CourseTeacherDto {
 
     private Integer courseId;
 
+    private String avatar;
+
     private String realname;
 
     private String wechat;
 
     private String qr;
 
+    private String description;
+
     private Date createTime;
 
     public Integer getId() {
@@ -66,4 +70,20 @@ public class CourseTeacherDto {
     public void setCreateTime(Date createTime) {
         this.createTime = createTime;
     }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
 }

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

@@ -14,10 +14,14 @@ public class UserAskCourseDto {
     @NotEmpty(message = "回答不允许为空")
     private String answer;
 
+    private String content;
+
     private Integer[] other;
 
     private Integer showStatus;
 
+    private Integer ignoreStatus;
+
     public Integer getId() {
         return id;
     }
@@ -49,4 +53,20 @@ public class UserAskCourseDto {
     public void setShowStatus(Integer showStatus) {
         this.showStatus = showStatus;
     }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Integer getIgnoreStatus() {
+        return ignoreStatus;
+    }
+
+    public void setIgnoreStatus(Integer ignoreStatus) {
+        this.ignoreStatus = ignoreStatus;
+    }
 }

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

@@ -1,32 +0,0 @@
-package com.qxgmat.dto.admin.request;
-
-import com.nuliji.tools.annotation.Dto;
-import com.qxgmat.data.dao.entity.UserAskQuestion;
-
-import javax.validation.constraints.NotEmpty;
-
-@Dto(entity = UserAskQuestion.class)
-public class UserAskOrderDto {
-
-    @NotEmpty(message = "提问id不允许为空!")
-    private Integer id;
-
-    @NotEmpty(message = "排序不允许为空")
-    private Integer order;
-
-    public Integer getId() {
-        return id;
-    }
-
-    public void setId(Integer id) {
-        this.id = id;
-    }
-
-    public Integer getOrder() {
-        return order;
-    }
-
-    public void setOrder(Integer order) {
-        this.order = order;
-    }
-}

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

@@ -14,10 +14,14 @@ public class UserAskQuestionDto {
     @NotEmpty(message = "回答不允许为空")
     private String answer;
 
+    private String content;
+
     private Integer[] other;
 
     private Integer showStatus;
 
+    private Integer ignoreStatus;
+
     public Integer getId() {
         return id;
     }
@@ -49,4 +53,20 @@ public class UserAskQuestionDto {
     public void setShowStatus(Integer showStatus) {
         this.showStatus = showStatus;
     }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Integer getIgnoreStatus() {
+        return ignoreStatus;
+    }
+
+    public void setIgnoreStatus(Integer ignoreStatus) {
+        this.ignoreStatus = ignoreStatus;
+    }
 }

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

@@ -0,0 +1,37 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserInvoice;
+
+@Dto(entity = UserInvoice.class)
+public class UserInvoiceDto {
+    private Integer id;
+
+    private Integer isDownload;
+
+    private Integer isFinish;
+
+    public Integer getIsDownload() {
+        return isDownload;
+    }
+
+    public void setIsDownload(Integer isDownload) {
+        this.isDownload = isDownload;
+    }
+
+    public Integer getIsFinish() {
+        return isFinish;
+    }
+
+    public void setIsFinish(Integer isFinish) {
+        this.isFinish = isFinish;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+}

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

@@ -18,6 +18,8 @@ public class CourseListDto {
 
     private String vsType;
 
+    private String videoType;
+
     private String title;
 
     private String crowd;
@@ -34,6 +36,8 @@ public class CourseListDto {
 
     private Integer saleNumber;
 
+    private Integer packageSaleNumber;
+
     private Date createTime;
 
     private Date updateTime;
@@ -158,4 +162,20 @@ public class CourseListDto {
     public void setVsType(String vsType) {
         this.vsType = vsType;
     }
+
+    public String getVideoType() {
+        return videoType;
+    }
+
+    public void setVideoType(String videoType) {
+        this.videoType = videoType;
+    }
+
+    public Integer getPackageSaleNumber() {
+        return packageSaleNumber;
+    }
+
+    public void setPackageSaleNumber(Integer packageSaleNumber) {
+        this.packageSaleNumber = packageSaleNumber;
+    }
 }

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

@@ -13,6 +13,8 @@ public class UserAskQuestionDetailDto {
 
     private Integer userQuestionId;
 
+    private String questionModule;
+
     private QuestionExtendDto question;
 
     private QuestionNoExtendDto questionNo;
@@ -128,4 +130,12 @@ public class UserAskQuestionDetailDto {
     public void setUserQuestionId(Integer userQuestionId) {
         this.userQuestionId = userQuestionId;
     }
+
+    public String getQuestionModule() {
+        return questionModule;
+    }
+
+    public void setQuestionModule(String questionModule) {
+        this.questionModule = questionModule;
+    }
 }

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

@@ -19,6 +19,8 @@ public class UserAskQuestionListDto {
 
     private String target;
 
+    private String questionModule;
+
     private QuestionExtendDto question;
 
     private QuestionNoExtendDto questionNo;
@@ -120,4 +122,12 @@ public class UserAskQuestionListDto {
     public void setAnswerTime(Date answerTime) {
         this.answerTime = answerTime;
     }
+
+    public String getQuestionModule() {
+        return questionModule;
+    }
+
+    public void setQuestionModule(String questionModule) {
+        this.questionModule = questionModule;
+    }
 }

+ 15 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserInvoiceListDto.java

@@ -0,0 +1,15 @@
+package com.qxgmat.dto.admin.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserInvoice;
+import com.qxgmat.dto.admin.extend.UserExtendDto;
+import com.qxgmat.dto.admin.extend.UserOrderExtendDto;
+
+@Dto(entity = UserInvoice.class)
+public class UserInvoiceListDto {
+    private Integer id;
+
+    private UserOrderExtendDto order;
+
+    private UserExtendDto user;
+}

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

@@ -1,14 +1,38 @@
 package com.qxgmat.service.extend;
 
+import com.nuliji.tools.Tools;
+import com.qxgmat.data.constants.enums.module.ProductType;
+import com.qxgmat.data.dao.entity.Course;
+import com.qxgmat.data.dao.entity.UserOrderRecord;
 import com.qxgmat.service.inline.CourseService;
+import com.qxgmat.service.inline.UserOrderRecordService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.Date;
+
 @Service
 public class CourseExtendService {
 
     @Autowired
     private CourseService courseService;
 
+    @Autowired
+    private UserOrderRecordService userOrderRecordService;
+
+    /**
+     * 计算vs课程有效期
+     * @param startTime
+     * @param vsNumber
+     * @param courseId
+     */
+    public Date computeExpire(Date startTime, Integer vsNumber, Integer courseId){
+        // 计算有效期
+        Course course = courseService.get(courseId);
+        // day / 10
+        Integer expireDays = course.getExpireDays();
 
+        int day = Math.min(vsNumber / 10, 0)  * expireDays;
+        return Tools.addDate(startTime, day);
+    }
 }

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

@@ -76,7 +76,6 @@ public class TradeService extends AbstractService {
             JSONObject extend = JSONObject.parseObject(pay.getModuleExtend());
             // 写入用户购买
             userOrderService.add(UserOrder.builder()
-                    .module(pay.getModule())
                     .userId(userId).build());
             return true;
         });
@@ -86,7 +85,6 @@ public class TradeService extends AbstractService {
             JSONObject extend = JSONObject.parseObject(pay.getModuleExtend());
             // 写入用户购买
             userOrderService.add(UserOrder.builder()
-                    .module(pay.getModule())
                     .userId(userId).build());
             return true;
         });
@@ -96,7 +94,6 @@ public class TradeService extends AbstractService {
             JSONObject extend = JSONObject.parseObject(pay.getModuleExtend());
             // 写入用户购买
             userOrderService.add(UserOrder.builder()
-                    .module(pay.getModule())
                     .userId(userId).build());
             // 直接绑定数据关系
             return true;

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

@@ -78,6 +78,10 @@ public class ManagerRoleService extends AbstractService {
         return select(managerRoleMapper, example, page, pageSize);
     }
 
+    public Page<ManagerRole> select(Integer[] ids){
+        return page(()->select(managerRoleMapper, ids), 1, ids.length);
+    }
+
     public List<ManagerRole> select(Collection ids){
         return select(managerRoleMapper, ids);
     }

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

@@ -37,7 +37,7 @@ public class PreviewPaperService extends AbstractService {
     @Resource
     private PreviewPaperMapper previewPaperMapper;
 
-    public Page<PreviewPaper> listByAdmin(int page, int pageSize, Integer category, PreviewStatus status){
+    public Page<PreviewPaper> listAdmin(int page, int pageSize, Integer category, PreviewStatus status){
         Example example = new Example(PreviewPaper.class);
         if(category != null && category>0)
             example.and(

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

@@ -34,7 +34,7 @@ public class UserAskQuestionService extends AbstractService {
     @Resource
     private UserAskQuestionRelationMapper userAskQuestionRelationMapper;
 
-    public Page<UserAskQuestion> listWithUser(int page, int size, String questionType, Number userId, Number questionNoId, AskTarget target, AskStatus status, Integer showStatus, MoneyRange moneyRange, String order, DirectionStatus direction){
+    public Page<UserAskQuestion> listWithUser(int page, int size, String questionType, String questionModule, Number userId, Number questionNoId, AskTarget target, AskStatus status, Integer showStatus, MoneyRange moneyRange, String order, DirectionStatus direction){
         String tk = target != null ? target.key : "";
         Integer statusIndex = status != null ? status.index : null;
         Integer max = moneyRange != null ? moneyRange.max == Integer.MAX_VALUE ? null : moneyRange.max : null;
@@ -46,7 +46,7 @@ public class UserAskQuestionService extends AbstractService {
         String finalOrder = order;
         DirectionStatus finalDirection = direction;
         Page<UserAskQuestion> p = page(
-                ()-> userAskQuestionRelationMapper.listWithUser(questionType, userId, questionNoId, tk, statusIndex, showStatus, min, max, finalOrder, finalDirection.key)
+                ()-> userAskQuestionRelationMapper.listWithUser(questionType, questionModule, userId, questionNoId, tk, statusIndex, showStatus, min, max, finalOrder, finalDirection.key)
         , page, size);
 
         Collection ids = Transform.getIds(p, UserAskQuestion.class, "id");

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

@@ -8,8 +8,10 @@ import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
 import com.qxgmat.data.constants.enums.module.FeedbackModule;
 import com.qxgmat.data.constants.enums.status.FeedbackStatus;
+import com.qxgmat.data.constants.enums.user.MoneyRange;
 import com.qxgmat.data.dao.UserFeedbackErrorMapper;
 import com.qxgmat.data.dao.entity.UserFeedbackError;
+import com.qxgmat.data.relation.UserFeedbackErrorRelationMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -25,27 +27,22 @@ public class UserFeedbackErrorService extends AbstractService {
     @Resource
     private UserFeedbackErrorMapper userFeedbackErrorMapper;
 
+    @Resource
+    private UserFeedbackErrorRelationMapper userFeedbackErrorRelationMapper;
+
+
+    public Page<UserFeedbackError> listAdmin(int page, int size, FeedbackModule module, FeedbackStatus status, String keyword, Integer userId, MoneyRange moneyRange){
+        String moduleKey = module == null ? null : module.key;
+        Integer statusIndex = status == null ? null : status.index;
+        Integer max = moneyRange != null ? moneyRange.max == Integer.MAX_VALUE ? null : moneyRange.max : null;
+        Integer min = moneyRange != null ? moneyRange.min : null;
+        Page<UserFeedbackError> p = page(
+                ()-> userFeedbackErrorRelationMapper.listAdmin(moduleKey, statusIndex, userId, keyword, min, max)
+        , page, size);
 
-    public Page<UserFeedbackError> listAdmin(int page, int pageSize, FeedbackModule module, FeedbackStatus status, String keyword){
-//        String moduleKey = module != null ? module.key : "";
-//        Integer statusIndex = status != null ? status.index : null;
-        Example example = new Example(UserFeedbackError.class);
-        if (module != null)
-            example.and(
-                    example.createCriteria()
-                    .andEqualTo("module", module.key)
-            );
-        if (status != null)
-            example.and(
-                    example.createCriteria()
-                            .andEqualTo("status", status.index)
-            );
-        if(keyword != null)
-            example.and(
-                    example.createCriteria()
-                            .andLike("title", "%"+keyword+"%")
-            );
-        return select(userFeedbackErrorMapper, example, page, pageSize);
+        Collection ids = Transform.getIds(p, UserFeedbackError.class, "id");
+        Transform.replace(p, select(ids), UserFeedbackError.class, "id");
+        return p;
     }
 
     public UserFeedbackError add(UserFeedbackError feedbackError){

+ 106 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserInvoiceService.java

@@ -0,0 +1,106 @@
+package com.qxgmat.service.inline;
+
+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.UserInvoiceMapper;
+import com.qxgmat.data.dao.entity.UserInvoice;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class UserInvoiceService extends AbstractService {
+    private static final Logger logger = LoggerFactory.getLogger(UserInvoice.class);
+
+    @Resource
+    private UserInvoiceMapper userInvoiceMapper;
+
+    public Page<UserInvoice> listAdmin(int page, int size, Integer userId, Boolean isDownload, Boolean isFinish){
+        Example example = new Example(UserInvoice.class);
+        if(userId != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("userId", userId)
+            );
+        }
+        if(isDownload != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("isDownload", isDownload ? 1 : 0)
+            );
+        }
+        if(isFinish != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("isFinish", isFinish ? 1 : 0)
+            );
+        }
+        example.orderBy("id").desc();
+        return select(userInvoiceMapper, example, page, size);
+    }
+
+    public void finish(Integer[] ids){
+        Example example = new Example(UserInvoice.class);
+        example.and(
+                example.createCriteria().andIn("id", Arrays.stream(ids).collect(Collectors.toList()))
+        );
+        update(userInvoiceMapper, example, UserInvoice.builder().isFinish(1).build());
+    }
+
+    public UserInvoice add(UserInvoice invoice){
+        int result = insert(userInvoiceMapper, invoice);
+        invoice = one(userInvoiceMapper, invoice.getId());
+        if(invoice == null){
+            throw new SystemException("发票添加失败");
+        }
+        return invoice;
+    }
+
+    public UserInvoice edit(UserInvoice invoice){
+        UserInvoice in = one(userInvoiceMapper, invoice.getId());
+        if(in == null){
+            throw new ParameterException("发票不存在");
+        }
+        int result = update(userInvoiceMapper, invoice);
+        return invoice;
+    }
+
+    public boolean delete(Number id){
+        UserInvoice in = one(userInvoiceMapper, id);
+        if(in == null){
+            throw new ParameterException("发票不存在");
+        }
+        int result = delete(userInvoiceMapper, id);
+        return result > 0;
+    }
+
+    public UserInvoice get(Number id){
+        UserInvoice in = one(userInvoiceMapper, id);
+
+        if(in == null){
+            throw new ParameterException("发票不存在");
+        }
+        return in;
+    }
+
+    public Page<UserInvoice> select(int page, int pageSize){
+        return select(userInvoiceMapper, page, pageSize);
+    }
+
+    public Page<UserInvoice> select(Integer[] ids){
+        return page(()->select(userInvoiceMapper, ids), 1, ids.length);
+    }
+
+    public List<UserInvoice> select(Collection ids){
+        return select(userInvoiceMapper, ids);
+    }
+}