Sfoglia il codice sorgente

Merge branch 'master' of www.gitinn.com:zaixianjiaoyu/sourcecode

KaysonCui 5 anni fa
parent
commit
439c1d9237
40 ha cambiato i file con 1749 aggiunte e 212 eliminazioni
  1. 3 0
      front/project/Constant.js
  2. 15 0
      front/project/admin/routes/ready/feedback/index.js
  3. 3 0
      front/project/admin/routes/ready/feedback/index.less
  4. 187 0
      front/project/admin/routes/ready/feedback/page.js
  5. 2 1
      front/project/admin/routes/ready/index.js
  6. 12 0
      front/project/admin/stores/user.js
  7. 5 6
      front/project/www/components/Other/index.js
  8. 9 3
      front/project/www/components/OtherModal/index.js
  9. 39 15
      front/project/www/components/Video/index.js
  10. 19 0
      front/project/www/components/Video/index.less
  11. 1 1
      front/project/www/routes/course/answer/page.js
  12. 30 33
      front/project/www/routes/course/dataDetail/page.js
  13. 2 2
      front/project/www/routes/course/detail/index.less
  14. 380 93
      front/project/www/routes/course/detail/page.js
  15. 13 4
      front/project/www/routes/course/main/page.js
  16. 4 4
      front/project/www/routes/course/online/index.less
  17. 7 3
      front/project/www/routes/course/online/page.js
  18. 38 28
      front/project/www/routes/course/vs/page.js
  19. 1 1
      front/project/www/routes/my/course/page.js
  20. 7 0
      server/data/src/main/java/com/qxgmat/data/dao/UserReadyRoomFeedbackMapper.java
  21. 282 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/UserReadyRoomFeedback.java
  22. 34 0
      server/data/src/main/java/com/qxgmat/data/dao/mapping/UserReadyRoomFeedbackMapper.xml
  23. 70 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  24. 75 3
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  25. 15 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  26. 77 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/extend/ReadyRoomExtendDto.java
  27. 27 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserReadyRoomFeedbackDto.java
  28. 27 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/request/UserTextbookFeedbackDto.java
  29. 22 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserFeedbackErrorInfoDto.java
  30. 101 0
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserReadyRoomFeedbackInfoDto.java
  31. 5 5
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserTextbookFeedbackInfoDto.java
  32. 28 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserReadyRoomFeedbackDto.java
  33. 72 4
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDetailDto.java
  34. 13 1
      server/gateway-api/src/main/java/com/qxgmat/service/UserNoteCourseService.java
  35. 0 2
      server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java
  36. 4 0
      server/gateway-api/src/main/java/com/qxgmat/service/extend/PreviewService.java
  37. 9 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserAskCourseService.java
  38. 10 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserCourseProgressService.java
  39. 100 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserReadyRoomFeedbackService.java
  40. 1 1
      server/gateway-api/src/main/resources/application.yml

+ 3 - 0
front/project/Constant.js

@@ -155,6 +155,9 @@ export const FaqChannel = [
   { label: '非CAT', value: 'base', parent: 'examination' },
   { label: '数学机经', value: 'textbook' },
   { label: '换库', value: 'library' },
+  { label: '换库知识', value: 'baselibrary', parent: 'library' },
+  { label: '机经知识', value: 'basetextbook', parent: 'library' },
+  { label: '千行机经', value: 'qxtextbook', parent: 'library' },
   { label: '课堂-课程', value: 'course' },
   { label: '首页', value: 'index', parent: 'course' },
   { label: '视频首页', value: 'video_index', parent: 'course' },

+ 15 - 0
front/project/admin/routes/ready/feedback/index.js

@@ -0,0 +1,15 @@
+import module from '../../module';
+import group from '../group';
+
+export default {
+  path: '/ready/feedback',
+  key: 'ready-feedback',
+  title: '考场补充',
+  needLogin: true,
+  module,
+  group,
+  index: true,
+  component() {
+    return import('./page');
+  },
+};

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

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

+ 187 - 0
front/project/admin/routes/ready/feedback/page.js

@@ -0,0 +1,187 @@
+import React from 'react';
+import { Modal, Button } from 'antd';
+import './index.less';
+import Page from '@src/containers/Page';
+import Block from '@src/components/Block';
+import FilterLayout from '@src/layouts/FilterLayout';
+import ActionLayout from '@src/layouts/ActionLayout';
+import TableLayout from '@src/layouts/TableLayout';
+import { getMap, formatDate } from '@src/services/Tools';
+import { asyncSMessage, asyncDelConfirm } from '@src/services/AsyncTools';
+import { FeedbackStatus } from '../../../../Constant';
+import { User } from '../../../stores/user';
+
+const FeedbackStatusMap = getMap(FeedbackStatus, 'value', 'label');
+export default class extends Page {
+  init() {
+    this.actionList = [{
+      key: 'handle',
+      type: 'danger',
+      name: '批量采纳',
+      needSelect: 1,
+    }, {
+      key: 'nohandle',
+      type: 'danger',
+      name: '批量不修改',
+      needSelect: 1,
+    }, {
+      key: 'ignore',
+      type: 'danger',
+      name: '批量忽略',
+      needSelect: 1,
+    }];
+    this.filterForm = [{
+      key: 'status',
+      type: 'select',
+      name: '处理状态',
+      allowClear: true,
+      select: FeedbackStatus,
+      number: true,
+      placeholder: '请选择',
+    }];
+    this.columns = [{
+      title: '考场',
+      dataIndex: 'room.title',
+    }, {
+      title: '提交时间',
+      dataIndex: 'createTime',
+      render: (text) => {
+        return formatDate(text);
+      },
+    }, {
+      title: '提交人',
+      dataIndex: 'user.nickname',
+    }, {
+      title: '处理状态',
+      dataIndex: 'status',
+      render: (text) => {
+        return FeedbackStatusMap[text] || text;
+      },
+    }, {
+      title: '操作',
+      dataIndex: 'handler',
+      render: (text, record) => {
+        return <div className="table-button">
+          {(
+            <a onClick={() => {
+              this.detailAction(record);
+            }}>查看</a>
+          )}
+        </div>;
+      },
+    }];
+  }
+
+  initData() {
+    User.listReadyRoomFeedback(this.state.search).then(result => {
+      this.setTableData(result.list, result.total);
+    });
+  }
+
+  detailAction(row) {
+    this.setState({ detail: row });
+  }
+
+  handleDetail() {
+    const { detail } = this.state;
+    asyncDelConfirm('采纳确认', '是否采纳选中记录?', () => {
+      return User.editReadyRoomFeedback({ id: detail.id, status: 1 }).then(() => {
+        asyncSMessage('操作成功!');
+        this.setState({ detail: null });
+        this.refresh();
+      });
+    });
+  }
+
+  nohandleDetail() {
+    const { detail } = this.state;
+    asyncDelConfirm('不处理确认', '是否不处理选中记录?', () => {
+      return User.editReadyRoomFeedback({ id: detail.id, status: 3 }).then(() => {
+        asyncSMessage('操作成功!');
+        this.setState({ detail: null });
+        this.refresh();
+      });
+    });
+  }
+
+  ignoreDetail() {
+    const { detail } = this.state;
+    asyncDelConfirm('忽略确认', '是否忽略选中记录?', () => {
+      return User.editReadyRoomFeedback({ id: detail.id, status: 2 }).then(() => {
+        asyncSMessage('操作成功!');
+        this.setState({ detail: null });
+        this.refresh();
+      });
+    });
+  }
+
+  handleAction() {
+    const { selectedKeys } = this.state;
+    asyncDelConfirm('采纳确认', '是否采纳选中记录?', () => {
+      return Promise.all(selectedKeys.map(row => User.editReadyRoomFeedback({ id: row, status: 1 }))).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
+    });
+  }
+
+  nohandleAction() {
+    const { selectedKeys } = this.state;
+    asyncDelConfirm('不修改确认', '是否不修改选中记录?', () => {
+      return Promise.all(selectedKeys.map(row => User.editReadyRoomFeedback({ id: row, status: 3 }))).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
+    });
+  }
+
+  ignoreAction() {
+    const { selectedKeys } = this.state;
+    asyncDelConfirm('忽略确认', '是否忽略选中记录?', () => {
+      return Promise.all(selectedKeys.map(row => User.editReadyRoomFeedback({ id: row, status: 2 }))).then(() => {
+        asyncSMessage('操作成功!');
+        this.refresh();
+      });
+    });
+  }
+
+  renderView() {
+    return <Block flex>
+      <FilterLayout
+        show
+        itemList={this.filterForm}
+        data={this.state.search}
+        onChange={data => {
+          this.search(data);
+        }} />
+      <ActionLayout
+        itemList={this.actionList}
+        selectedKeys={this.state.selectedKeys}
+        onAction={key => this.onAction(key)}
+      />
+      <TableLayout
+        select
+        columns={this.tableSort(this.columns)}
+        list={this.state.list}
+        pagination={this.state.page}
+        loading={this.props.core.loading}
+        onChange={(pagination, filters, sorter) => this.tableChange(pagination, filters, sorter)}
+        onSelect={(keys, rows) => this.tableSelect(keys, rows)}
+        selectedKeys={this.state.selectedKeys}
+      />
+      {this.state.detail && <Modal visible title='补充详情' footer={null} closable onCancel={() => {
+        this.setState({ detail: null });
+      }}>
+        <p>考场:{this.state.detail.room.title}</p>
+        <p>补充内容{this.state.detail.content}</p>
+        {!this.state.detail.status && <p><Button type="primary" onClick={() => {
+          this.handleDetail();
+        }}>采纳</Button><Button type="primary" onClick={() => {
+          this.nohandleDetail();
+        }}>无需修改</Button><Button type="ghost" onClick={() => {
+          this.ignoreDetail();
+        }}>忽略</Button></p>}
+      </Modal>}
+    </Block>;
+  }
+}

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

@@ -1,8 +1,9 @@
 import article from './article';
 import articleDetail from './articleDetail';
 import room from './room';
+import feedback from './feedback';
 import data from './data';
 import read from './read';
 import readDetail from './readDetail';
 
-export default [article, articleDetail, room, data, read, readDetail];
+export default [article, articleDetail, room, feedback, data, read, readDetail];

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

@@ -69,6 +69,18 @@ export default class UserStore extends BaseStore {
     return this.apiGet('/user/textbook_feedback/detail', params);
   }
 
+  listReadyRoomFeedback(params) {
+    return this.apiGet('/user/ready_room_feedback/list', params);
+  }
+
+  editReadyRoomFeedback(params) {
+    return this.apiPut('/user/ready_room_feedback/edit', params);
+  }
+
+  getReadyRoomFeedback(params) {
+    return this.apiGet('/user/ready_room_feedback/detail', params);
+  }
+
   listService(params) {
     return this.apiGet('/user/service/list', params);
   }

+ 5 - 6
front/project/www/components/Other/index.js

@@ -171,16 +171,15 @@ export class Contact extends Component {
 
 export class Comment extends Component {
   render() {
+    const { data } = this.props;
     return (
       <div className="comment-item">
-        <Assets className="m-r-1" src="" />
+        <Assets className="m-r-1" src={data.user ? data.user.avatar : data.avatar} />
         <div className="d-i-b">
-          <div className="t-1 t-s-18">王大锤</div>
-          <div className="t-3">2018-10-20</div>
-        </div>
-        <div className="t-1 t-s-18 m-t-1">
-          掌握了学习方法以后,再加上刻苦勤奋的练习,第一次考试就过关了。掌握了学习方法以后,再加上刻苦勤奋的练习,第一次考试就过关了掌握了学习方法以后,再加上刻苦勤奋的练习,第一次考试就过关了。
+          <div className="t-1 t-s-18">{data.user ? data.user.nickname : data.nickname}</div>
+          <div className="t-3">{formatDate(data.createTime, 'YYYY-MM-DD')}</div>
         </div>
+        <div className="t-1 t-s-18 m-t-1">{data.content}</div>
       </div>
     );
   }

+ 9 - 3
front/project/www/components/OtherModal/index.js

@@ -818,7 +818,10 @@ export class AskCourseModal extends Component {
         onCancel={() => this.onCancel()}
       >
         <div className="t-2 m-b-1 t-s-16">
-          针对<span className="t-4">课时{courseNo.no}</span>的 <Select value={data.position} theme="white" list={selectList} />
+          针对<span className="t-4">课时{courseNo.no}</span>的 <Select value={data.position} theme="white" list={selectList} onChange={(item) => {
+          data.position = item.key;
+          this.setState({ data });
+        }} />
           进行提问.
         </div>
         <div className="t-2 t-s-16">老师讲解的内容是:</div>
@@ -882,7 +885,7 @@ export class CourseNoteModal extends Component {
   }
 
   render() {
-    const { show, course = {}, courseNos = [] } = this.props;
+    const { show, course = {}, courseNos = [], noteMap = {} } = this.props;
     const { data } = this.state;
     return (
       <Modal
@@ -901,7 +904,10 @@ export class CourseNoteModal extends Component {
               key: row.id,
             };
           })} onChange={(item) => {
-            data.courseNoId = item.id;
+            if (data.courseNoId !== item.key) {
+              data.courseNoId = item.key;
+              data.content = noteMap[item.key] ? noteMap[item.key].content : '';
+            }
             this.setState({ data });
           }} />
         </div>

+ 39 - 15
front/project/www/components/Video/index.js

@@ -81,16 +81,38 @@ export default class Video extends Component {
     }
   }
 
+  refreshTimeUpdate() {
+    if (this.timeInterval) {
+      clearInterval(this.timeInterval);
+      this.timeInterval = null;
+    }
+    this.timeInterval = setInterval(() => {
+      const { onTimeUpdate } = this.props;
+      if (onTimeUpdate) onTimeUpdate(this.player.currentTime());
+    }, this.props.duration ? this.props.duration * 1000 : 1000);
+  }
+
   onPlay() {
     if (!this.ready) return;
+    const { onPlay } = this.props;
     this.player.play();
     this.setState({ playing: true });
+    if (onPlay) onPlay();
   }
 
-  onPuase() {
+  onPause() {
     if (!this.ready) return;
+    const { onPause } = this.props;
     this.player.pause();
     this.setState({ playing: false });
+    if (onPause) onPause();
+  }
+
+  onNext() {
+    const { onNext } = this.props;
+    this.player.pause();
+    this.setState({ playing: false });
+    if (onNext) onNext();
   }
 
   onSpeed(speed) {
@@ -112,7 +134,7 @@ export default class Video extends Component {
   }
 
   render() {
-    const { action = true, btnList = [], children, onAction } = this.props;
+    const { action = true, btnList = [], children, onAction, hideAction } = this.props;
     const { playing, fulling, id } = this.state;
     return (
       <div id={id} className={`video-item ${action ? 'action' : ''} ${fulling ? 'full' : ''}`}>
@@ -125,34 +147,36 @@ export default class Video extends Component {
           {!playing && <Assets className="play" name="play" onClick={() => this.onPlay()} />}
           {playing && <Assets className="stop" name="stop" onClick={() => this.onPuase()} />}
         </div>
-        <div className="video-bottom">
+        {!hideAction && <div className="video-bottom">
           <div className="progress" />
           {action && (
             <div className="action-bar">
               <div className="d-i-b m-r-1">
                 {!playing && <Assets name="play2" onClick={() => this.onPlay()} />}
-                {playing && <Assets name="stop2" onClick={() => this.onPuase()} />}
+                {playing && <Assets name="stop2" onClick={() => this.onPause()} />}
               </div>
               <div className="d-i-b m-r-1">
-                <Assets name="next2" />
+                <Assets name="next2" onClick={() => this.onNext()} />
               </div>
               <div className="flex-block" />
               {btnList.map(btn => {
                 if (btn.full && !fulling) return '';
+                if (!btn.show) return '';
                 return (
                   <div className="d-i-b m-r-1">
                     {btn.render ? (
-                      <div className="fix-btn d-i-b" onClick={() => onAction && onAction(btn.key)}>
+                      <div className="fix-btn d-i-b" onClick={() => {
+                        if (btn.pause) this.onPause();
+                        if (onAction) onAction(btn.key);
+                      }}>
                         {btn.render(btn.active)}
                       </div>
-                    ) : (
-                      <div
-                        className={`btn ${btn.active ? 'active' : ''}`}
-                        onClick={() => onAction && onAction(btn.key)}
-                      >
-                        {btn.title}
-                      </div>
-                    )}
+                    ) : (<div
+                      className={`btn ${btn.active ? 'active' : ''}`}
+                      onClick={() => onAction && onAction(btn.key)}
+                    >
+                      {btn.title}
+                    </div>)}
                   </div>
                 );
               })}
@@ -165,7 +189,7 @@ export default class Video extends Component {
               </div>
             </div>
           )}
-        </div>
+        </div>}
         {children}
       </div>
     );

+ 19 - 0
front/project/www/components/Video/index.less

@@ -6,6 +6,25 @@
   overflow: hidden;
   background: #3A3A3AFF;
 
+  .video-wrapper {
+
+    .vjs-loading-spinner {
+      display: none;
+    }
+
+    .vjs-big-play-button {
+      display: none;
+    }
+
+    .vjs-control-bar {
+      display: none;
+    }
+
+    .vjs-modal-dialog {
+      display: none;
+    }
+  }
+
   .video-bottom {
     position: absolute;
     bottom: 0;

+ 1 - 1
front/project/www/routes/course/answer/page.js

@@ -216,7 +216,7 @@ export default class extends Page {
                 )}
                 <div>
                   <div className="small-tag">提问</div>
-                  <div className="f-r t-2 t-s-12">{formatDate(item.createTime, 'YYYY-MM-DD HH:mm:ss')}</div>
+                  <div className="f-r t-2 t-s-12">{formatDate(item.createTime, 'YYYY-MM-DD HH:mm:ss')} <span hidden={tab === 'my'}>阅读 {item.viewNumber}</span></div>
                 </div>
                 <div className="desc">
                   <OpenText>{item.content}</OpenText>

+ 30 - 33
front/project/www/routes/course/dataDetail/page.js

@@ -5,6 +5,7 @@ import Assets from '@src/components/Assets';
 import { getMap, formatDate } from '@src/services/Tools';
 import Footer from '../../../components/Footer';
 import { Contact, AnswerCarousel, Comment } from '../../../components/Other';
+import { FaqModal, CommentModal, FinishModal } from '../../../components/OtherModal';
 import Tabs from '../../../components/Tabs';
 import Button from '../../../components/Button';
 import { User } from '../../../stores/user';
@@ -80,7 +81,7 @@ export default class extends Page {
   }
 
   renderView() {
-    const { base = {}, data = {}, dataStructMap = {} } = this.state;
+    const { base = {}, data = {}, dataStructMap = {}, showComment, showFinish, comment = {}, showFaq, faq = {} } = this.state;
     return (
       <div>
         <div className="top content t-8">
@@ -90,6 +91,18 @@ export default class extends Page {
         {this.renderDetail()}
         <Contact data={base.contact} />
         <Footer />
+        <CommentModal
+          show={showComment}
+          defaultData={comment}
+          onConfirm={() => this.setState({ showComment: false, showFinish: true })}
+          onCancel={() => this.setState({ showComment: false })}
+          onClose={() => this.setState({ showComment: false })}
+        />
+        <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false, showFinish: true })} />
+        <FinishModal
+          show={showFinish}
+          onConfirm={() => this.setState({ showFinish: false })}
+        />
       </div>
     );
   }
@@ -195,73 +208,57 @@ export default class extends Page {
   }
 
   renderTab1() {
+    const { data } = this.state;
     return (
       <div className="tab-layout">
-        <div className="tab-desc">
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-        </div>
+        <div className="tab-desc" dangerouslySetInnerHTML={{ __html: data.content }} />
       </div>
     );
   }
 
   renderTab2() {
+    const { data } = this.state;
     return (
       <div className="tab-layout">
-        <div className="tab-desc">
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-        </div>
+        <div className="tab-desc" dangerouslySetInnerHTML={{ __html: data.authorContent }} />
       </div>
     );
   }
 
   renderTab3() {
+    const { data } = this.state;
     return (
       <div className="tab-layout">
-        <div className="tab-desc">
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-        </div>
+        <div className="tab-desc" dangerouslySetInnerHTML={{ __html: data.methodContent }} />
       </div>
     );
   }
 
   renderTab4() {
+    const { faqs, data = {} } = this.state;
     return (
       <div className="tab-layout">
         <AnswerCarousel
           hideBtn
-          list={[
-            { answer: '123123', content: '12312312' },
-            { answer: '123123', content: '12312312' },
-            { answer: '123123', content: '12312312' },
-            { answer: '123123', content: '12312312' },
-          ]}
+          list={faqs}
+          onFaq={() => this.setState({ showFaq: true, faq: { channel: 'course_data', position: data.id } })}
         />
       </div>
     );
   }
 
   renderTab5() {
+    const { data, comments } = this.state;
     return (
       <div className="tab-layout">
-        <div className="m-b-1 t-r">
-          <Button width={100} radius>
+        {data.have && <div className="m-b-1 t-r">
+          <Button width={100} radius onClick={() => User.needLogin().then(() => this.setState({ showComment: true, comment: { channel: 'course_data', position: data.id } }))}>
             写评论
           </Button>
-        </div>
-        <Comment />
-        <Comment />
-        <Comment />
-        <Comment />
-        <Comment />
+        </div>}
+        {(comments || []).map(item => {
+          return <Comment data={item} />;
+        })}
       </div>
     );
   }

+ 2 - 2
front/project/www/routes/course/detail/index.less

@@ -149,11 +149,11 @@
         padding-left: 790px;
 
         .answer-layout {
-          height: 430px;
+          height: 480px;
         }
 
         .item-layout {
-          height: 480px;
+          height: 530px;
         }
       }
 

+ 380 - 93
front/project/www/routes/course/detail/page.js

@@ -1,42 +1,138 @@
 import React from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
+import { getMap, formatPercent, formatDate } from '@src/services/Tools';
 import Footer from '../../../components/Footer';
+import { FaqModal, CommentModal, FinishModal, CourseNoteModal, AskCourseModal } from '../../../components/OtherModal';
 import { Contact, AnswerCarousel, Comment } from '../../../components/Other';
 import Tabs from '../../../components/Tabs';
+import { Icon as GIcon } from '../../../components/Icon';
 import Button from '../../../components/Button';
+import IconButton from '../../../components/IconButton';
 import UserTable from '../../../components/UserTable';
 import ProgressText from '../../../components/ProgressText';
 import { OpenText } from '../../../components/Open';
 import Video from '../../../components/Video';
+import { Main } from '../../../stores/main';
+import { Course } from '../../../stores/course';
+import { User } from '../../../stores/user';
+import { Order } from '../../../stores/order';
+import { Question } from '../../../stores/question';
+import { My } from '../../../stores/my';
 
 export default class extends Page {
   initState() {
     this.columns = [
       {
-        key: '1',
         title: '学习内容',
+        key: 'title',
+        render: (text, record) => {
+          const { no } = this.state;
+          if (no === record.no) {
+            // 当前正在
+          }
+          return `课时 ${record.no}: ${text}`;
+        },
       },
       {
-        key: '2',
         title: '预习作业',
+        key: 'paper',
+        render: text => {
+          text = text || {};
+          const progress = text.report ? formatPercent(text.report.userNumber, text.report.questionNumber) : 0;
+          const times = text.paper ? text.paper.times : 0;
+          return (
+            <div>
+              <div className="v-a-m d-i-b">
+                <ProgressText width={50} size="small" times={times} progress={progress} unit="次" />
+              </div>
+              {!text.report && (
+                <IconButton
+                  className="m-l-2"
+                  type="start"
+                  tip="Start"
+                  onClick={() => {
+                    User.needLogin().then(() => {
+                      Question.startLink('preview', text);
+                    });
+                  }}
+                />
+              )}
+              {text.report && !text.report.isFinish && (
+                <IconButton
+                  className="m-l-2"
+                  type="continue"
+                  tip="Continue"
+                  onClick={() => {
+                    User.needLogin().then(() => {
+                      Question.continueLink('preview', text);
+                    });
+                  }}
+                />
+              )}
+              {text.report && !!text.report.isFinish && (
+                <IconButton
+                  className="m-l-2"
+                  type="restart"
+                  tip="Restart"
+                  onClick={() => {
+                    User.needLogin().then(() => {
+                      Question.restart('preview', text);
+                    });
+                  }}
+                />
+              )}
+              {text.report && !!text.report.isFinish && (
+                <IconButton
+                  className="m-l-5"
+                  type="report"
+                  tip="Report"
+                  onClick={() => {
+                    User.needLogin().then(() => {
+                      Question.reportLink('preview', text);
+                    });
+                  }}
+                />
+              )}
+            </div>
+          );
+        },
       },
       {
-        key: '3',
-        title: '进展',
+        title: '进度',
+        key: 'progress',
+        render: (text, record) => {
+          const { paper = {} } = record;
+          return `${paper.paper && paper.paper.times > 0 ? `${paper.paper.times}次+` : ''}${
+            paper.report ? formatPercent(paper.report.userNumber, paper.report.questionNumber, false) : '0%'}`;
+        },
       },
       {
-        key: '4',
         title: '最近学习',
+        key: 'lastTime',
+        render: (text, record) => {
+          const { paper = {} } = record;
+          return paper.report && formatDate(paper.report.updateTime, 'YYYY-MM-DD HH:mm:ss');
+        },
       },
       {
-        key: '5',
         title: '笔记',
+        key: 'note',
+        render: (text, record) => {
+          return <GIcon name="note" active={record.note} />;
+        },
       },
       {
-        key: '6',
         title: '问答',
+        key: 'ask',
+        render: (text, record) => {
+          return (
+            <Link to={`/course/answer/${record.courseId}?tab=my&courseNoId=${record.id}`}>{`${record.answerNumber ||
+              0}/${record.askNumber || 0}`}</Link>
+          );
+        },
       },
     ];
     return {
@@ -45,11 +141,97 @@ export default class extends Page {
       key: '1',
       add: false,
       list: [{ key: '1' }, { key: '2' }, { key: '3' }],
-      progress: 0,
-      data: { title: '语法SC系统授课—课时10:逻辑语义解题专题讲解(1)' },
+      data: {},
+      position: 0,
     };
   }
 
+  init() {
+    Main.dataStruct().then(result => {
+      const dataStructSelect = result.map(row => {
+        return {
+          title: `${row.titleZh}${row.titleEn}`,
+          key: row.id,
+        };
+      });
+      const dataStructMap = getMap(dataStructSelect, 'key');
+      this.setState({ dataStructSelect, dataStructMap });
+    });
+    Main.getBase().then(result => {
+      this.setState({ base: result });
+    });
+  }
+
+  formatRecord(row) {
+    row.paperMap = {};
+    if (row.papers) {
+      row.papers.forEach(paper => {
+        if (paper.courseNo) row.paperMap[paper.courseNo] = paper;
+      });
+    }
+    row.progressMap = {};
+    if (row.progress) {
+      row.progress.forEach(progress => {
+        row.progressMap[progress.courseNoId] = progress;
+      });
+    }
+
+    row.courseNoMap = {};
+    row.courseTime = 0;
+    if (row.courseNos) {
+      row.courseNos.forEach(no => {
+        row.courseNoMap[no.id] = no;
+        row.courseTime += no.time;
+        no.paper = row.paperMap[no.id];
+        no.progress = row.progressMap[no.id];
+      });
+    }
+    if (row.currentNo) {
+      row.currentCourseNo = row.courseNoMap[row.currentNo];
+    } else {
+      row.currentNo = 0;
+    }
+    return row;
+  }
+
+  initData() {
+    const { id } = this.params;
+    Course.detail(id).then(result => {
+      result = this.formatRecord(result);
+      this.setState({ data: result });
+      // 选择课时
+      if (this.state.search.no) {
+        this.onChangeItem(this.state.search.no);
+      } else {
+        this.onChangeItem(1);
+      }
+      this.refreshNote();
+    });
+  }
+
+  refreshAsk(position) {
+    const { id } = this.params;
+    const { item } = this.state;
+    Course.listAsk(Object.assign({ page: 1, size: 1000, courseId: id, courseNoId: item.id, position })).then(result => {
+      this.setState({ asks: result.list });
+    });
+  }
+
+  refreshNote() {
+    const { id } = this.params;
+    const { data } = this.state;
+    if (!data.have) return;
+    My.listCourseNote({ courseId: id, page: 1, size: data.courseNos.length })
+      .then((result) => {
+        this.noteMap = getMap(result.list, 'courseNoId');
+        data.courseNos.forEach((row) => {
+          const note = this.noteMap[row.id];
+          if (note) row.note = true;
+        });
+        this.setState({ data });
+      });
+  }
+
   onChangeRightTab(rightTab) {
     this.setState({ rightTab });
   }
@@ -59,16 +241,77 @@ export default class extends Page {
   }
 
   onChangeItem(key) {
-    this.setState({ key });
+    key = Number(key);
+    this.changeQuery({ no: key });
+    this.setState({ no: key });
+    const { data, item } = this.state;
+    const index = key - 1;
+    const timelineSelect = [];
+    const current = data.courseNos[index];
+    if (current) {
+      const max = current.time;
+      let start = 0;
+      let end = start + 5;
+      while (start < max) {
+        timelineSelect.push({
+          title: `${start} - ${end}min`,
+          key: `${start}`,
+        });
+        start += 5;
+        end = Math.min(start + 5, max);
+      }
+    }
+    // 切换播放,记录进度
+    if (item) {
+      this.updateProgress(item.id, this.lastSecond, item.time, true);
+    }
+    this.setState({ item: current, timelineSelect });
+  }
+
+  onTimeUpdate(second) {
+    const { position, item, data } = this.state;
+    if (!data.have) {
+      // 如果是试用,则按秒数增加
+      second += item.startTrail * 60;
+    }
+    const minute = second / 60;
+    const nowPosition = (minute / 5) * 5;
+    if (nowPosition !== position) {
+      this.refreshAsk(position);
+      this.setState({ position: nowPosition });
+      // 定时更新进度
+      this.updateProgress(item.id, second, item.time);
+    }
+    this.lastSecond = second;
+  }
+
+  playVideo() {
+    // 开始计时
+    this.lastTime = new Date();
+  }
+
+  pauseVideo() {
+    // 停止计时
+    const now = new Date();
+    this.time += (now.getTime() - this.lastTime.getTime()) / 1000;
+    this.lastTime = null;
+  }
+
+  next() {
+    const { data, item } = this.state;
+    if (data.courseNos.length === item.no) {
+      return;
+    }
+    this.onChangeItem(item.no + 1);
   }
 
   onVideoAction(key) {
-    const { rightTab, showTab, showQuestion, showNote } = this.state;
+    const { rightTab, showTab, showAsk, showNote, item } = this.state;
     switch (key) {
-      case 'question':
-        return this.setState({ showQuestion: !showQuestion });
+      case 'ask':
+        return this.setState({ showAsk: !showAsk });
       case 'note':
-        return this.setState({ showNote: !showNote });
+        return this.setState({ showNote: !showNote, note: this.noteMap ? this.noteMap[item.id] || {} : {} });
       case 'answer':
         return this.setState({ showTab: rightTab === '1' ? !showTab : true, rightTab: '1' });
       case 'list':
@@ -78,42 +321,94 @@ export default class extends Page {
     }
   }
 
+  updateProgress(courseNoId, currentTime, totalTime, record) {
+    if (!this.lastTime) return;
+    const { id } = this.params;
+    const now = new Date();
+    this.time += (now.getTime() - this.lastTime.getTime()) / 1000;
+    this.lastTime = now;
+    const progress = formatPercent(currentTime, totalTime);
+    if (record || this.time > 600) {
+      // 最长5分钟记录一次
+      Course.noProgress(id, courseNoId, progress, this.time, courseNoId);
+      this.time = 0;
+    } else {
+      Course.noProgress(id, courseNoId, progress, null, null);
+    }
+  }
+
+  buy() {
+    const { data } = this.props;
+    User.needLogin().then(() => {
+      Order.speedPay({ productType: 'course', productId: data.id }).then(result => {
+        User.needPay(result).then(() => {
+          this.refresh();
+        });
+      });
+    });
+  }
+
+  add() {
+    const { data } = this.props;
+    User.needLogin().then(() => {
+      Order.addCheckout({ productType: 'course', productId: data.id }).then(() => {
+        this.setState({ add: true });
+      });
+    });
+  }
+
+  viewAsk(id) {
+    Course.askView(id);
+  }
+
+  setVideo(video) {
+    this.video = video;
+  }
+
   renderView() {
-    const { base = {}, data = {}, add, progress, rightTab, showTab, showQuestion, showNote } = this.state;
+    const { base = {}, data = {}, item = {}, add, progress, rightTab, showTab, showAsk, showNote, dataStructMap = {}, showComment, comment = {}, showFaq, faq = {}, showFinish, note = {}, ask = {}, timelineSelect = [] } = this.state;
+    const { courseNos = [] } = data;
     return (
       <div>
         <div className="top content t-8">
-          千行课堂 > 全部套餐 > {data.title} > <span className="t-1">课程详情</span>
+          千行课堂 > 全部课程 > {data.parentStructId > 0 ? `${(dataStructMap[data.parentStructId] || {}).title} >` : ''}{' '}
+          {(dataStructMap[data.structId] || {}).title} > {data.title} > <span className="t-1">课程详情</span>
         </div>
         <div className="center content">
           <div className="t-1 t-s-20">
             {data.title}
             <div className="action f-r">
-              <Button className="m-r-1" radius size="lager" onClick={() => this.buy()}>
+              {!data.have && <Button className="m-r-1" radius size="lager" onClick={() => this.buy()}>
                 立即购买
-              </Button>
-              <Button theme="default" radius size="lager" disabled={data.add || add} onClick={() => this.add()}>
+              </Button>}
+              {!data.have && <Button theme="default" radius size="lager" disabled={data.add || add} onClick={() => this.add()}>
                 <Assets name="add" />
-              </Button>
+              </Button>}
+              {data.have && <Button className="m-r-1" radius size="lager" onClick={() => linkTo('/my/course')}>
+                我的课程
+              </Button>}
             </div>
           </div>
-          <div className="t-2 m-b-1">授课老师:李奕都</div>
+          <div className="t-2 m-b-1">授课老师:{data.teacher}</div>
           <div className="detail">
             <div className="left">
-              <div hidden={progress === 0} className="left-top">
+              {data.have && <div hidden={(item.paper && item.paper.times > 0)} className="left-top">
                 <span className="d-i-b m-r-1">预习作业</span>
                 <span className="d-i-b m-r-2">
-                  <ProgressText width={480} size="small" progress={progress} />
+                  <ProgressText width={480} size="small" progress={item.report ? formatPercent(item.report.userNumber, item.report.questionNumber) : 0} />
                 </span>
-                <Button className="f-r" radius>
+                <Button className="f-r" radius onClick={() => (item.report ? Question.continueLink('preview', item) : Question.startLink('preview', item))}>
                   做题
                 </Button>
-              </div>
+              </div>}
               <div className="video-layout">
-                <Video
-                  src="/01.mp4"
+                {item && <Video
+                  key={item.id}
+                  src={item.resource}
+                  duration={10}
+                  ref={ref => this.setVideo(ref)}
                   btnList={[
-                    { title: '提问', key: 'question', active: showQuestion },
+                    { title: '提问', key: 'ask', show: data.have, active: showAsk, pause: true },
                     {
                       key: 'answer',
                       render(active) {
@@ -122,10 +417,14 @@ export default class extends Page {
                       full: true,
                       active: showTab && rightTab === '1',
                     },
-                    { title: '笔记', key: 'note', active: showNote },
+                    { title: '笔记', key: 'note', show: data.have, active: showNote, pause: true },
                     { title: '课表', key: 'list', full: true, active: showTab && rightTab === '2' },
                   ]}
+                  onPlay={() => this.playVideo()}
+                  onPause={() => this.pauseVideo()}
+                  onNext={() => this.next()}
                   onAction={key => this.onVideoAction(key)}
+                  onTimeUpdate={time => this.onTimeUpdate(time)}
                   onFullChange={() => this.setState({ showTab: true, rightTab: '1' })}
                 >
                   <div hidden={!showTab} className="video-fixed tab-warpper">
@@ -139,7 +438,7 @@ export default class extends Page {
                     />
                     <div className="tab-body">{this[`renderRightTab${rightTab}`]()}</div>
                   </div>
-                </Video>
+                </Video>}
               </div>
             </div>
             <div className={`right ${progress > 0 ? 'progress' : ''}  tab-warpper`}>
@@ -154,27 +453,44 @@ export default class extends Page {
               <div className="tab-body">{this[`renderRightTab${rightTab}`]()}</div>
             </div>
           </div>
-          <UserTable columns={this.columns} />
+          {data.have && <UserTable columns={this.columns} list={courseNos} />}
         </div>
-        <div className="bottom">
+        <div hidden={data.have} className="bottom">
           <div className="content">{this.renderTab()}</div>
         </div>
         <Contact data={base.contact} />
         <Footer />
-      </div>
+        <AskCourseModal show={showAsk} defaultData={ask} course={data} courseNo={item} selectList={timelineSelect} onConfirm={() => this.setState({ showAsk: false })} onCancel={() => this.setState({ showAsk: false })} />
+        <CourseNoteModal show={showNote} defaultData={note} course={data} courseNos={courseNos} noteMap={this.noteMap} onConfirm={() => {
+          this.setState({ showNote: false });
+          this.refreshNote();
+        }} onCancel={() => this.setState({ showNote: false })} />
+        <CommentModal
+          show={showComment}
+          defaultData={comment}
+          onConfirm={() => this.setState({ showComment: false, showFinish: true })}
+          onCancel={() => this.setState({ showComment: false })}
+          onClose={() => this.setState({ showComment: false })}
+        />
+        <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false, showFinish: true })} />
+        <FinishModal
+          show={showFinish}
+          onConfirm={() => this.setState({ showFinish: false })}
+        />,
+      </div >
     );
   }
 
   renderRightTab1() {
-    const { list = [] } = this.state;
+    const { asks = [], data = {}, position } = this.state;
     return [
       <div className="all-answer">
         <span className="d-i-b b m-r-5" />
-        <span className="d-i-b t-6">35:00 ~ 40:00</span>
-        <span className="f-r d-i-b t-4 c-p">全部问答 ></span>
+        <span className="d-i-b t-6">{position}:00~{position + 5}:00</span>
+        <a className="f-r d-i-b t-4 c-p" href={`/course/answer/${data.id}`} target="_blank">全部问答 ></a>
       </div>,
       <div className="answer-layout">
-        {list.map(item => {
+        {asks.map(item => {
           return (
             <div className="answer-item">
               <div>
@@ -190,7 +506,7 @@ export default class extends Page {
               )}
               {item.answerStatus > 0 && (
                 <div className="desc">
-                  <OpenText>{item.answer}</OpenText>
+                  <OpenText onOpen={() => this.viewAsk(item.id)}>{item.answer}</OpenText>
                 </div>
               )}
             </div>
@@ -201,14 +517,15 @@ export default class extends Page {
   }
 
   renderRightTab2() {
-    const { list = [], key } = this.state;
+    const { data = {}, no } = this.state;
+    const { courseNos = [] } = data;
     return (
       <div className="item-layout">
-        {list.map(item => {
+        {courseNos.map(item => {
           return (
-            <div className={`item ${item.key === key ? 'active' : ''}`} onClick={() => this.onChangeItem(item.key)}>
-              <span className="t-1">课时1</span>
-              <span className="t-2">解读句子结构</span>
+            <div className={`item ${item.no === no ? 'active' : ''}`} onClick={() => this.onChangeItem(item.no)}>
+              <span className="t-1">课时{item.no}</span>
+              <span className="t-2">{item.title}</span>
             </div>
           );
         })}
@@ -242,49 +559,26 @@ export default class extends Page {
   }
 
   renderTab1() {
+    const { data = {} } = this.state;
     return (
       <div className="tab-layout">
         <div className="tab-title">老师资历</div>
-        <div className="tab-desc">
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-        </div>
+        <div className="tab-desc" dangerouslySetInnerHTML={{ __html: data.teacherContent }} />
         <div className="tab-title">基本参数</div>
-        <div className="tab-desc">
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-        </div>
+        <div className="tab-desc" dangerouslySetInnerHTML={{ __html: data.baseContent }} />
         <div className="tab-title">授课重点</div>
-        <div className="tab-desc">
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-        </div>
+        <div className="tab-desc" dangerouslySetInnerHTML={{ __html: data.pointContent }} />
         <div className="tab-title">适合人群</div>
-        <div className="tab-desc">
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-        </div>
+        <div className="tab-desc" dangerouslySetInnerHTML={{ __html: data.crowdContent }} />
       </div>
     );
   }
 
   renderTab2() {
+    const { data } = this.state;
     return (
       <div className="tab-layout">
-        <div className="tab-desc">
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-        </div>
+        <div className="tab-desc" dangerouslySetInnerHTML={{ __html: data.syllabusContent }} />
       </div>
     );
   }
@@ -306,47 +600,40 @@ export default class extends Page {
   }
 
   renderTab4() {
+    const { faqs, data = {}, showFaq, faq } = this.state;
     return (
       <div className="tab-layout">
         <AnswerCarousel
           hideBtn
-          list={[
-            { answer: '123123', content: '12312312' },
-            { answer: '123123', content: '12312312' },
-            { answer: '123123', content: '12312312' },
-            { answer: '123123', content: '12312312' },
-          ]}
+          list={faqs}
+          onFaq={() => this.setState({ showFaq: true, faq: { channel: 'course-video', position: data.id } })}
         />
+        <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false })} />
       </div>
     );
   }
 
   renderTab5() {
+    const { data = {} } = this.state;
     return (
       <div className="tab-layout">
-        <div className="tab-desc">
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
-          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
-        </div>
+        <div className="tab-desc" dangerouslySetInnerHTML={{ __html: data.promoteContent }} />
       </div>
     );
   }
 
   renderTab6() {
+    const { data = {}, comments = [] } = this.state;
     return (
       <div className="tab-layout">
-        <div className="m-b-1 t-r">
-          <Button width={100} radius>
+        {data.have && <div className="m-b-1 t-r">
+          <Button width={100} radius onClick={() => User.needLogin().then(() => this.setState({ showComment: true, comment: { channel: 'course-video', position: data.id } }))}>
             写评论
           </Button>
-        </div>
-        <Comment />
-        <Comment />
-        <Comment />
-        <Comment />
-        <Comment />
+        </div>}
+        {(comments || []).map(item => {
+          return <Comment data={item} />;
+        })}
       </div>
     );
   }

+ 13 - 4
front/project/www/routes/course/main/page.js

@@ -4,7 +4,7 @@ import Assets from '@src/components/Assets';
 import Page from '@src/containers/Page';
 import { getMap } from '@src/services/Tools';
 import Footer from '../../../components/Footer';
-import { FaqModal } from '../../../components/OtherModal';
+import { FaqModal, FinishModal } from '../../../components/OtherModal';
 import { CommentFalls, AnswerCarousel, Consultation, Contact } from '../../../components/Other';
 import Button from '../../../components/Button';
 import { User } from '../../../stores/user';
@@ -12,6 +12,7 @@ import { Main } from '../../../stores/main';
 import { Course } from '../../../stores/course';
 import { ServiceKey, ServiceParamMap } from '../../../../Constant';
 import { Order } from '../../../stores/order';
+import Video from '../../../components/Video';
 
 export default class extends Page {
   initState() {
@@ -40,7 +41,7 @@ export default class extends Page {
   }
 
   renderView() {
-    const { courseIndex = {}, base = {}, packages = [], faqs = [], comments = [], showFaq, faq = {} } = this.state;
+    const { courseIndex = {}, base = {}, packages = [], faqs = [], comments = [], showFaq, faq = {}, showFinish } = this.state;
     return (
       <div>
         <div className="block-1">
@@ -61,11 +62,15 @@ export default class extends Page {
           <div className="main-title">找到你的Style</div>
           <div className="video-list">
             <div className="video-item">
-              <Assets width={70} height={70} name="play" className="play" src={courseIndex.onlineVideo} />
+              <div style={{ width: 70, height: 70 }}>
+                <Video src={courseIndex.onlineVideo} hideAction />
+              </div>
               <div className="name" onClick={() => linkTo('/course/online')}>在线课程 ></div>
             </div>
             <div className="video-item">
-              <Assets width={70} height={70} name="play" className="play" src={courseIndex.vsVideo} />
+              <div style={{ width: 70, height: 70 }}>
+                <Video src={courseIndex.vsVideo} hideAction />
+              </div>
               <div className="name" onClick={() => linkTo('/course/vs')}>1v1私教 ></div>
             </div>
           </div>
@@ -145,6 +150,10 @@ export default class extends Page {
         <Contact data={base.contact} />
         <Footer />
         <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false })} />
+        <FinishModal
+          show={showFinish}
+          onConfirm={() => this.setState({ showFinish: false })}
+        />
       </div >
     );
   }

+ 4 - 4
front/project/www/routes/course/online/index.less

@@ -35,8 +35,8 @@
 
       .single-item {
         margin-bottom: 20px;
-        margin-left: 20px;
-        margin-right: 20px;
+        margin-left: 10px;
+        margin-right: 10px;
       }
     }
 
@@ -47,8 +47,8 @@
 
       .package-item {
         margin-bottom: 20px;
-        margin-left: 20px;
-        margin-right: 20px;
+        margin-left: 10px;
+        margin-right: 10px;
       }
     }
   }

+ 7 - 3
front/project/www/routes/course/online/page.js

@@ -4,7 +4,7 @@ import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
 import { getMap, formatTreeData } from '@src/services/Tools';
 import Footer from '../../../components/Footer';
-import { FaqModal } from '../../../components/OtherModal';
+import { FaqModal, FinishModal } from '../../../components/OtherModal';
 import { CommentFalls, AnswerCarousel, Consultation, Contact } from '../../../components/Other';
 import Tabs from '../../../components/Tabs';
 import Filter from '../../../components/Filter';
@@ -124,7 +124,7 @@ export default class extends Page {
 
   renderView() {
     const { number } = this.props.order;
-    const { tab, promote = {}, base = {}, comments, faqs, showFaq, faq } = this.state;
+    const { tab, promote = {}, base = {}, comments, faqs, showFaq, faq, showFinish } = this.state;
     return (
       <div>
         <div className="top content">
@@ -156,7 +156,11 @@ export default class extends Page {
         <AnswerCarousel list={faqs} onFaq={() => this.setState({ showFaq: true, faq: { channel: tab === 'single' ? 'course-video_index' : 'course-package_index' } })} />
         <Contact data={base.contact} />
         <Footer />
-        <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false })} />
+        <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false, showFinish: true })} />
+        <FinishModal
+          show={showFinish}
+          onConfirm={() => this.setState({ showFinish: false })}
+        />
       </div>
     );
   }

+ 38 - 28
front/project/www/routes/course/vs/page.js

@@ -4,6 +4,7 @@ import { Icon } from 'antd';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
 import Footer from '../../../components/Footer';
+import { FaqModal, CommentModal, FinishModal } from '../../../components/OtherModal';
 import { Contact, Comment, Consultation, AnswerCarousel } from '../../../components/Other';
 import Tabs from '../../../components/Tabs';
 import Button from '../../../components/Button';
@@ -112,7 +113,7 @@ export default class extends Page {
 
   renderView() {
     const { number } = this.props.order;
-    const { promote = {}, base = {} } = this.state;
+    const { promote = {}, base = {}, showComment, comment = {}, showFaq, faq = {}, showFinish } = this.state;
     return (
       <div>
         <div className="top content">
@@ -133,6 +134,18 @@ export default class extends Page {
         {this.renderDetail()}
         <Contact data={base.contact} />
         <Footer />
+        <CommentModal
+          show={showComment}
+          defaultData={comment}
+          onConfirm={() => this.setState({ showComment: false, showFinish: true })}
+          onCancel={() => this.setState({ showComment: false })}
+          onClose={() => this.setState({ showComment: false })}
+        />
+        <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false, showFinish: true })} />
+        <FinishModal
+          show={showFinish}
+          onConfirm={() => this.setState({ showFinish: false })}
+        />
       </div>
     );
   }
@@ -210,12 +223,15 @@ export default class extends Page {
                 <div className="d-i-b t-7 t-s-20 f-w-b"> ¥ {price}</div>
               </div>
               <div className="action">
-                <Button className="m-r-1" radius size="lager" onClick={() => this.buy()}>
+                {data.have && <Button className="m-r-1" radius size="lager" onClick={() => linkTo('/my/course')}>
+                  我的课程
+                </Button>}
+                {!data.have && <Button className="m-r-1" radius size="lager" onClick={() => this.buy()}>
                   立即购买
-                </Button>
-                <Button theme="default" radius size="lager" onClick={() => this.add()}>
+                </Button>}
+                {!data.have && <Button theme="default" disabled={this.state.add || data.add} radius size="lager" onClick={() => this.add()}>
                   <Assets name="add" />
-                </Button>
+                </Button>}
               </div>
             </div>
           </div>
@@ -242,61 +258,55 @@ export default class extends Page {
   }
 
   renderTab1() {
+    const { teachers = [] } = this.state;
+    const [teacher] = teachers;
     return (
       <div className="tab-layout">
         <div className="teach-item">
           <div className="left t-c">
-            <Assets className="m-b-1" />
-            <div className="t-1 t-s-20">李奕都(DUKB24)</div>
-          </div>
-          <div className="right t-1 t-s-16">
-            针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练。针对 0G20 的集中训练针对
-            0G20 的集中训练针对 0G20 的集,中训练针对 0G20 的集中训练针对 0G20 的集中训练针对。 针对 0G20
-            的集中训。练针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练。 针对 0G20 的集中训练针对 0G20
-            的集中训练针对 0G。
+            <Assets className="m-b-1" src={teacher.avatar} />
+            <div className="t-1 t-s-20">{teacher.realname}</div>
           </div>
+          <div className="right t-1 t-s-16 ws-p">{teacher.description}</div>
         </div>
       </div>
     );
   }
 
   renderTab2() {
+    const { base } = this.state;
     return (
       <div className="tab-layout">
-        <Consultation />
+        <Consultation data={base.contact} />
       </div>
     );
   }
 
   renderTab3() {
+    const { faqs, data = {} } = this.state;
     return (
       <div className="tab-layout">
         <AnswerCarousel
           hideBtn
-          list={[
-            { answer: '123123', content: '12312312' },
-            { answer: '123123', content: '12312312' },
-            { answer: '123123', content: '12312312' },
-            { answer: '123123', content: '12312312' },
-          ]}
+          list={faqs}
+          onFaq={() => this.setState({ showFaq: true, faq: { channel: 'course-vs', position: data.id } })}
         />
       </div>
     );
   }
 
   renderTab4() {
+    const { data, comments } = this.state;
     return (
       <div className="tab-layout">
-        <div className="m-b-1 t-r">
-          <Button width={100} radius>
+        {data.have && <div className="m-b-1 t-r">
+          <Button width={100} radius onClick={() => User.needLogin().then(() => this.setState({ showComment: true, comment: { channel: 'course-vs', position: data.id } }))}>
             写评论
           </Button>
-        </div>
-        <Comment />
-        <Comment />
-        <Comment />
-        <Comment />
-        <Comment />
+        </div>}
+        {(comments || []).map(item => {
+          return <Comment data={item} />;
+        })}
       </div>
     );
   }

+ 1 - 1
front/project/www/routes/my/course/page.js

@@ -711,7 +711,7 @@ class CourseOnline extends Component {
         key: 'ask',
         render: (text, record) => {
           return (
-            <Link to={`/course/ask/${record.courseId}?no=${record.id}`}>{`${record.answerNumber ||
+            <Link to={`/course/answer/${record.courseId}?tab=my&courseNoId=${record.id}`}>{`${record.answerNumber ||
               0}/${record.askNumber || 0}`}</Link>
           );
         },

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

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

+ 282 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/UserReadyRoomFeedback.java

@@ -0,0 +1,282 @@
+package com.qxgmat.data.dao.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.persistence.*;
+
+@Table(name = "user_ready_room_feedback")
+public class UserReadyRoomFeedback implements Serializable {
+    @Id
+    @Column(name = "`id`")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    /**
+     * 用户id
+     */
+    @Column(name = "`user_id`")
+    private Integer userId;
+
+    /**
+     * 考场id
+     */
+    @Column(name = "`room_id`")
+    private Integer roomId;
+
+    @Column(name = "`manager_id`")
+    private Integer managerId;
+
+    /**
+     * 审核状态
+     */
+    @Column(name = "`status`")
+    private Integer status;
+
+    @Column(name = "`create_time`")
+    private Date createTime;
+
+    @Column(name = "`handle_time`")
+    private Date handleTime;
+
+    /**
+     * 补充内容
+     */
+    @Column(name = "`content`")
+    private String content;
+
+    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 room_id - 考场id
+     */
+    public Integer getRoomId() {
+        return roomId;
+    }
+
+    /**
+     * 设置考场id
+     *
+     * @param roomId 考场id
+     */
+    public void setRoomId(Integer roomId) {
+        this.roomId = roomId;
+    }
+
+    /**
+     * @return manager_id
+     */
+    public Integer getManagerId() {
+        return managerId;
+    }
+
+    /**
+     * @param managerId
+     */
+    public void setManagerId(Integer managerId) {
+        this.managerId = managerId;
+    }
+
+    /**
+     * 获取审核状态
+     *
+     * @return status - 审核状态
+     */
+    public Integer getStatus() {
+        return status;
+    }
+
+    /**
+     * 设置审核状态
+     *
+     * @param status 审核状态
+     */
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    /**
+     * @return create_time
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * @param createTime
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * @return handle_time
+     */
+    public Date getHandleTime() {
+        return handleTime;
+    }
+
+    /**
+     * @param handleTime
+     */
+    public void setHandleTime(Date handleTime) {
+        this.handleTime = handleTime;
+    }
+
+    /**
+     * 获取补充内容
+     *
+     * @return content - 补充内容
+     */
+    public String getContent() {
+        return content;
+    }
+
+    /**
+     * 设置补充内容
+     *
+     * @param content 补充内容
+     */
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", userId=").append(userId);
+        sb.append(", roomId=").append(roomId);
+        sb.append(", managerId=").append(managerId);
+        sb.append(", status=").append(status);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", handleTime=").append(handleTime);
+        sb.append(", content=").append(content);
+        sb.append("]");
+        return sb.toString();
+    }
+
+    public static UserReadyRoomFeedback.Builder builder() {
+        return new UserReadyRoomFeedback.Builder();
+    }
+
+    public static class Builder {
+        private UserReadyRoomFeedback obj;
+
+        public Builder() {
+            this.obj = new UserReadyRoomFeedback();
+        }
+
+        /**
+         * @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 roomId 考场id
+         */
+        public Builder roomId(Integer roomId) {
+            obj.setRoomId(roomId);
+            return this;
+        }
+
+        /**
+         * @param managerId
+         */
+        public Builder managerId(Integer managerId) {
+            obj.setManagerId(managerId);
+            return this;
+        }
+
+        /**
+         * 设置审核状态
+         *
+         * @param status 审核状态
+         */
+        public Builder status(Integer status) {
+            obj.setStatus(status);
+            return this;
+        }
+
+        /**
+         * @param createTime
+         */
+        public Builder createTime(Date createTime) {
+            obj.setCreateTime(createTime);
+            return this;
+        }
+
+        /**
+         * @param handleTime
+         */
+        public Builder handleTime(Date handleTime) {
+            obj.setHandleTime(handleTime);
+            return this;
+        }
+
+        /**
+         * 设置补充内容
+         *
+         * @param content 补充内容
+         */
+        public Builder content(String content) {
+            obj.setContent(content);
+            return this;
+        }
+
+        public UserReadyRoomFeedback build() {
+            return this.obj;
+        }
+    }
+}

+ 34 - 0
server/data/src/main/java/com/qxgmat/data/dao/mapping/UserReadyRoomFeedbackMapper.xml

@@ -0,0 +1,34 @@
+<?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.UserReadyRoomFeedbackMapper">
+  <resultMap id="BaseResultMap" type="com.qxgmat.data.dao.entity.UserReadyRoomFeedback">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+    <result column="user_id" jdbcType="INTEGER" property="userId" />
+    <result column="room_id" jdbcType="INTEGER" property="roomId" />
+    <result column="manager_id" jdbcType="INTEGER" property="managerId" />
+    <result column="status" jdbcType="INTEGER" property="status" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="handle_time" jdbcType="TIMESTAMP" property="handleTime" />
+  </resultMap>
+  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.qxgmat.data.dao.entity.UserReadyRoomFeedback">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <result column="content" jdbcType="LONGVARCHAR" property="content" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `id`, `user_id`, `room_id`, `manager_id`, `status`, `create_time`, `handle_time`
+  </sql>
+  <sql id="Blob_Column_List">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    `content`
+  </sql>
+</mapper>

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

@@ -80,6 +80,9 @@ public class UserController {
     private QuestionNoService questionNoService;
 
     @Autowired
+    private ReadyRoomService readyRoomService;
+
+    @Autowired
     private UsersService usersService;
 
     @Autowired
@@ -128,6 +131,9 @@ public class UserController {
     private UserTextbookFeedbackService userTextbookFeedbackService;
 
     @Autowired
+    private UserReadyRoomFeedbackService userReadyRoomFeedbackService;
+
+    @Autowired
     private UserOrderRecordService userOrderRecordService;
 
     @Autowired
@@ -209,6 +215,8 @@ public class UserController {
         User entity = Transform.dtoToEntity(dto);
         entity.setRealStatus(1);
         entity.setRealTime(new Date());
+        // 实名认证,半价机经券
+        entity.setTextbookHalf(1);
         usersService.edit(entity);
 
         orderFlowService.giveReal(in);
@@ -553,7 +561,7 @@ public class UserController {
 
     @RequestMapping(value = "/textbook_feedback/edit", method = RequestMethod.PUT)
     @ApiOperation(value = "修改机经勘误信息", httpMethod = "PUT")
-    public Response<Boolean> editTextbookFeedback(@RequestBody @Validated UserFeedbackErrorDto dto, HttpServletRequest request) {
+    public Response<Boolean> editTextbookFeedback(@RequestBody @Validated UserTextbookFeedbackDto dto, HttpServletRequest request) {
         UserTextbookFeedback entity = Transform.dtoToEntity(dto);
         UserTextbookFeedback in = userTextbookFeedbackService.get(entity.getId());
 
@@ -632,6 +640,67 @@ public class UserController {
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
+    @RequestMapping(value = "/ready_room_feedback/edit", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改考场补充信息", httpMethod = "PUT")
+    public Response<Boolean> editReadyRoomFeedback(@RequestBody @Validated UserReadyRoomFeedbackDto dto, HttpServletRequest request) {
+        UserReadyRoomFeedback entity = Transform.dtoToEntity(dto);
+        UserReadyRoomFeedback in = userReadyRoomFeedbackService.get(entity.getId());
+
+        // 处理设定
+        if(in.getHandleTime() == null){
+            entity.setHandleTime(new Date());
+            Manager manager = shiroHelp.getLoginManager();
+            entity.setManagerId(manager.getId());
+        }
+
+        entity = userReadyRoomFeedbackService.edit(entity);
+
+        managerLogService.log(request);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/ready_room_feedback/detail", method = RequestMethod.GET)
+    @ApiOperation(value = "考场补充详情", httpMethod = "GET")
+    public Response<UserReadyRoomFeedbackInfoDto> detailReadyRoomFeedback(@RequestParam int id, HttpServletRequest request) {
+        UserReadyRoomFeedback entity = userReadyRoomFeedbackService.get(id);
+        UserReadyRoomFeedbackInfoDto dto = Transform.convert(entity, UserReadyRoomFeedbackInfoDto.class);
+
+        User user = usersService.get(entity.getUserId());
+        UserExtendDto userDto = Transform.convert(user, UserExtendDto.class);
+        dto.setUser(userDto);
+
+        ReadyRoom room = readyRoomService.get(entity.getRoomId());
+        ReadyRoomExtendDto roomDto = Transform.convert(room, ReadyRoomExtendDto.class);
+        dto.setRoom(roomDto);
+
+        return ResponseHelp.success(dto);
+    }
+
+    @RequestMapping(value = "/ready_room_feedback/list", method = RequestMethod.GET)
+    @ApiOperation(value = "考场补充列表", httpMethod = "GET")
+    public Response<PageMessage<UserReadyRoomFeedbackInfoDto>> listReadyRoomFeedback(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) Integer status,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
+            HttpSession session) {
+        Page<UserReadyRoomFeedback> p = userReadyRoomFeedbackService.listAdmin(page, size, FeedbackStatus.ValueOf(status), order, DirectionStatus.ValueOf(direction));
+        List<UserReadyRoomFeedbackInfoDto> pr = Transform.convert(p, UserReadyRoomFeedbackInfoDto.class);
+
+        // 绑定用户
+        Collection userIds = Transform.getIds(p, UserReadyRoomFeedback.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Transform.combine(pr, userList, UserReadyRoomFeedbackInfoDto.class, "userId", "user", User.class, "id", UserExtendDto.class);
+
+        // 绑定考场
+        Collection roomIds = Transform.getIds(p, UserReadyRoomFeedback.class, "roomId");
+        List<ReadyRoom> roomList = readyRoomService.select(roomIds);
+        Transform.combine(pr, roomList, UserReadyRoomFeedbackInfoDto.class, "roomId", "room", ReadyRoom.class, "id", ReadyRoomExtendDto.class);
+
+        return ResponseHelp.success(pr, page, size, p.getTotal());
+    }
+
     @RequestMapping(value = "/course/appointment/add", method = RequestMethod.POST)
     @ApiOperation(value = "添加课程预约", httpMethod = "POST")
     public Response<UserCourseAppointment> addCourseAppointment(@RequestBody @Validated UserCourseAppointmentDto dto, HttpServletRequest request) {

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

@@ -10,6 +10,7 @@ import com.qxgmat.data.constants.enums.ExperienceScoreRange;
 import com.qxgmat.data.constants.enums.module.CourseModule;
 import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.module.VsCourseType;
+import com.qxgmat.data.constants.enums.status.AnswerStatus;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.user.DataType;
 import com.qxgmat.data.dao.entity.*;
@@ -20,6 +21,7 @@ import com.qxgmat.dto.request.*;
 import com.qxgmat.dto.response.*;
 import com.qxgmat.help.ShiroHelp;
 import com.qxgmat.service.UserCollectExperienceService;
+import com.qxgmat.service.UserNoteCourseService;
 import com.qxgmat.service.UsersService;
 import com.qxgmat.service.extend.CourseExtendService;
 import com.qxgmat.service.extend.PreviewService;
@@ -97,6 +99,9 @@ public class CourseController {
     private UserAskCourseService userAskCourseService;
 
     @Autowired
+    private UserNoteCourseService userNoteCourseService;
+
+    @Autowired
     private UserOrderRecordService userOrderRecordService;
 
     @Autowired
@@ -115,8 +120,26 @@ public class CourseController {
     @RequestMapping(value = "/vs", method = RequestMethod.GET)
     @ApiOperation(value = "获取1v1课程信息", notes = "获取1v1课程信息", httpMethod = "GET")
     public Response<List<CourseListDto>> vs()  {
+        User user = (User) shiroHelp.getLoginUser();
         List<Course> p = courseService.all(CourseModule.VS);
         List<CourseListDto> pr = Transform.convert(p, CourseListDto.class);
+        Collection ids = Transform.getIds(pr, CourseListDto.class, "id");
+
+        if(user != null){
+            // 已购买: 查看当前服务
+            List<UserCourse> userCourseList = userCourseService.listByCourse(user.getId(), ids);
+            Map userCourseMap = Transform.getMap(userCourseList, UserCourse.class, "courseId");
+
+            // 添加购物车
+            List<UserOrderCheckout> userOrderCheckoutList = userOrderCheckoutService.listWithProduct(user.getId(), ProductType.COURSE, ids);
+            Map userOrderCheckoutMap = Transform.getMap(userOrderCheckoutList, UserOrderCheckout.class, "productId");
+
+            for(CourseListDto dto : pr){
+                dto.setHave(userCourseMap.containsKey(dto.getId()));
+                dto.setAdd(userOrderCheckoutMap.containsKey(dto.getId()));
+            }
+        }
+
         return ResponseHelp.success(pr);
     }
 
@@ -195,9 +218,9 @@ public class CourseController {
         dto.setFaqs(Transform.convert(faqList, FaqExtendDto.class));
 
         // 优质问答
-        UserAskCourseStatRelation relation = userAskCourseService.statCourse(courseId, 1);
-        if (relation != null){
-            dto.setAskNumber(relation.getNumber());
+        UserAskCourseStatRelation askCourseStatRelation = userAskCourseService.statCourse(courseId, 1);
+        if (askCourseStatRelation != null){
+            dto.setAskSpecialNumber(askCourseStatRelation.getNumber());
         }
 
         if(user != null){
@@ -209,6 +232,54 @@ public class CourseController {
 
             dto.setHave(userCourse != null);
             dto.setAdd(userOrderCheckout != null);
+
+            if (userCourse != null){
+
+                // 进度信息
+                Collection<UserCourseProgress> progressList = userCourseProgressService.getByRecordId(userCourse.getRecordId());
+
+                dto.setCurrentNo(courseExtendService.computeCourseNoCurrent(courseNoList, progressList));
+                dto.setProgress(Transform.convert(progressList, UserCourseProgressExtendDto.class));
+
+                // 获取每个科目的所有作业
+                Collection<UserPreviewPaperRelation> previewList = previewService.getByRecordId(user.getId(), userCourse.getRecordId(), 1000);
+                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(previewList.size()> 0 ? finish * 100 / previewList.size(): 0);
+
+                // 提问数、笔记数
+                Collection<UserAskCourse> askList = userAskCourseService.getByRecordId(userCourse.getRecordId());
+                Collection<UserNoteCourse> noteList = userNoteCourseService.getByCourse(user.getId(), courseId);
+                Map<Object, List<UserAskCourse>> askListMap = Transform.getMapList(askList, UserAskCourse.class, "courseNoId");
+                Map notes = Transform.getMap(noteList, UserNoteCourse.class, "courseNoId");
+                Collection<CourseNoExtendDto> courseNos = dto.getCourseNos();
+                int noteNumber = 0;
+                int askNumber = askList == null ? 0: askList.size();
+                int answerNumber = askList == null ? 0 : (int)askList.stream().filter(r->r.getAnswerStatus()== AnswerStatus.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()== AnswerStatus.ANSWER.index).count());
+                    }
+                }
+                dto.setNoteNumber(noteNumber);
+                dto.setAskNumber(askNumber);
+                dto.setAnswerNumber(answerNumber);
+            }
+
+
         }
         return ResponseHelp.success(dto);
     }
@@ -273,6 +344,7 @@ public class CourseController {
                     .courseId(dto.getCourseId())
                     .courseNoId(dto.getCurrentCourseNoId())
                     .recordId(userCourse.getRecordId())
+                    .userTime(dto.getTime())
                     .build());
         }
 

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

@@ -186,6 +186,9 @@ public class MyController {
     private UserTextbookFeedbackService userTextbookFeedbackService;
 
     @Autowired
+    private UserReadyRoomFeedbackService userReadyRoomFeedbackService;
+
+    @Autowired
     private UserQuestionService userQuestionService;
 
     @Autowired
@@ -1535,6 +1538,17 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/feedback/ready/room", method = RequestMethod.POST)
+    @ApiOperation(value = "添加考场反馈", notes = "添加考场反馈", httpMethod = "POST")
+    public Response<Boolean> addFeedbackTextbook(@RequestBody @Validated UserReadyRoomFeedbackDto dto)  {
+        UserReadyRoomFeedback entity = Transform.dtoToEntity(dto);
+        User user = (User) shiroHelp.getLoginUser();
+        entity.setUserId(user.getId());
+        userReadyRoomFeedbackService.add(entity);
+
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/faq", method = RequestMethod.POST)
     @ApiOperation(value = "添加faq", notes = "添加faq", httpMethod = "POST")
     public Response<Boolean> addFaq(@RequestBody @Validated FaqDto dto)  {
@@ -1732,7 +1746,7 @@ public class MyController {
 
         // 提问数、笔记数
         Map<Object, Collection<UserAskCourse>> askMap = userAskCourseService.groupByRecordId(recordIds);
-        Map<Object, Collection<UserNoteCourse>> noteMap = userNoteCourseService.groupByCourse(courseIds);
+        Map<Object, Collection<UserNoteCourse>> noteMap = userNoteCourseService.groupByCourse(user.getId(), courseIds);
         for(UserCourseDetailDto dto : pr){
             Collection<CourseNoExtendDto> courseNos = dto.getCourseNos();
             if (courseNos == null) continue;

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

@@ -0,0 +1,77 @@
+package com.qxgmat.dto.admin.extend;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.ReadyRoom;
+
+@Dto(entity = ReadyRoom.class)
+public class ReadyRoomExtendDto {
+    private Integer id;
+
+    private String position;
+
+    private Integer areaId;
+
+    private Integer isOverseas;
+
+    private String title;
+
+    private String address;
+
+    private String description;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getPosition() {
+        return position;
+    }
+
+    public void setPosition(String position) {
+        this.position = position;
+    }
+
+    public Integer getAreaId() {
+        return areaId;
+    }
+
+    public void setAreaId(Integer areaId) {
+        this.areaId = areaId;
+    }
+
+    public Integer getIsOverseas() {
+        return isOverseas;
+    }
+
+    public void setIsOverseas(Integer isOverseas) {
+        this.isOverseas = isOverseas;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+}

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

@@ -0,0 +1,27 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserReadyRoomFeedback;
+
+@Dto(entity = UserReadyRoomFeedback.class)
+public class UserReadyRoomFeedbackDto {
+    private Integer id;
+
+    private Integer status;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+}

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

@@ -0,0 +1,27 @@
+package com.qxgmat.dto.admin.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserTextbookFeedback;
+
+@Dto(entity = UserTextbookFeedback.class)
+public class UserTextbookFeedbackDto {
+    private Integer id;
+
+    private Integer status;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+}

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

@@ -4,6 +4,8 @@ import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.UserFeedbackError;
 import com.qxgmat.dto.admin.extend.UserExtendDto;
 
+import java.util.Date;
+
 @Dto(entity = UserFeedbackError.class)
 public class UserFeedbackErrorInfoDto {
 
@@ -25,6 +27,10 @@ public class UserFeedbackErrorInfoDto {
 
     private String content;
 
+    private Date createTime;
+
+    private Date handleTime;
+
     public Integer getId() {
         return id;
     }
@@ -96,4 +102,20 @@ public class UserFeedbackErrorInfoDto {
     public void setTitle(String title) {
         this.title = title;
     }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getHandleTime() {
+        return handleTime;
+    }
+
+    public void setHandleTime(Date handleTime) {
+        this.handleTime = handleTime;
+    }
 }

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

@@ -0,0 +1,101 @@
+package com.qxgmat.dto.admin.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserReadyRoomFeedback;
+import com.qxgmat.dto.admin.extend.ReadyRoomExtendDto;
+import com.qxgmat.dto.admin.extend.UserExtendDto;
+
+import java.util.Date;
+
+@Dto(entity = UserReadyRoomFeedback.class)
+public class UserReadyRoomFeedbackInfoDto {
+    private Integer id;
+
+    private Integer userId;
+
+    private UserExtendDto user;
+
+    private Integer roomId;
+
+    private ReadyRoomExtendDto room;
+
+    private String content;
+
+    private Integer status;
+
+    private Date createTime;
+
+    private Date handleTime;
+
+    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 UserExtendDto getUser() {
+        return user;
+    }
+
+    public void setUser(UserExtendDto user) {
+        this.user = user;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Date getHandleTime() {
+        return handleTime;
+    }
+
+    public void setHandleTime(Date handleTime) {
+        this.handleTime = handleTime;
+    }
+
+    public Integer getRoomId() {
+        return roomId;
+    }
+
+    public void setRoomId(Integer roomId) {
+        this.roomId = roomId;
+    }
+
+    public ReadyRoomExtendDto getRoom() {
+        return room;
+    }
+
+    public void setRoom(ReadyRoomExtendDto room) {
+        this.room = room;
+    }
+}

+ 5 - 5
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserTextbookFeedbackInfoDto.java

@@ -30,7 +30,7 @@ public class UserTextbookFeedbackInfoDto {
 
     private Date createTime;
 
-    private Date updateTime;
+    private Date handleTime;
 
     public Integer getId() {
         return id;
@@ -112,11 +112,11 @@ public class UserTextbookFeedbackInfoDto {
         this.createTime = createTime;
     }
 
-    public Date getUpdateTime() {
-        return updateTime;
+    public Date getHandleTime() {
+        return handleTime;
     }
 
-    public void setUpdateTime(Date updateTime) {
-        this.updateTime = updateTime;
+    public void setHandleTime(Date handleTime) {
+        this.handleTime = handleTime;
     }
 }

+ 28 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserReadyRoomFeedbackDto.java

@@ -0,0 +1,28 @@
+package com.qxgmat.dto.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserReadyRoomFeedback;
+import com.qxgmat.data.dao.entity.UserTextbookFeedback;
+
+@Dto(entity = UserReadyRoomFeedback.class)
+public class UserReadyRoomFeedbackDto {
+    private Integer roomId;
+
+    private String content;
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Integer getRoomId() {
+        return roomId;
+    }
+
+    public void setRoomId(Integer roomId) {
+        this.roomId = roomId;
+    }
+}

+ 72 - 4
server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDetailDto.java

@@ -2,9 +2,7 @@ package com.qxgmat.dto.response;
 
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.Course;
-import com.qxgmat.dto.extend.CommentExtendDto;
-import com.qxgmat.dto.extend.CourseNoExtendDto;
-import com.qxgmat.dto.extend.FaqExtendDto;
+import com.qxgmat.dto.extend.*;
 
 import java.math.BigDecimal;
 import java.util.Collection;
@@ -43,7 +41,7 @@ public class CourseDetailDto extends Course {
 
     private Integer useExpireDays;
 
-    private Integer askNumber;
+    private Integer askSpecialNumber;
 
     private Boolean have;
 
@@ -55,6 +53,20 @@ public class CourseDetailDto extends Course {
 
     private Collection<FaqExtendDto> faqs;
 
+    private Integer currentNo;
+
+    private Integer previewProgress;
+
+    private Integer askNumber;
+
+    private Integer answerNumber;
+
+    private Integer noteNumber;
+
+    private Collection<UserCourseProgressExtendDto> progress;
+
+    private Collection<BasePaperExtendDto> papers;
+
     public Integer getId() {
         return id;
     }
@@ -227,6 +239,30 @@ public class CourseDetailDto extends Course {
         this.add = add;
     }
 
+    public Integer getAskSpecialNumber() {
+        return askSpecialNumber;
+    }
+
+    public void setAskSpecialNumber(Integer askSpecialNumber) {
+        this.askSpecialNumber = askSpecialNumber;
+    }
+
+    public Integer getCurrentNo() {
+        return currentNo;
+    }
+
+    public void setCurrentNo(Integer currentNo) {
+        this.currentNo = currentNo;
+    }
+
+    public Integer getPreviewProgress() {
+        return previewProgress;
+    }
+
+    public void setPreviewProgress(Integer previewProgress) {
+        this.previewProgress = previewProgress;
+    }
+
     public Integer getAskNumber() {
         return askNumber;
     }
@@ -234,4 +270,36 @@ public class CourseDetailDto extends Course {
     public void setAskNumber(Integer askNumber) {
         this.askNumber = askNumber;
     }
+
+    public Integer getAnswerNumber() {
+        return answerNumber;
+    }
+
+    public void setAnswerNumber(Integer answerNumber) {
+        this.answerNumber = answerNumber;
+    }
+
+    public Integer getNoteNumber() {
+        return noteNumber;
+    }
+
+    public void setNoteNumber(Integer noteNumber) {
+        this.noteNumber = noteNumber;
+    }
+
+    public Collection<UserCourseProgressExtendDto> getProgress() {
+        return progress;
+    }
+
+    public void setProgress(Collection<UserCourseProgressExtendDto> progress) {
+        this.progress = progress;
+    }
+
+    public Collection<BasePaperExtendDto> getPapers() {
+        return papers;
+    }
+
+    public void setPapers(Collection<BasePaperExtendDto> papers) {
+        this.papers = papers;
+    }
 }

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

@@ -83,17 +83,29 @@ public class UserNoteCourseService extends AbstractService {
         }
     }
 
+    public List<UserNoteCourse> getByCourse(Integer userId, Integer courseId){
+        Example example = new Example(UserNoteCourse.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("courseId", courseId)
+        );
+        return select(userNoteCourseMapper, example);
+    }
+
+
     /**
      * 获取课程记录分组列表
      * @param courseIds
      * @return
      */
-    public Map<Object, Collection<UserNoteCourse>> groupByCourse(Collection courseIds){
+    public Map<Object, Collection<UserNoteCourse>> groupByCourse(Integer userId, 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()
+                        .andEqualTo("userId", userId)
                         .andIn("courseId", courseIds)
         );
         List<UserNoteCourse> nos =  select(userNoteCourseMapper, example);

+ 0 - 2
server/gateway-api/src/main/java/com/qxgmat/service/extend/MessageExtendService.java

@@ -1,6 +1,5 @@
 package com.qxgmat.service.extend;
 
-import com.alipay.api.domain.TransOrderDetail;
 import com.nuliji.tools.Transform;
 import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.*;
@@ -19,7 +18,6 @@ import com.qxgmat.service.inline.*;
 import org.apache.logging.log4j.util.Strings;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
-import sun.plugin2.message.Message;
 
 import javax.annotation.Resource;
 import java.text.SimpleDateFormat;

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

@@ -70,6 +70,10 @@ public class PreviewService extends AbstractService {
         return relationMap;
     }
 
+    public List<UserPreviewPaperRelation> getByRecordId(Integer userId, Integer recordId, Integer top){
+        return list(1, top, recordId, userId,  null, 0);
+    }
+
     /**
      * 获取用户分组作业列表
      * @param userId

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

@@ -198,6 +198,15 @@ public class UserAskCourseService extends AbstractService {
         }
     }
 
+    public List<UserAskCourse> getByRecordId(Integer recordId){
+        Example example = new Example(UserAskCourse.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("recordId", recordId)
+        );
+        return select(userAskCourseMapper, example);
+    }
+
     /**
      * 获取课程记录分组列表
      * @param recordIds

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

@@ -79,6 +79,16 @@ public class UserCourseProgressService extends AbstractService {
         return select(userCourseProgressMapper, example);
     }
 
+    public List<UserCourseProgress> getByRecordId(Integer recordId){
+        Example example = new Example(UserCourseProgress.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("recordId", recordId)
+        );
+        return select(userCourseProgressMapper, example);
+    }
+
+
     /**
      * 获取课程记录分组列表
      * @param recordIds

+ 100 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserReadyRoomFeedbackService.java

@@ -0,0 +1,100 @@
+package com.qxgmat.service.inline;
+
+import com.github.pagehelper.Page;
+import com.nuliji.tools.AbstractService;
+import com.nuliji.tools.Tools;
+import com.nuliji.tools.Transform;
+import com.nuliji.tools.exception.ParameterException;
+import com.nuliji.tools.exception.SystemException;
+import com.nuliji.tools.mybatis.Example;
+import com.qxgmat.data.constants.enums.status.DirectionStatus;
+import com.qxgmat.data.constants.enums.status.FeedbackStatus;
+import com.qxgmat.data.dao.UserReadyRoomFeedbackMapper;
+import com.qxgmat.data.dao.entity.UserReadyRoomFeedback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class UserReadyRoomFeedbackService extends AbstractService {
+    private static final Logger logger = LoggerFactory.getLogger(UserReadyRoomFeedbackService.class);
+
+    @Resource
+    private UserReadyRoomFeedbackMapper userReadyRoomFeedbackMapper;
+
+    private Map<String, String> adminMap = new HashMap<String, String>(){{
+    }};
+
+    public Page<UserReadyRoomFeedback> listAdmin(int page, int size, FeedbackStatus status, String order, DirectionStatus direction){
+        Example example = new Example(UserReadyRoomFeedback.class);
+        if (status != null)
+            example.and(
+                    example.createCriteria().andEqualTo("status", status.index)
+            );
+        if(order == null || order.isEmpty()) order = "id";
+        switch(direction){
+            case ASC:
+                example.orderBy(order).asc();
+                break;
+            case DESC:
+            default:
+                example.orderBy(order).desc();
+        }
+        return select(userReadyRoomFeedbackMapper, example, page, size);
+    }
+
+    public UserReadyRoomFeedback add(UserReadyRoomFeedback ad){
+        int result = insert(userReadyRoomFeedbackMapper, ad);
+        ad = one(userReadyRoomFeedbackMapper, ad.getId());
+        if(ad == null){
+            throw new SystemException("记录添加失败");
+        }
+        return ad;
+    }
+
+    public UserReadyRoomFeedback edit(UserReadyRoomFeedback ad){
+        UserReadyRoomFeedback in = one(userReadyRoomFeedbackMapper, ad.getId());
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        int result = update(userReadyRoomFeedbackMapper, ad);
+        return ad;
+    }
+
+    public boolean delete(Number id){
+        UserReadyRoomFeedback in = one(userReadyRoomFeedbackMapper, id);
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        int result = delete(userReadyRoomFeedbackMapper, id);
+        return result > 0;
+    }
+
+    public UserReadyRoomFeedback get(Number id){
+        UserReadyRoomFeedback in = one(userReadyRoomFeedbackMapper, id);
+
+        if(in == null){
+            throw new ParameterException("记录不存在");
+        }
+        return in;
+    }
+
+    public Page<UserReadyRoomFeedback> select(int page, int pageSize){
+        return select(userReadyRoomFeedbackMapper, page, pageSize);
+    }
+
+    public Page<UserReadyRoomFeedback> select(Integer[] ids){
+        return page(()->select(userReadyRoomFeedbackMapper, ids), 1, ids.length);
+    }
+
+    public List<UserReadyRoomFeedback> select(Collection ids){
+        return select(userReadyRoomFeedbackMapper, ids);
+    }
+
+}

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

@@ -58,7 +58,7 @@ pay:
     pid: 2088521305942904
 
   wechat:
-    appKey: hanruizhangnachenxuanchenyue1416
+    appKey: 0118wasteless0118wasteless0118wa
     pid: 1531541431
 
 ip: