Browse Source

feat(front): video

Go 5 years ago
parent
commit
f3f172c70a

+ 1 - 1
front/project/www/components/Item/index.js

@@ -30,7 +30,7 @@ export class SingleItem extends Component {
 
   add() {
     const { data } = this.props;
-    User.needLogin().thenn(() => {
+    User.needLogin().then(() => {
       Order.addCheckout({ productType: 'course', productId: data.id })
         .then(() => {
           this.setState({ add: true });

+ 2 - 0
front/project/www/components/Modal/index.js

@@ -23,12 +23,14 @@ export default class extends Component {
       btnType,
       center,
       height,
+      getContainer,
     } = this.props;
     return (
       <Modal
         wrapClassName={`g-modal ${className || ''}`}
         visible={show}
         closable={false}
+        getContainer={getContainer}
         maskClosable={maskClosable}
         footer={false}
         width={width}

+ 4 - 2
front/project/www/components/OtherModal/index.js

@@ -805,7 +805,7 @@ export class AskCourseModal extends Component {
   }
 
   render() {
-    const { show, selectList, courseNo } = this.props;
+    const { show, selectList, courseNo, getContainer } = this.props;
     const { data } = this.state;
     return (
       <Modal
@@ -813,6 +813,7 @@ export class AskCourseModal extends Component {
         title="提问"
         btnType="link"
         width={630}
+        getContainer={getContainer}
         confirmText="提交"
         onConfirm={() => this.onConfirm()}
         onCancel={() => this.onCancel()}
@@ -885,13 +886,14 @@ export class CourseNoteModal extends Component {
   }
 
   render() {
-    const { show, course = {}, courseNos = [], noteMap = {} } = this.props;
+    const { show, course = {}, courseNos = [], noteMap = {}, getContainer } = this.props;
     const { data } = this.state;
     return (
       <Modal
         show={show}
         title="笔记"
         width={630}
+        getContainer={getContainer}
         confirmText="提交"
         onConfirm={() => this.onConfirm()}
         onCancel={() => this.onCancel()}

+ 2 - 2
front/project/www/components/PayModal/index.js

@@ -164,7 +164,7 @@ export class PayModal extends Component {
         checkout={order.checkouts[0]}
         onConfirm={() => this.close()}
       />,
-      show && (order.checkouts.length > 1 || order.productTypes.indexof('course_package') < 0) && <PayMutilModal
+      show && (order.checkouts.length > 1 || order.productTypes.indexOf('course_package') < 0) && <PayMutilModal
         show
         contract={contract}
         order={order}
@@ -173,7 +173,7 @@ export class PayModal extends Component {
         onClose={() => this.close()}
         onConfirm={() => this.confirm()}
       />,
-      show && order.checkouts.length === 1 && order.productTypes.indexof('course_package') < 0 && <PayMModal
+      show && order.checkouts.length === 1 && order.productTypes.indexOf('course_package') < 0 && <PayMModal
         show
         contract={contract}
         order={order}

+ 75 - 47
front/project/www/components/Video/index.js

@@ -1,4 +1,5 @@
 import React, { Component } from 'react';
+import { Slider } from 'antd';
 import './index.less';
 import videojs from 'video.js';
 import Assets from '@src/components/Assets';
@@ -54,20 +55,22 @@ export default class Video extends Component {
   constructor(props) {
     super(props);
     this.ready = false;
-    this.state = { id: generateUUID(8), playing: false, fulling: false };
+    this.state = { id: generateUUID(8), playing: false, fulling: false, progress: 0 };
   }
 
   componentDidMount() {
     this.player = videojs(
       this.videoNode,
       {
-        controls: false,
+        controls: true,
         sources: [
           {
             src: this.props.src,
             type: 'video/mp4',
           },
         ],
+        width: this.props.width,
+        height: this.props.height,
       },
       () => {
         this.ready = true;
@@ -81,6 +84,13 @@ export default class Video extends Component {
     }
   }
 
+  clearTimeUpdate() {
+    if (this.timeInterval) {
+      clearInterval(this.timeInterval);
+      this.timeInterval = null;
+    }
+  }
+
   refreshTimeUpdate() {
     if (this.timeInterval) {
       clearInterval(this.timeInterval);
@@ -89,7 +99,14 @@ export default class Video extends Component {
     this.timeInterval = setInterval(() => {
       const { onTimeUpdate } = this.props;
       if (onTimeUpdate) onTimeUpdate(this.player.currentTime());
-    }, this.props.duration ? this.props.duration * 1000 : 1000);
+      // this.setState({ progress: this.player.currentTime() * 100 / this.player.duration() });
+    }, 1000);
+  }
+
+  onChangeProgress(value) {
+    if (!this.ready) return;
+    this.player.currentTime(this.player.duration() * value / 100);
+    this.setState({ progress: this.player.currentTime() * 100 / this.player.duration() });
   }
 
   onPlay() {
@@ -98,6 +115,7 @@ export default class Video extends Component {
     this.player.play();
     this.setState({ playing: true });
     if (onPlay) onPlay();
+    this.refreshTimeUpdate();
   }
 
   onPause() {
@@ -106,11 +124,13 @@ export default class Video extends Component {
     this.player.pause();
     this.setState({ playing: false });
     if (onPause) onPause();
+    this.clearTimeUpdate();
   }
 
   onNext() {
     const { onNext } = this.props;
     this.player.pause();
+    this.clearTimeUpdate();
     this.setState({ playing: false });
     if (onNext) onNext();
   }
@@ -134,64 +154,72 @@ export default class Video extends Component {
   }
 
   render() {
-    const { action = true, btnList = [], children, onAction, hideAction } = this.props;
+    const { btnList = [], children, onAction, hideAction } = this.props;
     const { playing, fulling, id } = this.state;
     return (
-      <div id={id} className={`video-item ${action ? 'action' : ''} ${fulling ? 'full' : ''}`}>
+      <div id={id} className={`video-item ${!hideAction ? 'action' : ''} ${fulling ? 'full' : ''}`}>
         <div className="video-wrapper">
           <video
             ref={node => {
               this.videoNode = node;
             }}
+          // vjs-fluid
           />
           {!playing && <Assets className="play" name="play" onClick={() => this.onPlay()} />}
-          {playing && <Assets className="stop" name="stop" onClick={() => this.onPuase()} />}
+          {playing && <Assets className="stop" name="stop" onClick={() => this.onPause()} />}
         </div>
-        {!hideAction && <div className="video-bottom">
+        <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.onPause()} />}
-              </div>
-              <div className="d-i-b m-r-1">
-                <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={() => {
-                        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>
-                );
-              })}
-              <div className="d-i-b m-r-1">
-                <div className="btn">倍速</div>
-              </div>
-              <div className="d-i-b">
-                {!fulling && <Assets name="full2" onClick={() => this.onFull()} />}
-                {fulling && <Assets name="reduction2" onClick={() => this.onExitFull()} />}
-              </div>
+          {/* {this.renderProgress()} */}
+          {!hideAction && <div className="action-bar">
+            <div className="d-i-b m-r-1">
+              <Assets name={!playing ? 'play2' : 'stop2'} onClick={() => (playing ? this.onPause() : this.onPlay())} />
+              {/* {playing && <Assets name="stop2" onClick={() => this.onPause()} />} */}
+            </div>
+            <div className="d-i-b m-r-1">
+              <Assets name="next2" onClick={() => this.onNext()} />
+            </div>
+            {/* <div className="m-r-1">{this.ready ? (formatMinuteSecond(this.player.currentTime())) : ('00:00')}</div>
+            <div className="m-r-1">/{this.ready ? (formatMinuteSecond(this.player.duration())) : ('00:00')}</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-action d-i-b" onClick={() => {
+                      if (btn.pause) this.onPause();
+                      if (onAction) onAction(btn.key);
+                    }}>
+                      {btn.render(btn.active)}
+                    </div>
+                  ) : (<div
+                    className={`btn-action ${btn.active ? 'active' : ''}`}
+                    onClick={() => onAction && onAction(btn.key)}
+                  >
+                    {btn.title}
+                  </div>)}
+                </div>
+              );
+            })}
+            <div className="d-i-b m-r-1">
+              <div className="btn-action">倍速</div>
+            </div>
+            <div className="d-i-b">
+              {!fulling && <Assets name="full2" onClick={() => this.onFull()} />}
+              {fulling && <Assets name="reduction2" onClick={() => this.onExitFull()} />}
             </div>
-          )}
-        </div>}
+          </div>}
+        </div>
         {children}
       </div>
     );
   }
+
+  renderProgress() {
+    const { hideProgress } = this.props;
+    const { progress } = this.state;
+    return !hideProgress && <Slider value={progress || 0} tooltipVisible={false} onChange={(value) => this.onChangeProgress(value)} />;
+  }
 }

+ 41 - 18
front/project/www/components/Video/index.less

@@ -6,24 +6,24 @@
   overflow: hidden;
   background: #3A3A3AFF;
 
-  .video-wrapper {
+  // .video-wrapper {
 
-    .vjs-loading-spinner {
-      display: none;
-    }
+  //   .vjs-loading-spinner {
+  //     display: none;s
+  //   }
 
-    .vjs-big-play-button {
-      display: none;
-    }
+  //   .vjs-big-play-button {
+  //     display: none;
+  //   }
 
-    .vjs-control-bar {
-      display: none;
-    }
+  //   .vjs-control-bar {
+  //     display: none;
+  //   }
 
-    .vjs-modal-dialog {
-      display: none;
-    }
-  }
+  //   .vjs-modal-dialog {
+  //     display: none;
+  //   }
+  // }
 
   .video-bottom {
     position: absolute;
@@ -32,6 +32,29 @@
     right: 0;
   }
 
+  .ant-slider {
+    margin: 0;
+
+    .ant-slider-rail {
+      border-radius: 0;
+      background: #616161FF;
+    }
+
+    .ant-slider-track {
+      background: #4292f0;
+    }
+
+    .ant-slider-handle {
+      border: none;
+      background-color: transparent;
+      box-shadow: none;
+    }
+
+    .ant-slider-handle-click-focused {
+      box-shadow: none;
+    }
+  }
+
   .progress {
     height: 3px;
     background: #616161FF;
@@ -47,11 +70,11 @@
       cursor: pointer;
     }
 
-    .fix-btn {
+    .fix-btn-action {
       transform: translateY(-1px);
     }
 
-    .btn {
+    .btn-action {
       display: inline-block;
       font-size: 12px;
       line-height: 12px;
@@ -67,8 +90,8 @@
     //   background: darken(#696969, 10);
     // }
 
-    .btn.active,
-    .btn.active:hover {
+    .btn-action.active,
+    .btn-action.active:hover {
       background: #4292f0;
     }
   }

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

@@ -61,15 +61,16 @@
             position: absolute;
 
             .tab-body {
-              padding: 0 15px;
+              padding: 5px 15px;
+
             }
 
             .answer-layout {
-              height: 480px;
+              height: 430px;
             }
 
             .item-layout {
-              height: 530px;
+              height: 480px;
             }
           }
         }
@@ -145,25 +146,25 @@
         }
       }
 
-      .right {
+      .detail-right {
         padding-left: 790px;
 
         .answer-layout {
-          height: 480px;
+          height: 430px;
         }
 
         .item-layout {
-          height: 530px;
+          height: 480px;
         }
       }
 
-      .right.progress {
+      .detail-right.have {
         .answer-layout {
-          height: 500px;
+          height: 480px;
         }
 
         .item-layout {
-          height: 550px;
+          height: 530px;
         }
       }
     }

+ 19 - 15
front/project/www/routes/course/detail/page.js

@@ -198,6 +198,7 @@ export default class extends Page {
     const { id } = this.params;
     Course.detail(id).then(result => {
       result = this.formatRecord(result);
+      result.have = true;
       this.setState({ data: result });
       // 选择课时
       if (this.state.search.no) {
@@ -211,7 +212,7 @@ export default class extends Page {
 
   refreshAsk(position) {
     const { id } = this.params;
-    const { item } = this.state;
+    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 });
     });
@@ -269,13 +270,13 @@ export default class extends Page {
   }
 
   onTimeUpdate(second) {
-    const { position, item, data } = this.state;
+    const { position, item = {}, data } = this.state;
     if (!data.have) {
       // 如果是试用,则按秒数增加
-      second += item.startTrail * 60;
+      second += (item.startTrail || 0) * 60;
     }
-    const minute = second / 60;
-    const nowPosition = (minute / 5) * 5;
+    const minute = parseInt(second / 60, 10);
+    const nowPosition = parseInt(minute / 5, 10) * 5;
     if (nowPosition !== position) {
       this.refreshAsk(position);
       this.setState({ position: nowPosition });
@@ -306,7 +307,7 @@ export default class extends Page {
   }
 
   onVideoAction(key) {
-    const { rightTab, showTab, showAsk, showNote, item } = this.state;
+    const { rightTab, showTab, showAsk, showNote, item = {} } = this.state;
     switch (key) {
       case 'ask':
         return this.setState({ showAsk: !showAsk });
@@ -366,7 +367,7 @@ export default class extends Page {
   }
 
   renderView() {
-    const { base = {}, data = {}, item = {}, add, progress, rightTab, showTab, showAsk, showNote, dataStructMap = {}, showComment, comment = {}, showFaq, faq = {}, showFinish, note = {}, ask = {}, timelineSelect = [] } = this.state;
+    const { base = {}, data = {}, item = {}, add, rightTab, showTab, showAsk, showNote, dataStructMap = {}, showComment, comment = {}, showFaq, faq = {}, showFinish, note = {}, ask = {}, timelineSelect = [] } = this.state;
     const { courseNos = [] } = data;
     return (
       <div>
@@ -390,7 +391,7 @@ export default class extends Page {
             </div>
           </div>
           <div className="t-2 m-b-1">授课老师:{data.teacher}</div>
-          <div className="detail">
+          <div className={'detail'}>
             <div className="left">
               {data.have && <div hidden={(item.paper && item.paper.times > 0)} className="left-top">
                 <span className="d-i-b m-r-1">预习作业</span>
@@ -404,8 +405,9 @@ export default class extends Page {
               <div className="video-layout">
                 {item && <Video
                   key={item.id}
-                  src={item.resource}
-                  duration={10}
+                  src={item.resource || '/1.mp4'}
+                  width={750}
+                  height={467}
                   ref={ref => this.setVideo(ref)}
                   btnList={[
                     { title: '提问', key: 'ask', show: data.have, active: showAsk, pause: true },
@@ -415,10 +417,11 @@ export default class extends Page {
                         return <Assets name={active ? 'question_on' : 'question_off'} />;
                       },
                       full: true,
+                      show: true,
                       active: showTab && rightTab === '1',
                     },
                     { title: '笔记', key: 'note', show: data.have, active: showNote, pause: true },
-                    { title: '课表', key: 'list', full: true, active: showTab && rightTab === '2' },
+                    { title: '课表', key: 'list', show: true, full: true, active: showTab && rightTab === '2' },
                   ]}
                   onPlay={() => this.playVideo()}
                   onPause={() => this.pauseVideo()}
@@ -441,7 +444,7 @@ export default class extends Page {
                 </Video>}
               </div>
             </div>
-            <div className={`right ${progress > 0 ? 'progress' : ''}  tab-warpper`}>
+            <div className={`detail-right ${data.have && !(item.paper && item.paper.times > 0) ? 'have' : ''}  tab-warpper`}>
               <Tabs
                 type="division"
                 theme="gray"
@@ -460,8 +463,8 @@ export default class extends Page {
         </div>
         <Contact data={base.contact} />
         <Footer />
-        <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={() => {
+        <AskCourseModal getContainer={() => document.getElementById(this.video.state.id)} show={showAsk} defaultData={ask} course={data} courseNo={item} selectList={timelineSelect} onConfirm={() => this.setState({ showAsk: false })} onCancel={() => this.setState({ showAsk: false })} />
+        <CourseNoteModal getContainer={() => document.getElementById(this.video.state.id)} show={showNote} defaultData={note} course={data} courseNos={courseNos} noteMap={this.noteMap} onConfirm={() => {
           this.setState({ showNote: false });
           this.refreshNote();
         }} onCancel={() => this.setState({ showNote: false })} />
@@ -474,9 +477,10 @@ export default class extends Page {
         />
         <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false, showFinish: true })} />
         <FinishModal
+          getContainer={() => document.getElementById(this.video.state.id)}
           show={showFinish}
           onConfirm={() => this.setState({ showFinish: false })}
-        />,
+        />
       </div >
     );
   }

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

@@ -47,7 +47,7 @@
     .video-list {
       margin-bottom: 80px;
 
-      .video-item {
+      .video-div {
         display: inline-block;
         width: 580px;
         height: 360px;

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

@@ -61,16 +61,12 @@ export default class extends Page {
         <div className="block-2">
           <div className="main-title">找到你的Style</div>
           <div className="video-list">
-            <div className="video-item">
-              <div style={{ width: 70, height: 70 }}>
-                <Video src={courseIndex.onlineVideo} hideAction />
-              </div>
+            <div className="video-div">
+              <Video src={courseIndex.onlineVideo || '/1.mp4'} width={580} height={360} hideAction />
               <div className="name" onClick={() => linkTo('/course/online')}>在线课程 ></div>
             </div>
-            <div className="video-item">
-              <div style={{ width: 70, height: 70 }}>
-                <Video src={courseIndex.vsVideo} hideAction />
-              </div>
+            <div className="video-div">
+              <Video src={'/1.mp4'} width={580} height={360} hideAction />
               <div className="name" onClick={() => linkTo('/course/vs')}>1v1私教 ></div>
             </div>
           </div>

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

@@ -131,7 +131,7 @@ export default class extends Page {
           <Tabs type="text" active={'online'} tabs={[{ title: '在线课程', key: 'online', path: '/course/online' }, { title: '1v1私教', key: 'vs', path: '/course/vs' }]} />
           <div className="f-r">
             <span className="t-2 m-r-1">{(promote.video || {}).text ? `优惠活动:${promote.video.text}` : ''}</span>
-            <Assets name="cart" onClick={() => linkTo('cart')} />
+            <Assets name="cart" onClick={() => linkTo('/cart')} />
             <span className="t-2">( {number || 0} )</span>
           </div>
         </div>

+ 4 - 4
front/project/www/routes/page/cart/page.js

@@ -30,10 +30,10 @@ export default class extends Page {
   }
 
   initData() {
-    Order.getOrder(34)
-      .then((order) => {
-        User.needPay(order);
-      });
+    // Order.getOrder(34)
+    //   .then((order) => {
+    //     User.needPay(order);
+    //   });
     Order.allCheckout()
       .then(result => {
         User.formatOrder(result);

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

@@ -1381,6 +1381,18 @@ CREATE TABLE user_question (
   KEY report_id (report_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-做题记录';
 
+CREATE TABLE user_ready_room_feedback (
+  id int(11) unsigned NOT NULL AUTO_INCREMENT,
+  user_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',
+  room_id int(11) unsigned NOT NULL COMMENT '考场id',
+  content text COMMENT '补充内容',
+  manager_id int(11) unsigned NOT NULL DEFAULT '0',
+  status tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '审核状态',
+  create_time datetime DEFAULT NULL,
+  handle_time datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-考场-补充';
+
 CREATE TABLE user_report (
   id int(11) unsigned NOT NULL AUTO_INCREMENT,
   user_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',

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

@@ -330,7 +330,6 @@ public class CourseController {
     @RequestMapping(value = "/no/progress", method = RequestMethod.PUT)
     @ApiOperation(value = "更新课时进度", httpMethod = "PUT")
     public Response<Boolean> noProgress(@RequestBody @Validated UserCourseNoProgressDto dto) {
-        UserSentenceProgress entity = Transform.dtoToEntity(dto);
         User user = (User) shiroHelp.getLoginUser();
         if (user == null) throw new AuthException("需要登录");
 

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

@@ -152,7 +152,7 @@ public class OrderFlowService {
             // 先加入列表,统一处理
             userOrderCheckoutList.add(checkout);
             // 判断是否符合套餐情况: 剩余的独立课程
-            List<UserOrderCheckout> courseCheckout = userOrderCheckoutList.stream().filter((in)-> in.getProductType().equals(ProductType.COURSE.key) && in.getParentId() == 0).collect(Collectors.toList());
+            List<UserOrderCheckout> courseCheckout = userOrderCheckoutList.stream().filter((in)-> in.getProductType().equals(ProductType.COURSE.key) && (in.getParentId() ==null || in.getParentId()==0)).collect(Collectors.toList());
             Collection courseIds = Transform.getIds(courseCheckout, UserOrderCheckout.class, "productId");
             CoursePackage coursePackage = coursePackageService.combineCourse(courseIds);
             if(coursePackage != null){