Pārlūkot izejas kodu

feat(front): 个人中心课程

Go 4 gadi atpakaļ
vecāks
revīzija
44b4eab016
48 mainītis faili ar 2248 papildinājumiem un 380 dzēšanām
  1. 55 31
      front/project/admin/routes/setting/promote/page.js
  2. 30 1
      front/project/admin/routes/student/studyDetail/index.less
  3. 92 33
      front/project/admin/routes/student/studyDetail/page.js
  4. 16 0
      front/project/admin/stores/user.js
  5. 1 0
      front/project/www/components/More/index.js
  6. 10 5
      front/project/www/components/Note/index.js
  7. 14 0
      front/project/www/components/Note/index.less
  8. 5 5
      front/project/www/routes/exercise/main/page.js
  9. 31 1
      front/project/www/routes/my/course/index.less
  10. 453 191
      front/project/www/routes/my/course/page.js
  11. 11 3
      front/project/www/routes/my/tools/page.js
  12. 24 0
      front/project/www/stores/my.js
  13. 7 0
      server/data/src/main/java/com/qxgmat/data/dao/UserCourseAppointmentCommentMapper.java
  14. 70 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserCourseAppointment.java
  15. 386 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserCourseAppointmentComment.java
  16. 52 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrderCheckout.java
  17. 52 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrderRecord.java
  18. 39 0
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserCourseAppointmentCommentMapper.xml
  19. 5 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserCourseAppointmentMapper.xml
  20. 3 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderCheckoutMapper.xml
  21. 4 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderRecordMapper.xml
  22. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/PreviewAssignRelationMapper.xml
  23. 4 3
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserOrderRecordRelationMapper.xml
  24. 22 0
      server/data/src/main/resources/db/migration/V1__init_table.sql
  25. 4 4
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java
  26. 54 0
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  27. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  28. 162 31
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  29. 12 11
      server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java
  30. 98 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserCourseAppointmentCommentDto.java
  31. 57 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserCourseProgressExtendDto.java
  32. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserOrderRecordExtendDto.java
  33. 77 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCourseAppointmentCommentDto.java
  34. 37 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCourseAppointmentQuestionDto.java
  35. 27 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCourseCCTalkNameDto.java
  36. 41 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseDetailDto.java
  37. 1 1
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderPreDto.java
  38. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderRecordListDto.java
  39. 13 1
      server/gateway-api/src/main/java/com/qxgmat/service/UserNoteCourseService.java
  40. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java
  41. 61 38
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  42. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/PreviewService.java
  43. 25 13
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ToolsService.java
  44. 1 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseNoService.java
  45. 13 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserAskCourseService.java
  46. 133 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseAppointmentCommentService.java
  47. 1 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseProgressService.java
  48. 1 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseRecordService.java

+ 55 - 31
front/project/admin/routes/setting/promote/page.js

@@ -23,11 +23,14 @@ export default class extends Page {
         form.getFieldDecorator(`ask_time[${index}].money`);
         form.getFieldDecorator(`ask_time[${index}].hour`);
       });
-      (result.vs_record || []).forEach((row, index) => {
-        form.getFieldDecorator(`vs_record[${index}].money`);
-        form.getFieldDecorator(`vs_record[${index}].number`);
+      (result.vs_video_money || []).forEach((row, index) => {
+        form.getFieldDecorator(`vs_video_money[${index}].money`);
+        form.getFieldDecorator(`vs_video_money[${index}].value`);
+      });
+      (result.vs_vs_number || []).forEach((row, index) => {
+        form.getFieldDecorator(`vs_vs_number[${index}].number`);
+        form.getFieldDecorator(`vs_vs_number[${index}].value`);
       });
-      form.getFieldDecorator('coach.number');
       form.setFieldsValue(flattenObject(result));
       this.setState({ load: true, data: result });
     });
@@ -227,7 +230,8 @@ export default class extends Page {
     const { getFieldDecorator } = this.props.form;
     const { data } = this.state;
     const ask_time = data.ask_time || [];
-    const vs_record = data.vs_record || [];
+    const vs_video_money = data.vs_video_money || [];
+    const vs_vs_number = data.vs_vs_number || [];
     return <Block>
       <h1>课程赠品</h1>
       <Form>
@@ -272,64 +276,84 @@ export default class extends Page {
         }}><Icon type={'plus'} /></Button>
 
         <Form.Item label='满额送1v1辅导' />
-        {vs_record.map((row, index) => {
+        {vs_video_money.map((row, index) => {
           return <Row>
             <Col span={6}>
               <Form.Item labelCol={{ span: 10 }} wrapperCol={{ span: 14 }} label='实付金额'>
-                {getFieldDecorator(`vs_record[${index}].money`, {
+                {getFieldDecorator(`vs_video_money[${index}].money`, {
                   rules: [
                     { required: true, message: '输入金额' },
                   ],
                 })(
                   <Input placeholder={'输入金额'} onChange={(value) => {
-                    this.changeMapValue('vs_record', index, 'money', value);
+                    this.changeMapValue('vs_video_money', index, 'money', value);
                   }} />,
                 )}
               </Form.Item>
             </Col>
             <Col span={6}>
               <Form.Item labelCol={{ span: 10 }} wrapperCol={{ span: 14 }} label='赠送'>
-                {getFieldDecorator(`vs_record[${index}].number`, {
+                {getFieldDecorator(`vs_video_money[${index}].value`, {
                   rules: [
                     { required: true, message: '输入课时' },
                   ],
                 })(
                   <Input placeholder={'输入课时'} onChange={(value) => {
-                    this.changeMapValue('vs_record', index, 'number', value);
+                    this.changeMapValue('vs_video_money', index, 'value', value);
                   }} />,
                 )}
               </Form.Item>
             </Col>
             <Col span={1} onClick={() => {
-              this.deleteLength('vs_record', index, 1);
+              this.deleteLength('vs_video_money', index, 1);
             }}>
               <Button><Icon type="minus" /></Button>
             </Col>
           </Row>;
         })}
         <Button onClick={() => {
-          this.addLength('vs_record', { money: 0, number: 0 });
+          this.addLength('vs_video_money', { money: 0, value: 0 });
         }}><Icon type={'plus'} /></Button>
 
-        <Form.Item label='满课时送复习辅导规划' />
-        <Row>
-          <Col span={6}>
-            <Form.Item labelCol={{ span: 10 }} wrapperCol={{ span: 14 }} label='购买课时'>
-              {getFieldDecorator('coach.number', {
-                rules: [
-                  { required: true, message: '输入课时' },
-                ],
-              })(
-                <Input placeholder={'输入课时'} onChange={(value) => {
-                  this.changeValue('coach', 'number', value);
-                }} />,
-              )}
-            </Form.Item>
-          </Col>
-          <Col span={6}>
-            <Form.Item labelCol={{ span: 1 }}>赠送</Form.Item>
-          </Col>
-        </Row>
+        <Form.Item label='满课时送1v1辅导' />
+        {vs_vs_number.map((row, index) => {
+          return <Row>
+            <Col span={6}>
+              <Form.Item labelCol={{ span: 10 }} wrapperCol={{ span: 14 }} label='购买课时'>
+                {getFieldDecorator(`vs_vs_number[${index}].number`, {
+                  rules: [
+                    { required: true, message: '输入课时' },
+                  ],
+                })(
+                  <Input placeholder={'输入课时'} onChange={(value) => {
+                    this.changeMapValue('vs_vs_number', index, 'number', value);
+                  }} />,
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={6}>
+              <Form.Item labelCol={{ span: 10 }} wrapperCol={{ span: 14 }} label='赠送'>
+                {getFieldDecorator(`vs_vs_number[${index}].value`, {
+                  rules: [
+                    { required: true, message: '输入课时' },
+                  ],
+                })(
+                  <Input placeholder={'输入课时'} onChange={(value) => {
+                    this.changeMapValue('vs_vs_number', index, 'value', value);
+                  }} />,
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={1} onClick={() => {
+              this.deleteLength('vs_vs_number', index, 1);
+            }}>
+              <Button><Icon type="minus" /></Button>
+            </Col>
+          </Row>;
+        })}
+        <Button onClick={() => {
+          this.addLength('vs_vs_number', { number: 0, value: 0 });
+        }}><Icon type={'plus'} /></Button>
       </Form>
     </Block>;
   }

+ 30 - 1
front/project/admin/routes/student/studyDetail/index.less

@@ -1,3 +1,32 @@
 @charset "utf-8";
 
-#course-study-detail {}
+#course-study-detail {}
+
+.comment-list {
+  overflow: hidden;
+  height: 400px;
+
+  >div {
+    overflow-y: auto;
+    height: 100%;
+  }
+
+  .ant-comment-inner {
+    padding-top: 0;
+  }
+
+  .ant-comment-actions {
+    position: absolute;
+    right: 0;
+    top: 0;
+    margin: 0;
+  }
+
+  .ant-comment-content-detail {
+    span {
+      display: block;
+      padding: 5px;
+      border-left: 4px solid #ccc;
+    }
+  }
+}

+ 92 - 33
front/project/admin/routes/student/studyDetail/page.js

@@ -1,6 +1,6 @@
 import React from 'react';
 import moment from 'moment';
-import { DatePicker, Checkbox, Modal, Form, Input, Row, Col, Button, Upload } from 'antd';
+import { DatePicker, Checkbox, Modal, Form, Input, Row, Col, Button, Upload, List, Comment } from 'antd';
 import './index.less';
 import Page from '@src/containers/Page';
 import Block from '@src/components/Block';
@@ -181,6 +181,18 @@ export default class extends Page {
     });
   }
 
+  refreshComment(appointmentId, type) {
+    this.setState({ comment: {} });
+    User.allCourseAppointmentComment({ appointmentId, type })
+      .then(result => {
+        const commentMap = {};
+        result.forEach((row) => {
+          commentMap[row.id] = row;
+        });
+        this.setState({ commentList: result, commentMap });
+      });
+  }
+
   changeAppointment(id, data) {
     data.id = id;
     return User.editCourseAppointment(data).then(() => {
@@ -205,10 +217,34 @@ export default class extends Page {
 
   noteAction(record) {
     this.open(record, 'note');
+    this.refreshComment(record.id, 'note');
   }
 
   supplyAction(record) {
     this.open(record, 'supply');
+    this.refreshComment(record.id, 'supply');
+  }
+
+  submitComment(comment) {
+    if (comment.id) {
+      User.editCourseAppointmentComment(comment)
+        .then(() => {
+          this.refreshComment(comment.appointmentId, comment.type);
+          this.setState({ comment: {} });
+        });
+    } else {
+      User.addCourseAppointmentComment(comment)
+        .then(() => {
+          this.refreshComment(comment.appointmentId, comment.type);
+          this.setState({ comment: {} });
+        });
+    }
+  }
+
+  deleteComment(comment) {
+    User.delCourseAppointmentComment({ id: comment }).then(() => {
+      this.refreshComment(comment.appointmentId, comment.type);
+    });
   }
 
   deleteAppointment(row) {
@@ -223,12 +259,8 @@ export default class extends Page {
     this.setState({ [key]: Object.assign(info, data) });
   }
 
-  cleanInfo() {
-    this.setState({ supplyInfo: null, noteInfo: null });
-  }
-
   renderVs() {
-    const { data, page } = this.state;
+    const { data, page, commentMap } = this.state;
     return <div flex>
       {data.number > page.total && <ActionLayout
         itemList={this.vsAction}
@@ -245,63 +277,90 @@ export default class extends Page {
         selectedKeys={this.state.selectedKeys}
       />
       {this.state.note && <Modal visible closable title='课后笔记' footer={null} onCancel={() => {
-        this.cleanInfo();
         this.close(false, 'note');
       }}>
-        {(this.state.note.noteList || []).map(row => {
-          return <p>{formatDate(row.time)}: {<a href={row.url} target='_blank'>{row.name}</a>}</p>;
-        })}
+        <List
+          className="comment-list"
+          itemLayout="horizontal"
+          dataSource={this.state.commentList}
+          renderItem={item => (
+            <Comment
+              actions={[!!item.userId && <span onClick={() => this.changeInfo('comment', { id: null, parentId: item.id, file: null, content: null })}>回复</span>, !item.userId && <span onClick={() => this.changeInfo('comment', item)}>编辑</span>, !item.userId && <span onClick={() => this.deleteComment(item)}>删除</span>]}
+              author={item.userId ? '学生' : '老师'}
+              // avatar={item.userId ? '学' : '师'}
+              content={<p>{item.reply && <span>{item.reply}</span>}{item.content}<br /> {item.file && <a href={item.file} target="_blank">{item.name || '文件'}</a>}</p>}
+              datetime={formatDate(item.createTime, 'YYYY-MM-DD')}
+            />
+          )}
+        />
         <Form>
           <Row>
+            {this.state.comment.parentId && <Col span={24}><span>回复:{(commentMap[this.state.comment.parentId] || {}).content}</span></Col>}
+            <Col span={12}>
+              <Input value={(this.state.comment || {}).content || ''} onChange={e => {
+                this.changeInfo('comment', { content: e.target.value });
+              }} />
+            </Col>
             <Col span={1}><Upload
               showUploadList={false}
               beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                this.changeInfo('noteInfo', { url: result.url, name: file.name, time: new Date() });
+                this.changeInfo('comment', { file: result.url, name: file.name });
                 return Promise.reject();
               })}
             >
-              <Button>上传文档{(this.state.noteInfo || {}).url ? '(已上传)' : ''}</Button>
+              <Button>上传文档{(this.state.comment || {}).file ? '(已上传)' : ''}</Button>
             </Upload></Col>
-            <Col span={1} offset={20}><Button type='primary' onClick={() => {
-              if (!this.state.noteInfo || !this.state.noteInfo.url) return;
-              let { noteList = [] } = this.state.note;
-              noteList = noteList || [];
-              noteList.push(this.state.noteInfo);
-              this.changeAppointment(this.state.note.id, { noteList });
-            }}>发布</Button></Col>
+            <Col span={1} offset={8}><Button type='primary' onClick={() => {
+              if (!this.state.comment || !this.state.comment.content) return;
+              this.state.comment.appointmentId = this.state.note.id;
+              this.state.comment.type = 'note';
+              this.state.comment.recordId = this.state.note.recordId;
+              this.submitComment(this.state.comment);
+            }}>{(this.state.comment || {}).id ? '修改' : '发布'}</Button></Col>
           </Row>
         </Form>
       </Modal>}
       {this.state.supply && <Modal visible closable title='课后补充' footer={null} onCancel={() => {
-        this.cleanInfo();
         this.close(false, 'supply');
       }}>
-        {(this.state.supply.supplyList || []).map(row => {
-          return [<p>{formatDate(row.time)}:{<a href={row.url} target='_blank'>{row.name}</a>}</p>, <p>留言 - {row.content}</p>];
-        })}
+        <List
+          className="comment-list"
+          itemLayout="horizontal"
+          dataSource={this.state.commentList}
+          renderItem={item => (
+            <Comment
+              actions={[!!item.userId && <span onClick={() => this.changeInfo('comment', { id: null, parentId: item.id, file: null, content: null })}>回复</span>, !item.userId && <span onClick={() => this.changeInfo('comment', item)}>编辑</span>, !item.userId && <span onClick={() => this.deleteComment(item)}>删除</span>]}
+              author={item.userId ? '学生' : '老师'}
+              // avatar={item.userId ? '学' : '师'}
+              content={<p>{item.reply && <span>{item.reply}</span>}{item.content}<br /> {item.file && <a href={item.file} target="_blank">{item.name || '文件'}</a>}</p>}
+              datetime={formatDate(item.createTime, 'YYYY-MM-DD')}
+            />
+          )}
+        />
         <Form>
           <Row>
+            {this.state.comment.parentId && <Col span={24}><span>回复:{(commentMap[this.state.comment.parentId] || {}).content}</span></Col>}
             <Col span={12}>
-              <Input value={(this.state.supplyInfo || {}).content || ''} onChange={value => {
-                this.changeInfo('supplyInfo', { content: value });
+              <Input value={(this.state.comment || {}).content || ''} onChange={e => {
+                this.changeInfo('comment', { content: e.target.value });
               }} />
             </Col>
             <Col span={1}><Upload
               showUploadList={false}
               beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                this.changeInfo('supplyInfo', { url: result.url, name: file.name, time: new Date() });
+                this.changeInfo('comment', { file: result.url, name: file.name });
                 return Promise.reject();
               })}
             >
-              <Button>上传文档{(this.state.supplyInfo || {}).url ? '(已上传)' : ''}</Button>
+              <Button>上传文档{(this.state.comment || {}).file ? '(已上传)' : ''}</Button>
             </Upload></Col>
             <Col span={1} offset={8}><Button type='primary' onClick={() => {
-              if (!this.state.supplyInfo || !this.state.supplyInfo.url) return;
-              let { supplyList = [] } = this.state.supply;
-              supplyList = supplyList || [];
-              supplyList.push(this.state.supplyInfo);
-              this.changeAppointment(this.state.supply.id, { supplyList });
-            }}>发布</Button></Col>
+              if (!this.state.comment || !this.state.comment.content) return;
+              this.state.comment.appointmentId = this.state.supply.id;
+              this.state.comment.type = 'supply';
+              this.state.comment.recordId = this.state.supply.recordId;
+              this.submitComment(this.state.comment);
+            }}>{(this.state.comment || {}).id ? '修改' : '发布'}</Button></Col>
           </Row>
         </Form>
       </Modal>}

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

@@ -109,6 +109,22 @@ export default class UserStore extends BaseStore {
     return this.apiDel('/user/course/appointment/delete', params);
   }
 
+  allCourseAppointmentComment(params) {
+    return this.apiGet('/user/course/appointment/comment/all', params);
+  }
+
+  addCourseAppointmentComment(params) {
+    return this.apiPost('/user/course/appointment/comment/add', params);
+  }
+
+  editCourseAppointmentComment(params) {
+    return this.apiPut('/user/course/appointment/comment/edit', params);
+  }
+
+  delCourseAppointmentComment(params) {
+    return this.apiDel('/user/course/appointment/comment/delete', params);
+  }
+
   listInvoice(params) {
     return this.apiGet('/user/invoice/list', params);
   }

+ 1 - 0
front/project/www/components/More/index.js

@@ -9,6 +9,7 @@ function More(props) {
       overlay={
         <Menu onClick={key => onClick && onClick(key)}>
           {menu.map(item => {
+            if (!item.key) return null;
             return <Menu.Item key={item.key}>{item.label}</Menu.Item>;
           })}
         </Menu>

+ 10 - 5
front/project/www/components/Note/index.js

@@ -2,22 +2,27 @@ import React, { Component } from 'react';
 import './index.less';
 import Assets from '@src/components/Assets';
 import More from '../More';
+import { formatDate } from '../../../../src/services/Tools';
+import GIcon from '../Icon';
 
 export default class Note extends Component {
   render() {
-    const { data = { file: true }, theme = 'default', actionList, onAction } = this.props;
+    const { data = { file: true }, teacher, theme = 'default', reply, actionList, onAction } = this.props;
+    const { info } = this.props.user;
     return (
       <div className={`note-detail ${theme}`}>
-        <Assets className="note-avatar" name="sun_blue" src={data.avatar} />
+        <Assets className="note-avatar" name="sun_blue" src={data.userId ? info.avatar : teacher.avatar} />
         {onAction && actionList && <More menu={actionList} onClick={onAction} />}
+        {onAction && reply && <GIcon className="reply" onClick={() => onAction('reply')} />}
         <div className="t-2 t-s-12 m-b-1">
-          学生ID <span className="t-3">2019-07-02 19:00</span>
+          {data.userId ? info.nickname : teacher.realname} <span className="t-3">{formatDate(data.createTime, 'YYYY-MM-DD HH:mm')}</span>
         </div>
         <div className="t-1 t-s-12 m-b-1">
-          学生留言内容学生留言内容学生留言内容学,生留言内容学生留言。内容学生 留言内容学生留言内容学生留言内容。
+          {data.reply && <div className="reply">{data.reply}</div>}
+          {data.content}
         </div>
         <a hidden={!data.file} className="link t-s-12">
-          学生上传文档1.doc
+          {data.name}
         </a>
       </div>
     );

+ 14 - 0
front/project/www/components/Note/index.less

@@ -17,4 +17,18 @@
     position: absolute;
     right: 0;
   }
+
+  .reply-action {
+    position: absolute;
+    right: 0;
+  }
+
+  .reply {
+    padding-left: 5px;
+    border-left: 4px solid #DFDFDF;
+    margin: 4px;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+  }
 }

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

@@ -456,10 +456,10 @@ export default class extends Page {
         return a.sort < b.sort ? -1 : (a.sort > b.sort ? 1 : 0);
       });
       const now = new Date().getTime();
-      courseMap.open = result.filter(row => !row.isUsed);
-      courseMap.end = result.filter(row => row.isUsed && (!row.isStop || (row.isStop && row.restoreTime)) && new Date(row.useEndTime).getTime() < now);
+      courseMap.open = result.filter(row => !row.isUsed && (!row.useEndTime || new Date(row.useEndTime).getTime() > now));
+      courseMap.end = result.filter(row => (!row.isSuspend || (row.isSuspend && row.restoreTime)) && new Date(row.useEndTime).getTime() < now);
 
-      courseMap.process = result.filter(row => row.isUsed && (row.isStop || row.restoreTime) && new Date(row.useEndTime).getTime() >= now);
+      courseMap.process = result.filter(row => row.isUsed && (row.isSuspend || row.restoreTime) && new Date(row.useEndTime).getTime() >= now);
       this.setState({ courseMap });
     });
 
@@ -769,7 +769,7 @@ export default class extends Page {
           <div className="left">待开通</div>
         </div>
         <Division col="3">
-          {(courseMap.wait || []).map(row => {
+          {(courseMap.open || []).map(row => {
             return <Card1
               title={`${row.course.title}${row.vsNo > 0 ? `V${row.vsNo}` : ''}${row.number > 0 ? `(${row.number}课时)` : ''}`}
               tag={CourseModuleMap[row.course.courseModule]}
@@ -808,7 +808,7 @@ export default class extends Page {
     if (endTime) {
       const endTimeD = new Date(endTime);
       const now = new Date();
-      if (now.getTime() + 86400000 > endTimeD.getTime()) {
+      if ((now.getTime() + 86400000) > endTimeD.getTime()) {
         finishTime = 'today';
       } else {
         finishTime = 'tomorrow';

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

@@ -60,6 +60,11 @@
 
       .title {
         margin-bottom: 10px;
+        position: relative;
+
+        .button {
+          margin-left: 10px;
+        }
 
         .tag {
           color: #fff;
@@ -79,6 +84,12 @@
           font-size: 20px;
           line-height: 24px;
         }
+
+        .right {
+          position: absolute;
+          right: 0;
+          top: 0;
+        }
       }
 
       .continue {
@@ -160,6 +171,11 @@
 
       .title {
         margin-bottom: 10px;
+        position: relative;
+
+        .button {
+          margin-left: 10px;
+        }
 
         .tag {
           color: #fff;
@@ -180,8 +196,11 @@
           line-height: 24px;
         }
 
+
         .right {
-          float: right;
+          position: absolute;
+          right: 0;
+          top: 0;
         }
       }
 
@@ -371,6 +390,17 @@
           }
         }
 
+        .time-line-item.not {
+
+          .title {
+            color: #A7A7B7;
+          }
+
+          .time-line-detail {
+            color: #8D8D94;
+          }
+        }
+
         .time-line-item.end {
           .title {
             color: #A7A7B7;

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 453 - 191
front/project/www/routes/my/course/page.js


+ 11 - 3
front/project/www/routes/my/tools/page.js

@@ -20,12 +20,13 @@ import { My } from '../../../stores/my';
 import { User } from '../../../stores/user';
 import { Order } from '../../../stores/order';
 import { Textbook } from '../../../stores/textbook';
-import { DataType, ServiceKey } from '../../../../Constant';
+import { DataType, ServiceKey, RecordSource } from '../../../../Constant';
 import { Main } from '../../../stores/main';
 import { Question } from '../../../stores/question';
 import Select from '../../../components/Select';
 
 const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
+const RecordSourceMap = getMap(RecordSource, 'value', 'label');
 
 const dataHistoryColumns = [
   { title: '更新时间', key: 'time', width: 120 },
@@ -43,6 +44,7 @@ const textbookHistoryColumns = [
 
 const openColumns = [
   { title: '商品名称', key: 'title', width: 240 },
+  { title: '获取方式', key: 'source', width: 240 },
   { title: '开通期限', key: 'endTime', width: 240 },
   { title: '操作', key: 'handler', width: 90 },
 ];
@@ -115,6 +117,7 @@ export default class extends Page {
         }),
         // 不显示分页
         updateTotal: 0,
+        maxHeight: 730,
         updatePage: page,
         updateData: { page, size, subject, columns: textbookHistoryColumns, type: 'textbook' },
       });
@@ -143,6 +146,7 @@ export default class extends Page {
       this.setState({
         updateList: result.map(row => {
           row.title = ServiceKeyMap[service];
+          row.source = RecordSourceMap[row.source];
           row.handler = (
             <a
               onClick={() => {
@@ -213,14 +217,16 @@ export default class extends Page {
   }
 
   dataHistory({ dataId, page, size }) {
-    My.listDataHistory({ page, size, dataId }).then(result => {
+    My.listDataHistory({ page, size: 10000, dataId }).then(result => {
       result.list = result.list.map(row => {
         row.time = formatDate(row.time, 'YYYY-MM-DD\nHH:mm:ss');
         return row;
       });
       this.setState({
+        // 不显示分页
+        updateTotal: 0,
+        maxHeight: 730,
         updateList: result.list,
-        updateTotal: result.total,
         updatePage: page,
         updateData: { page, size, dataId, columns: dataHistoryColumns, type: 'data' },
       });
@@ -305,6 +311,7 @@ export default class extends Page {
       showFeedback,
       updateList,
       updateTotal,
+      maxHeight,
       updateData = {},
     } = this.state;
     return (
@@ -351,6 +358,7 @@ export default class extends Page {
               }
             }}
             total={updateTotal}
+            maxHeight={maxHeight}
           />
         </Modal>
         <Modal

+ 24 - 0
front/project/www/stores/my.js

@@ -406,6 +406,30 @@ export default class MyStore extends BaseStore {
     return this.apiGet('/my/course/list', { page, size, courseModule, isUsed, isEnd, order, direction });
   }
 
+  detailCourse(recordId) {
+    return this.apiGet('/my/course/detail', { recordId });
+  }
+
+  setCCTalkName(id, cctalkName) {
+    return this.apiPost('/my/course/cctalk_name', { id, cctalkName });
+  }
+
+  uploadAppointmentQuestion({ id, questionFile, questionFileName }) {
+    return this.apiPost('/my/course/appointment/question', { id, questionFile, questionFileName });
+  }
+
+  addAppointmentComment({ appointmentId, type, parentId, content, file, name }) {
+    return this.apiPost('/my/course/appointment/comment/add', { appointmentId, type, parentId, content, file, name });
+  }
+
+  editAppointmentComment({ id, content, file, name }) {
+    return this.apiPost('/my/course/appointment/comment/edit', { id, content, file, name });
+  }
+
+  delAppointmentComment(id) {
+    return this.apiDel('/my/course/appointment/comment/delete', { id });
+  }
+
   /**
    * 申请停课
    * @param {*} recordId

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

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

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

@@ -49,6 +49,18 @@ public class UserCourseAppointment implements Serializable {
     private String cctalkChannel;
 
     /**
+     * 答疑文档
+     */
+    @Column(name = "`question_file`")
+    private String questionFile;
+
+    /**
+     * 答疑文档名称
+     */
+    @Column(name = "`question_file_name`")
+    private String questionFileName;
+
+    /**
      * 预约开始时间
      */
     @Column(name = "`start_time`")
@@ -206,6 +218,42 @@ public class UserCourseAppointment implements Serializable {
     }
 
     /**
+     * 获取答疑文档
+     *
+     * @return question_file - 答疑文档
+     */
+    public String getQuestionFile() {
+        return questionFile;
+    }
+
+    /**
+     * 设置答疑文档
+     *
+     * @param questionFile 答疑文档
+     */
+    public void setQuestionFile(String questionFile) {
+        this.questionFile = questionFile;
+    }
+
+    /**
+     * 获取答疑文档名称
+     *
+     * @return question_file_name - 答疑文档名称
+     */
+    public String getQuestionFileName() {
+        return questionFileName;
+    }
+
+    /**
+     * 设置答疑文档名称
+     *
+     * @param questionFileName 答疑文档名称
+     */
+    public void setQuestionFileName(String questionFileName) {
+        this.questionFileName = questionFileName;
+    }
+
+    /**
      * 获取预约开始时间
      *
      * @return start_time - 预约开始时间
@@ -322,6 +370,8 @@ public class UserCourseAppointment implements Serializable {
         sb.append(", recordId=").append(recordId);
         sb.append(", courseId=").append(courseId);
         sb.append(", cctalkChannel=").append(cctalkChannel);
+        sb.append(", questionFile=").append(questionFile);
+        sb.append(", questionFileName=").append(questionFileName);
         sb.append(", startTime=").append(startTime);
         sb.append(", endTime=").append(endTime);
         sb.append(", isFinish=").append(isFinish);
@@ -422,6 +472,26 @@ public class UserCourseAppointment implements Serializable {
         }
 
         /**
+         * 设置答疑文档
+         *
+         * @param questionFile 答疑文档
+         */
+        public Builder questionFile(String questionFile) {
+            obj.setQuestionFile(questionFile);
+            return this;
+        }
+
+        /**
+         * 设置答疑文档名称
+         *
+         * @param questionFileName 答疑文档名称
+         */
+        public Builder questionFileName(String questionFileName) {
+            obj.setQuestionFileName(questionFileName);
+            return this;
+        }
+
+        /**
          * 设置预约开始时间
          *
          * @param startTime 预约开始时间

+ 386 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserCourseAppointmentComment.java

@@ -0,0 +1,386 @@
+package com.qxgmat.data.dao.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.persistence.*;
+
+@Table(name = "user_course_appointment_comment")
+public class UserCourseAppointmentComment implements Serializable {
+    @Id
+    @Column(name = "`id`")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    /**
+     * 用户id
+     */
+    @Column(name = "`user_id`")
+    private Integer userId;
+
+    /**
+     * 记录id
+     */
+    @Column(name = "`record_id`")
+    private Integer recordId;
+
+    /**
+     * 预约id
+     */
+    @Column(name = "`appointment_id`")
+    private Integer appointmentId;
+
+    @Column(name = "`type`")
+    private String type;
+
+    /**
+     * 引用id
+     */
+    @Column(name = "`parent_id`")
+    private Integer parentId;
+
+    @Column(name = "`file`")
+    private String file;
+
+    @Column(name = "`name`")
+    private String name;
+
+    @Column(name = "`create_time`")
+    private Date createTime;
+
+    @Column(name = "`delete_time`")
+    private Date deleteTime;
+
+    @Column(name = "`content`")
+    private String content;
+
+    @Column(name = "`reply`")
+    private String reply;
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * @return id
+     */
+    public Integer getId() {
+        return id;
+    }
+
+    /**
+     * @param id
+     */
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    /**
+     * 获取用户id
+     *
+     * @return user_id - 用户id
+     */
+    public Integer getUserId() {
+        return userId;
+    }
+
+    /**
+     * 设置用户id
+     *
+     * @param userId 用户id
+     */
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    /**
+     * 获取记录id
+     *
+     * @return record_id - 记录id
+     */
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    /**
+     * 设置记录id
+     *
+     * @param recordId 记录id
+     */
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+
+    /**
+     * 获取预约id
+     *
+     * @return appointment_id - 预约id
+     */
+    public Integer getAppointmentId() {
+        return appointmentId;
+    }
+
+    /**
+     * 设置预约id
+     *
+     * @param appointmentId 预约id
+     */
+    public void setAppointmentId(Integer appointmentId) {
+        this.appointmentId = appointmentId;
+    }
+
+    /**
+     * @return type
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * @param type
+     */
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    /**
+     * 获取引用id
+     *
+     * @return parent_id - 引用id
+     */
+    public Integer getParentId() {
+        return parentId;
+    }
+
+    /**
+     * 设置引用id
+     *
+     * @param parentId 引用id
+     */
+    public void setParentId(Integer parentId) {
+        this.parentId = parentId;
+    }
+
+    /**
+     * @return file
+     */
+    public String getFile() {
+        return file;
+    }
+
+    /**
+     * @param file
+     */
+    public void setFile(String file) {
+        this.file = file;
+    }
+
+    /**
+     * @return name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @param name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return create_time
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * @param createTime
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * @return delete_time
+     */
+    public Date getDeleteTime() {
+        return deleteTime;
+    }
+
+    /**
+     * @param deleteTime
+     */
+    public void setDeleteTime(Date deleteTime) {
+        this.deleteTime = deleteTime;
+    }
+
+    /**
+     * @return content
+     */
+    public String getContent() {
+        return content;
+    }
+
+    /**
+     * @param content
+     */
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    /**
+     * @return reply
+     */
+    public String getReply() {
+        return reply;
+    }
+
+    /**
+     * @param reply
+     */
+    public void setReply(String reply) {
+        this.reply = reply;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", userId=").append(userId);
+        sb.append(", recordId=").append(recordId);
+        sb.append(", appointmentId=").append(appointmentId);
+        sb.append(", type=").append(type);
+        sb.append(", parentId=").append(parentId);
+        sb.append(", file=").append(file);
+        sb.append(", name=").append(name);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", deleteTime=").append(deleteTime);
+        sb.append(", content=").append(content);
+        sb.append(", reply=").append(reply);
+        sb.append("]");
+        return sb.toString();
+    }
+
+    public static UserCourseAppointmentComment.Builder builder() {
+        return new UserCourseAppointmentComment.Builder();
+    }
+
+    public static class Builder {
+        private UserCourseAppointmentComment obj;
+
+        public Builder() {
+            this.obj = new UserCourseAppointmentComment();
+        }
+
+        /**
+         * @param id
+         */
+        public Builder id(Integer id) {
+            obj.setId(id);
+            return this;
+        }
+
+        /**
+         * 设置用户id
+         *
+         * @param userId 用户id
+         */
+        public Builder userId(Integer userId) {
+            obj.setUserId(userId);
+            return this;
+        }
+
+        /**
+         * 设置记录id
+         *
+         * @param recordId 记录id
+         */
+        public Builder recordId(Integer recordId) {
+            obj.setRecordId(recordId);
+            return this;
+        }
+
+        /**
+         * 设置预约id
+         *
+         * @param appointmentId 预约id
+         */
+        public Builder appointmentId(Integer appointmentId) {
+            obj.setAppointmentId(appointmentId);
+            return this;
+        }
+
+        /**
+         * @param type
+         */
+        public Builder type(String type) {
+            obj.setType(type);
+            return this;
+        }
+
+        /**
+         * 设置引用id
+         *
+         * @param parentId 引用id
+         */
+        public Builder parentId(Integer parentId) {
+            obj.setParentId(parentId);
+            return this;
+        }
+
+        /**
+         * @param file
+         */
+        public Builder file(String file) {
+            obj.setFile(file);
+            return this;
+        }
+
+        /**
+         * @param name
+         */
+        public Builder name(String name) {
+            obj.setName(name);
+            return this;
+        }
+
+        /**
+         * @param createTime
+         */
+        public Builder createTime(Date createTime) {
+            obj.setCreateTime(createTime);
+            return this;
+        }
+
+        /**
+         * @param deleteTime
+         */
+        public Builder deleteTime(Date deleteTime) {
+            obj.setDeleteTime(deleteTime);
+            return this;
+        }
+
+        /**
+         * @param content
+         */
+        public Builder content(String content) {
+            obj.setContent(content);
+            return this;
+        }
+
+        /**
+         * @param reply
+         */
+        public Builder reply(String reply) {
+            obj.setReply(reply);
+            return this;
+        }
+
+        public UserCourseAppointmentComment build() {
+            return this.obj;
+        }
+    }
+}

+ 52 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserOrderCheckout.java

@@ -75,6 +75,12 @@ public class UserOrderCheckout implements Serializable {
     @Column(name = "`create_time`")
     private Date createTime;
 
+    @Column(name = "`expire_days`")
+    private Integer expireDays;
+
+    @Column(name = "`use_expire_days`")
+    private Integer useExpireDays;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -285,6 +291,34 @@ public class UserOrderCheckout implements Serializable {
         this.createTime = createTime;
     }
 
+    /**
+     * @return expire_days
+     */
+    public Integer getExpireDays() {
+        return expireDays;
+    }
+
+    /**
+     * @param expireDays
+     */
+    public void setExpireDays(Integer expireDays) {
+        this.expireDays = expireDays;
+    }
+
+    /**
+     * @return use_expire_days
+     */
+    public Integer getUseExpireDays() {
+        return useExpireDays;
+    }
+
+    /**
+     * @param useExpireDays
+     */
+    public void setUseExpireDays(Integer useExpireDays) {
+        this.useExpireDays = useExpireDays;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -303,6 +337,8 @@ public class UserOrderCheckout implements Serializable {
         sb.append(", money=").append(money);
         sb.append(", originMoney=").append(originMoney);
         sb.append(", createTime=").append(createTime);
+        sb.append(", expireDays=").append(expireDays);
+        sb.append(", useExpireDays=").append(useExpireDays);
         sb.append("]");
         return sb.toString();
     }
@@ -434,6 +470,22 @@ public class UserOrderCheckout implements Serializable {
             return this;
         }
 
+        /**
+         * @param expireDays
+         */
+        public Builder expireDays(Integer expireDays) {
+            obj.setExpireDays(expireDays);
+            return this;
+        }
+
+        /**
+         * @param useExpireDays
+         */
+        public Builder useExpireDays(Integer useExpireDays) {
+            obj.setUseExpireDays(useExpireDays);
+            return this;
+        }
+
         public UserOrderCheckout build() {
             return this.obj;
         }

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

@@ -189,6 +189,12 @@ public class UserOrderRecord implements Serializable {
     @Column(name = "`comment_tips`")
     private Integer commentTips;
 
+    @Column(name = "`expire_days`")
+    private Integer expireDays;
+
+    @Column(name = "`use_expire_days`")
+    private Integer useExpireDays;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -751,6 +757,34 @@ public class UserOrderRecord implements Serializable {
         this.commentTips = commentTips;
     }
 
+    /**
+     * @return expire_days
+     */
+    public Integer getExpireDays() {
+        return expireDays;
+    }
+
+    /**
+     * @param expireDays
+     */
+    public void setExpireDays(Integer expireDays) {
+        this.expireDays = expireDays;
+    }
+
+    /**
+     * @return use_expire_days
+     */
+    public Integer getUseExpireDays() {
+        return useExpireDays;
+    }
+
+    /**
+     * @param useExpireDays
+     */
+    public void setUseExpireDays(Integer useExpireDays) {
+        this.useExpireDays = useExpireDays;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -789,6 +823,8 @@ public class UserOrderRecord implements Serializable {
         sb.append(", originMoney=").append(originMoney);
         sb.append(", courseAward=").append(courseAward);
         sb.append(", commentTips=").append(commentTips);
+        sb.append(", expireDays=").append(expireDays);
+        sb.append(", useExpireDays=").append(useExpireDays);
         sb.append("]");
         return sb.toString();
     }
@@ -1116,6 +1152,22 @@ public class UserOrderRecord implements Serializable {
             return this;
         }
 
+        /**
+         * @param expireDays
+         */
+        public Builder expireDays(Integer expireDays) {
+            obj.setExpireDays(expireDays);
+            return this;
+        }
+
+        /**
+         * @param useExpireDays
+         */
+        public Builder useExpireDays(Integer useExpireDays) {
+            obj.setUseExpireDays(useExpireDays);
+            return this;
+        }
+
         public UserOrderRecord build() {
             return this.obj;
         }

+ 39 - 0
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserCourseAppointmentCommentMapper.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qxgmat.data.dao.UserCourseAppointmentCommentMapper">
+  <resultMap id="BaseResultMap" type="com.qxgmat.data.dao.entity.UserCourseAppointmentComment">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+    <result column="user_id" jdbcType="INTEGER" property="userId" />
+    <result column="record_id" jdbcType="INTEGER" property="recordId" />
+    <result column="appointment_id" jdbcType="INTEGER" property="appointmentId" />
+    <result column="type" jdbcType="VARCHAR" property="type" />
+    <result column="parent_id" jdbcType="INTEGER" property="parentId" />
+    <result column="file" jdbcType="VARCHAR" property="file" />
+    <result column="name" jdbcType="VARCHAR" property="name" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="delete_time" jdbcType="TIMESTAMP" property="deleteTime" />
+  </resultMap>
+  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.UserCourseAppointmentComment">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <result column="content" jdbcType="LONGVARCHAR" property="content" />
+    <result column="reply" jdbcType="LONGVARCHAR" property="reply" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `id`, `user_id`, `record_id`, `appointment_id`, `type`, `parent_id`, `file`, `name`, 
+    `create_time`, `delete_time`
+  </sql>
+  <sql id="Blob_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `content`, `reply`
+  </sql>
+</mapper>

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

@@ -12,6 +12,8 @@
     <result column="record_id" jdbcType="INTEGER" property="recordId" />
     <result column="course_id" jdbcType="INTEGER" property="courseId" />
     <result column="cctalk_channel" jdbcType="VARCHAR" property="cctalkChannel" />
+    <result column="question_file" jdbcType="VARCHAR" property="questionFile" />
+    <result column="question_file_name" jdbcType="VARCHAR" property="questionFileName" />
     <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
     <result column="end_time" jdbcType="TIMESTAMP" property="endTime" />
     <result column="is_finish" jdbcType="INTEGER" property="isFinish" />
@@ -23,7 +25,8 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `user_id`, `no`, `title`, `record_id`, `course_id`, `cctalk_channel`, `start_time`, 
-    `end_time`, `is_finish`, `supply_list`, `note_list`, `create_time`
+    `id`, `user_id`, `no`, `title`, `record_id`, `course_id`, `cctalk_channel`, `question_file`, 
+    `question_file_name`, `start_time`, `end_time`, `is_finish`, `supply_list`, `note_list`, 
+    `create_time`
   </sql>
 </mapper>

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

@@ -17,12 +17,14 @@
     <result column="money" jdbcType="DECIMAL" property="money" />
     <result column="origin_money" jdbcType="DECIMAL" property="originMoney" />
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="expire_days" jdbcType="INTEGER" property="expireDays" />
+    <result column="use_expire_days" jdbcType="INTEGER" property="useExpireDays" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
     `id`, `user_id`, `order_id`, `parent_id`, `product_type`, `product_id`, `service`, 
-    `param`, `number`, `money`, `origin_money`, `create_time`
+    `param`, `number`, `money`, `origin_money`, `create_time`, `expire_days`, `use_expire_days`
   </sql>
 </mapper>

+ 4 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserOrderRecordMapper.xml

@@ -37,6 +37,8 @@
     <result column="origin_money" jdbcType="DECIMAL" property="originMoney" />
     <result column="course_award" jdbcType="INTEGER" property="courseAward" />
     <result column="comment_tips" jdbcType="INTEGER" property="commentTips" />
+    <result column="expire_days" jdbcType="INTEGER" property="expireDays" />
+    <result column="use_expire_days" jdbcType="INTEGER" property="useExpireDays" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
@@ -46,6 +48,7 @@
     `param`, `source`, `teacher_id`, `number`, `vs_no`, `time_id`, `ask_time`, `cctalk_name`, 
     `is_subscribe`, `start_time`, `end_time`, `use_start_time`, `use_end_time`, `is_used`, 
     `use_time`, `is_stop`, `stop_time`, `is_suspend`, `suspend_time`, `restore_time`, 
-    `create_time`, `money`, `origin_money`, `course_award`, `comment_tips`
+    `create_time`, `money`, `origin_money`, `course_award`, `comment_tips`, `expire_days`, 
+    `use_expire_days`
   </sql>
 </mapper>

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

@@ -73,7 +73,7 @@
       and pa.`course_id` = #{courseId,jdbcType=VARCHAR}
     </if>
     <if test="appointmentIds != null">
-      pa.`course_appointment` IN
+      and pa.`course_appointment` IN
       <foreach collection="appointmentIds" item="item" index="index" open="(" close=")" separator=",">
         #{item}
       </foreach>

+ 4 - 3
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserOrderRecordRelationMapper.xml

@@ -105,13 +105,14 @@
       and uor.`user_id` = #{userId,jdbcType=VARCHAR}
     </if>
     <if test="isUsed != null">
-      and uor.`isUsed` = #{userId,jdbcType=INTEGER}
+      and uor.`is_used` = #{userId,jdbcType=INTEGER}
     </if>
     <if test="ltUseEndTime != null">
-      and uor.`useEndTime` &lt; #{ltUseEndTime,jdbcType=INTEGER}
+      and uor.`use_end_time` &lt; #{ltUseEndTime,jdbcType=INTEGER}
+      and ((uor.`is_suspend` = 1 and uor.`restore_time` is not null) or (uor.`is_suspend` = 0))
     </if>
     <if test="gtUseEndTime != null">
-      and uor.`useEndTime` &gt; #{gtUseEndTime,jdbcType=INTEGER}
+      and ((uor.`is_suspend` = 1 and uor.`restore_time` is null) or (uor.`is_suspend` = 0 and uor.`use_end_time` &gt; #{gtUseEndTime,jdbcType=INTEGER}))
     </if>
     order by ${order} ${direction}
   </select>

+ 22 - 0
server/data/src/main/resources/db/migration/V1__init_table.sql

@@ -1049,6 +1049,8 @@ CREATE TABLE user_course_appointment (
   record_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '关联购课记录',
   course_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '课程id',
   cctalk_channel varchar(255) NOT NULL DEFAULT '' COMMENT '频道号',
+  question_file varchar(255) NOT NULL DEFAULT '' COMMENT '答疑文档',
+  question_file_name varchar(255) NOT NULL DEFAULT '' COMMENT '答疑文档名称',
   start_time datetime DEFAULT NULL COMMENT '预约开始时间',
   end_time datetime DEFAULT NULL COMMENT '预约结束时间',
   is_finish tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否完成',
@@ -1059,6 +1061,22 @@ CREATE TABLE user_course_appointment (
   KEY user_id (user_id,record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户课程预约';
 
+CREATE TABLE user_course_appointment_comment (
+  id int(11) unsigned NOT NULL AUTO_INCREMENT,
+  user_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',
+  record_id int(11) unsigned NOT NULL COMMENT '记录id',
+  appointment_id int(11) unsigned NOT NULL COMMENT '预约id',
+  type varchar(20) NOT NULL DEFAULT '',
+  parent_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '引用id',
+  content text,
+  reply text,
+  file varchar(255) DEFAULT NULL,
+  name varchar(255) DEFAULT NULL,
+  create_time datetime DEFAULT NULL,
+  delete_time datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-预约-聊天';
+
 CREATE TABLE user_course_progress (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) unsigned NOT NULL COMMENT '用户id',
@@ -1206,6 +1224,8 @@ CREATE TABLE user_order_checkout (
   money decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '购买金额',
   origin_money decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '原价',
   create_time datetime DEFAULT NULL,
+  expire_days int(10) unsigned NOT NULL DEFAULT '0' COMMENT '',
+  use_expire_days int(10) unsigned NOT NULL DEFAULT '0' COMMENT '',
   PRIMARY KEY (id),
   KEY user_id (user_id,order_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-购物车';
@@ -1243,6 +1263,8 @@ CREATE TABLE user_order_record (
   origin_money decimal(10,2) unsigned NOT NULL DEFAULT '0.00',
   course_award int(10) unsigned NOT NULL DEFAULT '0' COMMENT '奖励天数',
   comment_tips tinyint(10) unsigned NOT NULL DEFAULT '0' COMMENT '评价提醒:0显示,1关闭',
+  expire_days int(10) unsigned NOT NULL DEFAULT '0' COMMENT '',
+  use_expire_days int(10) unsigned NOT NULL DEFAULT '0' COMMENT '',
   PRIMARY KEY (id),
   KEY user_id (user_id,product_type),
   KEY order_id (order_id),

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

@@ -564,10 +564,10 @@ public class CourseController {
         entity.setProductType(ProductType.COURSE.key);
         entity.setTimeId(dto.getTimeId());
         entity.setProductId(dto.getCourseId());
-        entity.setIsUsed(1);
-        entity.setUseTime(new Date());
-        entity.setStartTime(courseTime.getStartTime());
-        entity.setEndTime(courseTime.getEndTime());
+        entity.setUseStartTime(courseTime.getStartTime());
+        entity.setUseEndTime(courseTime.getEndTime());
+        // 计算有效期
+        entity.setUseExpireDays((int)(courseTime.getEndTime().getTime() - courseTime.getStartTime().getTime())/86400000);
 
         // 默认回答时间
         JSONObject askInfo = toolsService.getAskTime(BigDecimal.valueOf(0));

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

@@ -117,6 +117,9 @@ public class UserController {
     private UserCourseAppointmentService userCourseAppointmentService;
 
     @Autowired
+    private UserCourseAppointmentCommentService userCourseAppointmentCommentService;
+
+    @Autowired
     private UserFeedbackErrorService userFeedbackErrorService;
 
     @Autowired
@@ -703,6 +706,57 @@ public class UserController {
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
+    @RequestMapping(value = "/course/appointment/comment/add", method = RequestMethod.POST)
+    @ApiOperation(value = "添加课程预约评论", httpMethod = "POST")
+    public Response<UserCourseAppointment> addCourseAppointmentComment(@RequestBody @Validated UserCourseAppointmentCommentDto dto, HttpServletRequest request) {
+        UserCourseAppointmentComment entity = Transform.dtoToEntity(dto);
+        if (entity.getParentId() > 0){
+            UserCourseAppointmentComment comment = userCourseAppointmentCommentService.get(entity.getParentId());
+            if (comment != null){
+                entity.setReply(comment.getContent());
+            }
+        }
+        entity = userCourseAppointmentCommentService.add(entity);
+        managerLogService.log(request);
+        return ResponseHelp.success(Transform.convert(entity, CourseTime.class));
+    }
+
+    @RequestMapping(value = "/course/appointment/comment/edit", method = RequestMethod.PUT)
+    @ApiOperation(value = "编辑课程预约聊天", httpMethod = "PUT")
+    public Response<Boolean> editCourseAppointmentComment(@RequestBody @Validated UserCourseAppointmentCommentDto dto, HttpServletRequest request) {
+        UserCourseAppointmentComment entity = Transform.dtoToEntity(dto);
+        if (entity.getParentId() > 0){
+            UserCourseAppointmentComment comment = userCourseAppointmentCommentService.get(entity.getParentId());
+            if (comment != null){
+                entity.setReply(comment.getContent());
+            }
+        }
+        entity = userCourseAppointmentCommentService.edit(entity);
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/course/appointment/comment/delete", method = RequestMethod.DELETE)
+    @ApiOperation(value = "删除课程预约聊天", httpMethod = "DELETE")
+    public Response<Boolean> deleteCourseAppointmentComment(@RequestParam int id, HttpServletRequest request) {
+        UserCourseAppointmentComment in = userCourseAppointmentCommentService.get(id);
+        userCourseAppointmentCommentService.delete(id);
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/course/appointment/comment/all", method = RequestMethod.GET)
+    @ApiOperation(value = "课程预约评论列表", httpMethod = "GET")
+    public Response<List<UserCourseAppointmentComment>> listCourseAppointment(
+            @RequestParam(required = false) Integer appointmentId,
+            @RequestParam(required = false) String type,
+            HttpSession session) {
+
+        List<UserCourseAppointmentComment> p = userCourseAppointmentCommentService.allAdmin(appointmentId, type);
+
+        return ResponseHelp.success(p);
+    }
+
     @RequestMapping(value = "/course/record/all", method = RequestMethod.GET)
     @ApiOperation(value = "课程学习列表", httpMethod = "GET")
     public Response<List<UserCourseProgressInfoDto>> allCourseRecord(

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

@@ -434,7 +434,7 @@ public class CourseController {
         Date now = new Date();
         List<UserOrderRecord> processList = userOrderRecordList.stream().filter(row-> row.getIsUsed() > 0 && row.getUseEndTime().after(now)).collect(Collectors.toList());
         Collection processIds = Transform.getIds(processList, UserOrderRecord.class, "id");
-        Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByCourseId(user.getId(), processIds, 2);
+        Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByRecordId(user.getId(), processIds, 2);
         Transform.combine(dtos, previewMap, UserCourseProgressDto.class, "productId", "papers", UserPaperBaseExtendDto.class);
 
         // 绑定老师

+ 162 - 31
server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java

@@ -31,7 +31,6 @@ import com.qxgmat.service.extend.*;
 import com.qxgmat.service.inline.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
-import org.apache.logging.log4j.util.Strings;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.MediaType;
@@ -41,7 +40,6 @@ import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
-import javax.tools.Tool;
 import javax.validation.Validator;
 import java.io.File;
 import java.io.IOException;
@@ -143,6 +141,9 @@ public class MyController {
     private UserCourseAppointmentService userCourseAppointmentService;
 
     @Autowired
+    private UserCourseAppointmentCommentService userCourseAppointmentCommentService;
+
+    @Autowired
     private UserCourseProgressService userCourseProgressService;
 
     @Autowired
@@ -1595,10 +1596,10 @@ public class MyController {
         Page<UserOrderRecord> p;
         if (module == CourseModule.ONLINE){
             // 在线课程包含:视频课程、小班课程
-            p = userOrderRecordService.listWithCourse(page, size, user.getId(), new String[]{CourseModule.VIDEO.key, CourseModule.ONLINE.key}, null,null, order, DirectionStatus.ValueOf(direction));
+            p = userOrderRecordService.listWithCourse(page, size, user.getId(), new String[]{CourseModule.VIDEO.key}, isUsed,isEnd, order, DirectionStatus.ValueOf(direction));
         } else if (module == CourseModule.VS){
             // 1v1课程:只有系统授课有作业
-            p = userOrderRecordService.listWithCourse(page, size, user.getId(), new String[]{CourseModule.VS.key}, null,null, order, DirectionStatus.ValueOf(direction));
+            p = userOrderRecordService.listWithCourse(page, size, user.getId(), new String[]{CourseModule.VS.key}, isUsed,isEnd, order, DirectionStatus.ValueOf(direction));
         }else{
             throw new ParameterException("课程类型错误");
         }
@@ -1615,34 +1616,22 @@ public class MyController {
         List<Course> courseList = courseService.select(courseIds);
         Map courseMap = Transform.getMap(courseList, Course.class, "id");
         Transform.combine(pr, courseList, UserCourseDetailDto.class, "productId", "course", Course.class, "id", CourseExtendDto.class);
-        for(UserCourseDetailDto dto : pr){
-            Course course = (Course)courseMap.get(dto.getProductId());
-            CourseExtendDto courseExtendDto = dto.getCourse();
-            if (course.getCourseModule().equals(CourseModule.VS.key)){
-                // 根据课时数进行计算
-                courseExtendDto.setExpireDays(courseExtendService.computeExpire(dto.getNumber(), course));
-            }else if(course.getCourseModule().equals(CourseModule.VIDEO.key)){
-                // 根据设置进行计算
-                courseExtendDto.setExpireDays(courseExtendService.computeExpire(course));
-            }else if(course.getCourseModule().equals(CourseModule.ONLINE.key)){
-                // 根据使用时长计算
-                courseExtendDto.setExpireDays((int)(dto.getUseEndTime().getTime() - dto.getUseStartTime().getTime() / 86400000));
-            }
-        }
 
         // 绑定课时、预约、进度
         Map<Object, Collection<CourseNo>> courseNoMap = courseNoService.groupByCourseId(courseIds);
         Transform.combine(pr, courseNoMap, UserCourseDetailDto.class, "productId", "courseNos", CourseNoExtendDto.class);
 
         Map<Object, Collection<UserCourseAppointment>> appointmentMap = userCourseAppointmentService.groupByRecordId(recordIds);
-        Transform.combine(pr, appointmentMap, UserCourseDetailDto.class, "productId", "appointments", UserCourseAppointmentExtendDto.class);
+        Transform.combine(pr, appointmentMap, UserCourseDetailDto.class, "id", "appointments", UserCourseAppointmentExtendDto.class);
 
         Map<Object, Collection<UserCourseProgress>> progressMap = userCourseProgressService.groupByRecordId(recordIds);
+        Transform.combine(pr, progressMap, UserCourseDetailDto.class, "id", "progress", UserCourseProgressExtendDto.class);
         Map<Object, Collection<UserCourseRecord>> recordMap = userCourseRecordService.groupByRecordId(recordIds);
         for(UserCourseDetailDto dto : pr){
+            Collection<CourseNo> courseNos = courseNoMap.get(dto.getProductId());
+            if (courseNos == null || courseNos.size() == 0) continue;
             Collection<UserCourseProgress> list = progressMap.get(dto.getId());
             if (list == null || list.size() == 0) continue;
-            Collection<CourseNo> courseNos = courseNoMap.get(dto.getProductId());
             dto.setCurrentNo(courseExtendService.computeCourseNoCurrent(courseNos, list));
             Collection<UserCourseRecord> userCourseRecords = recordMap.get(dto.getId());
             dto.setTotalTime(courseExtendService.computeCourseTime(userCourseRecords));
@@ -1650,8 +1639,8 @@ public class MyController {
         }
 
         // 获取每个科目的所有作业
-        Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByCourseId(user.getId(), recordIds, 1000);
-        Transform.combine(pr, previewMap, UserCourseDetailDto.class, "productId", "papers", BasePaperExtendDto.class);
+        Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByRecordId(user.getId(), recordIds, 1000);
+        Transform.combine(pr, previewMap, UserCourseDetailDto.class, "id", "papers", BasePaperExtendDto.class);
         for(UserCourseDetailDto dto : pr){
             Collection<UserPreviewPaperRelation> list = previewMap.get(dto.getId());
             if (list == null || list.size() == 0) continue;
@@ -1673,10 +1662,7 @@ public class MyController {
 
         // 提问数、笔记数
         Map<Object, Collection<UserAskCourse>> askMap = userAskCourseService.groupByRecordId(recordIds);
-        Transform.combine(pr, appointmentMap, UserCourseDetailDto.class, "productId", "appointments", UserCourseAppointmentExtendDto.class);
-
         Map<Object, Collection<UserNoteCourse>> noteMap = userNoteCourseService.groupByCourse(courseIds);
-        Transform.combine(pr, appointmentMap, UserCourseDetailDto.class, "productId", "notes", UserNoteCourseExtendDto.class);
         for(UserCourseDetailDto dto : pr){
             Collection<CourseNoExtendDto> courseNos = dto.getCourseNos();
             if (courseNos == null) continue;
@@ -1685,6 +1671,83 @@ public class MyController {
             Collection<UserNoteCourse> noteList = noteMap.get(dto.getProductId());
             Map notes = Transform.getMap(noteList, UserNoteCourse.class, "courseNoId");
             int noteNumber = 0;
+            int askNumber = askList == null ? 0: askList.size();
+            int answerNumber = askList == null ? 0 : (int)askList.stream().filter(r->r.getAnswerStatus()==AskStatus.ANSWER.index).count();
+            for(CourseNoExtendDto courseNo : courseNos){
+                if (notes.get(courseNo.getId()) != null){
+                    courseNo.setNote(true);
+                    noteNumber += 1;
+                }
+                List<UserAskCourse> askListNo = askListMap.get(courseNo.getId());
+                if (askListNo != null){
+                    courseNo.setAskNumber(askListNo.size());
+                    courseNo.setAnswerNumber((int)askListNo.stream().filter(r->r.getAnswerStatus()==AskStatus.ANSWER.index).count());
+                }
+            }
+            dto.setNoteNumber(noteNumber);
+            dto.setAskNumber(askNumber);
+            dto.setAnswerNumber(answerNumber);
+        }
+
+        // vs预约comment
+        Map<Object, Collection<UserCourseAppointmentComment>> commentMap = userCourseAppointmentCommentService.groupByRecordId(recordIds);
+        Transform.combine(pr, commentMap, UserCourseDetailDto.class, "id", "comments", UserCourseAppointmentComment.class);
+
+
+        return ResponseHelp.success(pr, page, size, p.getTotal());
+    }
+
+    @RequestMapping(value = "/course/detail", method = RequestMethod.GET)
+    @ApiOperation(value = "购买的课程记录", httpMethod = "GET")
+    public Response<UserCourseDetailDto> detailCourse(int recordId,
+            HttpSession session) {
+        User user = (User) shiroHelp.getLoginUser();
+
+        UserOrderRecord record = userOrderRecordService.get(recordId);
+        UserCourseDetailDto dto = Transform.convert(record, UserCourseDetailDto.class);
+
+        // 绑定课程
+        Course course = courseService.get(record.getProductId());
+        dto.setCourse(Transform.convert(course, CourseExtendDto.class));
+
+        // 绑定课时、预约、进度
+        List<CourseNo> courseNoList = courseNoService.allCourse(course.getId());
+        dto.setCourseNos(Transform.convert(courseNoList, CourseNoExtendDto.class));
+
+        List<UserCourseAppointment> appointmentList = userCourseAppointmentService.listByRecord(recordId);
+        dto.setAppointments(Transform.convert(appointmentList, UserCourseAppointmentExtendDto.class));
+
+        List<UserCourseProgress> progressList = userCourseProgressService.listCourse(recordId, course.getId());
+        List<UserCourseRecord> recordList = userCourseRecordService.allWithRecord(recordId);
+        if(progressList != null)dto.setCurrentNo(courseExtendService.computeCourseNoCurrent(courseNoList, progressList));
+        if(recordList != null)dto.setTotalTime(courseExtendService.computeCourseTime(recordList));
+        if(recordList != null)dto.setTotalDays(courseExtendService.computeCourseDay(record, recordList));
+
+        // 获取每个科目的所有作业
+        List<UserPreviewPaperRelation> previewList = previewService.list(1, 1000, recordId, user.getId(), null,null);
+        dto.setPapers(Transform.convert(previewList, BasePaperExtendDto.class));
+        int finish = 0;
+        for(UserPreviewPaperRelation relation : previewList){
+            if (relation.getPaper() == null) continue;
+            UserPaper paper = relation.getPaper();
+            if (paper.getTimes() > 0){
+                finish += 1;
+            }
+        }
+        dto.setPreviewProgress(finish * 100 / previewList.size());
+
+        // 绑定老师
+        CourseTeacher teacher = courseTeacherService.get(record.getTeacherId());
+        dto.setTeacher(Transform.convert(teacher, CourseTeacherExtendDto.class));
+
+        // 提问数、笔记数
+        Collection<CourseNoExtendDto> courseNos = dto.getCourseNos();
+        if (courseNos != null && courseNos.size() > 0) {
+            Collection<UserAskCourse> askList = userAskCourseService.listByRecord(recordId);
+            Map<Object, List<UserAskCourse>> askListMap = Transform.getMapList(askList, UserAskCourse.class, "courseNoId");
+            Collection<UserNoteCourse> noteList = userNoteCourseService.listByCourse(course.getId());
+            Map notes = Transform.getMap(noteList, UserNoteCourse.class, "courseNoId");
+            int noteNumber = 0;
             int askNumber = askList.size();
             int answerNumber = (int)askList.stream().filter(r->r.getAnswerStatus()==AskStatus.ANSWER.index).count();
             for(CourseNoExtendDto courseNo : courseNos){
@@ -1703,8 +1766,76 @@ public class MyController {
             dto.setAnswerNumber(answerNumber);
         }
 
+        // vs预约comment
+        List<UserCourseAppointmentComment> commentList = userCourseAppointmentCommentService.listByRecord(recordId);
+        dto.setComments(commentList);
 
-        return ResponseHelp.success(pr, page, size, p.getTotal());
+        return ResponseHelp.success(dto);
+    }
+
+    @RequestMapping(value = "/course/cctalk_name", method = RequestMethod.POST)
+    @ApiOperation(value = "设置课程cctalk", notes = "设置课程cctalk", httpMethod = "POST")
+    public Response<Boolean> setCourseCCTalkName(@RequestBody @Validated UserCourseCCTalkNameDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        UserOrderRecord entity = Transform.dtoToEntity(dto);
+        UserOrderRecord in = userOrderRecordService.get(dto.getId());
+        if (!in.getUserId().equals(user.getId())){
+            throw new ParameterException("记录不存在");
+        }
+        entity = userOrderRecordService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/course/appointment/question", method = RequestMethod.POST)
+    @ApiOperation(value = "预约提交答疑文档", notes = "预约提交答疑文档", httpMethod = "POST")
+    public Response<Boolean> uploadAppointmentQuestion(@RequestBody @Validated UserCourseAppointmentQuestionDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        UserCourseAppointment entity = Transform.dtoToEntity(dto);
+        UserCourseAppointment in = userCourseAppointmentService.get(dto.getId());
+        if (!in.getUserId().equals(user.getId())){
+            throw new ParameterException("记录不存在");
+        }
+        entity = userCourseAppointmentService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/course/appointment/comment/add", method = RequestMethod.POST)
+    @ApiOperation(value = "预约评论添加", notes = "预约评论添加", httpMethod = "POST")
+    public Response<Boolean> addAppointmentComment(@RequestBody @Validated UserCourseAppointmentCommentDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        UserCourseAppointmentComment entity = Transform.dtoToEntity(dto);
+        UserCourseAppointment appointment = userCourseAppointmentService.get(entity.getAppointmentId());
+        entity.setUserId(user.getId());
+        entity.setRecordId(appointment.getRecordId());
+        entity = userCourseAppointmentCommentService.add(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/course/appointment/comment/edit", method = RequestMethod.POST)
+    @ApiOperation(value = "预约评论编辑", notes = "预约评论编辑", httpMethod = "POST")
+    public Response<Boolean> editAppointmentComment(@RequestBody @Validated UserCourseAppointmentCommentDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        UserCourseAppointmentComment entity = Transform.dtoToEntity(dto);
+
+        UserCourseAppointmentComment in = userCourseAppointmentCommentService.get(dto.getId());
+        if (!in.getUserId().equals(user.getId())){
+            throw new ParameterException("记录不存在");
+        }
+        entity = userCourseAppointmentCommentService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/course/appointment/comment/delete", method = RequestMethod.DELETE)
+    @ApiOperation(value = "预约评论删除", notes = "预约评论删除", httpMethod = "DELETE")
+    public Response<Boolean> deleteAppointmentComment(int id)  {
+        User user = (User) shiroHelp.getLoginUser();
+
+        UserCourseAppointmentComment in = userCourseAppointmentCommentService.get(id);
+        if (!in.getUserId().equals(user.getId())){
+            throw new ParameterException("记录不存在");
+        }
+        userCourseAppointmentCommentService.delete(id);
+        return ResponseHelp.success(true);
     }
 
     @RequestMapping(value = "/course/suspend", method = RequestMethod.POST)
@@ -1729,11 +1860,11 @@ public class MyController {
 
     @RequestMapping(value = "/course/time", method = RequestMethod.GET)
     @ApiOperation(value = "时间表", notes = "时间表", httpMethod = "GET")
-    public Response<List<UserCourseTimeDto>> timeCourse(int id)  {
+    public Response<List<UserCourseTimeDto>> timeCourse(int recordId)  {
         User user = (User) shiroHelp.getLoginUser();
         List<UserCourseTimeDto> dtos = new ArrayList<>();
 
-        UserOrderRecord record = userOrderRecordService.get(id);
+        UserOrderRecord record = userOrderRecordService.get(recordId);
         if (record == null){
             throw new ParameterException("记录不存在");
         }
@@ -1759,7 +1890,7 @@ public class MyController {
         List<Long> tmpList = new ArrayList<>();
 
         // 获取听课记录
-        List<UserCourseRecord> userCourseRecordList = userCourseRecordService.allWithRecord(id);
+        List<UserCourseRecord> userCourseRecordList = userCourseRecordService.allWithRecord(recordId);
         tmpList.clear();
         for(UserCourseRecord userCourseRecord:userCourseRecordList){
             Date day = Tools.day(userCourseRecord.getCreateTime());
@@ -1778,7 +1909,7 @@ public class MyController {
         Collection courseNoIds = Transform.getIds(courseNoList, CourseNo.class, "id");
         List<PreviewAssign> previewAssignList = previewAssignService.listByCourseNos(courseId, courseNoIds);
         Collection assignIds = Transform.getIds(previewAssignList, PreviewAssign.class, "id");
-        List<UserPaper> userPaperList = userPaperService.listWithCourse(user.getId(), assignIds, id);
+        List<UserPaper> userPaperList = userPaperService.listWithCourse(user.getId(), assignIds, recordId);
         Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
         List<UserReport> userReportList = userReportService.listByPaper(paperIds);
         tmpList.clear();
@@ -1820,7 +1951,7 @@ public class MyController {
 
     @RequestMapping(value = "/course/comment/tips", method = RequestMethod.POST)
     @ApiOperation(value = "关闭评论提示提示", notes = "关闭评论提示提示", httpMethod = "POST")
-    public Response<Boolean> exportTips(@RequestBody @Validated RecordCommentTipsDto dto)  {
+    public Response<Boolean> closeCommentTips(@RequestBody @Validated RecordCommentTipsDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
         UserOrderRecord record = userOrderRecordService.get(dto.getRecordId());
         userOrderRecordService.edit(UserOrderRecord.builder()

+ 12 - 11
server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java

@@ -23,7 +23,7 @@ import com.qxgmat.dto.request.OrderConfirmDto;
 import com.qxgmat.dto.request.PayOrderDto;
 import com.qxgmat.dto.request.RecordAddDto;
 import com.qxgmat.dto.request.RecordUseDto;
-import com.qxgmat.dto.response.UserOrderPreDto;
+import com.qxgmat.dto.response.UserOrderDetailDto;
 import com.qxgmat.dto.response.UserOrderRecordListDto;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.extend.OrderFlowService;
@@ -82,7 +82,7 @@ public class OrderController {
 
     @RequestMapping(value = "/checkout/all", method = RequestMethod.GET)
     @ApiOperation(value = "全部购物车", notes = "全部购物车", httpMethod = "GET")
-    public Response<UserOrderPreDto> allCheckout(HttpServletRequest request) throws Exception {
+    public Response<UserOrderDetailDto> allCheckout(HttpServletRequest request) throws Exception {
         User user = (User) shiroHelp.getLoginUser();
         //
         List<UserOrderCheckout> list = userOrderCheckoutService.allByUser(user.getId(), 0);
@@ -110,7 +110,7 @@ public class OrderController {
 
     @RequestMapping(value = "/pay/confirm", method = RequestMethod.POST)
     @ApiOperation(value = "确认支付:创建订单", notes = "创建订单", httpMethod = "POST")
-    public Response<UserOrderPreDto> confirmPay(@RequestBody @Validated OrderConfirmDto dto, HttpServletRequest request) throws Exception {
+    public Response<UserOrderDetailDto> confirmPay(@RequestBody @Validated OrderConfirmDto dto, HttpServletRequest request) throws Exception {
         User user = (User) shiroHelp.getLoginUser();
         UserOrder order = orderFlowService.makeOrder(user.getId(), dto.getCourseId());
 
@@ -119,7 +119,7 @@ public class OrderController {
 
     @RequestMapping(value = "/pay/speed", method = RequestMethod.POST)
     @ApiOperation(value = "确认支付:单条记录创建订单", notes = "单条记录创建订单", httpMethod = "POST")
-    public Response<UserOrderPreDto> speedPay(@RequestBody @Validated RecordAddDto dto, HttpServletRequest request) throws Exception {
+    public Response<UserOrderDetailDto> speedPay(@RequestBody @Validated RecordAddDto dto, HttpServletRequest request) throws Exception {
         User user = (User) shiroHelp.getLoginUser();
         UserOrderCheckout checkout = Transform.dtoToEntity(dto);
         UserOrder order = orderFlowService.makeOrderWithSpeed(user.getId(), checkout);
@@ -232,17 +232,18 @@ public class OrderController {
 
     @RequestMapping(value = "/detail", method = RequestMethod.GET)
     @ApiOperation(value = "获取订单记录", notes = "获取订单记录", httpMethod = "GET")
-    public Response<UserOrderRecord> getOrder(
+    public Response<UserOrderDetailDto> getOrder(
             @RequestParam(required = true) Integer id
     )  {
         User user = (User) shiroHelp.getLoginUser();
-        UserOrderRecord record = userOrderRecordService.get(id);
-        if (!record.getUserId().equals(user.getId())){
+        UserOrder order = userOrderService.get(id);
+        if (!order.getUserId().equals(user.getId())){
             throw new ParameterException("记录不存在");
         }
-        // todo
+        List<UserOrderRecord> list = userOrderRecordService.allByUser(user.getId(), id);
+        List<UserOrderRecordExtendDto> dtos = Transform.convert(list, UserOrderRecordExtendDto.class);
 
-        return ResponseHelp.success(record);
+        return ResponseHelp.success(detail(user.getId(), order, dtos));
     }
 
     @RequestMapping(value = "/record/list", method = RequestMethod.GET)
@@ -324,8 +325,8 @@ public class OrderController {
      * @param list
      * @return
      */
-    public UserOrderPreDto detail(Integer userId, UserOrder order, List<UserOrderRecordExtendDto> list)  {
-        UserOrderPreDto dto = Transform.convert(order, UserOrderPreDto.class);
+    public UserOrderDetailDto detail(Integer userId, UserOrder order, List<UserOrderRecordExtendDto> list)  {
+        UserOrderDetailDto dto = Transform.convert(order, UserOrderDetailDto.class);
         if (list == null){
             list = Transform.convert(userOrderCheckoutService.allByUser(userId, order.getId()), UserOrderRecordExtendDto.class);
         }

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

@@ -0,0 +1,98 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserCourseAppointmentComment;
+
+
+@Dto(entity = UserCourseAppointmentComment.class)
+public class UserCourseAppointmentCommentDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private Integer recordId;
+
+    private Integer appointmentId;
+
+    private String type;
+
+    private Integer parentId;
+
+    private String content;
+
+    private String file;
+
+    private String name;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+
+    public Integer getAppointmentId() {
+        return appointmentId;
+    }
+
+    public void setAppointmentId(Integer appointmentId) {
+        this.appointmentId = appointmentId;
+    }
+
+    public Integer getParentId() {
+        return parentId;
+    }
+
+    public void setParentId(Integer parentId) {
+        this.parentId = parentId;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getFile() {
+        return file;
+    }
+
+    public void setFile(String file) {
+        this.file = file;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}

+ 57 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserCourseProgressExtendDto.java

@@ -0,0 +1,57 @@
+package com.qxgmat.dto.extend;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserCourseProgress;
+
+@Dto(entity = UserCourseProgress.class)
+public class UserCourseProgressExtendDto {
+    private Integer courseId;
+
+    private Integer courseNoId;
+
+    private Integer recordId;
+
+    private Integer progress;
+
+    private Integer times;
+
+    public Integer getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Integer courseId) {
+        this.courseId = courseId;
+    }
+
+    public Integer getCourseNoId() {
+        return courseNoId;
+    }
+
+    public void setCourseNoId(Integer courseNoId) {
+        this.courseNoId = courseNoId;
+    }
+
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+
+    public Integer getProgress() {
+        return progress;
+    }
+
+    public void setProgress(Integer progress) {
+        this.progress = progress;
+    }
+
+    public Integer getTimes() {
+        return times;
+    }
+
+    public void setTimes(Integer times) {
+        this.times = times;
+    }
+}

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

@@ -16,6 +16,10 @@ public class UserOrderRecordExtendDto {
 
     private String money;
 
+    private Integer expireDays;
+
+    private Integer useExpireDays;
+
     public String getProductType() {
         return productType;
     }
@@ -71,4 +75,20 @@ public class UserOrderRecordExtendDto {
     public void setMoney(String money) {
         this.money = money;
     }
+
+    public Integer getExpireDays() {
+        return expireDays;
+    }
+
+    public void setExpireDays(Integer expireDays) {
+        this.expireDays = expireDays;
+    }
+
+    public Integer getUseExpireDays() {
+        return useExpireDays;
+    }
+
+    public void setUseExpireDays(Integer useExpireDays) {
+        this.useExpireDays = useExpireDays;
+    }
 }

+ 77 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCourseAppointmentCommentDto.java

@@ -0,0 +1,77 @@
+package com.qxgmat.dto.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserCourseAppointmentComment;
+
+@Dto(entity = UserCourseAppointmentComment.class)
+public class UserCourseAppointmentCommentDto {
+    private Integer id;
+
+    private Integer recordId;
+
+    private Integer appointmentId;
+
+    private String type;
+
+    private Integer parentId;
+
+    private String content;
+
+    private String file;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(Integer recordId) {
+        this.recordId = recordId;
+    }
+
+    public Integer getAppointmentId() {
+        return appointmentId;
+    }
+
+    public void setAppointmentId(Integer appointmentId) {
+        this.appointmentId = appointmentId;
+    }
+
+    public Integer getParentId() {
+        return parentId;
+    }
+
+    public void setParentId(Integer parentId) {
+        this.parentId = parentId;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getFile() {
+        return file;
+    }
+
+    public void setFile(String file) {
+        this.file = file;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+}

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

@@ -0,0 +1,37 @@
+package com.qxgmat.dto.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserCourseAppointment;
+
+@Dto(entity = UserCourseAppointment.class)
+public class UserCourseAppointmentQuestionDto {
+    private Integer id;
+
+    private String questionFile;
+
+    private String questionFileName;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getQuestionFile() {
+        return questionFile;
+    }
+
+    public void setQuestionFile(String questionFile) {
+        this.questionFile = questionFile;
+    }
+
+    public String getQuestionFileName() {
+        return questionFileName;
+    }
+
+    public void setQuestionFileName(String questionFileName) {
+        this.questionFileName = questionFileName;
+    }
+}

+ 27 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCourseCCTalkNameDto.java

@@ -0,0 +1,27 @@
+package com.qxgmat.dto.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserOrderRecord;
+
+@Dto(entity = UserOrderRecord.class)
+public class UserCourseCCTalkNameDto {
+    private Integer id;
+
+    private String cctalkName;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getCctalkName() {
+        return cctalkName;
+    }
+
+    public void setCctalkName(String cctalkName) {
+        this.cctalkName = cctalkName;
+    }
+}

+ 41 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseDetailDto.java

@@ -1,6 +1,7 @@
 package com.qxgmat.dto.response;
 
 import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserCourseAppointmentComment;
 import com.qxgmat.data.dao.entity.UserOrderRecord;
 import com.qxgmat.dto.extend.*;
 
@@ -46,6 +47,10 @@ public class UserCourseDetailDto {
 
     private Integer courseAward;
 
+    private Integer expireDays;
+
+    private Integer useExpireDays;
+
     private Collection<CourseNoExtendDto> courseNos;
 
     private Integer currentNo;
@@ -64,8 +69,12 @@ public class UserCourseDetailDto {
 
     private Integer noteNumber;
 
+    private Collection<UserCourseProgressExtendDto> progress;
+
     private Collection<BasePaperExtendDto> papers;
 
+    private Collection<UserCourseAppointmentComment> comments;
+
     public Date getStartTime() {
         return startTime;
     }
@@ -289,4 +298,36 @@ public class UserCourseDetailDto {
     public void setCourseAward(Integer courseAward) {
         this.courseAward = courseAward;
     }
+
+    public Collection<UserCourseAppointmentComment> getComments() {
+        return comments;
+    }
+
+    public void setComments(Collection<UserCourseAppointmentComment> comments) {
+        this.comments = comments;
+    }
+
+    public Integer getExpireDays() {
+        return expireDays;
+    }
+
+    public void setExpireDays(Integer expireDays) {
+        this.expireDays = expireDays;
+    }
+
+    public Integer getUseExpireDays() {
+        return useExpireDays;
+    }
+
+    public void setUseExpireDays(Integer useExpireDays) {
+        this.useExpireDays = useExpireDays;
+    }
+
+    public Collection<UserCourseProgressExtendDto> getProgress() {
+        return progress;
+    }
+
+    public void setProgress(Collection<UserCourseProgressExtendDto> progress) {
+        this.progress = progress;
+    }
 }

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderPreDto.java

@@ -11,7 +11,7 @@ import com.qxgmat.dto.extend.UserOrderRecordExtendDto;
 import java.math.BigDecimal;
 import java.util.List;
 
-public class UserOrderPreDto {
+public class UserOrderDetailDto {
     private BigDecimal money;
 
     private BigDecimal originMoney;

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

@@ -63,6 +63,10 @@ public class UserOrderRecordListDto {
 
     private Date createTime;
 
+    private Integer expireDays;
+
+    private Integer useExpireDays;
+
     public Integer getId() {
         return id;
     }
@@ -262,4 +266,20 @@ public class UserOrderRecordListDto {
     public void setRecords(List<UserOrderRecordExtendDto> records) {
         this.records = records;
     }
+
+    public Integer getExpireDays() {
+        return expireDays;
+    }
+
+    public void setExpireDays(Integer expireDays) {
+        this.expireDays = expireDays;
+    }
+
+    public Integer getUseExpireDays() {
+        return useExpireDays;
+    }
+
+    public void setUseExpireDays(Integer useExpireDays) {
+        this.useExpireDays = useExpireDays;
+    }
 }

+ 13 - 1
server/gateway-api/src/main/java/com/qxgmat/service/UserNoteCourseService.java

@@ -22,6 +22,17 @@ public class UserNoteCourseService extends AbstractService {
     @Resource
     private UserNoteCourseMapper userNoteCourseMapper;
 
+    public List<UserNoteCourse> listByCourse(Number courseId){
+        Example example = new Example(UserNoteCourse.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("courseId", courseId)
+
+        );
+        example.orderBy("id").asc();
+        return select(userNoteCourseMapper, example);
+    }
+
     /**
      * 更新用户笔记:没有则添加
      * @param note
@@ -54,10 +65,11 @@ public class UserNoteCourseService extends AbstractService {
      */
     public Map<Object, Collection<UserNoteCourse>> groupByCourse(Collection courseIds){
         Map<Object, Collection<UserNoteCourse>> relationMap = new HashMap<>();
+        if(courseIds == null || courseIds.size() == 0) return relationMap;
         Example example = new Example(UserNoteCourse.class);
         example.and(
                 example.createCriteria()
-                        .andIn("courseIds", courseIds)
+                        .andIn("courseId", courseIds)
         );
         List<UserNoteCourse> nos =  select(userNoteCourseMapper, example);
         Collection<UserNoteCourse> list;

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

@@ -96,7 +96,7 @@ public class CourseExtendService {
         userOrderRecordService.edit(UserOrderRecord.builder()
                 .id(record.getId())
                 .restoreTime(new Date())
-                .useEndTime(Tools.addDate(record.getUseEndTime(), (int)(new Date().getTime() - record.getSuspendTime().getTime() / 86400000)))
+                .useEndTime(Tools.addDate(record.getUseEndTime(), (int)((new Date().getTime() - record.getSuspendTime().getTime()) / 86400000)))
                 .build());
     }
 

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

@@ -118,12 +118,19 @@ public class OrderFlowService {
                 checkout.setOriginMoney(mainCourse.getPrice().multiply(BigDecimal.valueOf(checkout.getNumber())));
                 int percent = toolsService.computeVsMoney(checkout);
                 checkout.setMoney(checkout.getOriginMoney().multiply(BigDecimal.valueOf(percent)).divide(BigDecimal.valueOf(100), BigDecimal.ROUND_HALF_UP));
+
+                // 设定有效期
+                checkout.setExpireDays(mainCourse.getExpireDays());
+                checkout.setUseExpireDays(courseExtendService.computeExpire(checkout.getNumber(), mainCourse));
                 return checkout;
             }else{
                 // 课程已添加,无需添加
                 if(tmp != null){
                     return null;
                 }
+                // 设定有效期信息
+                checkout.setExpireDays(mainCourse.getExpireDays());
+                checkout.setUseExpireDays(courseExtendService.computeExpire(mainCourse));
             }
 
             // 填充金额
@@ -142,7 +149,7 @@ public class OrderFlowService {
                 for(Course course : courseList){
                     originMoney = originMoney.add(course.getPrice());
                 }
-                // 加入套餐
+                // 加入套餐: 无需进入course_package的初始化流程
                 UserOrderCheckout packageCheckout = UserOrderCheckout.builder()
                         .productType(ProductType.COURSE_PACKAGE.key)
                         .productId(coursePackage.getId())
@@ -195,6 +202,7 @@ public class OrderFlowService {
             List<UserOrderCheckout> courseCheckout = userOrderCheckoutList.stream().filter((in)-> in.getProductType().equals(ProductType.COURSE.key) && in.getParentId() == 0).collect(Collectors.toList());
             for(UserOrderCheckout in : courseCheckout){
                 if (ids.contains(in.getProductId())){
+                    in.setParentId(checkout.getId());
                     // 转移该购物车记录
                     userOrderCheckoutService.edit(UserOrderCheckout.builder()
                             .id(in.getId())
@@ -205,14 +213,14 @@ public class OrderFlowService {
             }
             // 添加还未添加的课程记录
             for(Integer id : ids){
-                Course course = courseMap.get(id);
-                userOrderCheckoutService.add(UserOrderCheckout.builder()
+                UserOrderCheckout tmp = UserOrderCheckout.builder()
                         .parentId(checkout.getId())
                         .productType(ProductType.COURSE.key)
                         .productId(id)
-                        .money(course.getPrice())
-                        .originMoney(course.getPrice())
-                        .build());
+                        .build();
+                // 获取有效期和金额
+                initCheckoutCallback.get(ProductType.COURSE).callback(tmp, userOrderCheckoutList);
+                userOrderCheckoutService.add(tmp);
             }
             return null;
         }));
@@ -237,6 +245,16 @@ public class OrderFlowService {
             BigDecimal money = BigDecimal.valueOf(toolsService.getServicePrice(checkout.getService(), checkout.getParam()));
             checkout.setOriginMoney(money);
             ServiceKey serviceKey = ServiceKey.ValueOf(checkout.getService());
+
+            Integer useExpireDay = serviceKey.useExpireDay;
+            Integer expireDay = serviceKey.expireDay;
+            if(serviceKey == ServiceKey.VIP){
+                ServiceVipKey vipKey = ServiceVipKey.ValueOf(checkout.getParam());
+                useExpireDay = vipKey.useExpireDay;
+            }
+            checkout.setExpireDays(expireDay);
+            checkout.setUseExpireDays(useExpireDay);
+
             if (serviceKey == ServiceKey.TEXTBOOK){
                 // 是否存在半价机经券
                 User user = usersService.get(checkout.getId());
@@ -335,10 +353,11 @@ public class OrderFlowService {
             }
             originMoney = originMoney.add(videoMoney);
 
+            BigDecimal vsMoney = BigDecimal.valueOf(0);
             // 1v1课程优惠: 添加时计算
             List<UserOrderCheckout> vsCheckout = courseCheckout.stream().filter((checkout)-> courseMap.get(checkout.getProductId()).getCourseModule().equals(CourseModule.VS.key)).collect(Collectors.toList());
             for(UserOrderCheckout checkout: vsCheckout){
-                courseMoney = courseMoney.add(checkout.getMoney());
+                vsMoney  = vsMoney.add(checkout.getMoney());
                 money = money.add(checkout.getMoney());
                 originMoney = originMoney.add(checkout.getMoney());
                 vsNumber += checkout.getNumber();
@@ -354,15 +373,15 @@ public class OrderFlowService {
             }
 
             // 满金额处理
-            // 回复时长
-            JSONObject askInfo = toolsService.getAskTime(courseMoney);
+            // 回复时长: 总金额
+            JSONObject askInfo = toolsService.getAskTime(courseMoney.add(vsMoney));
             if(askInfo != null) order.setAskTime(askInfo.getIntValue("hour") * 3600);
 
-            // 1v1辅导
-            JSONObject vsInfo = toolsService.getVsNumber(courseMoney);
+            // 1v1辅导:视频课程金额
+            JSONObject vsVideoMoney = toolsService.getVsVideoMoney(courseMoney);
 
-            // 满课时处理
-            Boolean coach = toolsService.getCoach(vsNumber);
+            // 满课时处理:vs课时
+            JSONObject vsVsNumber = toolsService.getVsVsNumber(vsNumber);
 
             JSONArray gift = order.getGift();
             if (askInfo!=null && askInfo.getIntValue("money") > 0){
@@ -374,18 +393,22 @@ public class OrderFlowService {
                 info.put("message", toolsService.askTimeMessage().replace("{}", askInfo.getInteger("hour").toString()));
                 gift.add(info);
             }
-            if (vsInfo != null){
+            // vsVsNumber与vsVideoMoney
+            if(vsVsNumber != null){
                 JSONObject info = new JSONObject();
                 info.put("key", "vs");
-                info.put("money", vsInfo.getIntValue("money"));
-                info.put("number", vsInfo.getIntValue("number"));
-                info.put("message", toolsService.vsRecordMessage().replace("{}", vsInfo.getInteger("number").toString()));
+                info.put("from", "vsNumber");
+                info.put("number", vsVsNumber.getIntValue("number"));
+                info.put("value", vsVsNumber.getIntValue("value"));
+                info.put("message", toolsService.vsVsNumberMessage().replace("{}", vsVsNumber.getInteger("value").toString()));
                 gift.add(info);
-            }
-            if(coach){
+            }else if (vsVideoMoney != null){
                 JSONObject info = new JSONObject();
-                info.put("key", "coach");
-                info.put("message", toolsService.coachMessage());
+                info.put("key", "vs");
+                info.put("from", "videoMoney");
+                info.put("money", vsVideoMoney.getIntValue("money"));
+                info.put("value", vsVideoMoney.getIntValue("value"));
+                info.put("message", toolsService.vsVideoMoneyMessage().replace("{}", vsVideoMoney.getInteger("value").toString()));
                 gift.add(info);
             }
 
@@ -404,7 +427,7 @@ public class OrderFlowService {
             CourseModule courseModule = CourseModule.ValueOf(course.getCourseModule());
             // 小班课程不会存在记录,在线视频和1v1授课都有有效期
             Date startTime = new Date();
-            Date endTime = Tools.addDate(startTime, courseModule.expireDay);
+            Date endTime = Tools.addDate(startTime, record.getExpireDays());
             record.setStartTime(startTime);
             record.setEndTime(endTime);
             // 绑定订单的回复时长
@@ -490,7 +513,7 @@ public class OrderFlowService {
             }else{
                 ServiceKey serviceKey = ServiceKey.ValueOf(record.getService());
                 Date startTime = new Date();
-                Date endTime = Tools.addMonth(startTime, serviceKey.expireDay);
+                Date endTime = Tools.addMonth(startTime, record.getExpireDays());
                 record.setStartTime(startTime);
                 record.setEndTime(endTime);
                 // 如果使用半价优惠券,则扣除
@@ -511,19 +534,17 @@ public class OrderFlowService {
             record.setUseTime(time);
 
             Course course = courseService.get(record.getProductId());
-            Integer expireDay = 0;
+            Integer expireDay = record.getUseExpireDays();
             if (course.getCourseModule().equals(CourseModule.ONLINE.key)){
                 // 小班课程,无需更新有效时间
                 return record;
-            }else if (course.getCourseModule().equals(CourseModule.VS.key)){
-                // 根据课时数进行计算
-                expireDay = courseExtendService.computeExpire(record.getNumber(), course);
-            }else{
-                // 根据设置进行计算
-                expireDay = courseExtendService.computeExpire(course);
             }
             Date startTime = time;
-            Date endTime = Tools.addDate(startTime, expireDay);
+            Date endTime = null;
+            if(expireDay > 0){
+                // 设置结束有效期
+                endTime = Tools.addDate(startTime, expireDay);
+            }
             UserCourse userCourse = userCourseService.getCourseBase(record.getUserId(), record.getProductId());
             if(userCourse == null){
                 userCourse = UserCourse.builder()
@@ -547,7 +568,7 @@ public class OrderFlowService {
                 userCourse = userCourseService.edit(userCourse);
             }
             record.setUseStartTime(startTime);
-            record.setUseEndTime(endTime);
+            if (endTime!=null )record.setUseEndTime(endTime);
             return record;
         }));
         useRecordCallback.put(ProductType.COURSE_PACKAGE, (record->{
@@ -564,11 +585,7 @@ public class OrderFlowService {
             record.setUseTime(time);
 
             ServiceKey serviceKey = ServiceKey.ValueOf(record.getService());
-            Integer expireDay = serviceKey.useExpireDay;
-            if(serviceKey == ServiceKey.VIP){
-                ServiceVipKey vipKey = ServiceVipKey.ValueOf(record.getParam());
-                expireDay = vipKey.useExpireDay;
-            }
+            Integer expireDay = record.getUseExpireDays();
 
             Date startTime = time;
             Date endTime = null;
@@ -619,7 +636,7 @@ public class OrderFlowService {
             }
 
             record.setUseStartTime(startTime);
-            record.setUseEndTime(endTime);
+            if (endTime!=null )record.setUseEndTime(endTime);
             return record;
         }));
 
@@ -948,6 +965,8 @@ public class OrderFlowService {
                 .service(ServiceKey.VIP.key)
                 .param(ServiceVipKey.DAY7.key)
                 .source(RecordSource.INVITE.key)
+                .expireDays(0)
+                .useExpireDays(ServiceVipKey.DAY7.useExpireDay)
                 .build();
         // 虚拟order
         record = initRecordCallback.get(ProductType.SERVICE).callback(UserOrder.builder().build(), record);
@@ -964,6 +983,8 @@ public class OrderFlowService {
                 .service(ServiceKey.VIP.key)
                 .param(ServiceVipKey.DAY7.key)
                 .source(RecordSource.PREPARE.key)
+                .expireDays(0)
+                .useExpireDays(ServiceVipKey.DAY7.useExpireDay)
                 .build();
         // 虚拟order
         record = initRecordCallback.get(ProductType.SERVICE).callback(UserOrder.builder().build(), record);
@@ -980,6 +1001,8 @@ public class OrderFlowService {
                 .service(ServiceKey.VIP.key)
                 .param(ServiceVipKey.MONTH3.key)
                 .source(RecordSource.REAL.key)
+                .expireDays(0)
+                .useExpireDays(ServiceVipKey.MONTH3.useExpireDay)
                 .build();
         // 虚拟order
         record = initRecordCallback.get(ProductType.SERVICE).callback(UserOrder.builder().build(), record);

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

@@ -60,7 +60,7 @@ public class PreviewService extends AbstractService {
      * @param top
      * @return
      */
-    public Map<Object, Collection<UserPreviewPaperRelation>> groupByCourseId(Integer userId, Collection recordIds, Integer top){
+    public Map<Object, Collection<UserPreviewPaperRelation>> groupByRecordId(Integer userId, Collection recordIds, Integer top){
         Map<Object, Collection<UserPreviewPaperRelation>> relationMap = new HashMap<>();
         if(recordIds == null || recordIds.size() == 0) return relationMap;
         for(Object id : recordIds){

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

@@ -455,15 +455,15 @@ public class ToolsService {
     /**
      * @return
      */
-    public String vsRecordMessage(){
+    public String vsVideoMoneyMessage(){
         return "指定1v1辅导{}课时";
     }
 
     /**
      * @return
      */
-    public String coachMessage(){
-        return "课前1v1语音复习规划辅导";
+    public String vsVsNumberMessage(){
+        return "课前1v1辅导{}课时";
     }
     /**
      * 计算回复时长:{ask_time: [{"money":32,"hour":2}]}
@@ -496,15 +496,15 @@ public class ToolsService {
     }
 
     /**
-     * 计算赠送1v1课时:{vs_record: [{"money":32,"number":2}]}
+     * 计算赠送1v1课时:{vs_video_money: [{"money":32,"value":2}]}
      * @param totalMoney 课程总金额
      * @return
      */
-    public JSONObject getVsNumber(BigDecimal totalMoney){
+    public JSONObject getVsVideoMoney(BigDecimal totalMoney){
         Setting setting = settingService.getByKey(SettingKey.PROMOTE);
         JSONObject value = setting.getValue();
 
-        JSONArray settings = value.getJSONArray("vs_record");
+        JSONArray settings = value.getJSONArray("vs_video_money");
         int max = 0;
         int maxIndex = -1;
         for(int i = 0; i < settings.size(); i++){
@@ -525,20 +525,32 @@ public class ToolsService {
     }
 
     /**
-     * 计算赠送满课时辅导:{toach: {number: 2}}
+     * 计算赠送满课时辅导:{vs_vs_number: [{number: 2, value: 2}]}
      * @param vsNumber 总课时数
      * @return
      */
-    public Boolean getCoach(Integer vsNumber){
+    public JSONObject getVsVsNumber(Integer vsNumber){
         Setting setting = settingService.getByKey(SettingKey.PROMOTE);
         JSONObject value = setting.getValue();
 
-        JSONObject settings = value.getJSONObject("toach");
-        if (vsNumber >= settings.getIntValue("number")){
-            return true;
-        }else{
-            return false;
+        JSONArray settings = value.getJSONArray("vs_vs_number");
+        int max = 0;
+        int maxIndex = -1;
+        for(int i = 0; i < settings.size(); i++){
+            JSONObject o = settings.getJSONObject(i);
+            int number = o.getIntValue("number");
+            if(number < vsNumber){
+                if (number > max){
+                    max = number;
+                    maxIndex = i;
+                }
+            }
+        }
+        if (maxIndex >= 0){
+            JSONObject o = settings.getJSONObject(maxIndex);
+            return o;
         }
+        return null;
     }
 
     /**

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

@@ -40,6 +40,7 @@ public class CourseNoService extends AbstractService {
      */
     public Map<Object, Collection<CourseNo>> groupByCourseId(Collection courseIds){
         Map<Object, Collection<CourseNo>> relationMap = new HashMap<>();
+        if (courseIds == null || courseIds.size() == 0) return relationMap;
         Example example = new Example(CourseNo.class);
         example.and(
                 example.createCriteria()

+ 13 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserAskCourseService.java

@@ -80,6 +80,18 @@ public class UserAskCourseService extends AbstractService {
         return select(userAskCourseMapper, example);
     }
 
+    public List<UserAskCourse> listByRecord(Number recordId){
+        Example example = new Example(UserAskCourse.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("recordId", recordId)
+                        .andNotEqualTo("answerStatus", AskStatus.IGNORE.index)
+
+        );
+        example.orderBy("id").asc();
+        return select(userAskCourseMapper, example);
+    }
+
     /**
      * 根据列表顺序全部更新排序:从大到小
      * @param ids
@@ -110,6 +122,7 @@ public class UserAskCourseService extends AbstractService {
      */
     public Map<Object, Collection<UserAskCourse>> groupByRecordId(Collection recordIds){
         Map<Object, Collection<UserAskCourse>> relationMap = new HashMap<>();
+        if(recordIds == null || recordIds.size() == 0) return relationMap;
         Example example = new Example(UserAskCourse.class);
         example.and(
                 example.createCriteria()

+ 133 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseAppointmentCommentService.java

@@ -0,0 +1,133 @@
+package com.qxgmat.service.inline;
+
+import com.github.pagehelper.Page;
+import com.nuliji.tools.AbstractService;
+import com.nuliji.tools.exception.ParameterException;
+import com.nuliji.tools.exception.SystemException;
+import com.nuliji.tools.mybatis.Example;
+import com.qxgmat.data.dao.UserCourseAppointmentCommentMapper;
+import com.qxgmat.data.dao.entity.UserCourseAppointmentComment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+@Service
+public class UserCourseAppointmentCommentService extends AbstractService {
+    private static final Logger logger = LoggerFactory.getLogger(UserCourseAppointmentCommentService.class);
+
+    @Resource
+    private UserCourseAppointmentCommentMapper userCourseAppointmentCommentMapper;
+
+    public List<UserCourseAppointmentComment> allAdmin(Integer appointmentId, String type){
+        Example example = new Example(UserCourseAppointmentComment.class);
+        if (appointmentId != null)
+            example.and(
+                    example.createCriteria().andEqualTo("appointmentId", appointmentId)
+            );
+        if (type != null)
+            example.and(
+                    example.createCriteria().andEqualTo("type", type)
+            );
+        example.orderBy("id").asc();
+        return select(userCourseAppointmentCommentMapper, example);
+    }
+
+    /**
+     * 获取课程记录分组列表
+     * @param recordIds
+     * @return
+     */
+    public Map<Object, Collection<UserCourseAppointmentComment>> groupByRecordId(Collection recordIds){
+        Map<Object, Collection<UserCourseAppointmentComment>> relationMap = new HashMap<>();
+        if(recordIds == null || recordIds.size() == 0) return relationMap;
+        Example example = new Example(UserCourseAppointmentComment.class);
+        example.and(
+                example.createCriteria()
+                        .andIn("recordId", recordIds)
+        );
+        List<UserCourseAppointmentComment> nos =  select(userCourseAppointmentCommentMapper, example);
+        Collection<UserCourseAppointmentComment> list;
+        for(UserCourseAppointmentComment no : nos){
+            if (!relationMap.containsKey(no.getRecordId())){
+                list = new ArrayList<>();
+                relationMap.put(no.getRecordId(), list);
+            }else{
+                list = relationMap.get(no.getRecordId());
+            }
+            list.add(no);
+        }
+        return relationMap;
+    }
+
+    public List<UserCourseAppointmentComment> list(Collection recordIds){
+        Example example = new Example(UserCourseAppointmentComment.class);
+        if (recordIds != null)
+            example.and(
+                    example.createCriteria().andIn("recordId", recordIds)
+            );
+        example.orderBy("id").asc();
+        return select(userCourseAppointmentCommentMapper, example);
+    }
+
+    public List<UserCourseAppointmentComment> listByRecord(Integer recordId){
+        Example example = new Example(UserCourseAppointmentComment.class);
+        if (recordId != null)
+            example.and(
+                    example.createCriteria().andEqualTo("recordId", recordId)
+            );
+        example.orderBy("id").asc();
+        return select(userCourseAppointmentCommentMapper, example);
+    }
+
+    public UserCourseAppointmentComment add(UserCourseAppointmentComment ad){
+        int result = insert(userCourseAppointmentCommentMapper, ad);
+        ad = one(userCourseAppointmentCommentMapper, ad.getId());
+        if(ad == null){
+            throw new SystemException("记录添加失败");
+        }
+        return ad;
+    }
+
+    public UserCourseAppointmentComment edit(UserCourseAppointmentComment ad){
+        UserCourseAppointmentComment in = one(userCourseAppointmentCommentMapper, ad.getId());
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        int result = update(userCourseAppointmentCommentMapper, ad);
+        return ad;
+    }
+
+    public boolean delete(Number id){
+        UserCourseAppointmentComment in = one(userCourseAppointmentCommentMapper, id);
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        int result = delete(userCourseAppointmentCommentMapper, id);
+        return result > 0;
+    }
+
+    public UserCourseAppointmentComment get(Number id){
+        UserCourseAppointmentComment in = one(userCourseAppointmentCommentMapper, id);
+
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        return in;
+    }
+
+    public Page<UserCourseAppointmentComment> select(int page, int pageSize){
+        return select(userCourseAppointmentCommentMapper, page, pageSize);
+    }
+
+    public Page<UserCourseAppointmentComment> select(Integer[] ids){
+        return page(()->select(userCourseAppointmentCommentMapper, ids), 1, ids.length);
+    }
+
+    public List<UserCourseAppointmentComment> select(Collection ids){
+        return select(userCourseAppointmentCommentMapper, ids);
+    }
+
+}

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

@@ -86,6 +86,7 @@ public class UserCourseProgressService extends AbstractService {
      */
     public Map<Object, Collection<UserCourseProgress>> groupByRecordId(Collection recordIds){
         Map<Object, Collection<UserCourseProgress>> relationMap = new HashMap<>();
+        if (recordIds == null || recordIds.size() == 0) return relationMap;
         Example example = new Example(UserCourseProgress.class);
         example.and(
                 example.createCriteria()

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

@@ -62,6 +62,7 @@ public class UserCourseRecordService extends AbstractService {
      */
     public Map<Object, Collection<UserCourseRecord>> groupByRecordId(Collection recordIds){
         Map<Object, Collection<UserCourseRecord>> relationMap = new HashMap<>();
+        if (recordIds == null || recordIds.size() == 0) return relationMap;
         Example example = new Example(UserCourseRecord.class);
         example.and(
                 example.createCriteria()