Browse Source

Merge branch 'master' of https://git.proginn.com/zaixianjiaoyu/sourcecode

KaysonCui 5 years ago
parent
commit
ca6e08c7e9
51 changed files with 1381 additions and 97 deletions
  1. 0 4
      front/project/admin/routes/setting/index/index.less
  2. 15 1
      front/project/admin/routes/setting/struct/page.js
  3. 3 2
      front/project/admin/routes/subject/exercise/page.js
  4. 2 2
      front/project/admin/routes/subject/previewDetail/page.js
  5. 34 0
      front/project/admin/routes/subject/sentence/page.js
  6. 2 2
      front/project/admin/routes/subject/sentenceArticle/page.js
  7. 167 1
      front/project/admin/routes/user/ask/page.js
  8. 15 0
      front/project/admin/routes/user/askDetail/index.js
  9. 3 0
      front/project/admin/routes/user/askDetail/index.less
  10. 168 0
      front/project/admin/routes/user/askDetail/page.js
  11. 15 0
      front/project/admin/routes/user/detail/index.js
  12. 18 0
      front/project/admin/routes/user/detail/index.less
  13. 239 0
      front/project/admin/routes/user/detail/page.js
  14. 3 1
      front/project/admin/routes/user/index.js
  15. 1 1
      front/project/admin/routes/user/list/index.js
  16. 123 1
      front/project/admin/routes/user/list/page.js
  17. 173 1
      front/project/admin/routes/user/preview/page.js
  18. 5 1
      front/project/admin/stores/user.js
  19. 1 1
      front/src/containers/Page.js
  20. 13 1
      front/src/services/Constant.js
  21. 25 0
      front/src/style/admin.less
  22. 4 0
      front/src/style/adminLeft.less
  23. 8 2
      server/data/src/main/java/com/qxgmat/data/constants/enums/QuestionType.java
  24. 3 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/ServiceKey.java
  25. 14 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/module/PayModule.java
  26. 25 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/status/AskStatus.java
  27. 18 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/user/PrepareExaminationTime.java
  28. 19 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/user/PrepareStatus.java
  29. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/ExaminationStruct.java
  30. 39 4
      server/data/src/main/java/com/qxgmat/data/dao/entity/User.java
  31. 7 7
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserAsk.java
  32. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/ExaminationStructMapper.xml
  33. 3 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserMapper.xml
  34. 1 0
      server/data/src/main/java/com/qxgmat/data/relation/ExerciseQuestionRelationMapper.java
  35. 1 0
      server/data/src/main/java/com/qxgmat/data/relation/UserAskRelationMapper.java
  36. 3 0
      server/data/src/main/java/com/qxgmat/data/relation/mapping/ExerciseQuestionRelationMapper.xml
  37. 5 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserAskRelationMapper.xml
  38. 2 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/ExerciseController.java
  39. 9 4
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserAskController.java
  40. 28 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  41. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/UserClassExtendDto.java
  42. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/UserServiceExtendDto.java
  43. 30 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserAskDetailDto.java
  44. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserAskListDto.java
  45. 8 46
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserHomeworkPreviewListDto.java
  46. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserListDto.java
  47. 2 1
      server/gateway-api/src/main/java/com/qxgmat/service/UsersService.java
  48. 2 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExerciseQuestionService.java
  49. 12 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/SettingService.java
  50. 4 2
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserAskService.java
  51. 2 4
      server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java

+ 0 - 4
front/project/admin/routes/setting/index/index.less

@@ -1,10 +1,6 @@
 @charset "utf-8";
 
 #setting-index {
-  .block {
-    margin-bottom: 20px;
-  }
-
   .ant-card.plus {
     text-align: center;
     cursor: pointer;

+ 15 - 1
front/project/admin/routes/setting/struct/page.js

@@ -153,7 +153,21 @@ export default class extends Page {
     Examination.allStruct().then(result => {
       const list = result.map(row => { row.title = `${row.titleZh}/${row.titleEn}`; return row; });
       this.examinationItemList[1].tree = formatTreeData([{ title: '根节点', id: 0 }].concat(list), 'id', 'title', 'parentId');
-      this.setState({ examinationList: list, examinationStruct: formatTreeData(list, 'id', 'title', 'parentId') });
+      this.setState({
+        examinationList: list,
+        examinationStruct: formatTreeData(list.map(row => {
+          if (row.level !== 2) return row;
+          row = Object.assign({}, row);
+          row.title = <div className='node'>{row.title}<Button className='after-node' size='small' type={row.questionStatus > 0 ? 'primary' : 'ghost'} onClick={(e) => {
+            e.preventDefault();
+            row.payStatus = row.payStatus > 0 ? 0 : 1;
+            Examination.editStruct(row).then(() => {
+              this.refresh();
+            });
+          }}>{row.payStatus > 0 ? [<Icon type="pay-circle" />, <span>付费</span>] : [<Icon type="eye" />, <span>免费</span>]}</Button></div>;
+          return row;
+        }), 'id', 'title', 'parentId'),
+      });
     });
   }
 

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

@@ -71,6 +71,7 @@ const filterForm = [
   {
     key: 'question_no_id',
     type: 'select',
+    allowClear: true,
     name: '题目ID',
     select: [],
     number: true,
@@ -207,7 +208,7 @@ export default class extends Page {
         title: row.title,
         value: row.id,
       };
-    }, [], null);
+    }, this.state.search.paper_id ? [Number(this.state.search.paper_id)] : [], null);
     bindSearch(filterForm, 'question_no_id', this, (search) => {
       return Question.searchNo(search);
     }, (row) => {
@@ -215,7 +216,7 @@ export default class extends Page {
         title: row.no,
         value: row.id,
       };
-    }, [], null);
+    }, this.state.search.question_no_id ? [Number(this.state.search.question_no_id)] : [], null);
     this.initAuto();
   }
 

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

@@ -191,7 +191,7 @@ export default class extends Page {
   }
 
   renderView() {
-    return <Block flex>
+    return <div flex>
       {this.renderBase()}
       {this.renderQuestionList()}
 
@@ -202,6 +202,6 @@ export default class extends Page {
           }}>保存</Button>
         </Col>
       </Row>
-    </Block>;
+    </div>;
   }
 }

+ 34 - 0
front/project/admin/routes/subject/sentence/page.js

@@ -12,6 +12,7 @@ import TableLayout from '@src/layouts/TableLayout';
 import { getMap } from '@src/services/Tools';
 import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
 import { Sentence } from '../../../stores/sentence';
+import { Slient } from '../../../stores/slient';
 
 const filterForm = [
   {
@@ -39,6 +40,9 @@ export default class extends Page {
       key: 'struct',
       name: '编辑章节',
     }, {
+      key: 'auto',
+      name: '重新组卷',
+    }, {
       key: 'article',
       name: '新建文章',
       render: (item) => {
@@ -107,6 +111,27 @@ export default class extends Page {
     }];
   }
 
+  initAuto() {
+    this.outPage();
+    this.interval = setInterval(() => {
+      Slient.sentenceAuto().then((result) => {
+        if (result.process == null || result.process === 100) {
+          this.actionList[1].disabled = false;
+          result.process = 100;
+        } else {
+          this.actionList[1].disabled = true;
+        }
+        this.setState({ process: result.process });
+      });
+    }, 30000);
+  }
+
+  outPage() {
+    if (this.interval) {
+      clearInterval(this.interval);
+    }
+  }
+
   init() {
     Sentence.getStruct().then(result => {
       return this.refreshStruct(result);
@@ -141,6 +166,15 @@ export default class extends Page {
     this.setState({ struct: result });
   }
 
+  autoAction() {
+    asyncDelConfirm('组卷确认', '是否重新组卷?', () => {
+      return Sentence.auto();
+    }).then(() => {
+      asyncSMessage('开始组卷!');
+      this.refresh();
+    });
+  }
+
   structAction() {
     const { struct = {} } = this.state;
     this.open(struct);

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

@@ -154,7 +154,7 @@ export default class extends Page {
   }
 
   renderView() {
-    return <Block flex>
+    return <div flex>
       {this.renderBase()}
       {this.renderContent()}
       <Row type="flex" justify="center">
@@ -164,6 +164,6 @@ export default class extends Page {
           }}>保存</Button>
         </Col>
       </Row>
-    </Block>;
+    </div>;
   }
 }

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

@@ -1,10 +1,176 @@
 import React from 'react';
+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, bindSearch, formatDate } from '@src/services/Tools';
+import { QuestionType, AskStatus, MoneyRange, SwitchSelect, AskTarget } from '@src/services/Constant';
+import { User } from '../../../stores/user';
+import { Question } from '../../../stores/question';
 
+const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
+const AskStatusMap = getMap(AskStatus, 'value', 'label');
+const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
 export default class extends Page {
+  init() {
+    this.filterForm = [{
+      key: 'type',
+      type: 'select',
+      allowClear: true,
+      name: '题型',
+      select: QuestionType,
+      placeholder: '请选择',
+      number: true,
+    }, {
+      key: 'status',
+      type: 'select',
+      allowClear: true,
+      name: '状态',
+      select: AskStatus,
+    }, {
+      key: 'money',
+      type: 'select',
+      allowClear: true,
+      name: '消费金额',
+      select: MoneyRange,
+    }, {
+      key: 'show_status',
+      type: 'select',
+      allowClear: true,
+      name: '展示状态',
+      select: SwitchSelect,
+    }, {
+      key: 'target',
+      type: 'select',
+      allowClear: true,
+      name: '提问内容',
+      select: AskTarget,
+    }, {
+      key: 'question_no_id',
+      type: 'select',
+      allowClear: true,
+      name: '题目ID',
+      select: [],
+      number: true,
+      placeholder: '请输入',
+    }, {
+      key: 'user_id',
+      type: 'select',
+      name: '用户',
+      allowClear: true,
+      select: [],
+      number: true,
+      placeholder: '请输入',
+    }];
+    this.columns = [
+      {
+        title: '题型',
+        dataIndex: 'type',
+        render: (text, record) => {
+          return QuestionTypeMap[record.question.type];
+        },
+      },
+      {
+        title: '题目id',
+        dataIndex: 'questionNo.no',
+      },
+      {
+        title: '提问时间',
+        dataIndex: 'createTime',
+        render: (text) => {
+          return formatDate(text);
+        },
+      },
+      {
+        title: '提问摘要',
+        dataIndex: 'content',
+      }, {
+        title: '提问者',
+        dataIndex: 'user.nickname',
+      }, {
+        title: '回答状态',
+        dataIndex: 'answerStatus',
+        render: (text) => {
+          return AskStatusMap[text] || text;
+        },
+      }, {
+        title: '回答者',
+        dataIndex: 'manager.username',
+      }, {
+        title: '回答时间',
+        dataIndex: 'answerTime',
+      }, {
+        title: '展示状态',
+        dataIndex: 'showStatus',
+        render: (text) => {
+          return SwitchSelectMap[text] || text;
+        },
+      }, {
+        title: '操作',
+        dataIndex: 'handler',
+        render: (text, record) => {
+          return <div className="table-button">
+            {(
+              <Link to={`/user/ask/detail/${record.id}`}>编辑</Link>
+            )}
+          </div>;
+        },
+      },
+    ];
+    bindSearch(this.filterForm, 'user_id', this, (search) => {
+      return User.list(search);
+    }, (row) => {
+      return {
+        title: `${row.nickname}(${row.mobile})`,
+        value: row.id,
+      };
+    }, this.state.search.user_id ? [Number(this.state.search.user_id)] : [], null);
+    bindSearch(this.filterForm, 'question_no_id', this, (search) => {
+      return Question.searchNo(search);
+    }, (row) => {
+      return {
+        title: row.no,
+        value: row.id,
+      };
+    }, this.state.search.question_no_id ? [Number(this.state.search.question_no_id)] : [], null);
+  }
+
+  initData() {
+    User.listAsk(this.state.search).then(result => {
+      this.setTableData(result.list, result.total);
+    });
+  }
+
   renderView() {
-    return <Block flex />;
+    return <Block flex>
+      <FilterLayout
+        show
+        itemList={this.filterForm}
+        data={this.state.search}
+        onChange={data => {
+          if (data.time.length > 0) {
+            data.time = [data.time[0].format('YYYY-MM-DD HH:mm:ss'), data.time[1].format('YYYY-MM-DD HH:mm:ss')];
+          }
+          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>;
   }
 }

+ 15 - 0
front/project/admin/routes/user/askDetail/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/user/ask/detail/:id?',
+  key: 'user-ask-detail',
+  title: '问答详情',
+  needLogin: true,
+  module,
+  group,
+  showKey: 'user-ask',
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/admin/routes/user/askDetail/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#user-ask-detail {}

+ 168 - 0
front/project/admin/routes/user/askDetail/page.js

@@ -0,0 +1,168 @@
+import React from 'react';
+import { Form, Button, Row, Col, List, Icon, Switch, Typography } from 'antd';
+import './index.less';
+import Editor from '@src/components/Editor';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import DragList from '@src/components/DragList';
+// import FileUpload from '@src/components/FileUpload';
+import { formatFormError, formatDate, getMap } from '@src/services/Tools';
+import { AskTarget, QuestionType } from '@src/services/Constant';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { User } from '../../../stores/user';
+
+const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
+const AskTargetMap = getMap(AskTarget, 'value', 'label');
+export default class extends Page {
+  init() {
+    // Exercise.allStruct().then(result => {
+    //   result = result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; });
+    //   this.setState({ exercise: result });
+    // });
+  }
+
+  initData() {
+    const { id } = this.params;
+    let handler;
+    if (id) {
+      handler = User.getAsk({ id });
+    } else {
+      handler = Promise.resolve({ others: [{ content: 123123123, answer: 123123 }, {}] });
+    }
+    handler
+      .then(result => {
+        const { getFieldDecorator, setFieldsValue } = this.props.form;
+        getFieldDecorator('id');
+        getFieldDecorator('answer');
+        getFieldDecorator('showStatus');
+        setFieldsValue({ id: result.id, answer: result.answer, showStatus: result.showStatus });
+        this.setState({ data: result });
+      });
+  }
+
+  orderQuestion(oldIndex, newIndex) {
+    const { data } = this.state;
+    const { other = [] } = data;
+    const tmp = other[oldIndex];
+    other[oldIndex] = other[newIndex];
+    other[newIndex] = tmp;
+    this.setState({ other });
+  }
+
+  submit() {
+    const { form } = this.props;
+    form.validateFields((err) => {
+      if (!err) {
+        const data = form.getFieldsValue();
+        data.other = this.state.data.others.map(row => row.id);
+        User.editAsk(data).then(() => {
+          asyncSMessage('保存成功');
+        }).catch((e) => {
+          if (e.result) form.setFields(formatFormError(data, e.result));
+        });
+      }
+    });
+  }
+
+  renderBase() {
+    const { data } = this.state;
+    const { question = {}, questionNo = {} } = data;
+    return <Block>
+      <h1>题目信息</h1>
+      <Form>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题型'>
+          {QuestionTypeMap[question.type]}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题目id'>
+          <a href='' target='_blank'>{questionNo.no}</a>
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
+  renderAsk() {
+    const { data } = this.state;
+    const { user = {}, createTime, target, content } = data;
+    return <Block>
+      <h1>提问信息</h1>
+      <Form>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='用户姓名'>
+          {user.realName}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='提问时间'>
+          {formatDate(createTime)}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='提问模块'>
+          {AskTargetMap[target]}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='提问详情'>
+          {content}
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
+  renderQuestionList() {
+    return <Block>
+      <h1>该题目的展示中问题</h1>
+      <DragList
+        loading={this.props.core.loading}
+        dataSource={this.state.data.others || []}
+        handle={'.icon'}
+        onMove={(oldIndex, newIndex) => {
+          this.orderQuestion(oldIndex, newIndex);
+        }}
+        renderItem={(item) => (
+          <List.Item actions={[<Icon type='bars' className='icon' />, <Typography.Text copyable={{ text: 'url' }} />]}>
+            <Row style={{ width: '100%' }}>
+              <Col span={11}>问题:{item.content}</Col>
+              <Col span={11} offset={1}>答复:{item.answer}</Col>
+            </Row>
+          </List.Item>
+        )}
+      /></Block>;
+  }
+
+  renderAnswer() {
+    const { getFieldDecorator } = this.props.form;
+    return <Block>
+      <Form>
+        {getFieldDecorator('id')(<input hidden />)}
+        <Form.Item label='教师回复'>
+          {getFieldDecorator('answer', {
+          })(
+            <Editor placeholder='输入内容' />,
+          )}
+        </Form.Item>
+        <Row type="flex" justify="center">
+          <Col span={12}>
+            <Form.Item labelCol={{ span: 12 }} wrapperCol={{ span: 10 }} label='显示状态'>
+              {getFieldDecorator('showStatus', {
+                valuePropName: 'checked',
+              })(
+                <Switch checkedChildren='on' unCheckedChildren='off' />,
+              )}
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </Block>;
+  }
+
+  renderView() {
+    return <div flex>
+      {this.renderBase()}
+      {this.renderAsk()}
+      {this.renderQuestionList()}
+      {this.renderAnswer()}
+
+      <Row type="flex" justify="center">
+        <Col>
+          <Button type="primary" onClick={() => {
+            this.submit();
+          }}>保存</Button>
+        </Col>
+      </Row>
+    </div>;
+  }
+}

+ 15 - 0
front/project/admin/routes/user/detail/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/user/detail/:id?',
+  key: 'user-detail',
+  title: '用户详情',
+  needLogin: true,
+  module,
+  group,
+  showKey: 'user-list',
+  component() {
+    return import('./page');
+  },
+};

+ 18 - 0
front/project/admin/routes/user/detail/index.less

@@ -0,0 +1,18 @@
+@charset "utf-8";
+
+#user-detail {
+  .group {
+    padding-left: 50px;
+    padding-top: 20px;
+    padding-bottom: 20px;
+    padding-right: 20px;
+
+    h2 {
+      height: 30px;
+      line-height: 30px;
+      border-left: 2px solid #3f91f6;
+      padding-left: 10px;
+      font-size: 14px;
+    }
+  }
+}

+ 239 - 0
front/project/admin/routes/user/detail/page.js

@@ -0,0 +1,239 @@
+import React from 'react';
+import { Form, Row, Col, Avatar } from 'antd';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import TableLayout from '@src/layouts/TableLayout';
+import { PrepareStatus, PrepareExaminationTime, ServiceKey, PayModule } from '@src/services/Constant';
+import { formatDate, getMap, formatMoney } from '@src/services/Tools';
+import { User } from '../../../stores/user';
+import { Exercise } from '../../../stores/exercise';
+
+const PrepareStatusMap = getMap(PrepareStatus, 'value', 'label');
+const PrepareExaminationTimeMap = getMap(PrepareExaminationTime, 'value', 'label');
+const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
+const PayModuleMap = getMap(PayModule, 'value', 'label');
+
+export default class extends Page {
+  constructor(props) {
+    super(props);
+    this.categoryMap = {};
+  }
+
+  init() {
+    Exercise.allStruct().then(result => {
+      this.categoryMap = getMap(result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; }), 'id', 'title');
+      this.setState({ exercise: result });
+    });
+    this.columns = [{
+      title: '订单时间',
+      dataIndex: 'createTime',
+      render: (text) => {
+        return formatDate(text);
+      },
+    }, {
+      title: '购买',
+      dataIndex: 'module',
+      render: (text, record) => {
+        const m = PayModuleMap[text];
+        switch (text) {
+          case 'service':
+            return `${m}: ${ServiceKeyMap[record.moduleExtend]} ${record.isUse ? '已使用' : ''}`;
+          case 'class':
+            return `${m}: ${this.categoryMap[record.moduleExtend]} ${record.isUse ? '已使用' : ''}`;
+          case 'data':
+          default:
+            return `${m || text}`;
+        }
+      },
+    }, {
+      title: '消费金额',
+      dataIndex: 'money',
+      render: (text) => {
+        return formatMoney(text);
+      },
+    }];
+  }
+
+  initData() {
+    const { id } = this.params;
+    let handler;
+    if (id) {
+      handler = User.get({ id });
+    } else {
+      handler = Promise.resolve();
+    }
+    handler
+      .then(result => {
+        this.setState({ data: result });
+        this.refreshPay(this.state.search.page, this.state.search.size);
+      });
+  }
+
+  refreshPay(p, size) {
+    const { id } = this.params;
+    const { page } = this.state;
+    page.current = p || 1;
+    page.pageSize = size || 20;
+    this.setState({ page });
+    User.listPay({ user_id: id, page: page.current, size: page.pageSize })
+      .then(result => {
+        this.setTableData(result.list, result.total);
+      });
+  }
+
+  renderBase() {
+    const { data } = this.state;
+    return <Block>
+      <h1>用户基本信息</h1>
+      <Form>
+        <div className="group">
+          <h2>个人资料</h2>
+          <Row>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='用户ID'>
+                {data.id}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='用户昵称'>
+                {data.nickname}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='用户手机'>
+                {data.mobile}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='用户微信'>
+                {data.wechatOpenidPc !== '' ? '已绑定' : '未绑定'},{data.wechatOpenidWechat !== '' ? '已关注' : '未关注'}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='邮箱'>
+                {data.email !== '' ? '已绑定' : '未绑定'}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='注册时间'>
+                {formatDate(data.createTime)}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='头像'>
+                <Avatar src={data.avatar} />
+              </Form.Item>
+            </Col>
+          </Row>
+        </div>
+
+        <div className="group">
+          <h2>实名认证</h2>
+          <Row>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='身份证id'>
+                {data.realIdentity}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='真实姓名'>
+                {data.realName}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='地区'>
+                {data.realAddress}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='身份证照片'>
+                <Avatar src={data.realPhoto} />
+              </Form.Item>
+            </Col>
+          </Row>
+        </div>
+        <div className="group">
+          <h2>备考信息</h2>
+          <Row>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='身份'>
+                {PrepareStatusMap[data.prepareStatus]}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='考试时间'>
+                {PrepareExaminationTimeMap[data.PrepareExaminationTime]}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='出分时间'>
+                {data.prepareScoreTime ? formatDate(data.prepareScoreTime) : ''}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='目标成绩'>
+                {data.prepareGoal}
+              </Form.Item>
+            </Col>
+          </Row>
+        </div>
+      </Form>
+    </Block>;
+  }
+
+  renderService() {
+    const { data } = this.state;
+    return <Block>
+      <h1>服务开通</h1>
+      <div className="group">
+        <h2>累计消费金额</h2>
+        <span>{data.totalMoney}</span>
+      </div>
+      <div className="group">
+        <h2>已开通服务</h2>
+        {(data.services || []).map(service => {
+          return <p>{ServiceKeyMap[service.service]}: {formatDate(service.startTime)} - {formatDate(service.expireTime)}</p>;
+        })}
+      </div>
+      <div className="group">
+        <h2>已开通课程</h2>
+        {(data.classes || []).map(cls => {
+          return <p>{this.categoryMap[cls.category]}: {formatDate(cls.startTime)} - {formatDate(cls.expireTime)}</p>;
+        })}
+      </div>
+      <div className="group">
+        <h2>消费记录</h2>
+        <TableLayout
+          columns={this.columns}
+          list={this.state.list}
+          pagination={this.state.page}
+          loading={this.props.core.loading}
+          onChange={(pagination) => this.refreshPay(pagination.current, pagination.pageSize)}
+        />
+      </div>
+    </Block>;
+  }
+
+  renderStudy() {
+    return <Block>
+      <h1>学习统计</h1>
+      <div className="group">
+        <h2>累计学习时间</h2>
+
+      </div>
+      <div className="group">
+        <h2>学习数据</h2>
+
+      </div>
+    </Block>;
+  }
+
+  renderView() {
+    return <div flex>
+      {this.renderBase()}
+      {this.renderService()}
+      {this.renderStudy()}
+    </div>;
+  }
+}

+ 3 - 1
front/project/admin/routes/user/index.js

@@ -1,8 +1,10 @@
 import list from './list';
+import detail from './detail';
 import ask from './ask';
+import askDetail from './askDetail';
 import preview from './preview';
 import exercise from './exercise';
 import pay from './pay';
 import feedback from './feedback';
 
-export default [list, ask, preview, exercise, pay, feedback];
+export default [list, detail, ask, askDetail, preview, exercise, pay, feedback];

+ 1 - 1
front/project/admin/routes/user/list/index.js

@@ -4,7 +4,7 @@ import group from '../group';
 export default {
   path: '/user/list',
   key: 'user-list',
-  title: '用户列表',
+  title: '基本信息',
   needLogin: true,
   module,
   group,

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

@@ -1,10 +1,132 @@
 import React from 'react';
+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 { SwitchSelect, ServiceKey } from '@src/services/Constant';
+import { getMap, formatMoney } from '@src/services/Tools';
+import { User } from '../../../stores/user';
 
+const SwitchSelectMap = getMap(SwitchSelect, 'value', 'label');
+const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
 export default class extends Page {
+  constructor(props) {
+    super(props);
+    this.filterF = null;
+  }
+
+  init() {
+    this.filterForm = [
+      {
+        key: 'keyword',
+        type: 'input',
+        name: 'ID/手机号',
+        placeholder: '请输入',
+      },
+      {
+        key: 'real',
+        type: 'select',
+        allowClear: true,
+        name: '实名认证',
+        select: SwitchSelect,
+        placeholder: '请选择',
+        number: true,
+      },
+    ];
+    this.columns = [
+      {
+        title: 'ID',
+        dataIndex: 'id',
+      },
+      {
+        title: '手机号',
+        dataIndex: 'mobile',
+      },
+      {
+        title: '注册时间',
+        dataIndex: 'createTime',
+      },
+      {
+        title: '实名认证',
+        dataIndex: 'realStatus',
+        render: (text) => {
+          return SwitchSelectMap[text ? 1 : 0];
+        },
+      },
+      {
+        title: '备考信息',
+        dataIndex: 'prepareStatus',
+        render: (text) => {
+          return SwitchSelectMap[text ? 1 : 0];
+        },
+      }, {
+        title: '邀请人数',
+        dataIndex: 'inviteNumber',
+      }, {
+        title: '学习时长',
+        dataIndex: 'time',
+      }, {
+        title: '服务中',
+        dataIndex: 'services',
+        render: (text) => {
+          return (text || []).map(row => ServiceKeyMap[row.service]).join(', ');
+        },
+      }, {
+        title: '消费金额',
+        dataIndex: 'totalMoney',
+        render: (text) => {
+          return formatMoney(text);
+        },
+      }, {
+        title: '操作',
+        dataIndex: 'handler',
+        render: (text, record) => {
+          return <div className="table-button">
+            {(
+              <Link to={`/user/detail/${record.id}`}>查看</Link>
+            )}
+          </div>;
+        },
+      },
+    ];
+  }
+
+  initData() {
+    User.list(this.state.search).then(result => {
+      this.setTableData(result.list, result.total);
+    });
+  }
+
   renderView() {
-    return <Block flex />;
+    return <Block flex>
+      <FilterLayout
+        show
+        itemList={this.filterForm}
+        data={this.state.search}
+        onChange={data => {
+          this.search(data);
+        }}
+        ref={(ref) => {
+          if (ref) this.filterF = ref;
+        }} />
+      {/* <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>;
   }
 }

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

@@ -2,9 +2,181 @@ 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, bindSearch, formatDate } from '@src/services/Tools';
+import { Exercise } from '../../../stores/exercise';
+import { Preview } from '../../../stores/preview';
+import { User } from '../../../stores/user';
+// import { System } from '../../../stores/system';
 
 export default class extends Page {
+  constructor(props) {
+    super(props);
+    this.categoryMap = {};
+    this.filterF = null;
+  }
+
+  init() {
+    this.filterForm = [
+      {
+        key: 'category',
+        type: 'select',
+        allowClear: true,
+        name: '课程',
+        placeholder: '请选择',
+        number: true,
+        onChange: (value) => {
+          this.refreshPreview(value);
+          this.filterF.setFieldsValue({ preview_id: '' });
+        },
+      },
+      {
+        key: 'preview_id',
+        type: 'select',
+        name: '练习册',
+        allowClear: true,
+        select: [],
+        number: true,
+        placeholder: '请选择',
+      },
+      {
+        key: 'user_id',
+        type: 'select',
+        name: '用户',
+        allowClear: true,
+        select: [],
+        number: true,
+        placeholder: '请输入',
+      },
+      {
+        key: 'time',
+        type: 'daterange',
+        name: '做题时间',
+      },
+    ];
+    this.columns = [{
+      title: '用户id',
+      dataIndex: 'user_id',
+      render: (text, record) => {
+        return record.user.id;
+      },
+    }, {
+      title: '用户名称',
+      dataIndex: 'user_realname',
+      render: (text, record) => {
+        return record.user.realName;
+      },
+    }, {
+      title: '做题时间',
+      dataIndex: 'createTime',
+      render: (text) => {
+        return formatDate(text);
+      },
+    }, {
+      title: '学科',
+      dataIndex: 'category',
+      render: (text) => {
+        return this.categoryMap[text] || text;
+      },
+    }, {
+      title: '练习册',
+      dataIndex: 'title',
+      render: (text, record) => {
+        return record.preview.title;
+      },
+    }, {
+      title: '完成进度',
+      dataIndex: 'finish',
+      render: (text, record) => {
+        return `${record.report.userNumber * 100 / record.report.totalNumber}`;
+      },
+    }, {
+      title: '错误率',
+      dataIndex: 'correct',
+      render: (text, record) => {
+        return `${record.report.correctNumber * 100 / record.questionNo.totalNumber}%`;
+      },
+    }, {
+      title: '时长',
+      dataIndex: 'time',
+      render: (text, record) => {
+        return `${record.report.userTime}s`;
+      },
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          {(
+            <a href={`/subject/preview/detail/${record.id}`} target='_blank'>查看</a>
+          )}
+        </div>;
+      },
+    }];
+    Exercise.allStruct().then(result => {
+      this.filterForm[0].select = result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; });
+      this.categoryMap = getMap(this.filterForm[0].select, 'id', 'title');
+      this.setState({ exercise: result });
+    });
+    bindSearch(this.filterForm, 'user_id', this, (search) => {
+      return User.list(search);
+    }, (row) => {
+      return {
+        title: `${row.nickname}(${row.mobile})`,
+        value: row.id,
+      };
+    }, this.state.search.user_id ? [Number(this.state.search.user_id)] : [], null);
+    this.refreshPreview();
+  }
+
+  // 筛选条件
+  refreshPreview(category) {
+    Preview.list({ category })
+      .then(result => {
+        this.filterForm[1].select = result.list.map(row => { row.value = row.id; row.label = row.title; return row; });
+        this.setState({ load: false });
+      });
+  }
+
+  initData() {
+    const { search } = this.state;
+    const data = Object.assign({}, search);
+    if (data.time) {
+      data.startTime = data.time[0] || '';
+      data.endTime = data.time[1] || '';
+    }
+    Preview.list(data).then(result => {
+      this.setTableData(result.list, result.total);
+    });
+  }
+
   renderView() {
-    return <Block flex />;
+    return <Block flex>
+      <FilterLayout
+        show
+        itemList={this.filterForm}
+        data={this.state.search}
+        onChange={data => {
+          if (data.time.length > 0) {
+            data.time = [data.time[0].format('YYYY-MM-DD HH:mm:ss'), data.time[1].format('YYYY-MM-DD HH:mm:ss')];
+          }
+          this.search(data);
+        }}
+        ref={(ref) => {
+          if (ref) this.filterF = ref;
+        }} />
+      <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>;
   }
 }

+ 5 - 1
front/project/admin/stores/user.js

@@ -29,13 +29,17 @@ export default class UserStore extends BaseStore {
     return this.apiPut('/user/ask/edit', params);
   }
 
-  detailAsk(params) {
+  getAsk(params) {
     return this.apiGet('/user/ask/detail', params);
   }
 
   listPreview(params) {
     return this.apiGet('/user/preview/list', params);
   }
+
+  listPay(params) {
+    return this.apiGet('/user/pay/list', params);
+  }
 }
 
 export const User = new UserStore({ key: 'users' });

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

@@ -24,8 +24,8 @@ export default class extends Component {
       selectedRows: [],
       tab: '',
     };
-    this.init();
     this.state = Object.assign(state, this.initState());
+    this.init();
 
     this.changePageInfo(config);
   }

+ 13 - 1
front/src/services/Constant.js

@@ -14,10 +14,22 @@ export const FORM_LAYOUT = '@src/layouts/FormLayout';
 
 export const QuestionDifficult = [{ label: 'easy', value: 'easy' }, { label: 'medium', value: 'medium' }, { label: 'hard', value: 'hard' }];
 
-export const QuestionType = [{ label: 'SC/语法', value: 'sc' }, { label: 'RC/阅读', value: 'rc' }, { label: 'CR/逻辑', value: 'cr' }, { label: 'PS/数学', value: 'ps' }, { label: 'AWA/作文', value: 'awa' }];
+export const QuestionType = [{ label: 'SC/语法', value: 'sc' }, { label: 'RC/阅读', value: 'rc' }, { label: 'CR/逻辑', value: 'cr' }, { label: 'PS/数学', value: 'ps' }, { label: 'DS/数学', value: 'ds' }, { label: 'IR/综合推理', value: 'ir' }, { label: 'AWA/作文', value: 'awa' }];
 
 export const MoneyRange = [{ label: '0', value: 0 }, { label: '1-1000', value: 1 }, { label: '1000-5000', value: 2 }, { label: '5000-10000', value: 3 }, { label: '10000以上', value: 4 }];
 
 export const AskTarget = [{ label: '题目', value: 'question' }, { label: '官方', value: 'official' }, { label: '千行解析', value: 'qx' }, { label: '题源联想', value: 'association' }];
 
 export const PreviewStatus = [{ label: '全部', value: 0 }, { label: '未开始', value: 1 }, { label: '进行中', value: 2 }, { label: '已结束', value: 3 }];
+
+export const ServiceKey = [{ label: 'VIP', value: 'vip' }, { label: '机经', value: 'textbook' }, { label: '千行CAT', value: 'qx_cat' }];
+
+export const SwitchSelect = [{ value: 0, label: '否' }, { value: 1, label: '是' }];
+
+export const AskStatus = [{ value: 0, label: '新增' }, { value: 1, label: '已回答' }, { value: 2, label: '忽略' }];
+
+export const PrepareStatus = [{ label: '学生-Domestic', value: 'student_domestic' }, { label: '学生-Overseas', value: 'student_overseas' }, { label: '在职-Domestic', value: 'worker_domestic' }, { label: '在职-Overseas', value: 'worker_overseas' }, { label: 'Gap Year', value: 'gap_year' }];
+
+export const PrepareExaminationTime = [{ label: '近1个月', value: 'one_month' }, { label: '近2个月', value: 'two_month' }, { label: '近3个月', value: 'three_month' }, { label: '半年内', value: 'six_month' }];
+
+export const PayModule = [{ label: '服务', value: 'service' }, { label: '课程', value: 'class' }, { label: '资料', value: 'data' }];

+ 25 - 0
front/src/style/admin.less

@@ -1,5 +1,6 @@
 @charset "utf-8";
 @import './core';
+
 body {
   color: @base_color;
   font-size: @base_size;
@@ -21,25 +22,31 @@ body,
 }
 
 #root {
+
   .admin {
     background: @admin_bg_color;
     height: 100%;
+
     >.ant-layout {
       height: 100%;
     }
+
     #layout-header.collapsed {
       #logo {
         width: 80px;
+
         h1 {
           display: none;
         }
       }
     }
+
     #layout-header {
       overflow: hidden;
       padding: 0;
       padding-right: 24px;
       color: hsla(0, 0%, 100%, 0.65);
+
       #logo {
         width: 200px;
         height: 64px;
@@ -50,11 +57,13 @@ body,
         background: #002140;
         overflow: hidden;
         display: inline-block;
+
         img {
           display: inline-block;
           vertical-align: middle;
           height: 32px;
         }
+
         h1 {
           color: #fff;
           display: inline-block;
@@ -65,21 +74,25 @@ body,
           font-weight: 600;
         }
       }
+
       .header-menu {
         display: inline-block;
         line-height: 64px;
         padding: 0 24px;
         vertical-align: top;
       }
+
       .user-info {
         line-height: 64px;
         padding: 0 15px;
         cursor: pointer;
         transition: all 0.3s;
       }
+
       .user-info:hover {
         color: #fff;
       }
+
       .icon {
         font-size: 18px;
         line-height: 64px;
@@ -87,46 +100,58 @@ body,
         cursor: pointer;
         transition: all 0.3s;
       }
+
       .icon:hover {
         color: #fff;
       }
     }
+
     .left-slider {
       background: #fff;
       overflow-y: 'auto';
+
       .ant-layout-sider-trigger {
         background: #fff;
         color: @base_color;
       }
+
       .ant-layout-sider-trigger:hover {
         color: @theme_color;
       }
+
       .left-menu {
         padding: 10px 0;
       }
     }
+
     .page-layout {
       padding: 24px;
       overflow-y: auto;
     }
+
     .page-content {
       height: 100%;
       position: relative;
       padding-top: 20px;
+
       .ant-breadcrumb {
         top: -10px;
         position: absolute;
       }
+
       .page {
         height: 100%;
       }
     }
+
     .ant-form.ant-form-inline {
       margin-bottom: 10px;
     }
+
     .ant-form-inline .ant-form-item {
       margin-bottom: 10px;
     }
+
     .ant-table-row {
       span {
         a {

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

@@ -24,6 +24,10 @@ body,
 #root {
   background: @admin_bg_color;
 
+  .block {
+    margin-bottom: 20px;
+  }
+
   .admin {
     .ant-layout-sider-collapsed {
       #logo {

+ 8 - 2
server/data/src/main/java/com/qxgmat/data/constants/enums/QuestionType.java

@@ -4,8 +4,14 @@ package com.qxgmat.data.constants.enums;
  * Created by gaojie on 2017/11/19.
  */
 public enum QuestionType {
-    SC("sc", "语法"), RC("rc", "阅读"), CR("cr","逻辑"), PS("ps", "数学"), AWA("awa", "作文");
-    final static public String message = "试卷类型";
+    SC("sc", "语法"),
+    RC("rc", "阅读"),
+    CR("cr","逻辑"),
+    PS("ps", "数学"),
+    DS("ds", "数学"),
+    IR("ir", "综合推理"),
+    AWA("awa", "作文");
+    final static public String message = "题目类型";
 
     public String key;
     public String title;

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

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

+ 14 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/module/PayModule.java

@@ -0,0 +1,14 @@
+package com.qxgmat.data.constants.enums.module;
+
+public enum PayModule {
+    SERVICE("service"), CLASS("class"), DATA("data");
+    public String key;
+    private PayModule(String key){
+        this.key = key;
+    }
+
+    public static PayModule ValueOf(String name){
+        if (name == null) return null;
+        return PayModule.valueOf(name.toUpperCase());
+    }
+}

+ 25 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/status/AskStatus.java

@@ -0,0 +1,25 @@
+package com.qxgmat.data.constants.enums.status;
+
+
+public enum AskStatus {
+    NEW(0), ANSWER(1), IGNORE(2);
+    final static public String message = "状态:0新增,1回答,2忽略";
+
+    public int index;
+    private AskStatus(int index){
+        this.index = index;
+    }
+    public static AskStatus ValueOf(Integer index){
+        if (index == null) return null;
+        switch (index){
+            case 0:
+                return NEW;
+            case 1:
+                return ANSWER;
+            case 2:
+                return IGNORE;
+            default:
+                return null;
+        }
+    }
+}

+ 18 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/user/PrepareExaminationTime.java

@@ -0,0 +1,18 @@
+package com.qxgmat.data.constants.enums.user;
+
+public enum PrepareExaminationTime {
+    ONE_MONTH("one_month"),
+    TWO_MONTH("two_month"),
+    THREE_MONTH("three_month"),
+    SIX_MONTH("six_month");
+
+    public String key;
+    private PrepareExaminationTime(String key){
+        this.key = key;
+    }
+
+    public static PrepareExaminationTime ValueOf(String name){
+        if (name == "") return null;
+        return PrepareExaminationTime.valueOf(name.toUpperCase());
+    }
+}

+ 19 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/user/PrepareStatus.java

@@ -0,0 +1,19 @@
+package com.qxgmat.data.constants.enums.user;
+
+public enum PrepareStatus {
+    STUDENT_DOMESTIC("student_domestic"),
+    STUDENT_OVERSEAS("student_overseas"),
+    WORKER_DOMESTIC("worker_domestic"),
+    WORKER_OVERSEAS("worker_overseas"),
+    GAY_YEAR("gap_year");
+
+    public String key;
+    private PrepareStatus(String key){
+        this.key = key;
+    }
+
+    public static PrepareStatus ValueOf(String name){
+        if (name == "") return null;
+        return PrepareStatus.valueOf(name.toUpperCase());
+    }
+}

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

@@ -41,6 +41,12 @@ public class ExaminationStruct implements Serializable {
     private Integer level;
 
     /**
+     * 付费状态:0关闭,1开启
+     */
+    @Column(name = "`pay_status`")
+    private Integer payStatus;
+
+    /**
      * 说明
      */
     @Column(name = "`description`")
@@ -153,6 +159,24 @@ public class ExaminationStruct implements Serializable {
     }
 
     /**
+     * 获取付费状态:0关闭,1开启
+     *
+     * @return pay_status - 付费状态:0关闭,1开启
+     */
+    public Integer getPayStatus() {
+        return payStatus;
+    }
+
+    /**
+     * 设置付费状态:0关闭,1开启
+     *
+     * @param payStatus 付费状态:0关闭,1开启
+     */
+    public void setPayStatus(Integer payStatus) {
+        this.payStatus = payStatus;
+    }
+
+    /**
      * 获取说明
      *
      * @return description - 说明
@@ -182,6 +206,7 @@ public class ExaminationStruct implements Serializable {
         sb.append(", parentId=").append(parentId);
         sb.append(", order=").append(order);
         sb.append(", level=").append(level);
+        sb.append(", payStatus=").append(payStatus);
         sb.append(", description=").append(description);
         sb.append("]");
         return sb.toString();
@@ -257,6 +282,16 @@ public class ExaminationStruct implements Serializable {
         }
 
         /**
+         * 设置付费状态:0关闭,1开启
+         *
+         * @param payStatus 付费状态:0关闭,1开启
+         */
+        public Builder payStatus(Integer payStatus) {
+            obj.setPayStatus(payStatus);
+            return this;
+        }
+
+        /**
          * 设置说明
          *
          * @param description 说明

+ 39 - 4
server/data/src/main/java/com/qxgmat/data/dao/entity/User.java

@@ -127,7 +127,7 @@ public class User implements Serializable {
      * 备考:考试时间
      */
     @Column(name = "`prepare_examination_time`")
-    private Date prepareExaminationTime;
+    private Integer prepareExaminationTime;
 
     /**
      * 备考:出分时间
@@ -159,6 +159,12 @@ public class User implements Serializable {
     @Column(name = "`total_money`")
     private BigDecimal totalMoney;
 
+    /**
+     * 邀请人数
+     */
+    @Column(name = "`invite_number`")
+    private Integer inviteNumber;
+
     @Column(name = "`create_time`")
     private Date createTime;
 
@@ -521,7 +527,7 @@ public class User implements Serializable {
      *
      * @return prepare_examination_time - 备考:考试时间
      */
-    public Date getPrepareExaminationTime() {
+    public Integer getPrepareExaminationTime() {
         return prepareExaminationTime;
     }
 
@@ -530,7 +536,7 @@ public class User implements Serializable {
      *
      * @param prepareExaminationTime 备考:考试时间
      */
-    public void setPrepareExaminationTime(Date prepareExaminationTime) {
+    public void setPrepareExaminationTime(Integer prepareExaminationTime) {
         this.prepareExaminationTime = prepareExaminationTime;
     }
 
@@ -625,6 +631,24 @@ public class User implements Serializable {
     }
 
     /**
+     * 获取邀请人数
+     *
+     * @return invite_number - 邀请人数
+     */
+    public Integer getInviteNumber() {
+        return inviteNumber;
+    }
+
+    /**
+     * 设置邀请人数
+     *
+     * @param inviteNumber 邀请人数
+     */
+    public void setInviteNumber(Integer inviteNumber) {
+        this.inviteNumber = inviteNumber;
+    }
+
+    /**
      * @return create_time
      */
     public Date getCreateTime() {
@@ -670,6 +694,7 @@ public class User implements Serializable {
         sb.append(", originId=").append(originId);
         sb.append(", inviteCode=").append(inviteCode);
         sb.append(", totalMoney=").append(totalMoney);
+        sb.append(", inviteNumber=").append(inviteNumber);
         sb.append(", createTime=").append(createTime);
         sb.append("]");
         return sb.toString();
@@ -887,7 +912,7 @@ public class User implements Serializable {
          *
          * @param prepareExaminationTime 备考:考试时间
          */
-        public Builder prepareExaminationTime(Date prepareExaminationTime) {
+        public Builder prepareExaminationTime(Integer prepareExaminationTime) {
             obj.setPrepareExaminationTime(prepareExaminationTime);
             return this;
         }
@@ -943,6 +968,16 @@ public class User implements Serializable {
         }
 
         /**
+         * 设置邀请人数
+         *
+         * @param inviteNumber 邀请人数
+         */
+        public Builder inviteNumber(Integer inviteNumber) {
+            obj.setInviteNumber(inviteNumber);
+            return this;
+        }
+
+        /**
          * @param createTime
          */
         public Builder createTime(Date createTime) {

+ 7 - 7
server/data/src/main/java/com/qxgmat/data/dao/entity/UserAsk.java

@@ -36,7 +36,7 @@ public class UserAsk implements Serializable {
     private String target;
 
     /**
-     * 回答状态
+     * 回答状态: 0未回答,1回答,2忽略
      */
     @Column(name = "`answer_status`")
     private Integer answerStatus;
@@ -172,18 +172,18 @@ public class UserAsk implements Serializable {
     }
 
     /**
-     * 获取回答状态
+     * 获取回答状态: 0未回答,1回答,2忽略
      *
-     * @return answer_status - 回答状态
+     * @return answer_status - 回答状态: 0未回答,1回答,2忽略
      */
     public Integer getAnswerStatus() {
         return answerStatus;
     }
 
     /**
-     * 设置回答状态
+     * 设置回答状态: 0未回答,1回答,2忽略
      *
-     * @param answerStatus 回答状态
+     * @param answerStatus 回答状态: 0未回答,1回答,2忽略
      */
     public void setAnswerStatus(Integer answerStatus) {
         this.answerStatus = answerStatus;
@@ -419,9 +419,9 @@ public class UserAsk implements Serializable {
         }
 
         /**
-         * 设置回答状态
+         * 设置回答状态: 0未回答,1回答,2忽略
          *
-         * @param answerStatus 回答状态
+         * @param answerStatus 回答状态: 0未回答,1回答,2忽略
          */
         public Builder answerStatus(Integer answerStatus) {
             obj.setAnswerStatus(answerStatus);

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

@@ -11,6 +11,7 @@
     <result column="parent_id" jdbcType="INTEGER" property="parentId" />
     <result column="order" jdbcType="INTEGER" property="order" />
     <result column="level" jdbcType="INTEGER" property="level" />
+    <result column="pay_status" jdbcType="INTEGER" property="payStatus" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.ExaminationStruct">
     <!--
@@ -22,7 +23,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title_zh`, `title_en`, `parent_id`, `order`, `level`
+    `id`, `title_zh`, `title_en`, `parent_id`, `order`, `level`, `pay_status`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -25,12 +25,13 @@
     <result column="real_status" jdbcType="INTEGER" property="realStatus" />
     <result column="prepare_status" jdbcType="INTEGER" property="prepareStatus" />
     <result column="prepare_goal" jdbcType="INTEGER" property="prepareGoal" />
-    <result column="prepare_examination_time" jdbcType="TIMESTAMP" property="prepareExaminationTime" />
+    <result column="prepare_examination_time" jdbcType="INTEGER" property="prepareExaminationTime" />
     <result column="prepare_score_time" jdbcType="TIMESTAMP" property="prepareScoreTime" />
     <result column="last_exercise" jdbcType="INTEGER" property="lastExercise" />
     <result column="origin_id" jdbcType="INTEGER" property="originId" />
     <result column="invite_code" jdbcType="VARCHAR" property="inviteCode" />
     <result column="total_money" jdbcType="DECIMAL" property="totalMoney" />
+    <result column="invite_number" jdbcType="INTEGER" property="inviteNumber" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   </resultMap>
   <sql id="Base_Column_List">
@@ -41,6 +42,6 @@
     `wechat_openid_wechat`, `wechat_unionid`, `wechat_access_token`, `wechat_refresh_token`, 
     `wechat_expire_time`, `real_name`, `real_address`, `real_identity`, `real_photo`, 
     `real_status`, `prepare_status`, `prepare_goal`, `prepare_examination_time`, `prepare_score_time`, 
-    `last_exercise`, `origin_id`, `invite_code`, `total_money`, `create_time`
+    `last_exercise`, `origin_id`, `invite_code`, `total_money`, `invite_number`, `create_time`
   </sql>
 </mapper>

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

@@ -10,6 +10,7 @@ import java.util.List;
  */
 public interface ExerciseQuestionRelationMapper {
     List<ExercisePaperQuestion> listAdmin(
+            @Param("type") String type,
             @Param("structId") Number structId,
             @Param("questionNoId") Number questionNoId,
             @Param("paperId") Number paperId,

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

@@ -17,6 +17,7 @@ public interface UserAskRelationMapper {
     );
 
     List<UserAsk> listWithUser(
+            @Param("type") String type,
             @Param("category") Number category,
             @Param("userId") Number userId,
             @Param("questionNoId") Number questionNoId,

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

@@ -34,6 +34,9 @@
     <if test="questionNoId != null">
       and qn.`id` =#{questionNoId,jdbcType=VARCHAR}
     </if>
+    <if test="type != null">
+      and q.`type` =#{type,jdbcType=VARCHAR}
+    </if>
     <if test="place != null">
       and q.`place` =#{place,jdbcType=VARCHAR}
     </if>

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

@@ -27,7 +27,7 @@
   <select id="listWithUser" resultMap="IdMap">
     select
     <include refid="Id_Column_List" />
-    from `user_aks` ua
+    from `user_ask` ua
     left join `user` u on u.`id` = ua.`user_id`
       <if test="userId != null">
         and ua.`user_id` = #{userId,jdbcType=VARCHAR}
@@ -38,6 +38,7 @@
       <if test="min != null">
         and u.`total_money` &gt; ${min}
       </if>
+    left join `question` q on q.`id` = ua.`question_id`
     where
     u.`id` != null
     <if test="questionNoId != null">
@@ -52,6 +53,9 @@
     <if test="showStatus != null">
       and ua.`show_status` = #{showStatus,jdbcType=INT}
     </if>
+    <if test="type != null">
+      and q.`type` =#{type,jdbcType=VARCHAR}
+    </if>
     order by ${order} ${direction}
   </select>
 </mapper>

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

@@ -112,6 +112,7 @@ public class ExerciseController {
     public Response<PageMessage<ExerciseQuestionListDto>> listQuestion(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) String type,
             @RequestParam(required = false, name = "struct_id") Integer structId,
             @RequestParam(required = false, name = "question_no_id") Integer questionNoId,
             @RequestParam(required = false, name="paper_id") Integer paperId,
@@ -122,7 +123,7 @@ public class ExerciseController {
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<ExercisePaperQuestion> p = exerciseQuestionService.listAdmin(page, size, structId, questionNoId, paperId, place, difficult, startTime, endTime, order, DirectionStatus.ValueOf(direction));
+        Page<ExercisePaperQuestion> p = exerciseQuestionService.listAdmin(page, size, type, structId, questionNoId, paperId, place, difficult, startTime, endTime, order, DirectionStatus.ValueOf(direction));
         List<ExerciseQuestionListDto> pr = Transform.convert(p, ExerciseQuestionListDto.class);
 
         // 绑定题目

+ 9 - 4
server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserAskController.java

@@ -5,6 +5,7 @@ import com.nuliji.tools.PageMessage;
 import com.nuliji.tools.Response;
 import com.nuliji.tools.ResponseHelp;
 import com.nuliji.tools.Transform;
+import com.qxgmat.data.constants.enums.status.AskStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.user.AskTarget;
 import com.qxgmat.data.constants.enums.user.MoneyRange;
@@ -65,13 +66,16 @@ public class UserAskController {
         if(!entity.getAnswer().isEmpty()){
 
             entity.setAnswerTime(new Date());
-            entity.setAnswerStatus(1);
+            entity.setAnswerStatus(AskStatus.ANSWER.index);
         }
 
         Manager manager = shiroHelp.getLoginManager();
         entity.setManagerId(manager.getId());
         entity = userAskService.edit(entity);
         userAskService.updateOrder(dto.getOther());
+
+        // 更新回答排序
+
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -115,7 +119,8 @@ public class UserAskController {
     public Response<PageMessage<UserAskListDto>> list(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
-            @RequestParam(required = false) int category,
+            @RequestParam(required = false) String type,
+            @RequestParam(required = false) Integer category,
             @RequestParam(required = false, name = "user_id") Number userId,
             @RequestParam(required = false, name = "question_no_id") Number questionNoId,
             @RequestParam(required = false, name="target") String target,
@@ -125,7 +130,7 @@ public class UserAskController {
             @RequestParam(required = false, defaultValue = "id") String order,
             @RequestParam(required = false, defaultValue = "desc") String direction,
             HttpSession session) {
-        Page<UserAsk> p = userAskService.listWithUser(page, size, category, userId, questionNoId, AskTarget.ValueOf(target), status, showStatus, MoneyRange.ValueOf(moneyRang), order, DirectionStatus.ValueOf(direction));
+        Page<UserAsk> p = userAskService.listWithUser(page, size, type, category, userId, questionNoId, AskTarget.ValueOf(target), AskStatus.ValueOf(status), showStatus, MoneyRange.ValueOf(moneyRang), order, DirectionStatus.ValueOf(direction));
         List<UserAskListDto> pr = Transform.convert(p, UserAskListDto.class);
 
         // 绑定题目
@@ -136,7 +141,7 @@ public class UserAskController {
         // 绑定题目编号
         Collection questionNoIds = Transform.getIds(p, UserAsk.class, "questionNoId");
         List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
-        Transform.combine(pr, questionNoList, UserAskListDto.class, "questionNoId", "question", QuestionNo.class, "id", QuestionNoExtendDto.class);
+        Transform.combine(pr, questionNoList, UserAskListDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);
 
         // 绑定用户
         Collection userIds = Transform.getIds(p, UserAsk.class, "userId");

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

@@ -8,6 +8,7 @@ import com.nuliji.tools.Transform;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.entity.User;
 import com.qxgmat.data.dao.entity.UserClass;
+import com.qxgmat.data.dao.entity.UserPay;
 import com.qxgmat.data.dao.entity.UserService;
 import com.qxgmat.dto.admin.extend.UserClassExtendDto;
 import com.qxgmat.dto.admin.extend.UserServiceExtendDto;
@@ -15,6 +16,7 @@ import com.qxgmat.dto.admin.response.UserListDto;
 import com.qxgmat.service.UsersService;
 import com.qxgmat.service.inline.ManagerLogService;
 import com.qxgmat.service.inline.UserClassService;
+import com.qxgmat.service.inline.UserPayService;
 import com.qxgmat.service.inline.UserServiceService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -43,6 +45,9 @@ public class UserController {
     @Autowired
     private UserClassService userClassService;
 
+    @Autowired
+    private UserPayService userPayService;
+
 //    @RequestMapping(value = "/add", method = RequestMethod.POST)
 //    @ApiOperation(value = "添加用户信息", httpMethod = "POST")
 //    public Response<User> add(@RequestBody @Validated UserDto dto, HttpServletRequest request) {
@@ -87,6 +92,29 @@ public class UserController {
         Page<User> p = usersService.select(page, size, keyword, real, order, DirectionStatus.ValueOf(direction));
         List<UserListDto> pr = Transform.convert(p, UserListDto.class);
 
+        Collection userIds = Transform.getIds(p, User.class, "id");
+        // 绑定用户服务
+        Map<Object, Collection<UserService>> serviceByUser = userServiceService.mapByUser(userIds);
+        Transform.combine(pr, serviceByUser, UserListDto.class, "id", "services", UserServiceExtendDto.class);
+
+        // 绑定用户课程
+        Map<Object, Collection<UserClass>> classByUser = userClassService.mapByUser(userIds);
+        Transform.combine(pr, classByUser, UserListDto.class, "id", "classes", UserClassExtendDto.class);
+
+        return ResponseHelp.success(pr, page, size, p.getTotal());
+    }
+
+    @RequestMapping(value = "/pay/list", method = RequestMethod.GET)
+    @ApiOperation(value = "用户消费列表", httpMethod = "GET")
+    public Response<PageMessage<UserListDto>> listPay(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) Integer userId,
+            @RequestParam(required = false) String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
+            HttpSession session) {
+        Page<UserPay> p = userPayService.select(page, size);
+        List<UserListDto> pr = Transform.convert(p, UserListDto.class);
 
         Collection userIds = Transform.getIds(p, User.class, "id");
         // 绑定用户服务

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

@@ -1,9 +1,13 @@
 package com.qxgmat.dto.admin.extend;
 
+import java.util.Date;
+
 public class UserClassExtendDto {
 
     private Integer id;
     private Integer category;
+    private Date startTime;
+    private Date expireTime;
 
     public Integer getId() {
         return id;
@@ -20,4 +24,20 @@ public class UserClassExtendDto {
     public void setCategory(Integer category) {
         this.category = category;
     }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(Date expireTime) {
+        this.expireTime = expireTime;
+    }
 }

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

@@ -1,9 +1,13 @@
 package com.qxgmat.dto.admin.extend;
 
+import java.util.Date;
+
 public class UserServiceExtendDto {
 
     private Integer id;
     private String service;
+    private Date startTime;
+    private Date expireTime;
 
     public Integer getId() {
         return id;
@@ -20,4 +24,20 @@ public class UserServiceExtendDto {
     public void setService(String service) {
         this.service = service;
     }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(Date expireTime) {
+        this.expireTime = expireTime;
+    }
 }

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

@@ -24,6 +24,12 @@ public class UserAskDetailDto {
 
     private ManagerExtendDto manager;
 
+    private String target;
+
+    private String content;
+
+    private String answer;
+
     public Integer getId() {
         return id;
     }
@@ -79,4 +85,28 @@ public class UserAskDetailDto {
     public void setOthers(Collection<UserAskExtendDto> others) {
         this.others = others;
     }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+
+    public String getTarget() {
+        return target;
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
 }

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

@@ -5,6 +5,7 @@ import com.qxgmat.data.dao.entity.UserAsk;
 import com.qxgmat.data.dao.entity.UserPaper;
 import com.qxgmat.dto.admin.extend.ManagerExtendDto;
 import com.qxgmat.dto.admin.extend.QuestionExtendDto;
+import com.qxgmat.dto.admin.extend.QuestionNoExtendDto;
 import com.qxgmat.dto.admin.extend.UserExtendDto;
 
 import javax.validation.constraints.NotEmpty;
@@ -18,6 +19,7 @@ public class UserAskListDto {
     private String title;
 
     private QuestionExtendDto question;
+    private QuestionNoExtendDto questionNo;
 
     private UserExtendDto user;
 
@@ -62,4 +64,12 @@ public class UserAskListDto {
     public void setManager(ManagerExtendDto manager) {
         this.manager = manager;
     }
+
+    public QuestionNoExtendDto getQuestionNo() {
+        return questionNo;
+    }
+
+    public void setQuestionNo(QuestionNoExtendDto questionNo) {
+        this.questionNo = questionNo;
+    }
 }

+ 8 - 46
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserHomeworkPreviewListDto.java

@@ -1,14 +1,16 @@
 package com.qxgmat.dto.admin.response;
 
 import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserPaper;
 import com.qxgmat.data.dao.entity.UserReport;
 import com.qxgmat.dto.admin.extend.HomeworkPreviewExtendDto;
 import com.qxgmat.dto.admin.extend.UserExtendDto;
+import com.qxgmat.dto.admin.extend.UserReportExtendDto;
 
 import javax.validation.constraints.NotEmpty;
 import java.util.Date;
 
-@Dto(entity = UserReport.class)
+@Dto(entity = UserPaper.class)
 public class UserHomeworkPreviewListDto {
 
     private Integer id;
@@ -17,15 +19,7 @@ public class UserHomeworkPreviewListDto {
 
     private HomeworkPreviewExtendDto preview;
 
-    private Integer totalNumber;
-
-    private Integer userNumber;
-
-    private Integer correctNumber;
-
-    private Integer totalTime;
-
-    private Integer userTime;
+    private UserReportExtendDto report;
 
     public Integer getId() {
         return id;
@@ -51,43 +45,11 @@ public class UserHomeworkPreviewListDto {
         this.preview = preview;
     }
 
-    public Integer getTotalNumber() {
-        return totalNumber;
-    }
-
-    public void setTotalNumber(Integer totalNumber) {
-        this.totalNumber = totalNumber;
-    }
-
-    public Integer getUserNumber() {
-        return userNumber;
-    }
-
-    public void setUserNumber(Integer userNumber) {
-        this.userNumber = userNumber;
-    }
-
-    public Integer getCorrectNumber() {
-        return correctNumber;
-    }
-
-    public void setCorrectNumber(Integer correctNumber) {
-        this.correctNumber = correctNumber;
-    }
-
-    public Integer getTotalTime() {
-        return totalTime;
-    }
-
-    public void setTotalTime(Integer totalTime) {
-        this.totalTime = totalTime;
-    }
-
-    public Integer getUserTime() {
-        return userTime;
+    public UserReportExtendDto getReport() {
+        return report;
     }
 
-    public void setUserTime(Integer userTime) {
-        this.userTime = userTime;
+    public void setReport(UserReportExtendDto report) {
+        this.report = report;
     }
 }

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

@@ -20,6 +20,10 @@ public class UserListDto {
 
     private Integer realStatus;
 
+    private Integer prepareStatus;
+
+    private Integer inviteNumber;
+
     private BigDecimal totalMoney;
 
     private Collection<UserServiceExtendDto> services;
@@ -81,4 +85,20 @@ public class UserListDto {
     public void setClasses(Collection<UserClassExtendDto> classes) {
         this.classes = classes;
     }
+
+    public Integer getInviteNumber() {
+        return inviteNumber;
+    }
+
+    public void setInviteNumber(Integer inviteNumber) {
+        this.inviteNumber = inviteNumber;
+    }
+
+    public Integer getPrepareStatus() {
+        return prepareStatus;
+    }
+
+    public void setPrepareStatus(Integer prepareStatus) {
+        this.prepareStatus = prepareStatus;
+    }
 }

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

@@ -149,6 +149,7 @@ public class UsersService extends AbstractService {
             User origin = getByInviteCode(inviteCode);
             user.setOriginId(origin.getId());
             // todo 按逻辑进行奖励
+            edit(User.builder().id(origin.getId()).inviteNumber(origin.getInviteNumber() + 1).build());
         }
         // 绑定第三方登录信息
         if (openUser != null){
@@ -289,7 +290,7 @@ public class UsersService extends AbstractService {
             example.and(
                     example.createCriteria().andEqualTo("realStatus", real?1:0)
             );
-        if(order.isEmpty()) order = "id";
+        if(order==null||order.isEmpty()) order = "id";
         if (direction != null){
             switch(direction){
                 case ASC:

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

@@ -37,7 +37,7 @@ public class ExerciseQuestionService extends AbstractService {
     private QuestionNoService questionNoService;
 
 
-    public Page<ExercisePaperQuestion> listAdmin(int page, int pageSize, Number structId, Number questionNo, Number paperId, String place, String difficult, String startTime, String endTime, String order, DirectionStatus direction){
+    public Page<ExercisePaperQuestion> listAdmin(int page, int pageSize, String type, Number structId, Number questionNo, Number paperId, String place, String difficult, String startTime, String endTime, String order, DirectionStatus direction){
         if(order.isEmpty()) order = "id";
         if (direction == null){
             direction = DirectionStatus.DESC;
@@ -46,7 +46,7 @@ public class ExerciseQuestionService extends AbstractService {
         DirectionStatus finalDirection = direction;
 
         Page<ExercisePaperQuestion> p = page(() -> {
-            exerciseQuestionRelationMapper.listAdmin(structId, questionNo, paperId, place, difficult, startTime, endTime, finalOrder, finalDirection.key);
+            exerciseQuestionRelationMapper.listAdmin(type, structId, questionNo, paperId, place, difficult, startTime, endTime, finalOrder, finalDirection.key);
         }, page, pageSize);
 
         Collection ids = Transform.getIds(p, ExercisePaperQuestion.class, "id");

+ 12 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/SettingService.java

@@ -10,6 +10,7 @@ import com.qxgmat.data.dao.SettingMapper;
 import com.qxgmat.data.dao.entity.Setting;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.CachePut;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
@@ -34,6 +35,17 @@ public class SettingService extends AbstractService {
         return one(settingMapper, example);
     }
 
+    @CacheEvict(value = "setting", key="#settingKey.key")
+    public boolean editByKey(SettingKey settingKey, Setting setting){
+        Example example = new Example(Setting.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("key", settingKey.key)
+        );
+        int result = update(settingMapper, example, setting);
+        return result > 0;
+    }
+
     public Setting add(Setting setting){
         int result = insert(settingMapper, setting);
         setting = one(settingMapper, setting.getId());

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

@@ -6,6 +6,7 @@ import com.nuliji.tools.Transform;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
+import com.qxgmat.data.constants.enums.status.AskStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.user.AskTarget;
 import com.qxgmat.data.constants.enums.user.MoneyRange;
@@ -31,8 +32,9 @@ public class UserAskService extends AbstractService {
     @Resource
     private UserAskRelationMapper userAskRelationMapper;
 
-    public Page<UserAsk> listWithUser(int page, int size, Number category, Number userId, Number questionNoId, AskTarget target, Integer status, Integer showStatus, MoneyRange moneyRange, String order, DirectionStatus direction){
+    public Page<UserAsk> listWithUser(int page, int size, String type, Number category, 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;
         Integer min = moneyRange != null ? moneyRange.min : null;
         if(order.isEmpty()) order = "id";
@@ -42,7 +44,7 @@ public class UserAskService extends AbstractService {
         String finalOrder = order;
         DirectionStatus finalDirection = direction;
         Page<UserAsk> p = page(
-                ()-> userAskRelationMapper.listWithUser(category, userId, questionNoId, tk, status, showStatus, min, max, finalOrder, finalDirection.key)
+                ()-> userAskRelationMapper.listWithUser(type, category, userId, questionNoId, tk, statusIndex, showStatus, min, max, finalOrder, finalDirection.key)
         , page, size);
 
         Collection ids = Transform.getIds(p, UserAsk.class, "id");

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

@@ -48,10 +48,8 @@ public class ScheduledTask {
     @Scheduled(cron="0 0 * * * *")
     public void refreshPrepare(){
         // 每小时刷新备考统计信息
-
-        Setting setting = Setting.builder().key(SettingKey.PREPARE_INFO.key).build();
-        setting.setValue(null);
-        settingService.edit(setting);
+        Setting setting = Setting.builder().value(new JSONObject()).build();
+        settingService.editByKey(SettingKey.PREPARE_INFO, setting);
     }
 
     @Scheduled(cron="0 1 0 * * *")