ソースを参照

feat(server|admin): 后台设置相关完成

Go 6 年 前
コミット
9d43a0a11a
100 ファイル変更2171 行追加340 行削除
  1. 0 3
      front/project/admin/routes/setting/explain/index.less
  2. 3 3
      front/project/admin/routes/setting/index.js
  3. 2 2
      front/project/admin/routes/setting/index/index.js
  4. 20 1
      front/project/admin/routes/setting/index/index.less
  5. 444 1
      front/project/admin/routes/setting/index/page.js
  6. 15 0
      front/project/admin/routes/setting/place/index.js
  7. 22 0
      front/project/admin/routes/setting/place/index.less
  8. 60 0
      front/project/admin/routes/setting/place/page.js
  9. 15 0
      front/project/admin/routes/setting/rank/index.js
  10. 3 0
      front/project/admin/routes/setting/rank/index.less
  11. 0 0
      front/project/admin/routes/setting/rank/page.js
  12. 2 2
      front/project/admin/routes/setting/report/index.js
  13. 1 1
      front/project/admin/routes/setting/report/index.less
  14. 2 2
      front/project/admin/routes/setting/service/index.js
  15. 1 1
      front/project/admin/routes/setting/service/index.less
  16. 2 2
      front/project/admin/routes/setting/struct/index.js
  17. 10 1
      front/project/admin/routes/setting/struct/index.less
  18. 298 1
      front/project/admin/routes/setting/struct/page.js
  19. 15 0
      front/project/admin/routes/setting/time/index.js
  20. 3 0
      front/project/admin/routes/setting/time/index.less
  21. 256 0
      front/project/admin/routes/setting/time/page.js
  22. 1 1
      front/project/admin/routes/subject/examination/index.less
  23. 1 1
      front/project/admin/routes/subject/exercise/index.less
  24. 2 1
      front/project/admin/routes/subject/index.js
  25. 1 1
      front/project/admin/routes/subject/preview/index.less
  26. 157 1
      front/project/admin/routes/subject/preview/page.js
  27. 4 4
      front/project/admin/routes/setting/explain/index.js
  28. 3 0
      front/project/admin/routes/subject/previewDetail/index.less
  29. 159 0
      front/project/admin/routes/subject/previewDetail/page.js
  30. 1 1
      front/project/admin/routes/subject/question/index.less
  31. 1 1
      front/project/admin/routes/subject/sentence/index.less
  32. 1 1
      front/project/admin/routes/subject/textbook/index.less
  33. 7 10
      front/project/admin/routes/system/manager/list/page.js
  34. 2 2
      front/project/admin/routes/user/askList/index.js
  35. 1 1
      front/project/admin/routes/user/payList/index.less
  36. 0 0
      front/project/admin/routes/user/ask/page.js
  37. 0 3
      front/project/admin/routes/user/askList/index.less
  38. 2 2
      front/project/admin/routes/user/exerciseList/index.js
  39. 3 0
      front/project/admin/routes/user/exercise/index.less
  40. 0 0
      front/project/admin/routes/user/exercise/page.js
  41. 0 3
      front/project/admin/routes/user/exerciseList/index.less
  42. 2 2
      front/project/admin/routes/user/feedbackList/index.js
  43. 3 0
      front/project/admin/routes/user/feedback/index.less
  44. 0 0
      front/project/admin/routes/user/feedback/page.js
  45. 0 3
      front/project/admin/routes/user/feedbackList/index.less
  46. 6 6
      front/project/admin/routes/user/index.js
  47. 1 1
      front/project/admin/routes/user/list/index.less
  48. 2 2
      front/project/admin/routes/user/payList/index.js
  49. 3 0
      front/project/admin/routes/user/pay/index.less
  50. 0 0
      front/project/admin/routes/user/pay/page.js
  51. 2 2
      front/project/admin/routes/user/previewList/index.js
  52. 3 0
      front/project/admin/routes/user/preview/index.less
  53. 0 0
      front/project/admin/routes/user/preview/page.js
  54. 0 3
      front/project/admin/routes/user/previewList/index.less
  55. 21 0
      front/project/admin/stores/examination.js
  56. 21 0
      front/project/admin/stores/exercise.js
  57. 5 1
      front/project/admin/stores/index.js
  58. 25 0
      front/project/admin/stores/preview.js
  59. 61 5
      front/project/admin/stores/system.js
  60. 25 0
      front/project/admin/stores/users.js
  61. 1 1
      front/src/components/Multiple/index.js
  62. 1 1
      front/src/components/Radio/index.js
  63. 1 1
      front/src/components/Select/index.js
  64. 3 2
      front/src/components/TreeSelect/index.js
  65. 0 1
      front/src/containers/AdminLeft.js
  66. 27 43
      front/src/containers/App.js
  67. 5 2
      front/src/containers/Page.js
  68. 6 1
      front/src/layouts/ActionLayout/index.js
  69. 17 5
      front/src/layouts/FilterLayout/index.js
  70. 13 10
      front/src/layouts/FormLayout/index.js
  71. 1 0
      front/src/layouts/TreeLayout/index.js
  72. 10 4
      front/src/services/AsyncTools.js
  73. 77 29
      front/src/services/Tools.js
  74. 2 2
      front/src/stores/core.js
  75. 3 3
      front/src/stores/other.js
  76. 3 3
      front/src/stores/path.js
  77. 2 1
      server/.gitignore
  78. 1 1
      server/data/src/main/java/com/qxgmat/data/constants/enums/SettingKey.java
  79. 59 24
      server/data/src/main/java/com/qxgmat/data/dao/entity/ExaminationStruct.java
  80. 59 24
      server/data/src/main/java/com/qxgmat/data/dao/entity/ExerciseStruct.java
  81. 4 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/ExaminationStructMapper.xml
  82. 4 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/ExerciseStructMapper.xml
  83. 14 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/AuthController.java
  84. 12 5
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/CommonController.java
  85. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/ExerciseController.java
  86. 46 10
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java
  87. 7 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CommonController.java
  88. 1 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/ManagerRoleExtendDto.java
  89. 1 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/AdDto.java
  90. 18 19
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/ExaminationStructDto.java
  91. 21 20
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/ExerciseStructDto.java
  92. 4 4
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExaminationStructService.java
  93. 16 4
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExerciseStructService.java
  94. 9 4
      server/gateway-api/src/main/java/com/qxgmat/service/inline/SettingService.java
  95. 7 13
      server/gateway-api/src/main/java/com/qxgmat/util/shiro/ManagerRealm.java
  96. 2 2
      server/gateway-api/src/main/profile/dev/application-runtime.yml
  97. 2 1
      server/gateway-api/src/main/resources/application.yml
  98. 10 10
      server/tools/src/main/java/com/nuliji/tools/PageMessage.java
  99. 1 3
      server/tools/src/main/java/com/nuliji/tools/shiro/RoleFilter.java
  100. 0 0
      server/tools/src/main/java/com/nuliji/tools/shiro/cache/RedisManager.java

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

@@ -1,3 +0,0 @@
-@charset "utf-8";
-
-.system-setting-explain {}

+ 3 - 3
front/project/admin/routes/setting/index.js

@@ -1,7 +1,7 @@
 import struct from './struct';
-import explain from './explain';
-import report from './report';
 import service from './service';
 import index from './index/';
+import place from './place';
+import time from './time';
 
-export default [struct, explain, report, service, index];
+export default [struct, service, index, place, time];

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

@@ -2,8 +2,8 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/system/setting/index',
-  key: 'system-setting-index',
+  path: '/setting/index',
+  key: 'setting-index',
   title: '首页设置',
   needLogin: true,
   module,

+ 20 - 1
front/project/admin/routes/setting/index/index.less

@@ -1,3 +1,22 @@
 @charset "utf-8";
 
-.system-setting-index {}
+#setting-index {
+  .block {
+    margin-bottom: 20px;
+  }
+
+  .ant-card.plus {
+    text-align: center;
+    cursor: pointer;
+  }
+
+  .ant-card {
+    margin-bottom: 20px;
+  }
+
+  .delete-button {
+    position: absolute;
+    right: 10px;
+    top: 10px;
+  }
+}

+ 444 - 1
front/project/admin/routes/setting/index/page.js

@@ -1,10 +1,453 @@
 import React from 'react';
+import { Form, Input, InputNumber, Card, Icon, Button, Row, Col, Upload, Affix } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
+// import FileUpload from '@src/components/FileUpload';
+import { flattenObject, formatFormError } from '@src/services/Tools';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { System } from '../../../stores/system';
 
 export default class extends Page {
+  initData() {
+    System.getIndex().then(result => {
+      const { form } = this.props;
+      form.setFieldsValue(flattenObject(result));
+      this.setState({ load: true, data: result });
+    });
+  }
+
+  addLength(field, info) {
+    let { data } = this.state;
+    data = data || {};
+    data[field] = data[field] || [];
+    data[field].push(info);
+    this.setState({ data });
+  }
+
+  deleteLength(field, start, length) {
+    let { data } = this.state;
+    data = data || {};
+    data[field] = data[field] || [];
+    data[field].splite(start, length);
+    this.setState({ data });
+  }
+
+  submit() {
+    const { form } = this.props;
+    form.validateFields((err) => {
+      if (!err) {
+        const data = form.getFieldsValue();
+        data.class = Object.keys(data.class).map((key) => data.class[key]);
+        data.activity = Object.keys(data.activity).map((key) => data.activity[key]);
+        data.evaluation = Object.keys(data.evaluation).map((key) => data.evaluation[key]);
+        System.setIndex(data)
+          .then(() => {
+            this.setState(data);
+            asyncSMessage('保存成功');
+          }).catch((e) => {
+            form.setFields(formatFormError(data, e.result));
+          });
+      }
+    });
+  }
+
+  renderPrepare() {
+    const { getFieldDecorator } = this.props.form;
+    return <Block>
+      <h1>备考攻略</h1>
+      <Form>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='自学-从零开始'>
+          {getFieldDecorator('prepare.first', {
+            rules: [
+              { required: false, message: '请输入跳转地址' },
+            ],
+          })(
+            <Input placeholder='请输入跳转地址' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='自学-继续练习'>
+          {getFieldDecorator('prepare.continue', {
+            rules: [
+              { required: false, message: '请输入跳转地址' },
+            ],
+          })(
+            <Input placeholder='请输入跳转地址' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='课程-初学'>
+          {getFieldDecorator('prepare.classJunior', {
+            rules: [
+              { required: false, message: '请输入跳转地址' },
+            ],
+          })(
+            <Input placeholder='请输入跳转地址' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='课程-中级'>
+          {getFieldDecorator('prepare.classMiddle', {
+            rules: [
+              { required: false, message: '请输入跳转地址' },
+            ],
+          })(
+            <Input placeholder='请输入跳转地址' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='课程-高级'>
+          {getFieldDecorator('prepare.classSenior', {
+            rules: [
+              { required: false, message: '请输入跳转地址' },
+            ],
+          })(
+            <Input placeholder='请输入跳转地址' />,
+          )}
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
+  renderUser() {
+    const { getFieldDecorator } = this.props.form;
+    return <Block>
+      <h1>用户数据</h1>
+      <Form>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='线下用户量'>
+          {getFieldDecorator('user.numberOffline', {
+            rules: [
+              { required: false, message: '' },
+            ],
+          })(
+            <InputNumber placeholder='请输入线下用户量' style={{ width: '200px' }} />,
+          )}
+        </Form.Item>
+
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='700+学员数'>
+          {getFieldDecorator('user.number700', {
+            rules: [
+              { required: false, message: '' },
+            ],
+          })(
+            <InputNumber placeholder='请输入700+学员数' style={{ width: '200px' }} />,
+          )}
+        </Form.Item>
+
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='学员平均分'>
+          {getFieldDecorator('user.numberScore', {
+            rules: [
+              { required: false, message: '' },
+            ],
+          })(
+            <InputNumber placeholder='请输入学员平均分' style={{ width: '200px' }} />,
+          )}
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
+  renderClass() {
+    const { getFieldDecorator, getFieldValue, setFieldsValue } = this.props.form;
+    const { data } = this.state;
+    const classes = data.class || [];
+    return <Block>
+      <h1>千行课堂</h1>
+      <Form>
+        <Row>
+          {classes.map((row, index) => {
+            const image = getFieldValue(`class.${index}.image`) || null;
+            return <Col span={7} offset={index % 3 ? 1 : 0}><Card>
+              <Button className="delete-button" size="small" onClick={() => {
+                this.deleteLength('class', index, 1);
+              }}>
+                <Icon type="delete" />
+              </Button>
+              <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='课程名称'>
+                {getFieldDecorator(`class.${index}.title`, {
+                  rules: [
+                    { required: true, message: '输入课程名称' },
+                  ],
+                  initialValue: row.title,
+                })(
+                  <Input placeholder='请输入课程名称' />,
+                )}
+              </Form.Item>
+              <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='跳转链接'>
+                {getFieldDecorator(`class.${index}.link`, {
+                  rules: [
+                    { required: true, message: '输入跳转链接' },
+                  ],
+                  initialValue: row.link,
+                })(
+                  <Input placeholder='请输入跳转链接' />,
+                )}
+              </Form.Item>
+              <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='背景图片'>
+                {getFieldDecorator(`class.${index}.image`, {
+                  rules: [
+                    { required: true, message: '上传图片' },
+                  ],
+                })(
+                  <Upload
+                    listType="picture-card"
+                    showUploadList={false}
+                    beforeUpload={(file) => System.uploadImage(file).then((result) => {
+                      setFieldsValue({ [`class.${index}.image`]: result });
+                    })}
+                    onChange={this.handleChange}
+                  >
+                    {image ? <img src={image} alt="avatar" /> : <div>
+                      <Icon type={this.state.loading ? 'loading' : 'plus'} />
+                      <div className="ant-upload-text">Upload</div>
+                    </div>}
+                  </Upload>,
+                  // <FileUpload type='image'
+                  //   onChange={(file) => System.uploadImage(file).then((result) => {
+                  //     setFieldsValue({ [`class.${index}.image`]: result });
+                  //   })}
+                  // />,
+                )}
+              </Form.Item>
+            </Card></Col>;
+          })}
+          <Col span={7} offset={classes.length % 3 ? 1 : 0}>
+            <Card className="plus" onClick={() => {
+              this.addLength('class', { title: '', link: '', image: '' });
+            }}>
+              <Icon type={'plus'} />
+            </Card>
+          </Col>
+        </Row>
+      </Form>
+    </Block >;
+  }
+
+  renderActivity() {
+    const { getFieldDecorator, getFieldValue, setFieldsValue } = this.props.form;
+    const { data } = this.state;
+    const activity = data.activity || [];
+    return <Block>
+      <h1>活动信息</h1>
+      <Form>
+        <Row>
+          {activity.map((row, index) => {
+            const image = getFieldValue(`activity.${index}.image`) || null;
+            return <Col span={7} offset={index % 3 ? 1 : 0}><Card>
+              <Button className="delete-button" size="small" onClick={() => {
+                this.deleteLength('activity', index, 1);
+              }}>
+                <Icon type="delete" />
+              </Button>
+              <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='跳转链接'>
+                {getFieldDecorator(`activity.${index}.link`, {
+                  rules: [
+                    { required: true, message: '输入跳转链接' },
+                  ],
+                  initialValue: row.link,
+                })(
+                  <Input placeholder='请输入跳转链接' />,
+                )}
+              </Form.Item>
+              <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='活动图片'>
+                {getFieldDecorator(`activity.${index}.image`, {
+                  rules: [
+                    { required: true, message: '上传图片' },
+                  ],
+                })(
+                  <Upload
+                    listType="picture-card"
+                    showUploadList={false}
+                    beforeUpload={(file) => System.uploadImage(file).then((result) => {
+                      setFieldsValue({ [`claactivityss.${index}.image`]: result });
+                    })}
+                    onChange={this.handleChange}
+                  >
+                    {image ? <img src={image} alt="avatar" /> : <div>
+                      <Icon type={this.state.loading ? 'loading' : 'plus'} />
+                      <div className="ant-upload-text">Upload</div>
+                    </div>}
+                  </Upload>,
+                  // <FileUpload type='image'
+                  //   onChange={(file) => System.uploadImage(file).then((result) => {
+                  //     setFieldsValue({ [`class.${index}.image`]: result });
+                  //   })}
+                  // />,
+                )}
+              </Form.Item>
+            </Card></Col>;
+          })}
+          <Col span={7} offset={activity.length % 3 ? 1 : 0}>
+            <Card className="plus" onClick={() => {
+              this.addLength('activity', { link: '', image: '' });
+            }}>
+              <Icon type={'plus'} />
+            </Card>
+          </Col>
+        </Row>
+      </Form>
+    </Block>;
+  }
+
+  renderEvaluation() {
+    const { getFieldDecorator, getFieldValue, setFieldsValue } = this.props.form;
+    const { data } = this.state;
+    const evaluation = data.evaluation || [];
+    return <Block>
+      <h1>学员评价</h1>
+      <Form>
+        <Row>
+          {evaluation.map((row, index) => {
+            const avatar = getFieldValue(`evaluation.${index}.avatar`) || null;
+            return <Col span={7} offset={index % 3 ? 1 : 0}><Card>
+              <Button className="delete-button" size="small" onClick={() => {
+                this.deleteLength('evaluation', index, 1);
+              }}>
+                <Icon type="delete" />
+              </Button>
+              <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='学员昵称'>
+                {getFieldDecorator(`evaluation.${index}.nickname`, {
+                  rules: [
+                    { required: true, message: '输入学员昵称' },
+                  ],
+                  initialValue: row.nickname,
+                })(
+                  <Input placeholder='请输入学员昵称' />,
+                )}
+              </Form.Item>
+              <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='学员头像'>
+                {getFieldDecorator(`evaluation.${index}.avatar`, {
+                  rules: [
+                    { required: true, message: '上传图片' },
+                  ],
+                })(
+                  <Upload
+                    listType="picture-card"
+                    showUploadList={false}
+                    beforeUpload={(file) => System.uploadImage(file).then((result) => {
+                      setFieldsValue({ [`evaluation.${index}.avatar`]: result });
+                    })}
+                  >
+                    {avatar ? <img src={avatar} alt="avatar" /> : <div>
+                      <Icon type={this.state.loading ? 'loading' : 'plus'} />
+                      <div className="ant-upload-text">Upload</div>
+                    </div>}
+                  </Upload>,
+                  // <FileUpload type='image'
+                  //   onChange={(file) => System.uploadImage(file).then((result) => {
+                  //     setFieldsValue({ [`class.${index}.image`]: result });
+                  //   })}
+                  // />,
+                )}
+              </Form.Item>
+              <Form.Item labelCol={{ span: 7 }} wrapperCol={{ span: 15 }} label='评价内容'>
+                {getFieldDecorator(`evaluation.${index}.content`, {
+                  rules: [
+                    { required: true, message: '输入评价内容' },
+                  ],
+                  initialValue: row.content,
+                })(
+                  <Input placeholder='请输入评价内容' />,
+                )}
+              </Form.Item>
+            </Card></Col>;
+          })}
+          <Col span={7} offset={evaluation.length % 3 ? 1 : 0}>
+            <Card className="plus" onClick={() => {
+              this.addLength('evaluation', { title: '', link: '', image: '' });
+            }}>
+              <Icon type={'plus'} />
+            </Card>
+          </Col>
+        </Row>
+      </Form>
+    </Block>;
+  }
+
+  renderContact() {
+    const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
+    const wechatImage = getFieldValue('contact.wechatImage');
+    const weiboImage = getFieldValue('contacct.weiboImage');
+    return <Block>
+      <Form>
+        <h1>联系方式</h1>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='电话'>
+          {getFieldDecorator('contact.phone', {
+            rules: [
+              { required: false, message: '请输入电话' },
+            ],
+          })(
+            <Input placeholder='请输入电话' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='邮箱'>
+          {getFieldDecorator('contact.email', {
+            rules: [
+              { required: false, message: '请输入邮箱' },
+            ],
+          })(
+            <Input placeholder='请输入邮箱' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='微信号'>
+          {getFieldDecorator('contact.wechat', {
+            rules: [
+              { required: false, message: '请输入微信号' },
+            ],
+          })(
+            <Input placeholder='请输入微信号' />,
+          )}
+        </Form.Item>
+
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='公众号二维码'>
+          {getFieldDecorator('contact.wechatImage')(
+            <Upload
+              listType="picture-card"
+              showUploadList={false}
+              beforeUpload={(file) => {
+                System.uploadImage(file).then((result) => {
+                  setFieldsValue({ 'contact.wechatImage': result });
+                });
+              }
+              }
+            >
+              {wechatImage ? <img src={wechatImage} 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: 16 }} label='微博二维码'>
+          {getFieldDecorator('contact.weiboImage')(
+            <Upload
+              listType="picture-card"
+              showUploadList={false}
+              beforeUpload={(file) => System.uploadImage(file).then((result) => {
+                setFieldsValue({ 'contact.weiboImage': result });
+              })}
+            >
+              {weiboImage ? <img src={weiboImage} alt="avatar" /> : <div>
+                <Icon type={this.state.loading ? 'loading' : 'plus'} />
+                <div className="ant-upload-text">Upload</div>
+              </div>}
+            </Upload>,
+          )}
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
   renderView() {
-    return <Block flex />;
+    return <div >
+      {this.renderPrepare()}
+      {this.renderUser()}
+      {this.renderClass()}
+      {this.renderActivity()}
+      {this.renderEvaluation()}
+      {this.renderContact()}
+      <Affix style={{ position: 'absolute', bottom: '50px', right: '50px' }}>
+        <Button type="primary" onClick={() => {
+          this.submit();
+        }}>保存</Button>
+      </Affix>
+    </div>;
   }
 }

+ 15 - 0
front/project/admin/routes/setting/place/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/setting/place',
+  key: 'setting-place',
+  title: '考点设置',
+  needLogin: true,
+  module,
+  group,
+  index: true,
+  component() {
+    return import('./page');
+  },
+};

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

@@ -0,0 +1,22 @@
+@charset "utf-8";
+
+#setting-place {
+  .block {
+    margin-bottom: 20px;
+  }
+
+  .ant-card.plus {
+    text-align: center;
+    cursor: pointer;
+  }
+
+  .ant-card {
+    margin-bottom: 20px;
+  }
+
+  .delete-button {
+    position: absolute;
+    right: 10px;
+    top: 10px;
+  }
+}

+ 60 - 0
front/project/admin/routes/setting/place/page.js

@@ -0,0 +1,60 @@
+import React from 'react';
+import { Form, Button, Select } from 'antd';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import { formatFormError } from '@src/services/Tools';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { QuestionType } from '@src/services/Constant';
+import { System } from '../../../stores/system';
+
+export default class extends Page {
+  initData() {
+    System.getPlace().then(result => {
+      const { form } = this.props;
+      form.setFieldsValue(result);
+      this.setState({ load: true, data: result });
+    });
+  }
+
+  submit() {
+    const { form } = this.props;
+    form.validateFields((err) => {
+      if (!err) {
+        const data = form.getFieldsValue();
+        System.setPlace(data)
+          .then(() => {
+            this.setState(data);
+            asyncSMessage('保存成功');
+          }).catch((e) => {
+            form.setFields(formatFormError(data, e.result));
+          });
+      }
+    });
+  }
+
+  renderView() {
+    const { getFieldDecorator } = this.props.form;
+    return <Block>
+      <Form>
+        {QuestionType.map(row => {
+          return <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label={row.label}>
+            {getFieldDecorator(row.value, {
+              rules: [
+                { required: false, message: '请输入跳转地址' },
+              ],
+            })(
+              <Select mode='tags' maxTagCount={200} notFoundContent={null} placeholder='输入多个考点, 逗号分隔' tokenSeparators={[',', ',']} />,
+            )}
+          </Form.Item>;
+        })}
+
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16, offset: 5 }}>
+          <Button type="primary" onClick={() => {
+            this.submit();
+          }}>保存</Button>
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+}

+ 15 - 0
front/project/admin/routes/setting/rank/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/setting/rank',
+  key: 'setting-rank',
+  title: '排名设置',
+  needLogin: true,
+  module,
+  group,
+  index: true,
+  component() {
+    return import('./page');
+  },
+};

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

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#setting-explain {}

front/project/admin/routes/setting/explain/page.js → front/project/admin/routes/setting/rank/page.js


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

@@ -2,8 +2,8 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/system/setting/report',
-  key: 'system-setting-report',
+  path: '/setting/report',
+  key: 'setting-report',
   title: '报告设置',
   needLogin: true,
   module,

+ 1 - 1
front/project/admin/routes/setting/report/index.less

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-.system-setting-report {}
+#setting-report {}

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

@@ -2,8 +2,8 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/system/setting/service',
-  key: 'system-setting-service',
+  path: '/setting/service',
+  key: 'setting-service',
   title: '服务设置',
   needLogin: true,
   module,

+ 1 - 1
front/project/admin/routes/setting/service/index.less

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-.system-setting-service {}
+#setting-service {}

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

@@ -2,8 +2,8 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/system/setting/structure',
-  key: 'system-setting-structure',
+  path: '/setting/struct',
+  key: 'setting-struct',
   title: '结构设置',
   needLogin: true,
   module,

+ 10 - 1
front/project/admin/routes/setting/struct/index.less

@@ -1,3 +1,12 @@
 @charset "utf-8";
 
-.system-setting-structure {}
+#setting-struct {
+  .node {
+    position: relative;
+
+    .after-node {
+      position: absolute;
+      right: -150px;
+    }
+  }
+}

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

@@ -1,10 +1,307 @@
 import React from 'react';
+import { Tabs, Button, Icon } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
+import TreeLayout from '@src/layouts/TreeLayout';
+import ActionLayout from '@src/layouts/ActionLayout';
+import { formatTreeData } from '@src/services/Tools';
+import { asyncDelConfirm, asyncForm, asyncSMessage } from '@src/services/AsyncTools';
+// import { System } from '../../../stores/system';
+import { Examination } from '../../../stores/examination';
+import { Exercise } from '../../../stores/exercise';
 
 export default class extends Page {
+  constructor(props) {
+    super(props);
+    this.exerciseActionList = [{
+      key: 'addChild',
+      name: '增加子节点',
+    }, {
+      key: 'addBrother',
+      name: '增加兄弟节点',
+      selectNum: 1,
+    }, {
+      key: 'edit',
+      name: '编辑',
+      selectNum: 1,
+    }, {
+      key: 'delete',
+      name: '批量删除',
+      type: 'danger',
+      needCheck: 1,
+    }];
+    this.exerciseItemList = [{
+      key: 'id',
+      type: 'hidden',
+    }, {
+      key: 'parentId',
+      type: 'tree',
+      name: '父节点',
+      tree: [],
+      fieldNames: { label: 'title', value: 'value', children: 'children' },
+      notFoundContent: null,
+      onChange: (value) => {
+        console.log(value);
+      },
+    }, {
+      key: 'titleZh',
+      name: '中文名称',
+      type: 'input',
+      placeholder: '请输入中文名称',
+      required: true,
+    }, {
+      key: 'titleEn',
+      name: '英文名称',
+      type: 'input',
+      placeholder: '请输入英文名称',
+      required: true,
+    }, {
+      key: 'description',
+      name: '描述',
+      type: 'textarea',
+      placeholder: '请输入描述',
+      required: true,
+    }];
+    this.examinationActionList = [{
+      key: 'addChild',
+      name: '增加子节点',
+    }, {
+      key: 'addBrother',
+      name: '增加兄弟节点',
+      selectNum: 1,
+    }, {
+      key: 'edit',
+      name: '编辑',
+      selectNum: 1,
+    }, {
+      key: 'delete',
+      name: '批量删除',
+      type: 'danger',
+      needCheck: 1,
+    }];
+    this.examinationItemList = [{
+      key: 'id',
+      type: 'hidden',
+    }, {
+      key: 'parentId',
+      type: 'tree',
+      name: '父节点',
+      tree: [],
+      fieldNames: { label: 'title', value: 'value', children: 'children' },
+      notFoundContent: null,
+      onChange: (value) => {
+        console.log(value);
+      },
+    }, {
+      key: 'titleZh',
+      name: '中文名称',
+      type: 'input',
+      placeholder: '请输入中文名称',
+      required: true,
+    }, {
+      key: 'titleEn',
+      name: '英文名称',
+      type: 'input',
+      placeholder: '请输入英文名称',
+      required: true,
+    }, {
+      key: 'description',
+      name: '描述',
+      type: 'textarea',
+      placeholder: '请输入描述',
+      required: true,
+    }];
+    this.state.tab = 'exercise';
+  }
+
+  initData() {
+    this.refresh();
+  }
+
+  refresh() {
+    const { tab } = this.state;
+    if (tab === 'exercise') {
+      return this.refreshExercise();
+    }
+    return this.refreshExamination();
+  }
+
+  refreshExercise() {
+    Exercise.allStruct().then(result => {
+      const list = result.map(row => { row.title = `${row.titleZh}/${row.titleEn}`; return row; });
+      this.exerciseItemList[1].tree = formatTreeData([{ title: '根节点', id: 0 }].concat(list));
+
+      this.setState({
+        exerciseList: list,
+        exerciseStruct: formatTreeData(list.map(row => {
+          if (row.level < 4) 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.questionStatus = row.questionStatus > 0 ? 0 : 1;
+            Exercise.editStruct(row).then(() => {
+              this.refresh();
+            });
+          }}>{row.questionStatus > 0 ? [<Icon type='pause' />, <span>提问中</span>] : [<Icon type="caret-right" />, <span>提问关闭</span>]}</Button></div>;
+          return row;
+        }), 'id', 'title'),
+      });
+    });
+  }
+
+  refreshExamination() {
+    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));
+      this.setState({ examinationList: list, examinationStruct: formatTreeData(list, 'id', 'title') });
+    });
+  }
+
+  addChildAction() {
+    const { tab, exerciseList, examinationList, selectedKeys } = this.state;
+    let itemList;
+    let node = 0;
+    if (tab === 'exercise') {
+      itemList = this.exerciseItemList;
+      if (selectedKeys.length > 0) {
+        node = exerciseList.filter(row => row.id === Number(selectedKeys[0]))[0].id;
+      }
+    } else {
+      itemList = this.examinationItemList;
+      if (selectedKeys.length > 0) {
+        node = examinationList.filter(row => row.id === Number(selectedKeys[0]))[0].id;
+      }
+    }
+    asyncForm('新增', itemList, { parentId: `${node}` }, data => {
+      console.log(data);
+      let handler;
+      if (tab === 'exercise') {
+        handler = Exercise.addStruct(data);
+      } else {
+        handler = Examination.addStruct(data);
+      }
+      return handler.then(() => {
+        asyncSMessage('新增成功!');
+        this.refresh();
+      });
+    });
+  }
+
+  addBrotherAction() {
+    const { tab, exerciseList, examinationList, selectedKeys } = this.state;
+    let itemList;
+    let node;
+    if (tab === 'exercise') {
+      itemList = this.exerciseItemList;
+      if (selectedKeys.length > 0) {
+        node = exerciseList.filter(row => row.id === Number(selectedKeys[0]))[0].parentId;
+      }
+    } else {
+      itemList = this.examinationItemList;
+      if (selectedKeys.length > 0) {
+        node = examinationList.filter(row => row.id === Number(selectedKeys[0]))[0].parentId;
+      }
+    }
+    asyncForm('新增', itemList, { parentId: `${node}` }, data => {
+      let handler;
+      if (tab === 'exercise') {
+        handler = Exercise.addStruct(data);
+      } else {
+        handler = Examination.addStruct(data);
+      }
+      return handler.then(() => {
+        asyncSMessage('新增成功!');
+        this.refresh();
+      });
+    });
+  }
+
+  editAction() {
+    const { tab, exerciseList, examinationList, selectedKeys } = this.state;
+    let itemList;
+    let list;
+    if (tab === 'exercise') {
+      itemList = this.exerciseItemList;
+      list = exerciseList;
+    } else {
+      itemList = this.examinationItemList;
+      list = examinationList;
+    }
+    asyncForm('编辑', itemList, list.filter(row => row.id === Number(selectedKeys[0])).map(row => {
+      row.parentId = `${row.parentId}`;
+      return row;
+    })[0], data => {
+      let handler;
+      if (tab === 'exercise') {
+        handler = Exercise.editStruct(data);
+      } else {
+        handler = Examination.editStruct(data);
+      }
+      return handler.then(() => {
+        asyncSMessage('编辑成功!');
+        this.refresh();
+      });
+    });
+  }
+
+  deleteAction() {
+    const { tab, checkedKeys } = this.state;
+    asyncDelConfirm('删除确认', '是否删除选中节点?', () => {
+      if (tab === 'exercise') {
+        return Promise.all(checkedKeys.map(row => Examination.delStruct({ id: row })));
+      }
+      return Promise.all(checkedKeys.map(row => Examination.delStruct({ id: row })));
+    }).then(() => {
+      asyncSMessage('删除成功!');
+      this.refresh();
+    });
+  }
+
+  renderExamination() {
+    const { loading } = this.state;
+    return <Block loading={loading}>
+      <ActionLayout
+        itemList={this.exerciseActionList}
+        selectedKeys={this.state.selectedKeys}
+        checkedKeys={this.state.checkedKeys}
+        onAction={key => this.onAction(key)} />
+      <TreeLayout autoExpandParent defaultExpandAll checkable itemList={this.state.examinationStruct} selectedKeys={this.state.selectedKeys} onSelect={(selectedKeys) => {
+        this.setState({ selectedKeys });
+      }} checkedKeys={this.state.checkedKeys} onCheck={(checkedKeys) => {
+        this.setState({ checkedKeys });
+      }} />
+    </Block>;
+  }
+
+  renderExercise() {
+    const { loading } = this.state;
+    return <Block loading={loading}>
+      <ActionLayout
+        itemList={this.exerciseActionList}
+        selectedKeys={this.state.selectedKeys}
+        checkedKeys={this.state.checkedKeys}
+        onAction={key => this.onAction(key)} />
+      <TreeLayout autoExpandParent defaultExpandAll checkable itemList={this.state.exerciseStruct} selectedKeys={this.state.selectedKeys} onSelect={(selectedKeys) => {
+        this.setState({ selectedKeys });
+      }} checkedKeys={this.state.checkedKeys} onCheck={(checkedKeys) => {
+        this.setState({ checkedKeys });
+      }} />
+    </Block>;
+  }
+
   renderView() {
-    return <Block flex />;
+    const { tab } = this.state;
+    return <Block><Tabs activeKey={tab} onChange={(value) => {
+      this.setState({ tab: value, selectedKeys: [], checkedKeys: [] });
+      this.refresh();
+    }}>
+      <Tabs.TabPane tab="练习结构" key="exercise">
+        {this.renderExercise()}
+      </Tabs.TabPane>
+      <Tabs.TabPane tab="模考结构" key="examination">
+        {this.renderExamination()}
+      </Tabs.TabPane>
+    </Tabs></Block>;
   }
 }

+ 15 - 0
front/project/admin/routes/setting/time/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/setting/time',
+  key: 'setting-time',
+  title: '时间设置',
+  needLogin: true,
+  module,
+  group,
+  index: true,
+  component() {
+    return import('./page');
+  },
+};

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

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#setting-time {}

+ 256 - 0
front/project/admin/routes/setting/time/page.js

@@ -0,0 +1,256 @@
+import React from 'react';
+import { Tabs, Form, Row, Col, InputNumber, Button } from 'antd';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import EditTableCell from '@src/components/EditTableCell';
+import { getMap, flattenObject } from '@src/services/Tools';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import TableLayout from '@src/layouts/TableLayout';
+import { QuestionDifficult } from '@src/services/Constant';
+import { System } from '../../../stores/system';
+import { Examination } from '../../../stores/examination';
+import { Exercise } from '../../../stores/exercise';
+
+export default class extends Page {
+  constructor(props) {
+    super(props);
+    this.exerciseColumns = [{
+      title: '学科',
+      dataIndex: 'title',
+    }].concat(QuestionDifficult.map(row => {
+      const { exercise = {} } = this.state;
+      return {
+        title: row.label,
+        dataIndex: row.value,
+        render: (text, result) => {
+          return <EditTableCell value={(exercise[result.id] || {})[text] || 0} onChange={(v) => {
+            this.changeMapValue('exercise', result.id, row.value, v);
+          }} />;
+        },
+      };
+    }));
+
+    this.examinationColumns = [{
+      title: '学科',
+      dataIndex: 'title',
+    }, {
+      title: '题目数',
+      dataIndex: 'number',
+      render: (text, result) => {
+        const { examination = [] } = this.state;
+        return <EditTableCell value={(examination[result.id] || {}).number || 0} onChange={(v) => {
+          this.changeMapValue('examination', result.id, 'number', v);
+        }} />;
+      },
+    }, {
+      title: '做题时间',
+      dataIndex: 'time',
+      render: (text, result) => {
+        const { examination } = this.state;
+        return <EditTableCell value={(examination[result.id] || {}).time || 0} onChange={(v) => {
+          this.changeValue('examination', result.id, 'time', v);
+        }} />;
+      },
+    }];
+    this.state.tab = 'exercise';
+  }
+
+  initData() {
+    Promise.all(this.structExamination(), this.structExercise())
+      .then(() => {
+        return this.refresh(this.state.tab);
+      });
+  }
+
+  refresh(tab) {
+    if (tab === 'exercise') {
+      return this.refreshExercise();
+    }
+    if (tab === 'examination') {
+      return this.refreshExamination();
+    }
+    if (tab === 'filter') {
+      return this.refreshFilter();
+    }
+    return Promise.reject();
+  }
+
+  refreshExercise() {
+    return System.getExerciseTime().then((result) => {
+      this.setState({ exercise: result || {} });
+    });
+  }
+
+  refreshExamination() {
+    return System.getExaminationTime().then((result) => {
+      this.setState({ examination: result || {} });
+    });
+  }
+
+  refreshFilter() {
+    return System.getFilterTime().then((result) => {
+      this.setState({ filter: result });
+
+      const { form } = this.props;
+      form.setFieldsValue(flattenObject(result, 'filter'));
+    });
+  }
+
+  structExercise() {
+    return Exercise.allStruct().then(result => {
+      const list = result.map(row => { row.title = `${row.titleZh}/${row.titleEn}`; return row; });
+      const map = getMap(list, 'id');
+      this.setState({
+        exerciseList: list.filter(row => row.level === 2).map(row => {
+          const parent = map[row.parentId];
+          row.title = `${parent.title}-${row.title}`;
+          return row;
+        }),
+      });
+    });
+  }
+
+  structExamination() {
+    return Examination.allStruct().then(result => {
+      const list = result.map(row => { row.title = `${row.titleZh}/${row.titleEn}`; return row; });
+      this.setState({
+        examinationList: list.filter(row => row.level === 1),
+      });
+    });
+  }
+
+  changeMapValue(field, index, key, value) {
+    const data = this.state[field] || {};
+    data[index] = data[index] || {};
+    data[index][key] = value;
+    this.setState({ [field]: data });
+  }
+
+  changeValue(field, key, value) {
+    const data = this.state[field] || {};
+    data[key] = value;
+    this.setState({ [field]: data });
+  }
+
+  submit(tab) {
+    let handler;
+    if (tab === 'exercise') {
+      handler = this.submitExercise();
+    }
+    if (tab === 'examination') {
+      handler = this.submitExamination();
+    }
+    if (tab === 'filter') {
+      handler = this.submitFilter();
+    }
+    handler.then(() => {
+      asyncSMessage('保存成功');
+    });
+  }
+
+  submitExercise() {
+    const { exercise } = this.state;
+    return System.setExerciseTime(exercise);
+  }
+
+  submitExamination() {
+    const { examination } = this.state;
+    return System.setExaminationTime(examination);
+  }
+
+  submitFilter() {
+    const { form } = this.props;
+    return new Promise((resolve, reject) => {
+      form.validateFields(['filter'], (err, values) => {
+        if (!err) {
+          System.setFilterTime(values.filter)
+            .then(() => {
+              resolve();
+            })
+            .catch((e) => {
+              reject(e);
+            });
+        }
+      });
+    });
+  }
+
+  renderExercise() {
+    return <TableLayout
+      columns={this.exerciseColumns}
+      list={this.state.exerciseList}
+      pagination={false}
+      loading={this.props.core.loading}
+    />;
+  }
+
+  renderFilterTime() {
+    const { getFieldDecorator } = this.props.form;
+    return <Form>
+      <Row>
+        <Col span={12}>
+          <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='最短时间/题'>
+            {getFieldDecorator('filter.min', {
+              rules: [
+                { required: true, message: '输入最短做题时间' },
+              ],
+            })(
+              <InputNumber placeholder='请输入最短做题时间' addonAfter="s" onChange={(value) => {
+                this.changeValue('filter', 'min', value);
+              }} style={{ width: '200px' }} />,
+            )}
+          </Form.Item>
+        </Col>
+        <Col span={12}>
+          <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='最长时间/题'>
+            {getFieldDecorator('filter.max', {
+              rules: [
+                { required: true, message: '输入最长做题时间' },
+              ],
+            })(
+              <InputNumber placeholder='请输入最长做题时间' addonAfter="s" onChange={(value) => {
+                this.changeValue('filter', 'max', value);
+              }} style={{ width: '200px' }} />,
+            )}
+          </Form.Item>
+        </Col>
+      </Row>
+    </Form>;
+  }
+
+  renderExamination() {
+    return <TableLayout
+      columns={this.exerciseColumns}
+      list={this.state.exerciseList}
+      pagination={false}
+      loading={this.props.core.loading}
+    />;
+  }
+
+  renderView() {
+    const { tab } = this.state;
+    return <Block><Tabs activeKey={tab} onChange={(value) => {
+      this.setState({ tab: value, selectedKeys: [], checkedKeys: [] });
+      this.refresh(value);
+    }}>
+      <Tabs.TabPane tab="预估做题时间" key="exercise">
+        {this.renderExercise()}
+      </Tabs.TabPane>
+      <Tabs.TabPane tab="数据剔除时间" key="filter">
+        {this.renderFilterTime()}
+      </Tabs.TabPane>
+      <Tabs.TabPane tab="预估考试时间" key="examination">
+        {this.renderExamination()}
+      </Tabs.TabPane>
+    </Tabs>
+      <Row type="flex" justify="center">
+        <Col>
+          <Button type="primary" onClick={() => {
+            this.submit(tab);
+          }}>保存</Button>
+        </Col>
+      </Row>
+    </Block>;
+  }
+}

+ 1 - 1
front/project/admin/routes/subject/examination/index.less

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-.subject-examination {}
+#subject-examination {}

+ 1 - 1
front/project/admin/routes/subject/exercise/index.less

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-.subject-exercise {}
+#subject-exercise {}

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

@@ -4,5 +4,6 @@ import examination from './examination';
 import textbook from './textbook';
 import sentence from './sentence';
 import preview from './preview';
+import previewDetail from './previewDetail';
 
-export default [question, exercise, examination, textbook, sentence, preview];
+export default [question, exercise, examination, textbook, sentence, preview, previewDetail];

+ 1 - 1
front/project/admin/routes/subject/preview/index.less

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-.subject-preview {}
+#subject-preview {}

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

@@ -1,10 +1,166 @@
 import React from 'react';
+import { Button, Upload } 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 { PreviewStatus } from '@src/services/Constant';
+import { getMap, formatDate } from '@src/services/Tools';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { Exercise } from '../../../stores/exercise';
+import { Preview } from '../../../stores/preview';
+import { System } from '../../../stores/system';
 
+const filterForm = [
+  {
+    key: 'category',
+    type: 'select',
+    allowClear: true,
+    name: '课程',
+    placeholder: '请选择',
+    number: true,
+  },
+  {
+    key: 'status',
+    type: 'select',
+    name: '状态',
+    select: PreviewStatus,
+    number: true,
+    placeholder: '请选择',
+  },
+];
 export default class extends Page {
+  constructor(props) {
+    super(props);
+    this.actionList = [{
+      key: 'add',
+      name: '新建作业',
+      render: (item) => {
+        // return <Link to='/subject/preview/add'><Button>{item.name}</Button></Link>;
+        return <Button onClick={() => {
+          linkTo('/subject/preview/detail');
+        }}>{item.name}</Button>;
+      },
+    }, {
+      key: 'template',
+      name: '下载批量导入模版',
+      render: (item) => {
+        return <a href={`/${encodeURIComponent('预习作业导入模板')}.xlsx`}>{item.name}</a>;
+      },
+    }, {
+      key: 'import',
+      name: '批量导入',
+      render: (item) => {
+        return <Upload
+          showUploadList={false}
+          beforeUpload={(file) => System.uploadImage(file).then((result) => {
+            asyncSMessage(result);
+          })}
+        >
+          <Button>{item.name}</Button>
+        </Upload>;
+      },
+    }, {
+      key: 'delete',
+      name: '批量删除',
+      needSelect: 1,
+    }];
+    this.categoryMap = {};
+    this.columns = [{
+      title: '课程',
+      dataIndex: 'category',
+      render: (text) => {
+        return this.categoryMap[text] || text;
+      },
+    }, {
+      title: '开始时间',
+      dataIndex: 'startTime',
+      render: (text) => {
+        return formatDate(text);
+      },
+    }, {
+      title: '结束时间',
+      dataIndex: 'endTime',
+      render: (text) => {
+        return formatDate(text);
+      },
+    }, {
+      title: '作业标题',
+      dataIndex: 'title',
+    }, {
+      title: '完成进度',
+      dataIndex: 'finish',
+      render: (text, record) => {
+        return `${text}/${record.userIds.length}`;
+      },
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          {(
+            <Link to={`/subject/preview/detail/${record.id}`}>编辑</Link>
+          )}
+          {(
+            <Link to={`/user/previewList?previewId=${record.id}&startTime=${record.startTime}&endTime=${record.endTime}`}>查看</Link>
+          )}
+          {(
+            <Button
+              size="small"
+              type="link"
+              onClick={() => {
+
+              }}
+            >
+              复制
+          </Button>
+          )}
+        </div>;
+      },
+    }];
+  }
+
+  init() {
+    Exercise.allStruct().then(result => {
+      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(filterForm[0].select, 'id', 'title');
+      this.setState({ exercise: result });
+    });
+  }
+
+  initData() {
+    Preview.list(this.state.search).then(result => {
+      this.setTableData(result.list, result.total);
+    });
+  }
+
   renderView() {
-    return <Block flex />;
+    return <Block flex>
+      <FilterLayout
+        show
+        itemList={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>;
   }
 }

+ 4 - 4
front/project/admin/routes/setting/explain/index.js

@@ -2,13 +2,13 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/system/setting/explain',
-  key: 'system-setting-explain',
-  title: '说明设置',
+  path: '/subject/preview/detail/:id?',
+  key: 'subject-preview-detail',
+  title: '新建预习作业',
   needLogin: true,
   module,
   group,
-  index: true,
+  showKey: 'subject-preview',
   component() {
     return import('./page');
   },

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

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

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

@@ -0,0 +1,159 @@
+import React from 'react';
+import { Form, Input, Button, Row, Col, DatePicker, List, Icon } from 'antd';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import Select from '@src/components/Select';
+// import FileUpload from '@src/components/FileUpload';
+import { formatFormError, generateSearch } from '@src/services/Tools';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { Preview } from '../../../stores/preview';
+import { Exercise } from '../../../stores/exercise';
+import { Users } from '../../../stores/users';
+import config from './index';
+
+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() {
+    console.log(Users);
+    const { id } = this.params;
+    const { form } = this.props;
+    let handler;
+    if (id) {
+      config.title = '编辑预习作业';
+      handler = Preview.get({ id });
+    } else {
+      config.title = '添加预习作业';
+      handler = Promise.resolve({});
+    }
+    handler
+      .then(result => {
+        form.setFieldsValue(result);
+        generateSearch('userIds', { mode: 'multiple' }, this, (search) => {
+          return Users.list(search);
+        }, (row) => {
+          return {
+            title: `${row.nickname}(${row.mobile})`,
+            value: row.id,
+          };
+        }, result.userIds || [], null);
+      });
+  }
+
+  submit() {
+    const { form } = this.props;
+    form.validateFields((err) => {
+      if (!err) {
+        const data = form.getFieldsValue();
+        let handler;
+        if (data.id) {
+          handler = Preview.add(data);
+        } else {
+          handler = Preview.edit(data);
+        }
+        handler.then(() => {
+          asyncSMessage('保存成功');
+        }).catch((e) => {
+          if (e.result) form.setFields(formatFormError(data, e.result));
+        });
+      }
+    });
+  }
+
+  searchQuestion(values) {
+    console.log(values);
+  }
+
+  renderBase() {
+    const { getFieldDecorator } = this.props.form;
+    return <Block>
+      <Form>
+        {getFieldDecorator('id')(<input hidden />)}
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='选择课程'>
+          {getFieldDecorator('category', {
+            rules: [
+              { required: true, message: '请选择课程' },
+            ],
+          })(
+            <Select select={this.state.exercise} placeholder='请选择课程' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='起止时间'>
+          {getFieldDecorator('time', {
+            rules: [
+              { required: true, message: '请输入起止时间' },
+            ],
+          })(
+            <DatePicker.RangePicker />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='作业标题'>
+          {getFieldDecorator('title', {
+            rules: [
+              { required: true, message: '请输入作业标题' },
+            ],
+          })(
+            <Input placeholder='请输入作业标题' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='指定做题人'>
+          {getFieldDecorator('userIds', {
+            rules: [
+              { required: true, message: '请指定做题人' },
+            ],
+          })(
+            <Select {...this.state.userIds} placeholder='请指定做题人' />,
+          )}
+        </Form.Item>
+        <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='选择作业题'>
+          {getFieldDecorator('questionIds', {
+            rules: [
+              { required: true, message: '请选择作业题' },
+            ],
+          })(
+            <Select select={[]} mode='tags' maxTagCount={200} notFoundContent={null} placeholder='输入题目id, 逗号分隔' tokenSeparators={[',', ',']} onSelect={(values) => {
+              this.searchQuestion(values);
+            }} />,
+          )}
+        </Form.Item>
+      </Form>
+    </Block>;
+  }
+
+  renderQuestionList() {
+    return <List
+      header={<h1>题目预览</h1>}
+      loading={this.props.core.loading}
+      itemLayout="horizontal"
+      dataSource={this.state.questionList || [{}]}
+      renderItem={item => (
+        <List.Item actions={[<Button type='link' onClick={() => {
+          console.log(item);
+        }}><Icon type='delete' /></Button>, <a>more</a>]}>
+          123123
+        </List.Item>
+      )}
+    />;
+  }
+
+  renderView() {
+    return <Block flex>
+      {this.renderBase()}
+      {this.renderQuestionList()}
+
+      <Row type="flex" justify="center">
+        <Col>
+          <Button type="primary" onClick={() => {
+            this.submit();
+          }}>保存</Button>
+        </Col>
+      </Row>
+    </Block>;
+  }
+}

+ 1 - 1
front/project/admin/routes/subject/question/index.less

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-.subject-question {}
+#subject-question {}

+ 1 - 1
front/project/admin/routes/subject/sentence/index.less

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-.subject-sentence {}
+#subject-sentence {}

+ 1 - 1
front/project/admin/routes/subject/textbook/index.less

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

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

@@ -12,16 +12,6 @@ const actionList = [
     key: 'add',
     name: '新增',
   },
-  {
-    key: 'edit',
-    name: '编辑',
-    selectNum: 1,
-  },
-  {
-    key: 'del',
-    name: '删除',
-    needSelect: 1,
-  },
 ];
 
 const itemList = [
@@ -36,6 +26,13 @@ const itemList = [
     placeholder: '请输入用户名',
     require: true,
   },
+  {
+    key: 'password',
+    name: '密码',
+    type: 'password',
+    placeholder: '请输入密码',
+    require: true,
+  },
 ];
 
 export default class extends Page {

+ 2 - 2
front/project/admin/routes/user/askList/index.js

@@ -2,8 +2,8 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/user/askList',
-  key: 'user-askList',
+  path: '/user/ask',
+  key: 'user-ask',
   title: '学生提问',
   needLogin: true,
   module,

+ 1 - 1
front/project/admin/routes/user/payList/index.less

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

front/project/admin/routes/user/askList/page.js → front/project/admin/routes/user/ask/page.js


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

@@ -1,3 +0,0 @@
-@charset "utf-8";
-
-.system-user-feedbackList {}

+ 2 - 2
front/project/admin/routes/user/exerciseList/index.js

@@ -2,8 +2,8 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/user/exerciseList',
-  key: 'user-exerciseList',
+  path: '/user/exercise',
+  key: 'user-exercise',
   title: '练习记录',
   needLogin: true,
   module,

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

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

front/project/admin/routes/user/exerciseList/page.js → front/project/admin/routes/user/exercise/page.js


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

@@ -1,3 +0,0 @@
-@charset "utf-8";
-
-.user-exerciseList {}

+ 2 - 2
front/project/admin/routes/user/feedbackList/index.js

@@ -2,8 +2,8 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/user/feedbackList',
-  key: 'user-feedbackList',
+  path: '/user/feedback',
+  key: 'user-feedback',
   title: '勘误反馈',
   needLogin: true,
   module,

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

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

front/project/admin/routes/user/feedbackList/page.js → front/project/admin/routes/user/feedback/page.js


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

@@ -1,3 +0,0 @@
-@charset "utf-8";
-
-.user-feedbackList {}

+ 6 - 6
front/project/admin/routes/user/index.js

@@ -1,8 +1,8 @@
 import list from './list';
-import askList from './askList';
-import previewList from './previewList';
-import exerciseList from './exerciseList';
-import payList from './payList';
-import feedbackList from './feedbackList';
+import ask from './ask';
+import preview from './preview';
+import exercise from './exercise';
+import pay from './pay';
+import feedback from './feedback';
 
-export default [list, askList, previewList, exerciseList, payList, feedbackList];
+export default [list, ask, preview, exercise, pay, feedback];

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

@@ -1,3 +1,3 @@
 @charset "utf-8";
 
-.user-list {}
+#user-list {}

+ 2 - 2
front/project/admin/routes/user/payList/index.js

@@ -2,8 +2,8 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/user/payList',
-  key: 'user-payList',
+  path: '/user/pay',
+  key: 'user-pay',
   title: '购买记录',
   needLogin: true,
   module,

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

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

front/project/admin/routes/user/payList/page.js → front/project/admin/routes/user/pay/page.js


+ 2 - 2
front/project/admin/routes/user/previewList/index.js

@@ -2,8 +2,8 @@ import module from '../../module';
 import group from '../group';
 
 export default {
-  path: '/user/previewList',
-  key: 'user-previewList',
+  path: '/user/preview',
+  key: 'user-preview',
   title: '预习作业',
   needLogin: true,
   module,

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

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

front/project/admin/routes/user/previewList/page.js → front/project/admin/routes/user/preview/page.js


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

@@ -1,3 +0,0 @@
-@charset "utf-8";
-
-.system-user-taskList {}

+ 21 - 0
front/project/admin/stores/examination.js

@@ -0,0 +1,21 @@
+import BaseStore from '@src/stores/base';
+
+export default class ExaminationStore extends BaseStore {
+  allStruct() {
+    return this.apiGet('/examination/struct/all');
+  }
+
+  addStruct(params) {
+    return this.apiPost('/examination/struct/add', params);
+  }
+
+  editStruct(params) {
+    return this.apiPut('/examination/struct/edit', params);
+  }
+
+  delStruct(params) {
+    return this.apiDel('/examination/struct/delete', params);
+  }
+}
+
+export const Examination = new ExaminationStore({ key: 'examination' });

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

@@ -0,0 +1,21 @@
+import BaseStore from '@src/stores/base';
+
+export default class ExerciseStore extends BaseStore {
+  allStruct() {
+    return this.apiGet('/exercise/struct/all');
+  }
+
+  addStruct(params) {
+    return this.apiPost('/exercise/struct/add', params);
+  }
+
+  editStruct(params) {
+    return this.apiPut('/exercise/struct/edit', params);
+  }
+
+  delStruct(params) {
+    return this.apiDel('/exercise/struct/delete', params);
+  }
+}
+
+export const Exercise = new ExerciseStore({ key: 'exercise' });

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

@@ -1,3 +1,7 @@
 import { System } from './system';
+import { Examination } from './examination';
+import { Exercise } from './exercise';
+import { Preview } from './preview';
+import { Users } from './users';
 
-export default [System];
+export default [System, Examination, Exercise, Preview, Users];

+ 25 - 0
front/project/admin/stores/preview.js

@@ -0,0 +1,25 @@
+import BaseStore from '@src/stores/base';
+
+export default class PreviewStore extends BaseStore {
+  list(params) {
+    return this.apiGet('/preview/list', params);
+  }
+
+  get(params) {
+    return this.apiPost('/preview/detail', params);
+  }
+
+  add(params) {
+    return this.apiPost('/preview/add', params);
+  }
+
+  edit(params) {
+    return this.apiPut('/preview/edit', params);
+  }
+
+  del(params) {
+    return this.apiDel('/preview/delete', params);
+  }
+}
+
+export const Preview = new PreviewStore({ key: 'preview' });

+ 61 - 5
front/project/admin/stores/system.js

@@ -2,23 +2,79 @@ import BaseStore from '@src/stores/base';
 
 export default class SystemStore extends BaseStore {
   listManager(params) {
-    return this.apiGet('/managers', params);
+    return this.apiGet('/manager/list', params);
   }
 
   addManager(params) {
-    return this.apiPost('/manager', params);
+    return this.apiPost('/manager/add', params);
   }
 
   getManager(params) {
-    return this.apiGet('/manager/detail', { manager_id: params.id, ...params });
+    return this.apiGet('/manager/detail', params);
   }
 
   putManager(params) {
-    return this.apiPut('/manager/{manager_id}', { manager_id: params.id, ...params });
+    return this.apiPut('/manager/edit', params);
   }
 
   delManager(params) {
-    return this.apiDel('/manager/{manager_id}', { manager_id: params.id, ...params });
+    return this.apiDel('/manager/delete', params);
+  }
+
+  getIndex() {
+    return this.apiGet('/setting/index');
+  }
+
+  setIndex(params) {
+    return this.apiPut('/setting/index', params);
+  }
+
+  getPlace() {
+    return this.apiGet('/setting/place');
+  }
+
+  setPlace(params) {
+    return this.apiPut('/setting/place', params);
+  }
+
+  getSentence() {
+    return this.apiGet('/setting/sentence');
+  }
+
+  setSentence(params) {
+    return this.apiPut('/setting/sentence', params);
+  }
+
+  getFilterTime() {
+    return this.apiGet('/setting/filter_time');
+  }
+
+  setFilterTime(params) {
+    return this.apiPut('/setting/filter_time', params);
+  }
+
+  getExerciseTime() {
+    return this.apiGet('/setting/exercise_time');
+  }
+
+  setExerciseTime(params) {
+    return this.apiPut('/setting/exercise_time', params);
+  }
+
+  getExaminationTime() {
+    return this.apiGet('/setting/examination_time');
+  }
+
+  setExaminationTime(params) {
+    return this.apiPut('/setting/examination_time', params);
+  }
+
+  uploadImage(file) {
+    return this.apiForm('/common/upload/image', { file });
+  }
+
+  uploadVideo(file) {
+    return this.apiForm('/common/upload/video', { file });
   }
 }
 

+ 25 - 0
front/project/admin/stores/users.js

@@ -0,0 +1,25 @@
+import BaseStore from '@src/stores/base';
+
+export default class UsersStore extends BaseStore {
+  list(params) {
+    return this.apiGet('/user/list', params);
+  }
+
+  get(params) {
+    return this.apiPost('/user/detail', params);
+  }
+
+  add(params) {
+    return this.apiPost('/user/add', params);
+  }
+
+  edit(params) {
+    return this.apiPut('/user/edit', params);
+  }
+
+  del(params) {
+    return this.apiDel('/user/delete', params);
+  }
+}
+
+export const Users = new UsersStore({ key: 'users' });

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

@@ -8,7 +8,7 @@ export default class Multiple extends Component {
     return (
       <ASelect className="select-layout" mode="multiple" {...this.props}>
         {select.map((i, index) => {
-          if (typeof item === 'string') {
+          if (typeof i === 'string') {
             return (
               <ASelect.Option key={index} value={i}>
                 {i}

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

@@ -6,7 +6,7 @@ export default class Radio extends Component {
   render() {
     const { select = [] } = this.props;
     return (
-      <ARadio.Group className="radio-layout">
+      <ARadio.Group className="radio-layout" {...this.props}>
         {select.map((i, index) => {
           if (typeof i === 'string') {
             return (

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

@@ -8,7 +8,7 @@ export default class Select extends Component {
     return (
       <ASelect className="select-layout" {...this.props}>
         {select.map((i, index) => {
-          if (typeof item === 'string') {
+          if (typeof i === 'string') {
             return (
               <ASelect.Option key={index} value={i}>
                 {i}

+ 3 - 2
front/src/components/TreeSelect/index.js

@@ -33,7 +33,7 @@ export default class TreeSelect extends Component {
     }
     this.nodeMap[node.key] = {
       key: node.key,
-      value: node.key,
+      value: node.value === undefined ? node.key : node.value,
       title: node.title,
       children: list,
     };
@@ -109,6 +109,7 @@ export default class TreeSelect extends Component {
   render() {
     return (
       <ATreeSelect
+        {...this.props}
         className="tree-select-layout"
         ref={ref => {
           this.Select = ref;
@@ -117,7 +118,7 @@ export default class TreeSelect extends Component {
         disabled={!!this.props.disabled}
         placeholder={this.props.placeholder}
         treeDefaultExpandAll
-        treeData={[...this.treeData]}
+        treeData={this.treeData}
         value={this.state.value}
         onSelect={value => this.onSelect(value)}
         onChange={value => this.onChange(value)}

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

@@ -38,7 +38,6 @@ export default class extends Component {
       }
     }
     this.state = state;
-    console.log(this.props);
   }
 
   render() {

+ 27 - 43
front/src/containers/App.js

@@ -64,7 +64,7 @@ export default class App extends Component {
           {routes.map(route => {
             return (
               <Route
-                exact
+                exact={!(route.path.indexOf(':') >= 0)}
                 key={route.key}
                 path={route.path}
                 render={props => {
@@ -89,50 +89,34 @@ export default class App extends Component {
 
   renderMode(route, props) {
     const { project } = this.props;
+    let c;
     if (typeof project.mode === 'function') {
-      return (
-        <UserStoreAsync
-          config={route}
-          {...props}
-          moduleList={this.moduleList}
-          moduleMap={this.moduleMap}
-          project={project}
-          component={project.mode}
-        >
-          {this.renderPage(route, props)}
-        </UserStoreAsync>
-      );
-    }
-    switch (project.mode) {
-      case 'admin':
-        return (
-          <UserStoreAsync
-            config={route}
-            {...props}
-            moduleList={this.moduleList}
-            moduleMap={this.moduleMap}
-            project={project}
-            component={() => import('./Admin')}
-          >
-            {this.renderPage(route, props)}
-          </UserStoreAsync>
-        );
-      case 'adminLeft':
-        return (
-          <UserStoreAsync
-            config={route}
-            {...props}
-            moduleList={this.moduleList}
-            moduleMap={this.moduleMap}
-            project={project}
-            component={() => import('./AdminLeft')}
-          >
-            {this.renderPage(route, props)}
-          </UserStoreAsync>
-        );
-      default:
-        return this.renderPage(route, props);
+      c = project.mode;
+    } else {
+      switch (project.mode) {
+        case 'admin':
+          c = () => import('./Admin');
+          break;
+        case 'adminLeft':
+          c = () => import('./AdminLeft');
+          break;
+        default:
+          return this.renderPage(route, props);
+      }
     }
+
+    return (
+      <UserStoreAsync
+        config={route}
+        {...props}
+        moduleList={this.moduleList}
+        moduleMap={this.moduleMap}
+        project={project}
+        component={c}
+      >
+        {this.renderPage(route, props)}
+      </UserStoreAsync>
+    );
   }
 
   renderPage(route, props) {

+ 5 - 2
front/src/containers/Page.js

@@ -4,8 +4,10 @@ import * as querystring from 'querystring';
 export default class extends Component {
   constructor(props) {
     super(props);
-    const { config, core } = this.props;
+    const { config, core, match } = this.props;
     this.inited = false;
+    this.loaded = 0;
+    this.params = match.params;
     const state = {
       page: {
         current: core.query.page ? Number(core.query.page) : 1,
@@ -63,7 +65,7 @@ export default class extends Component {
     this.outPage();
   }
 
-  init() {}
+  init() { }
 
   initState() {
     return {};
@@ -138,6 +140,7 @@ export default class extends Component {
   }
 
   refreshQuery(query) {
+    this.inited = false;
     replaceLink(`${this.props.location.pathname}?${querystring.stringify(query)}`);
   }
 

+ 6 - 1
front/src/layouts/ActionLayout/index.js

@@ -8,12 +8,17 @@ class ActionBar extends Component {
   }
 
   getItem(item) {
-    const { selectedKeys = [], onlySelect, readOnly } = this.props;
+    const { selectedKeys = [], checkedKeys = [], onlySelect, readOnly } = this.props;
     if (readOnly && !item.readOnly) return '';
     let { disabled } = item;
     if (item.selectNum) disabled = item.selectNum !== selectedKeys.length;
+    if (item.checkNum) disabled = item.checkNum !== checkedKeys.length;
+    if (item.needCheck) disabled = item.needCheck > checkedKeys.length;
     if (item.needSelect) disabled = item.needSelect > selectedKeys.length;
     if (item.needOnlySelect) disabled = !(onlySelect || onlySelect === 0);
+    if (item.render) {
+      return item.render(item, disabled);
+    }
     return (
       <Button type={item.type} disabled={disabled} onClick={() => this.onClick(item.key)}>
         {item.name}

+ 17 - 5
front/src/layouts/FilterLayout/index.js

@@ -25,7 +25,7 @@ class FilterLayout extends Component {
       case 'radio':
         return <Radio className={item.class} select={item.select} />;
       case 'select':
-        return <Select className={item.class} select={item.select} placeholder={item.placeholder} />;
+        return <Select {...item} className={item.class} select={item.select} placeholder={item.placeholder} />;
       case 'multiple':
         return (
           <Multiple
@@ -85,12 +85,24 @@ class FilterLayout extends Component {
         <Form layout="inline">
           {itemList.map((item, index) => {
             const option = item.option ? Object.assign({}, item.option) : {};
-            if (data && data[item.key]) {
-              option.initialValue = this.formatData(data, item);
+            if (data) {
+              option.initialValue = data[item.key] || '';
+              if (item.number && option.initialValue) option.initialValue = Number(option.initialValue);
+              if (item.type === 'date' && option.initialValue) {
+                const n = Number(option.initialValue);
+                if (n) {
+                  if (option.initialValue.length === 13) option.initialValue = moment(n);
+                  if (option.initialValue.length === 10) option.initialValue = moment.unix(n);
+                } else {
+                  option.initialValue = moment(option.initialValue);
+                }
+              }
+            }
+            if (item.type === 'hidden') {
+              return getFieldDecorator(item.key, option)(this.getFilterItem(item));
             }
-            if (item.hidden) return '';
             return (
-              <Form.Item key={index} label={hiddenLabel ? '' : `${item.name}:`}>
+              <Form.Item key={index} label={!hiddenLabel && item.name ? `${item.name}:` : ''}>
                 {getFieldDecorator(item.key, option)(this.getFilterItem(item))}
               </Form.Item>
             );

+ 13 - 10
front/src/layouts/FormLayout/index.js

@@ -42,11 +42,12 @@ class FormLayout extends Component {
     if (this.props.onCancel) this.props.onCancel();
   }
 
-  getItem(item, option) {
+  getItem(item) {
     switch (item.type) {
       case 'tree':
         return (
           <TreeSelect
+            {...item}
             className={item.class}
             disabled={!!item.disabled}
             placeholder={item.placeholder}
@@ -59,10 +60,11 @@ class FormLayout extends Component {
           />
         );
       case 'radio':
-        return <Radio className={item.class} select={item.select} disabled={!!item.disabled} />;
+        return <Radio {...item} className={item.class} select={item.select} disabled={!!item.disabled} />;
       case 'select':
         return (
           <Select
+            {...item}
             className={item.class}
             select={item.select}
             placeholder={item.placeholder}
@@ -72,6 +74,7 @@ class FormLayout extends Component {
       case 'multiple':
         return (
           <Multiple
+            {...item}
             className={item.class}
             select={item.select}
             placeholder={item.placeholder}
@@ -79,12 +82,13 @@ class FormLayout extends Component {
           />
         );
       case 'render':
-        return item.render(option);
+        return item.render(item, this.props.data, this.props.form);
       case 'textarea':
-        return <TextArea autosize className={item.class} placeholder={item.placeholder} disabled={!!item.disabled} />;
+        return <TextArea {...item} autosize className={item.class} placeholder={item.placeholder} disabled={!!item.disabled} />;
       case 'input':
         return (
           <Input
+            {...item}
             className={item.class}
             placeholder={item.placeholder}
             readOnly={!!item.readOnly}
@@ -94,6 +98,7 @@ class FormLayout extends Component {
       case 'number':
         return (
           <InputNumber
+            {...item}
             className={item.class}
             placeholder={item.placeholder}
             readOnly={!!item.readOnly}
@@ -101,9 +106,9 @@ class FormLayout extends Component {
           />
         );
       case 'switch':
-        return <Switch className={item.class} disabled={!!item.disabled} />;
+        return <Switch {...item} className={item.class} disabled={!!item.disabled} />;
       case 'date':
-        return <DatePicker className={item.class} placeholder={item.placeholder} disabled={!!item.disabled} />;
+        return <DatePicker {...item} className={item.class} placeholder={item.placeholder} disabled={!!item.disabled} />;
       case 'hidden':
         return <Input type="hidden" />;
       // case 'image':
@@ -152,7 +157,7 @@ class FormLayout extends Component {
           if (data && data[item.key]) {
             option.initialValue = this.formatData(data, item);
           }
-          if (item.require) {
+          if (item.required) {
             option.rules.push({ required: true, message: item.placeholder });
           }
           if (item.type === 'hidden') {
@@ -193,9 +198,7 @@ class FormLayout extends Component {
         {err && <Alert type="error" showIcon message={err} closable onClose={() => this.setState({ err: '' })} />}
         {this.getForm()}
       </Modal>
-    ) : (
-      <div className="form-layout">{this.getForm()}</div>
-    );
+    ) : (<div className="form-layout">{this.getForm()}</div>);
   }
 }
 

+ 1 - 0
front/src/layouts/TreeLayout/index.js

@@ -9,6 +9,7 @@ class TreeLayout extends Component {
     if (item.children && item.children.length > 0) {
       return (
         <Tree.TreeNode {...item} icon={<Icon type={item.icon} />}>
+          <div>12312</div>
           {item.children.map(subItem => {
             return this.getItem(subItem);
           })}

+ 10 - 4
front/src/services/AsyncTools.js

@@ -8,7 +8,7 @@ export function asyncGet(asyncDom, props, cb, confirm = 'onConfirm', cancel = 'o
     setTimeout(() => {
       ReactDOM.unmountComponentAtNode(node);
       document.body.removeChild(node);
-    }, 1000);
+    }, 100);
   }
   const params = {
     [confirm]: data => {
@@ -18,13 +18,19 @@ export function asyncGet(asyncDom, props, cb, confirm = 'onConfirm', cancel = 'o
       remove(NODE);
     },
   };
-  asyncDom().then(({ default: C }) => {
-    ReactDOM.render(<C {...props} {...params} />, NODE);
+  return asyncDom().then(({ default: C }) => {
+    return new Promise(resolve => {
+      ReactDOM.render(<C ref={ref => ref && resolve(ref)} {...props} {...params} />, NODE);
+    });
   });
 }
 
 export function asyncForm(title, itemList, data, cb) {
-  asyncGet(() => import('../layouts/FormLayout'), { title, itemList, data, modal: true }, cb);
+  return asyncGet(() => import('../layouts/FormLayout'), { title, itemList, data, modal: true }, cb);
+}
+
+export function asyncDrawerForm(title, itemList, data, cb, drawer = 'right') {
+  return asyncGet(() => import('../layouts/FormLayout'), { title, itemList, data, drawer }, cb);
 }
 
 function getAsyncAntd() {

+ 77 - 29
front/src/services/Tools.js

@@ -84,14 +84,14 @@ export function loadScript(url, callback) {
   script.async = true;
   script.defer = true;
   if (script.readyState) {
-    script.onreadystatechange = function() {
+    script.onreadystatechange = function () {
       if (script.readyState === 'loaded' || script.readyState === 'complete') {
         script.onreadystatechange = null;
         if (callback) callback();
       }
     };
   } else {
-    script.onload = function() {
+    script.onload = function () {
       if (callback) callback();
     };
   }
@@ -260,7 +260,7 @@ export function formatTreeData(list, key = 'id', title = 'title', index = 'paren
     row.children = [];
     row.title = row[title];
     row.key = `${row[key]}`;
-    row.value = `${row[key]}`;
+    row.value = row[key];
   });
   list.forEach(row => {
     if (row[index] && map[row[index]]) {
@@ -273,17 +273,17 @@ export function formatTreeData(list, key = 'id', title = 'title', index = 'paren
   return result;
 }
 
-export function flattenObject(ob) {
+export function flattenObject(ob, prefix = '') {
   const toReturn = {};
-
+  if (prefix) prefix = `${prefix}.`;
   Object.keys(ob).forEach(i => {
     if (typeof ob[i] === 'object' && ob[i] !== null) {
       const flatObject = flattenObject(ob[i]);
       Object.keys(flatObject).forEach(x => {
-        toReturn[`${i}.${x}`] = flatObject[x];
+        toReturn[`${prefix}${i}.${x}`] = flatObject[x];
       });
     } else {
-      toReturn[i] = ob[i];
+      toReturn[`${prefix}${i}`] = ob[i];
     }
   });
   return toReturn;
@@ -315,41 +315,89 @@ export function formatMoney(price) {
   return `¥${_formatMoney(price, 2)}`;
 }
 
-export function bindSearch(targetList, index, Component, listFunc, getFunc, render, notFound, limit = 5) {
-  if (!Component.lastFetchId) Component.lastFetchId = 0;
+export function bindTags(targetList, field, render, def, notFound) {
+  let index = -1;
+  targetList.forEach((row, i) => {
+    if (row.key === field) index = i;
+  });
+  targetList[index].notFoundContent = notFound;
+  targetList[index].select = (def || []).map(row => {
+    return render(row);
+  });
+}
+
+export function bindSearch(targetList, field, Component, listFunc, render, def, notFound) {
+  let index = -1;
+  targetList.forEach((row, i) => {
+    if (row.key === field) index = i;
+  });
+  const key = `lastFetchId${field}${index}`;
+  if (!Component[key]) Component[key] = 0;
+  const searchFunc = (data) => {
+    Component[key] += 1;
+    const fetchId = Component[key];
+    Component.setState({ fetching: true });
+    listFunc(data).then(result => {
+      if (fetchId !== Component[key]) {
+        // for fetch callback order
+        return;
+      }
+      targetList[index].select = result.list.map(row => {
+        return render(row);
+      });
+      Component.setState({ fetching: false });
+    });
+  };
   const item = {
-    number: true,
     showSearch: true,
     showArrow: true,
     filterOption: false,
     onSearch: keyword => {
-      Component.lastFetchId += 1;
-      const fetchId = Component.lastFetchId;
-      Component.setState({ fetching: true });
-      listFunc({ page: 1, number: limit, keyword }).then(result => {
-        if (fetchId !== Component.lastFetchId) {
-          // for fetch callback order
-          return;
-        }
-        targetList[index].select = result.list.map(row => {
-          return render(row);
-        });
-        Component.setState({ fetching: false });
-      });
+      searchFunc({ page: 1, number: 5, keyword });
     },
-    notFoundContent: Component.state.fetching ? notFound : null,
+    notFoundContent: notFound,
   };
-  const field = targetList[index].key;
   targetList[index] = Object.assign(targetList[index], item);
-  if (Component.state.search[field]) {
+  if (def) {
+    searchFunc({ ids: def, page: 1, number: def.length });
+  } else {
+    item.onSearch();
+  }
+}
+
+export function generateSearch(field, prop, Component, listFunc, render, def, notFound) {
+  const key = `lastFetchId${field}`;
+  if (!Component[key]) Component[key] = 0;
+  const searchFunc = (data) => {
+    Component[key] += 1;
+    const fetchId = Component[key];
     Component.setState({ fetching: true });
-    getFunc({ id: Component.state.search[field] }).then(result => {
-      targetList[index].select = [result].map(row => {
+    listFunc(data).then(result => {
+      if (fetchId !== Component[key]) {
+        // for fetch callback order
+        return;
+      }
+      const item = Component.state[field];
+      item.select = result.list.map(row => {
         return render(row);
       });
-      Component.setState({ fetching: false });
+      Component.setState({ [field]: item, fetching: false });
     });
+  };
+  const item = {
+    showSearch: true,
+    showArrow: true,
+    filterOption: false,
+    onSearch: keyword => {
+      searchFunc({ page: 1, number: 5, keyword });
+    },
+    notFoundContent: notFound,
+  };
+  prop = Object.assign(prop || {}, item);
+  if (def) {
+    searchFunc({ ids: def, page: 1, number: def.length });
   } else {
     item.onSearch();
   }
+  Component.setState({ [field]: prop });
 }

+ 2 - 2
front/src/stores/core.js

@@ -13,7 +13,7 @@ export default class CoreStore extends Base {
       needBack: false,
       isCallBack: this.isCallBack,
       location: window.location.pathname,
-      query: querystring.parse(window.location.search),
+      query: querystring.parse(window.location.search.replace('?', '')),
       loading: false,
     };
   }
@@ -72,7 +72,7 @@ export default class CoreStore extends Base {
     this.setState({
       location: location.pathname,
       isCallBack: this.isCallBack,
-      query: querystring.parse(window.location.search),
+      query: querystring.parse(window.location.search.replace('?', '')),
     });
   }
 

+ 3 - 3
front/src/stores/other.js

@@ -1,4 +1,4 @@
-import queryString from 'query-string';
+import * as querystring from 'querystring';
 import { History, linkTo, goBack, replaceLink } from '../services/History';
 import Base from './base';
 import { STORE_LOADING, STORE_LOADED } from '../services/Constant';
@@ -14,7 +14,7 @@ export default class OtherStore extends Base {
       needBack: true,
       isCallBack: this.isCallBack,
       location: window.location.pathname,
-      query: queryString.parse(window.location.search),
+      query: querystring.parse(window.location.search.replace('?', '')),
       loading: false,
     };
   }
@@ -73,7 +73,7 @@ export default class OtherStore extends Base {
     this.setState({
       location: location.pathname,
       isCallBack: this.isCallBack,
-      query: queryString.parse(window.location.search),
+      query: querystring.parse(window.location.search.replace('?', '')),
     });
   }
 

+ 3 - 3
front/src/stores/path.js

@@ -1,4 +1,4 @@
-import queryString from 'query-string';
+import * as querystring from 'querystring';
 import { History } from '../services/History';
 import Base from './base';
 
@@ -9,7 +9,7 @@ export default class PathStore extends Base {
     return {
       isCallBack: this.isCallBack,
       location: window.location.pathname,
-      query: queryString.parse(window.location.search),
+      query: querystring.parse(window.location.search.replace('?', '')),
     };
   }
 
@@ -50,7 +50,7 @@ export default class PathStore extends Base {
     this.setState({
       location: location.pathname,
       isCallBack: this.isCallBack,
-      query: queryString.parse(window.location.search),
+      query: querystring.parse(window.location.search.replace('?', '')),
     });
   }
 }

+ 2 - 1
server/.gitignore

@@ -13,4 +13,5 @@ logs
 gradle
 .gradle/
 build/
-out/
+out/
+upload/

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

@@ -4,7 +4,7 @@ package com.qxgmat.data.constants.enums;
  * Created by gaojie on 2017/11/19.
  */
 public enum SettingKey {
-    INDEX("index"), SENTENCE("sentence"), BUY_MESSAGE("buy_message"), PLACE("place"), TIME("time"), EXERCISE_PAPER("exercise_paper");
+    INDEX("index"), SENTENCE("sentence"), PLACE("place"), EXERCISE_TIME("exercise_time"), EXAMINATION_TIME("examination_time"), FILTER_TIME("filter_time"), EXERCISE_PAPER("exercise_paper");
     final static public String message = "设置key";
 
     public String key;

+ 59 - 24
server/data/src/main/java/com/qxgmat/data/dao/entity/ExaminationStruct.java

@@ -13,14 +13,14 @@ public class ExaminationStruct implements Serializable {
     /**
      * 中文名称
      */
-    @Column(name = "`name_zh`")
-    private String nameZh;
+    @Column(name = "`title_zh`")
+    private String titleZh;
 
     /**
      * 英文名称
      */
-    @Column(name = "`name_en`")
-    private String nameEn;
+    @Column(name = "`title_en`")
+    private String titleEn;
 
     /**
      * 父级id
@@ -35,6 +35,12 @@ public class ExaminationStruct implements Serializable {
     private Integer order;
 
     /**
+     * 层级:从1开始
+     */
+    @Column(name = "`level`")
+    private Integer level;
+
+    /**
      * 说明
      */
     @Column(name = "`description`")
@@ -59,37 +65,37 @@ public class ExaminationStruct implements Serializable {
     /**
      * 获取中文名称
      *
-     * @return name_zh - 中文名称
+     * @return title_zh - 中文名称
      */
-    public String getNameZh() {
-        return nameZh;
+    public String getTitleZh() {
+        return titleZh;
     }
 
     /**
      * 设置中文名称
      *
-     * @param nameZh 中文名称
+     * @param titleZh 中文名称
      */
-    public void setNameZh(String nameZh) {
-        this.nameZh = nameZh;
+    public void setTitleZh(String titleZh) {
+        this.titleZh = titleZh;
     }
 
     /**
      * 获取英文名称
      *
-     * @return name_en - 英文名称
+     * @return title_en - 英文名称
      */
-    public String getNameEn() {
-        return nameEn;
+    public String getTitleEn() {
+        return titleEn;
     }
 
     /**
      * 设置英文名称
      *
-     * @param nameEn 英文名称
+     * @param titleEn 英文名称
      */
-    public void setNameEn(String nameEn) {
-        this.nameEn = nameEn;
+    public void setTitleEn(String titleEn) {
+        this.titleEn = titleEn;
     }
 
     /**
@@ -129,6 +135,24 @@ public class ExaminationStruct implements Serializable {
     }
 
     /**
+     * 获取层级:从1开始
+     *
+     * @return level - 层级:从1开始
+     */
+    public Integer getLevel() {
+        return level;
+    }
+
+    /**
+     * 设置层级:从1开始
+     *
+     * @param level 层级:从1开始
+     */
+    public void setLevel(Integer level) {
+        this.level = level;
+    }
+
+    /**
      * 获取说明
      *
      * @return description - 说明
@@ -153,10 +177,11 @@ public class ExaminationStruct implements Serializable {
         sb.append(" [");
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
-        sb.append(", nameZh=").append(nameZh);
-        sb.append(", nameEn=").append(nameEn);
+        sb.append(", titleZh=").append(titleZh);
+        sb.append(", titleEn=").append(titleEn);
         sb.append(", parentId=").append(parentId);
         sb.append(", order=").append(order);
+        sb.append(", level=").append(level);
         sb.append(", description=").append(description);
         sb.append("]");
         return sb.toString();
@@ -184,20 +209,20 @@ public class ExaminationStruct implements Serializable {
         /**
          * 设置中文名称
          *
-         * @param nameZh 中文名称
+         * @param titleZh 中文名称
          */
-        public Builder nameZh(String nameZh) {
-            obj.setNameZh(nameZh);
+        public Builder titleZh(String titleZh) {
+            obj.setTitleZh(titleZh);
             return this;
         }
 
         /**
          * 设置英文名称
          *
-         * @param nameEn 英文名称
+         * @param titleEn 英文名称
          */
-        public Builder nameEn(String nameEn) {
-            obj.setNameEn(nameEn);
+        public Builder titleEn(String titleEn) {
+            obj.setTitleEn(titleEn);
             return this;
         }
 
@@ -222,6 +247,16 @@ public class ExaminationStruct implements Serializable {
         }
 
         /**
+         * 设置层级:从1开始
+         *
+         * @param level 层级:从1开始
+         */
+        public Builder level(Integer level) {
+            obj.setLevel(level);
+            return this;
+        }
+
+        /**
          * 设置说明
          *
          * @param description 说明

+ 59 - 24
server/data/src/main/java/com/qxgmat/data/dao/entity/ExerciseStruct.java

@@ -13,14 +13,14 @@ public class ExerciseStruct implements Serializable {
     /**
      * 中文名称
      */
-    @Column(name = "`name_zh`")
-    private String nameZh;
+    @Column(name = "`title_zh`")
+    private String titleZh;
 
     /**
      * 英文名称
      */
-    @Column(name = "`name_en`")
-    private String nameEn;
+    @Column(name = "`title_en`")
+    private String titleEn;
 
     /**
      * 父级id
@@ -35,6 +35,12 @@ public class ExerciseStruct implements Serializable {
     private Integer order;
 
     /**
+     * 层级:从1开始
+     */
+    @Column(name = "`level`")
+    private Integer level;
+
+    /**
      * 提问状态:0关闭,1开启
      */
     @Column(name = "`question_status`")
@@ -65,37 +71,37 @@ public class ExerciseStruct implements Serializable {
     /**
      * 获取中文名称
      *
-     * @return name_zh - 中文名称
+     * @return title_zh - 中文名称
      */
-    public String getNameZh() {
-        return nameZh;
+    public String getTitleZh() {
+        return titleZh;
     }
 
     /**
      * 设置中文名称
      *
-     * @param nameZh 中文名称
+     * @param titleZh 中文名称
      */
-    public void setNameZh(String nameZh) {
-        this.nameZh = nameZh;
+    public void setTitleZh(String titleZh) {
+        this.titleZh = titleZh;
     }
 
     /**
      * 获取英文名称
      *
-     * @return name_en - 英文名称
+     * @return title_en - 英文名称
      */
-    public String getNameEn() {
-        return nameEn;
+    public String getTitleEn() {
+        return titleEn;
     }
 
     /**
      * 设置英文名称
      *
-     * @param nameEn 英文名称
+     * @param titleEn 英文名称
      */
-    public void setNameEn(String nameEn) {
-        this.nameEn = nameEn;
+    public void setTitleEn(String titleEn) {
+        this.titleEn = titleEn;
     }
 
     /**
@@ -135,6 +141,24 @@ public class ExerciseStruct implements Serializable {
     }
 
     /**
+     * 获取层级:从1开始
+     *
+     * @return level - 层级:从1开始
+     */
+    public Integer getLevel() {
+        return level;
+    }
+
+    /**
+     * 设置层级:从1开始
+     *
+     * @param level 层级:从1开始
+     */
+    public void setLevel(Integer level) {
+        this.level = level;
+    }
+
+    /**
      * 获取提问状态:0关闭,1开启
      *
      * @return question_status - 提问状态:0关闭,1开启
@@ -177,10 +201,11 @@ public class ExerciseStruct implements Serializable {
         sb.append(" [");
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
-        sb.append(", nameZh=").append(nameZh);
-        sb.append(", nameEn=").append(nameEn);
+        sb.append(", titleZh=").append(titleZh);
+        sb.append(", titleEn=").append(titleEn);
         sb.append(", parentId=").append(parentId);
         sb.append(", order=").append(order);
+        sb.append(", level=").append(level);
         sb.append(", questionStatus=").append(questionStatus);
         sb.append(", description=").append(description);
         sb.append("]");
@@ -209,20 +234,20 @@ public class ExerciseStruct implements Serializable {
         /**
          * 设置中文名称
          *
-         * @param nameZh 中文名称
+         * @param titleZh 中文名称
          */
-        public Builder nameZh(String nameZh) {
-            obj.setNameZh(nameZh);
+        public Builder titleZh(String titleZh) {
+            obj.setTitleZh(titleZh);
             return this;
         }
 
         /**
          * 设置英文名称
          *
-         * @param nameEn 英文名称
+         * @param titleEn 英文名称
          */
-        public Builder nameEn(String nameEn) {
-            obj.setNameEn(nameEn);
+        public Builder titleEn(String titleEn) {
+            obj.setTitleEn(titleEn);
             return this;
         }
 
@@ -247,6 +272,16 @@ public class ExerciseStruct implements Serializable {
         }
 
         /**
+         * 设置层级:从1开始
+         *
+         * @param level 层级:从1开始
+         */
+        public Builder level(Integer level) {
+            obj.setLevel(level);
+            return this;
+        }
+
+        /**
          * 设置提问状态:0关闭,1开启
          *
          * @param questionStatus 提问状态:0关闭,1开启

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

@@ -6,10 +6,11 @@
       WARNING - @mbg.generated
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
-    <result column="name_zh" jdbcType="VARCHAR" property="nameZh" />
-    <result column="name_en" jdbcType="VARCHAR" property="nameEn" />
+    <result column="title_zh" jdbcType="VARCHAR" property="titleZh" />
+    <result column="title_en" jdbcType="VARCHAR" property="titleEn" />
     <result column="parent_id" jdbcType="INTEGER" property="parentId" />
     <result column="order" jdbcType="INTEGER" property="order" />
+    <result column="level" jdbcType="INTEGER" property="level" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.ExaminationStruct">
     <!--
@@ -21,7 +22,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `name_zh`, `name_en`, `parent_id`, `order`
+    `id`, `title_zh`, `title_en`, `parent_id`, `order`, `level`
   </sql>
   <sql id="Blob_Column_List">
     <!--

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

@@ -6,10 +6,11 @@
       WARNING - @mbg.generated
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
-    <result column="name_zh" jdbcType="VARCHAR" property="nameZh" />
-    <result column="name_en" jdbcType="VARCHAR" property="nameEn" />
+    <result column="title_zh" jdbcType="VARCHAR" property="titleZh" />
+    <result column="title_en" jdbcType="VARCHAR" property="titleEn" />
     <result column="parent_id" jdbcType="INTEGER" property="parentId" />
     <result column="order" jdbcType="INTEGER" property="order" />
+    <result column="level" jdbcType="INTEGER" property="level" />
     <result column="question_status" jdbcType="INTEGER" property="questionStatus" />
   </resultMap>
   <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.ExerciseStruct">
@@ -22,7 +23,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `name_zh`, `name_en`, `parent_id`, `order`, `question_status`
+    `id`, `title_zh`, `title_en`, `parent_id`, `order`, `level`, `question_status`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 14 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/AuthController.java

@@ -3,6 +3,7 @@ package com.qxgmat.controller.admin;
 import com.nuliji.tools.Response;
 import com.nuliji.tools.ResponseHelp;
 import com.nuliji.tools.Transform;
+import com.nuliji.tools.exception.AuthException;
 import com.qxgmat.data.dao.entity.Manager;
 import com.qxgmat.data.relation.entity.ManagerRelation;
 import com.qxgmat.dto.admin.request.LoginDto;
@@ -47,6 +48,19 @@ public class AuthController {
     @Autowired
     private ManagerLogService managerLogService;
 
+    @RequestMapping(value = "/token", method = RequestMethod.POST)
+    @ApiOperation(value = "验证token", httpMethod = "POST")
+    public Response<LoginUserDto> token(HttpSession session, HttpServletRequest request) {
+        Manager manager = shiroHelp.getLoginManager();
+        if (manager == null) {
+            throw new AuthException("未登录");
+        }
+        ManagerRelation managerRelation = Transform.convert(manager, ManagerRelation.class);
+        managerRelation.setRole(managerRoleService.selectOne(managerRelation.getRoleId()));
+        managerLogService.log(request);
+        return ResponseHelp.success(Transform.convert(managerRelation, LoginUserDto.class));
+    }
+
     @RequestMapping(value = "/login", method = RequestMethod.POST)
     @ApiOperation(value = "登录", httpMethod = "POST")
     public Response<LoginUserDto> login(@RequestBody @Validated LoginDto loginDto, HttpSession session, HttpServletRequest request) {

+ 12 - 5
server/gateway-api/src/main/java/com/qxgmat/controller/admin/CommonController.java

@@ -33,14 +33,17 @@ public class CommonController {
         if (multipartFile.isEmpty()) {
             throw new ParameterException("上传文件为空");
         }
+        String filename = multipartFile.getOriginalFilename();
+        String suffix = filename.substring(filename.lastIndexOf(".")+1);
         String contentType = multipartFile.getContentType();
         if (!contentType.contains("")) {
             throw new ParameterException("文件类型错误");
         }
         // todo 随机文件名
-        String file = UUID.randomUUID().toString();
+        String file = UUID.randomUUID().toString()+"."+suffix;
         try {
-            File dest = new File(localPath + File.separator+file);
+            File dir = new File(localPath);
+            File dest = new File(dir.getAbsolutePath() + File.separator+file);
             multipartFile.transferTo(dest);
             return ResponseHelp.success(webUrl+file);
         } catch (IOException e) {
@@ -48,20 +51,24 @@ public class CommonController {
             return ResponseHelp.exception(new SystemException("视频上传失败"));
         }
     }
-    @RequestMapping(value = "/upload/image", produces = MediaType.IMAGE_JPEG_VALUE, method = RequestMethod.POST)
+    @RequestMapping(value = "/upload/image", method = RequestMethod.POST)
     @ApiOperation(value = "上传文件", notes = "上传至本地服务器", httpMethod = "POST")
     public Response<String> uploadImage(@RequestParam("file") MultipartFile multipartFile)  {
         if (multipartFile.isEmpty()) {
             throw new ParameterException("上传文件为空");
         }
+        String filename = multipartFile.getOriginalFilename();
+        String suffix = filename.substring(filename.lastIndexOf(".")+1);
         String contentType = multipartFile.getContentType();
         if (!contentType.contains("")) {
             throw new ParameterException("文件类型错误");
         }
         // todo 随机文件名
-        String file = UUID.randomUUID().toString();
+        String file = UUID.randomUUID().toString()+"."+suffix;
         try {
-            File dest = new File(localPath + File.separator+file);
+            File dir = new File(localPath);
+            File dest = new File(dir.getAbsolutePath() + File.separator+file);
+
             multipartFile.transferTo(dest);
             return ResponseHelp.success(webUrl+file);
         } catch (IOException e) {

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

@@ -55,7 +55,7 @@ public class ExerciseController {
     @ApiOperation(value = "添加练习层级", httpMethod = "POST")
     public Response<ExerciseStruct> add(@RequestBody @Validated ExerciseStructDto dto, HttpServletRequest request) {
         ExerciseStruct entity = Transform.dtoToEntity(dto);
-        entity = exerciseStructService.edit(entity);
+        entity = exerciseStructService.add(entity);
         managerLogService.log(request);
         return ResponseHelp.success(entity);
     }

+ 46 - 10
server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java

@@ -8,6 +8,8 @@ import com.qxgmat.data.dao.entity.Setting;
 import com.qxgmat.service.inline.SettingService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
 import org.springframework.validation.annotation.Validated;
@@ -16,10 +18,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RestController;
 
+
 @RestController("AdminSettingController")
-@RequestMapping("/admin/common")
+@RequestMapping("/admin/setting")
 @Api(tags = "配置信息接口", description = "全局独立配置设置", produces = MediaType.APPLICATION_JSON_VALUE)
 public class SettingController {
+    private static final Logger logger = LoggerFactory.getLogger(SettingController.class);
 
     @Autowired
     private SettingService settingService;
@@ -37,7 +41,7 @@ public class SettingController {
     @ApiOperation(value = "获取首页配置", httpMethod = "GET")
     private Response<JSONObject> indexGet(){
         Setting entity = settingService.getByKey(SettingKey.INDEX.key);
-
+        logger.debug("{}", entity);
         return ResponseHelp.success(entity.getValue());
     }
 
@@ -77,19 +81,51 @@ public class SettingController {
     }
 
 
-    @RequestMapping(value = "/time", method = RequestMethod.PUT)
-    @ApiOperation(value = "修改时间设置", httpMethod = "PUT")
-    private Response<Boolean> timeEdit(@RequestBody @Validated JSONObject dto){
-        Setting entity = settingService.getByKey(SettingKey.TIME.key);
+    @RequestMapping(value = "/exercise_time", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改做题时间设置", httpMethod = "PUT")
+    private Response<Boolean> exerciseTimeEdit(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.EXERCISE_TIME.key);
+        entity.setValue(dto);
+        settingService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/exercise_time", method = RequestMethod.GET)
+    @ApiOperation(value = "获取做题时间配置", httpMethod = "GET")
+    private Response<JSONObject> exerciseTimeGet(){
+        Setting entity = settingService.getByKey(SettingKey.EXERCISE_TIME.key);
+
+        return ResponseHelp.success(entity.getValue());
+    }
+    @RequestMapping(value = "/examination_time", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改做题时间设置", httpMethod = "PUT")
+    private Response<Boolean> examinationTimeEdit(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.EXAMINATION_TIME.key);
+        entity.setValue(dto);
+        settingService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/examination_time", method = RequestMethod.GET)
+    @ApiOperation(value = "获取做题时间配置", httpMethod = "GET")
+    private Response<JSONObject> examinationTimeGet(){
+        Setting entity = settingService.getByKey(SettingKey.EXAMINATION_TIME.key);
+
+        return ResponseHelp.success(entity.getValue());
+    }
+    @RequestMapping(value = "/filter_time", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改剔除时间设置", httpMethod = "PUT")
+    private Response<Boolean> filterTimeEdit(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.FILTER_TIME.key);
         entity.setValue(dto);
         settingService.edit(entity);
         return ResponseHelp.success(true);
     }
 
-    @RequestMapping(value = "/time", method = RequestMethod.GET)
-    @ApiOperation(value = "获取时间配置", httpMethod = "GET")
-    private Response<JSONObject> timeGet(){
-        Setting entity = settingService.getByKey(SettingKey.TIME.key);
+    @RequestMapping(value = "/filter_time", method = RequestMethod.GET)
+    @ApiOperation(value = "获取做题时间配置", httpMethod = "GET")
+    private Response<JSONObject> filterTimeGet(){
+        Setting entity = settingService.getByKey(SettingKey.FILTER_TIME.key);
 
         return ResponseHelp.success(entity.getValue());
     }

+ 7 - 2
server/gateway-api/src/main/java/com/qxgmat/controller/api/CommonController.java

@@ -27,6 +27,7 @@ import javax.servlet.http.HttpSession;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
+import java.util.UUID;
 
 /**
  * Created by GaoJie on 2017/11/3.
@@ -121,14 +122,18 @@ public class CommonController {
         if (multipartFile.isEmpty()) {
             throw new ParameterException("上传文件为空");
         }
+        String filename = multipartFile.getOriginalFilename();
+        String suffix = filename.substring(filename.lastIndexOf(".")+1);
         String contentType = multipartFile.getContentType();
         if (!contentType.contains("")) {
             throw new ParameterException("文件类型错误");
         }
         // todo 随机文件名
-        String file = "";
+        String file = UUID.randomUUID().toString()+"."+suffix;
         try {
-            File dest = new File(localPath + File.separator+file);
+            File dir = new File(localPath);
+            File dest = new File(dir.getAbsolutePath() + File.separator+file);
+
             multipartFile.transferTo(dest);
             return ResponseHelp.success(webUrl+file);
         } catch (IOException e) {

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

@@ -1,5 +1,6 @@
 package com.qxgmat.dto.admin.extend;
 
+
 public class ManagerRoleExtendDto {
     private String[] permissionList;
 

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

@@ -1,5 +1,6 @@
 package com.qxgmat.dto.admin.request;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.Ad;
 

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

@@ -2,7 +2,6 @@ package com.qxgmat.dto.admin.request;
 
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.ExaminationStruct;
-import com.qxgmat.data.dao.entity.ExerciseStruct;
 
 import javax.validation.constraints.NotEmpty;
 
@@ -11,10 +10,10 @@ public class ExaminationStructDto {
     private Integer id;
 
     @NotEmpty(message = "中文名称不能为空!")
-    private String nameZh;
+    private String titleZh;
 
     @NotEmpty(message = "英文名称不能为空!")
-    private String nameEn;
+    private String titleEn;
 
     @NotEmpty(message = "描述不能为空!")
     private String description;
@@ -30,22 +29,6 @@ public class ExaminationStructDto {
         this.id = id;
     }
 
-    public String getNameZh() {
-        return nameZh;
-    }
-
-    public void setNameZh(String nameZh) {
-        this.nameZh = nameZh;
-    }
-
-    public String getNameEn() {
-        return nameEn;
-    }
-
-    public void setNameEn(String nameEn) {
-        this.nameEn = nameEn;
-    }
-
     public String getDescription() {
         return description;
     }
@@ -61,4 +44,20 @@ public class ExaminationStructDto {
     public void setParentId(Integer parentId) {
         this.parentId = parentId;
     }
+
+    public String getTitleZh() {
+        return titleZh;
+    }
+
+    public void setTitleZh(String titleZh) {
+        this.titleZh = titleZh;
+    }
+
+    public String getTitleEn() {
+        return titleEn;
+    }
+
+    public void setTitleEn(String titleEn) {
+        this.titleEn = titleEn;
+    }
 }

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

@@ -4,21 +4,23 @@ import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.ExerciseStruct;
 
 import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
 
 @Dto(entity= ExerciseStruct.class)
 public class ExerciseStructDto {
     private Integer id;
 
     @NotEmpty(message = "中文名称不能为空!")
-    private String nameZh;
+    private String titleZh;
 
     @NotEmpty(message = "英文名称不能为空!")
-    private String nameEn;
+    private String titleEn;
 
     @NotEmpty(message = "描述不能为空!")
     private String description;
 
-    @NotEmpty(message = "父级不能为空!")
+    @NotNull(message = "父级不能为空!")
+
     private Integer parentId;
 
     private Integer questionStatus;
@@ -30,23 +32,6 @@ public class ExerciseStructDto {
     public void setId(Integer id) {
         this.id = id;
     }
-
-    public String getNameZh() {
-        return nameZh;
-    }
-
-    public void setNameZh(String nameZh) {
-        this.nameZh = nameZh;
-    }
-
-    public String getNameEn() {
-        return nameEn;
-    }
-
-    public void setNameEn(String nameEn) {
-        this.nameEn = nameEn;
-    }
-
     public String getDescription() {
         return description;
     }
@@ -70,4 +55,20 @@ public class ExerciseStructDto {
     public void setQuestionStatus(Integer questionStatus) {
         this.questionStatus = questionStatus;
     }
+
+    public String getTitleEn() {
+        return titleEn;
+    }
+
+    public void setTitleEn(String titleEn) {
+        this.titleEn = titleEn;
+    }
+
+    public String getTitleZh() {
+        return titleZh;
+    }
+
+    public void setTitleZh(String titleZh) {
+        this.titleZh = titleZh;
+    }
 }

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

@@ -25,12 +25,12 @@ public class ExaminationStructService extends AbstractService {
     @Resource
     private ExaminationStructMapper examinationStructMapper;
 
-    @Cacheable("examination_struct")
+    @Cacheable(value = "examination_struct")
     public List<ExaminationStruct> all(){
-        return null;
+        return select(examinationStructMapper);
     }
 
-    @CacheEvict("examination_struct")
+    @CacheEvict(value="examination_struct",allEntries=true)
     public ExaminationStruct add(ExaminationStruct struct){
         int result = insert(examinationStructMapper, struct);
         struct = one(examinationStructMapper, struct.getId());
@@ -40,7 +40,7 @@ public class ExaminationStructService extends AbstractService {
         return struct;
     }
 
-    @CacheEvict("examination_struct")
+    @CacheEvict(value="examination_struct",allEntries=true)
     public ExaminationStruct edit(ExaminationStruct struct){
         ExaminationStruct in = one(examinationStructMapper, struct.getId());
         if(in == null){

+ 16 - 4
server/gateway-api/src/main/java/com/qxgmat/service/inline/ExerciseStructService.java

@@ -23,13 +23,19 @@ public class ExerciseStructService extends AbstractService {
     @Resource
     private ExerciseStructMapper exerciseStructMapper;
 
-    @Cacheable("exercise_struct")
+    @Cacheable(value = "exercise_struct")
     public List<ExerciseStruct> all(){
-        return null;
+        return select(exerciseStructMapper);
     }
 
-    @CacheEvict("exercise_struct")
+    @CacheEvict(value="exercise_struct",allEntries=true)
     public ExerciseStruct add(ExerciseStruct struct){
+        if(struct.getParentId() > 0){
+            ExerciseStruct parent = selectOne(struct.getParentId());
+            struct.setLevel(parent.getLevel()+1);
+        }else{
+            struct.setLevel(1);
+        }
         int result = insert(exerciseStructMapper, struct);
         struct = one(exerciseStructMapper, struct.getId());
         if(struct == null){
@@ -38,12 +44,18 @@ public class ExerciseStructService extends AbstractService {
         return struct;
     }
 
-    @CacheEvict("exercise_struct")
+    @CacheEvict(value="exercise_struct",allEntries=true)
     public ExerciseStruct edit(ExerciseStruct struct){
         ExerciseStruct in = one(exerciseStructMapper, struct.getId());
         if(in == null){
             throw new ParameterException("层级不存在");
         }
+        if (struct.getParentId() > 0){
+            ExerciseStruct parent = selectOne(struct.getParentId());
+            struct.setLevel(parent.getLevel()+1);
+        }else{
+            struct.setLevel(1);
+        }
         int result = update(exerciseStructMapper, struct);
         return struct;
     }

+ 9 - 4
server/gateway-api/src/main/java/com/qxgmat/service/inline/SettingService.java

@@ -4,6 +4,7 @@ import com.github.pagehelper.Page;
 import com.nuliji.tools.AbstractService;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
+import com.nuliji.tools.mybatis.Example;
 import com.qxgmat.data.dao.SettingMapper;
 import com.qxgmat.data.dao.entity.Setting;
 import org.slf4j.Logger;
@@ -22,10 +23,14 @@ public class SettingService extends AbstractService {
     @Resource
     private SettingMapper settingMapper;
 
-    @Cacheable(value="setting", key="'setting/' + #key", unless="#result==null")
+    @Cacheable(value="setting", key="#key", unless="#result==null")
     public Setting getByKey(String key) {
-        Setting setting = Setting.builder().key(key).build();
-        return one(settingMapper, setting);
+        Example example = new Example(Setting.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("key", key)
+        );
+        return one(settingMapper, example);
     }
 
     public Setting add(Setting setting){
@@ -36,7 +41,7 @@ public class SettingService extends AbstractService {
         }
         return setting;
     }
-    @CachePut(value = "setting", key="'setting/' + #key", unless="#result==null")
+    @CachePut(value = "setting", key="#setting.key", unless="#result==null")
     public Setting edit(Setting setting){
         Setting in = one(settingMapper, setting.getId());
         if(in == null){

+ 7 - 13
server/gateway-api/src/main/java/com/qxgmat/util/shiro/ManagerRealm.java

@@ -33,27 +33,21 @@ public class ManagerRealm extends AuthorizingRealm {
 
     @Override
     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
-        logger.debug("{}", principalCollection.getRealmNames());
+//        logger.debug("{}", principalCollection.getRealmNames());
         String className = ManagerRealm.class.getName();
-        Collection a = principalCollection.fromRealm(className);
-        if (a.size() == 0){
-            return null;
+        boolean selected = false;
+        for(String realmNames: principalCollection.getRealmNames()){
+            if(realmNames.contains(className)) selected = true;
         }
+        if(!selected) return null;
         Manager manager = (Manager)principalCollection.getPrimaryPrincipal();
         ManagerRole role = managerRoleService.selectOne(manager.getRoleId());
-//        boolean selected = false;
-//        for(String realmNames: principalCollection.getRealmNames()){
-//            if(realmNames.contains(className)){
-//                selected = true;
-//            }
-//            logger.debug("{}", realmNames);
-//            logger.debug("{}", className);
-//        }
-//        if(!selected) return null;
 
         SimpleAuthorizationInfo sa = new SimpleAuthorizationInfo();
         ArrayList<String> roleAuthorization = new ArrayList<String>();
         roleAuthorization.add("manager");
+
+//        logger.debug("{}", roleAuthorization);
         sa.addRoles(roleAuthorization);
 
         if (role != null){

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

@@ -84,8 +84,8 @@ spring:
     timeout: 1000
 
 upload:
-  local_path: ./upload
-  web_url: /
+  local_path: ./upload/
+  web_url: /upload/
 
 third:
   redirectUrl: http://127.0.0.1:8080

+ 2 - 1
server/gateway-api/src/main/resources/application.yml

@@ -25,7 +25,8 @@ spring:
 logging:
   level:
     org.springframework: INFO
-    com.example: DEBUG
+    com.qxgmat: DEBUG
+    com.nuliji: DEBUG
 
 management:
   security:

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

@@ -12,7 +12,7 @@ import java.util.List;
 public class PageMessage<T> implements Serializable {
 
     @ApiModelProperty(value = "记录列表", required = true)
-    private List<T> data = null;
+    private List<T> list = null;
 
     @ApiModelProperty(value = "当前页数", required = true)
     private int page = 0;
@@ -23,14 +23,14 @@ public class PageMessage<T> implements Serializable {
     @ApiModelProperty(value = "记录总数", required = true)
     private long count = 0;
 
-    public PageMessage(List<T> data, int page, int size, int count) {
-        this.data = data;
+    public PageMessage(List<T> list, int page, int size, int count) {
+        this.list = list;
         this.page = page;
         this.size = size;
-        this.count = Long.valueOf(count);
+        this.count = (long) count;
     }
-    public PageMessage(List<T> data, int page, int size, long count) {
-        this.data = data;
+    public PageMessage(List<T> list, int page, int size, long count) {
+        this.list = list;
         this.page = page;
         this.size = size;
         this.count = count;
@@ -60,11 +60,11 @@ public class PageMessage<T> implements Serializable {
         this.count = count;
     }
 
-    public List<T> getData() {
-        return data;
+    public List<T> getList() {
+        return list;
     }
 
-    public void setData(List<T> data) {
-        this.data = data;
+    public void setList(List<T> list) {
+        this.list = list;
     }
 }

+ 1 - 3
server/tools/src/main/java/com/nuliji/tools/shiro/RoleFilter.java

@@ -43,12 +43,10 @@ public class RoleFilter extends RolesAuthorizationFilter {
     @Override
     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
         saveRequest(request);
-        response.setCharacterEncoding("UTF-8");
-
         response.setContentType(MediaType.APPLICATION_JSON_VALUE); //设置ContentType
         response.setCharacterEncoding("UTF-8"); //避免乱码
         try {
-            response.getWriter().write(JSON.toJSONString( new Response(101, "未登录", null)));
+            response.getWriter().write(JSON.toJSONString( new Response(101, "未授权访问", null)));
         } finally {
         }
         return false;

+ 0 - 0
server/tools/src/main/java/com/nuliji/tools/shiro/cache/RedisManager.java


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません