Browse Source

Merge branch 'iteration-20240515-video' into online

lex 1 year ago
parent
commit
64a61bae52

File diff suppressed because it is too large
+ 9415 - 1
package-lock.json


BIN
src/views/student-register/images/new/icon-10.png


BIN
src/views/student-register/images/new/icon-5.png


BIN
src/views/student-register/images/new/icon-9.png


BIN
src/views/student-register/images/new/icon-n-10.png


BIN
src/views/student-register/images/new/icon-n-11.png


BIN
src/views/student-register/images/new/title-8.png


BIN
src/views/student-register/images/new/tuangou.png


+ 23 - 6
src/views/student-register/index-apply.module.less

@@ -103,8 +103,8 @@
   }
 
   .giftTip {
-    width: 52px;
-    height: 18px;
+    width: 94px;
+    height: 19px;
   }
 
   .needPrice {
@@ -529,7 +529,7 @@
   }
 
   .memberNumer {
-    margin: 0 12px 8px;
+    margin: 0 12px 14px;
     background: #E8F8FF;
     border-radius: 8px;
     padding: 8px 0 8px 10px;
@@ -558,7 +558,23 @@
 .goodsCell {
   position: relative;
   border-radius: 16px;
-  padding: 16px 14px 0;
+  padding: 14px 16px !important;
+
+
+
+  &.goodsBuyGoods {
+    padding: 10px 14px !important;
+
+    .goodsName {
+      width: 192px;
+      height: 68px;
+    }
+
+    .img {
+      width: 80px;
+      height: 80px;
+    }
+  }
 
   .iconChecked {
     width: 18px;
@@ -615,8 +631,9 @@
   }
 
   .goodsName {
-    width: 188px;
-    height: 81px;
+
+    width: 207px;
+    height: 71px;
     line-height: 0;
   }
 

+ 26 - 29
src/views/student-register/index-apply.tsx

@@ -49,8 +49,9 @@ import MDialog from '@/components/m-dialog';
 import tuangou from './images/new/tuangou.png';
 import icon3 from './images/new/icon-3.png';
 import icon5 from './images/new/icon-5.png';
+import icon10 from './images/new/icon-10.png';
 import icon6 from './images/new/icon-6.png';
-import giftTip from './images/new/icon-4.png';
+import giftTip from './images/new/icon-9.png';
 import iconGift from './images/new/icon-gift.png';
 import dayjs from 'dayjs';
 // import MMessageTip from '@/components/m-message-tip';
@@ -1630,7 +1631,11 @@ export default defineComponent({
             {/* <i class={styles.iconArrow}></i> */}
             <Cell
               border={false}
-              class={styles.goodsCell}
+              class={[
+                styles.goodsCell,
+                forms.registerType === 'SELECT_BUY_GOODS' &&
+                  styles.goodsBuyGoods
+              ]}
               center
               onClick={() => {
                 // console.log(forms.joinType, 'joinType');
@@ -1674,39 +1679,31 @@ export default defineComponent({
                       src={forms.detailVip.goodsUrl || tuangou}
                     />
                     <div class={styles.sectionContent}>
-                      <img src={icon5} class={styles.goodsName} />
-                      {/* <h2>
-
-                        <img src={icon5} class={styles.goodsName} />
-                      </h2>
-                      <p class={[styles.model]}>
-
-                        <p>
-                          <i></i>解决学生不会练、不知练的对错
-                        </p>
-                        <p>
-                          <i></i>家长无法辅导、无需再额外请老师
-                        </p>
-                      </p>
-
-                      <img src={icon6} class={styles.sendInstrument} /> */}
+                      <img
+                        src={
+                          forms.registerType === 'SELECT_BUY_GOODS'
+                            ? icon5
+                            : icon10
+                        }
+                        class={styles.goodsName}
+                      />
                     </div>
                   </div>
                 )
               }}
             </Cell>
 
-            {forms.detailVip.membershipDays ? (
-              <div class={[styles.memberNumer, styles.aiMemberNumber]}>
-                <img src={iconGift} class={styles.iconGift} />
-                <p>
-                  首次购买赠送乐器AI学练工具
-                  <span>{forms.detailVip.membershipDays || 0}</span>天有效期
-                </p>
-              </div>
-            ) : (
+            {/* {forms.detailVip.membershipDays ? ( */}
+            <div class={[styles.memberNumer, styles.aiMemberNumber]}>
+              <img src={iconGift} class={styles.iconGift} />
+              <p>
+                首次购买赠送乐器AI学练工具
+                <span>{forms.detailVip.membershipDays || 0}</span>天有效期
+              </p>
+            </div>
+            {/* ) : (
               ''
-            )}
+            )} */}
           </div>
 
           {/* {forms.joinType === 'tradition' && (
@@ -1758,7 +1755,7 @@ export default defineComponent({
             <MSticky position="bottom" ref={mstickyRef}>
               <div class={styles.paymentContainer}>
                 <div class={styles.payemntPrice}>
-                  {/* <img src={giftTip} class={styles.giftTip} /> */}
+                  <img src={giftTip} class={styles.giftTip} />
                   <div>
                     <span class={styles.needPrice}>
                       <i style="font-style: normal">¥ </i>

+ 146 - 29
src/views/student-register/index.module.less

@@ -12,6 +12,97 @@
     background: url('./images/new/banner-bg.png') no-repeat top center;
     background-size: contain;
   }
+
+
+  .video-content {
+    margin: 16px 14px 2px;
+    padding: 6px;
+    background: linear-gradient(270deg, #5BBEFC 0%, #5CC9FF 100%), #5BBEFC;
+    border-radius: 16px;
+    height: 171.125px;
+    // 154.125
+    box-sizing: content-box;
+
+    .coverImg {
+      width: 100%;
+      height: 100%;
+      border-radius: 30px;
+    }
+
+    video {
+      width: 100%;
+    }
+
+    :global {
+      .video-back {
+        position: absolute;
+        left: 20px;
+        top: 20px;
+        color: #fff;
+        z-index: 99;
+        font-size: 24px;
+        width: 30px;
+        height: 30px;
+        background-color: rgba(0, 0, 0, 0.5);
+        border-radius: 50%;
+        padding: 4px 5px 4px 3px;
+      }
+
+
+      .video-js {
+        width: 100%;
+        height: 100%;
+        border-radius: 30px;
+        overflow: hidden;
+      }
+
+      .vjs-poster {
+        background-size: cover;
+      }
+
+
+      .plyr__poster {
+        background-size: cover;
+      }
+
+      .plyr__control--overlaid {
+        border: 1px solid #fff;
+        background-color: rgba(0, 0, 0, 0.2) !important;
+      }
+
+      .plyr--video .plyr__control:hover {
+        background-color: transparent !important;
+      }
+
+      .video-js {
+        width: 100%;
+        height: 100%;
+      }
+
+      .tcp-skin .vjs-play-progress {
+        background-color: #fff !important;
+      }
+
+      .vjs-slider:focus {
+        box-shadow: none !important;
+      }
+
+      .video-js .vjs-progress-control:hover .vjs-progress-holder {
+        font-size: 1em !important;
+      }
+
+      .vjs-button-icon {
+        height: 42Px !important;
+      }
+
+    }
+
+    .video {
+      position: relative;
+      border-radius: 12px;
+      background-color: transparent !important;
+    }
+  }
 }
 
 .countdownSection {
@@ -85,7 +176,7 @@
 .paymentContainer {
   display: flex;
   align-items: center;
-  justify-content: space-between;
+  justify-content: flex-end;
   font-size: 14px;
   padding: 16px 14px calc(16px + env(safe-area-inset-bottom)) 12px;
   background: #FFFFFF;
@@ -95,11 +186,13 @@
 
   .payemntPrice {
     // line-height: 0;
+    position: absolute;
+    left: 14px;
   }
 
   .giftTip {
-    width: 52px;
-    height: 18px;
+    width: 94px;
+    height: 19px;
   }
 
   .needPrice {
@@ -180,7 +273,8 @@
   }
 
   .title1,
-  .title3 {
+  .title3,
+  .title4 {
     width: 212px;
     height: 22px;
     background: url('./images/new/title-7.png') no-repeat center;
@@ -201,6 +295,11 @@
     background-size: contain;
   }
 
+  .title4 {
+    background: url('./images/new/title-8.png') no-repeat center;
+    background-size: contain;
+  }
+
   .goodsGroup {
     display: flex;
     align-items: center;
@@ -273,6 +372,42 @@
       // }
     }
   }
+
+  .goodsTypeGroup {
+    padding-top: 14px;
+    padding-bottom: 2px;
+
+    .showImg {
+      height: 100px;
+      // width: 100%
+    }
+
+    .memberNumer {
+      margin: 14px 0 0;
+      background: #E8F8FF;
+      border-radius: 8px;
+      padding: 8px 0 8px 10px;
+      display: flex;
+      align-items: center;
+      // font-weight: 600;
+      font-size: 13px;
+      color: #131415;
+      line-height: 18px;
+
+      .iconGift {
+        width: 18px;
+        height: 18px;
+        margin-right: 6px;
+      }
+
+      span {
+        font-size: 15px;
+        color: #F62C2C;
+        padding: 0 5px;
+        font-weight: 600;
+      }
+    }
+  }
 }
 
 .registerForm {
@@ -283,38 +418,20 @@
   overflow: hidden;
 
   .selectStudentGroup {
-    background: #EDF6FD;
-    border-radius: 8px;
-    margin: 20px 14px 0;
-    padding: 8px 0;
-    font-weight: 600;
-    font-size: 16px;
+    margin-left: 10px;
+    font-size: 14px;
     color: #1189FF;
-    // line-height: 22px;
     display: flex;
     align-items: center;
     justify-content: center;
 
-    .studentIcon {
-      display: inline-block;
-      margin-right: 6px;
-      width: 20px;
-      height: 20px;
-      background: url('./images/new/icon-n-5.png') no-repeat center;
-      background-size: contain;
-
-      &.selectStudentGroupChecked {
-        span::after {
-          transform: rotate(180deg);
-        }
-      }
-
-      &.studentIconAdd {
-        background: url('./images/new/icon-n-4.png') no-repeat center;
-        background-size: contain;
+    &.selectStudentGroupChecked {
+      span::after {
+        transform: rotate(180deg);
       }
     }
 
+
     span {
       display: flex;
       align-items: center;
@@ -325,7 +442,7 @@
         content: '';
         width: 9px;
         height: 5px;
-        margin-left: 5px;
+        margin-left: 4px;
         background: url('./images/new/icon-n-6.png') no-repeat center;
         background-size: contain;
       }

+ 418 - 52
src/views/student-register/index.tsx

@@ -20,8 +20,11 @@ import {
   onMounted,
   onUnmounted,
   reactive,
-  ref
+  ref,
+  watch
 } from 'vue';
+import TCPlayer from 'tcplayer.js';
+import 'tcplayer.js/dist/tcplayer.css';
 import qs from 'query-string';
 import {
   state as baseState,
@@ -45,15 +48,17 @@ import MDialog from '@/components/m-dialog';
 // import f3 from './images/new/f-3.png';
 // import iconTip2 from './images/new/icon-tip2.png';
 // import functionBg from './images/new/function-bg.png';
-import tuangou from './images/new/tuangou.png';
+// import tuangou from './images/new/tuangou.png';
 import icon3 from './images/new/icon-3.png';
-import icon5 from './images/new/icon-7.png';
-import icon6 from './images/new/icon-6.png';
-import giftTip from './images/new/icon-4.png';
+// import icon5 from './images/new/icon-7.png';
+// import icon6 from './images/new/icon-6.png';
+import giftTip from './images/new/icon-9.png';
 import iconGift from './images/new/icon-gift.png';
+import icon10 from './images/new/icon-n-10.png';
+import icon11 from './images/new/icon-n-11.png';
 import dayjs from 'dayjs';
 // import MMessageTip from '@/components/m-message-tip';
-import { CurrentTime, useCountDown } from '@vant/use';
+import { CurrentTime, useCountDown, usePageVisibility } from '@vant/use';
 import Payment from '../adapay/payment';
 import QrcodePayment from './qrcode-payment';
 import MImgCode from '@/components/m-img-code';
@@ -63,6 +68,7 @@ import MPopup from '@/components/m-popup';
 import UserAuth from './component/user-auth';
 import MMessageTip from '@/components/m-message-tip';
 import SelectStudent from './modal/select-student';
+import { Timer } from './timer';
 
 const classList: any = [];
 for (let i = 1; i <= 40; i++) {
@@ -113,6 +119,7 @@ export default defineComponent({
   name: 'student-register',
   setup() {
     const route = useRoute();
+    const pageVisibility = usePageVisibility();
     const studentRegisterStore = useStudentRegisterStore();
     const router = useRouter();
     // 初始化学校编号
@@ -212,17 +219,6 @@ export default defineComponent({
       orderTimer: null as any
     });
 
-    /*
-      新用户:
-      autoRegister: true
-      loginType: 'SMS'
-
-      已存在用户:
-      autoRegister: false
-      loginType: 'TOKEN'
-      password: xxx
-    */
-
     const studentInfo = reactive({
       autoRegister: true,
       multiUser: true, // 是否为多用户
@@ -241,10 +237,70 @@ export default defineComponent({
       password: '',
       username: ''
     });
+
+    const videoForms = reactive({
+      introductionVideo: '',
+      introductionVideoTime: 0, // 视频总时长
+      videoBrowsePoint: 0, // 视频最后观看点
+      player: null as any,
+      playerSpeed: 1,
+      intervalFnRef: null as any,
+      videoDetails: [] as any, // 节点列表
+      pointVideo: {} as any, // 需要处理有效的时间段
+      pointVideoTime: 0 // 有效时长
+    });
+
+    // 播放视频总时长
+    const videoIntervalRef = useInterval(1000, {
+      controls: true
+    });
+    videoIntervalRef.pause();
+    const timer = new Timer();
+
     // 页面定时
     const pageTimer = useInterval(1000, { controls: true });
     pageTimer.pause();
 
+    /**
+     * 格式化视屏播放有效时间 - 合并区间
+     * @param intervals [[], []]
+     * @example [[4, 8],[0, 4],[10, 30]]
+     * @returns [[0, 8], [10, 30]]
+     */
+    const formatEffectiveTime = (intervals: any[]) => {
+      const res: any = [];
+      intervals.sort((a, b) => a[0] - b[0]);
+      let prev = intervals[0];
+      for (let i = 1; i < intervals.length; i++) {
+        const cur = intervals[i];
+        if (prev[1] >= cur[0]) {
+          // 有重合
+          prev[1] = Math.max(cur[1], prev[1]);
+        } else {
+          // 不重合,prev推入res数组
+          res.push(prev);
+          prev = cur; // 更新 prev
+        }
+      }
+      res.push(prev);
+      return res;
+    };
+
+    /**
+     * 获取数据有效期
+     * @param intervals [[], []]
+     * @returns 0s
+     */
+    const formatTimer = (intervals: any[]) => {
+      const afterIntervals = formatEffectiveTime(intervals);
+      // console.log(afterIntervals, 'afterIntervals')
+      let time = 0;
+      afterIntervals.forEach((t: any) => {
+        time += t[1] - t[0];
+      });
+      return time;
+    };
+
     const overCountDown = useCountDown({
       time: forms.activeOverTime,
       onFinish() {
@@ -544,6 +600,7 @@ export default defineComponent({
      */
     const onSubmit = async () => {
       forms.submitLoading = true;
+      forms.intervalFnRef.pause();
       try {
         if (checkForm() || checkSubmit()) {
           forms.submitLoading = false;
@@ -614,6 +671,8 @@ export default defineComponent({
           if (forms.joinType === 'tradition') {
             joinType = 'NOT_BUY_INSTRUMENT';
           }
+          // 单独存入时间,为了解决视频播放统计不全的问题
+          await updateStat(pageTimer.counter.value);
           // 更新时间
           const id = await updateStat(
             pageTimer.counter.value,
@@ -638,6 +697,7 @@ export default defineComponent({
         changeTipStatus(forms.isRegister === 'create' ? false : true, false);
       } finally {
         forms.submitLoading = false;
+        forms.intervalFnRef.resume();
       }
     };
 
@@ -919,7 +979,7 @@ export default defineComponent({
         });
         // 创建订单
         const updateStatus = await updateStudentInfo();
-        console.log(updateStatus, 'updateStatus');
+        // console.log(updateStatus, 'updateStatus');
         if (!updateStatus) return;
 
         const result = await request.post(
@@ -1132,18 +1192,56 @@ export default defineComponent({
       schoolId?: string
     ) => {
       try {
+        const videoBrowseData =
+          moreTime.value.length > 0 ? formatEffectiveTime(moreTime.value) : [];
+        const time =
+          videoBrowseData.length > 0 ? formatTimer(videoBrowseData) : 0;
+        // console.log(moreTime.value, videoBrowseData, 'video', time);
+        // const videoCountTime = videoIntervalRef?.counter.value
+        // 判断 视屏播放时间大于视屏播放有效时间则说明数据有问题,进行重置数据
+        const rate = Math.floor(
+          (time / Math.floor(videoForms.player.duration())) * 100
+        );
+
+        const params = {
+          id: forms.saveId,
+          useTime: pageBrowseTime, // 固定5秒
+          joinType,
+          userId,
+          schoolId
+        };
+        let otherParams = {};
+        if (!userId) {
+          otherParams = {
+            videoBrowseData: JSON.stringify(videoBrowseData), // 视屏播放数据
+            videoBrowseDataTime: time || 0, // 有效的视频观看时长
+            videoBrowsePercentage: rate || 0, // 有效的视频观看时长百分比
+            videoBrowseTime: timer.getTime(), // 视频观看时长
+            videoBrowsePoint: videoForms.player.currentTime() // 视频最后观看点 - 向下取整
+          };
+        }
+
+        // console.log(
+        //   videoIntervalRef.counter.value,
+        //   timer.getTime(),
+        //   videoForms.player.currentTime(),
+        //   videoBrowseData,
+        //   'timer count'
+        // );
         const { data } = await requestStudent.post(
           '/edu-app/open/studentRegisterPointRecord/update',
           {
             data: {
-              id: forms.saveId,
-              useTime: pageBrowseTime, // 固定10秒
-              joinType,
-              userId,
-              schoolId
+              ...params,
+              ...otherParams
             }
           }
         );
+
+        // 不相同则说明不是一个人,数据重置
+        if (data !== forms.saveId) {
+          moreTime.value = [];
+        }
         forms.saveId = data;
         return data;
       } catch {
@@ -1196,6 +1294,219 @@ export default defineComponent({
       }
     };
 
+    /**
+     * 视屏累计时长
+     * 1、视屏开始播放时-开始计时
+     * 2、视频暂停时暂停-停止计时
+     * 3、视频加载时-停止计时
+     * 4、视频倍数播放时,时间正常计时
+     * 5、点击视频进度或拖动进度时,时间暂停
+     */
+    const _init = () => {
+      const Button = TCPlayer.getComponent('Button');
+      const BigPlayButton = TCPlayer.getComponent('BigPlayButton');
+      BigPlayButton.prototype.createEl = function () {
+        const el = Button.prototype.createEl.call(this);
+        const _html =
+          '<button><svg width="42px" height="42px" viewBox="0 0 42 42" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><circle stroke="#FFFFFF" stroke-width="1.0112392" fill-opacity="0.3" fill="#000000" stroke-linecap="square" cx="21" cy="21" r="20.5056196"></circle><g  transform="translate(13.000000, 12.000000)" fill="#FFFFFF"><path d="M11.7432433,3.46351367 L18.2071084,14.4520843 C18.7734418,15.4148511 18.4520691,16.6544316 17.4893024,17.2207649 C17.1784947,17.403593 16.8244584,17.5 16.4638651,17.5 L3.53613489,17.5 C2.41915092,17.5 1.51365649,16.5945056 1.51365649,15.4775216 C1.51365649,15.1169283 1.6100635,14.762892 1.79289156,14.4520843 L8.25675667,3.46351367 C8.82309003,2.50074696 10.0626705,2.17937423 11.0254373,2.74570759 C11.3218389,2.92006148 11.5688894,3.16711204 11.7432433,3.46351367 Z" id="三角形" transform="translate(10.000000, 9.000000) rotate(-270.000000) translate(-10.000000, -9.000000) "></path></g></g></svg></button>';
+
+        el.appendChild(
+          TCPlayer.dom.createEl('div', {
+            className: 'vjs-button-icon',
+            innerHTML: _html
+          })
+        );
+        return el;
+      };
+      videoForms.player = TCPlayer('register-video', {
+        appID: '',
+        controls: true,
+        plugins: {}
+      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
+      if (videoForms.player) {
+        videoForms.player.src(
+          videoForms.introductionVideo ||
+            'https://oss.dayaedu.com/ktyq/1715849628836a8856d6a.mp4'
+        ); // url 播放地址
+        videoForms.player.poster(
+          'https://oss.dayaedu.com/ktyq/17158281330381317fd87.png'
+        );
+
+        videoForms.player.on('ready', (item: any) => {
+          // console.log('ready', item);
+          // videoForms.player.pause()
+        });
+        videoForms.player.on('loadedmetadata', () => {
+          console.log('loadedmetadata');
+          // videoForms.loading = false;
+          videoForms.player.currentTime(videoForms.videoBrowsePoint);
+        });
+
+        // 速度变化时
+        videoForms.player.on('ratechange', () => {
+          videoForms.playerSpeed =
+            videoForms.playerSpeed < videoForms.player.playbackRate()
+              ? videoForms.player.playbackRate()
+              : videoForms.playerSpeed;
+        });
+
+        videoForms.player.on('seeking', () => {
+          console.log('seeking');
+          videoIntervalRef.isActive.value && videoIntervalRef.pause();
+          timer.pause();
+        });
+
+        // // 拖动结束时
+        videoForms.player.on('seeked', () => {
+          console.log('seeked');
+          videoIntervalRef.isActive.value && videoIntervalRef.pause();
+          timer.pause();
+        });
+
+        // 正在搜索中
+        videoForms.player.on('waiting', () => {
+          // console.log('waiting pause')
+          videoIntervalRef.isActive.value && videoIntervalRef.pause();
+          timer.pause();
+        });
+
+        // 如何视频在缓存不会触发
+        videoForms.player.on('timeupdate', () => {
+          // console.log('timeupdate', videoForms.player.currentTime());
+          // 判断视频计时器是否暂停,如果暂停则恢复
+          // 添加 「videoForms.player.playing」 是由会跳转到上次播放时间,会触发些方法
+          if (
+            !videoIntervalRef.isActive.value &&
+            videoForms.player.currentTime() > 0 &&
+            !videoForms.player.paused()
+          ) {
+            // console.log('timeupdate play')
+            videoIntervalRef.resume();
+            timer.resume();
+          }
+        });
+
+        // 视屏播放时暂停
+        videoForms.player.on('ended', () => {
+          videoForms.player.pause();
+        });
+
+        // 开始播放
+        videoForms.player.on('play', () => {
+          console.log('play');
+          // 判断视频计时器是否暂停,如果暂停则恢复
+          videoIntervalRef.resume();
+          timer.resume();
+        });
+
+        // 暂停播放
+        videoForms.player.on('pause', () => {
+          console.log('pause', videoIntervalRef.isActive.value);
+
+          videoIntervalRef.pause();
+          timer.pause();
+        });
+
+        videoForms.player.on('fullscreenchange', () => {
+          if (videoForms.player.isFullscreen()) {
+            console.log('fullscreen');
+            const i = document.createElement('i');
+            i.id = 'fullscreen-back';
+            i.className = 'van-icon van-icon-arrow-left video-back';
+            i.addEventListener('click', () => {
+              videoForms.player.exitFullscreen();
+            });
+            document.getElementsByClassName('video-js')[0].appendChild(i);
+          } else {
+            console.log('exitfullscreen');
+            const i = document.getElementById('fullscreen-back');
+            i && i.remove();
+          }
+        });
+      }
+    };
+
+    // 保存零时时间
+    const moreTime: any = ref([]); // 多个观看时间段
+    let tempTime: any = []; // 临时存储时间
+
+    const currentTimer = useInterval(1000, { controls: true });
+    // 监听播放状态,
+    watch(
+      () => videoIntervalRef.isActive.value,
+      (newVal: boolean) => {
+        console.log(videoIntervalRef.isActive.value, 'videoIntervalRef');
+        initVideoCount(newVal);
+      }
+    );
+
+    /**
+     * 初始化视频时长
+     * @param newVal 播放状态
+     * @param repeat 是否为定时发送的
+     */
+    const initVideoCount = (newVal: any, repeat = false) => {
+      // console.log('watch', videoForms.player.currentTime())
+      const initTime = deepClone(tempTime);
+      if (repeat) {
+        if (tempTime.length > 0) {
+          // console.log('join video', tempTime, 'initTime', initTime)
+          tempTime[1] =
+            Math.floor(videoForms.player.currentTime() * 1000) / 1000;
+          // tempTime[1] = videoForms.player.currentTime();
+        }
+      } else {
+        if (newVal) {
+          // console.log(
+          //   videoForms.player.currentTime(),
+          //   'videoForms.player.currentTime()'
+          // );
+          tempTime[0] =
+            Math.floor(videoForms.player.currentTime() * 1000) / 1000;
+          // tempTime[0] = videoForms.player.currentTime();
+        } else {
+          tempTime[1] =
+            Math.floor(videoForms.player.currentTime() * 1000) / 1000;
+          // tempTime[1] = videoForms.player.currentTime();
+        }
+      }
+
+      // console.log(
+      //   newVal,
+      //   repeat,
+      //   tempTime,
+      //   tempTime.length,
+      //   'videoIntervalRef.isActive.value in'
+      // );
+      // console.log(videoForms.player.playbackRate(), 'speed');
+
+      if (tempTime.length >= 2) {
+        // console.log(tempTime, 'tempTime', moreTime.value)
+        // 处理在短时间内的时间差 【视屏拖动,点击可能会导致时间差太大】
+        const diffTime =
+          tempTime[1] -
+            tempTime[0] -
+            currentTimer.counter.value * videoForms.playerSpeed >
+          2;
+        // console.log(diffTime, 'diffTime', currentTimer.counter.value, videoForms.playerSpeed, 'value')
+        // 结束时间,如果 大于开始时间则清除
+        if (tempTime[1] >= tempTime[0] && !diffTime)
+          moreTime.value.push(tempTime);
+        if (repeat) {
+          tempTime = deepClone(initTime);
+        } else {
+          tempTime = [];
+          currentTimer.counter.value = 0;
+        }
+      }
+    };
+
+    watch(pageVisibility, (value: any) => {
+      if (value == 'hidden') {
+        videoForms.player.pause();
+      }
+    });
+
     const pagePointInit = async () => {
       try {
         // 判断是否获取微信code码
@@ -1212,16 +1523,30 @@ export default defineComponent({
         );
         forms.saveId = data.id;
         forms.openId = data.openId;
+
+        moreTime.value = data.videoBrowseData
+          ? JSON.parse(data.videoBrowseData)
+          : [];
+        videoForms.videoBrowsePoint = data.videoBrowsePoint || 0;
+        if (videoForms.player) {
+          videoForms.player.currentTime(data.videoBrowsePoint || 0);
+        }
+
         sessionStorage.setItem('active-open-id', data.openId);
 
         // 间隔多少时间同步数据
         forms.intervalFnRef = useIntervalFn(async () => {
           // 页面时间恢复
+          forms.intervalFnRef.pause();
           pageTimer.counter.value = 0;
           pageTimer.resume();
           // 同步数据时先进行有效时间进行保存
+          initVideoCount(false, true);
+          updateStat();
 
-          await updateStat();
+          timer.resetTime();
+          videoIntervalRef.counter.value = 0;
+          forms.intervalFnRef.resume();
         }, 5000);
       } catch {}
     };
@@ -1283,6 +1608,9 @@ export default defineComponent({
           }
         }
 
+        // 初始化视频播放器
+        _init();
+
         await getRegisterGoods();
       } catch {}
     });
@@ -1321,15 +1649,30 @@ export default defineComponent({
             </div>
           )}
 
+          <div class={[styles.studentSection, styles.studentSectionForm]}>
+            <div class={styles.title4}></div>
+            <div class={styles['video-content']}>
+              <video
+                id="register-video"
+                class={styles['video']}
+                src={'https://oss.dayaedu.com/ktyq/1715849628836a8856d6a.mp4'}
+                playsinline={true}
+                poster={
+                  'https://oss.dayaedu.com/ktyq/17158281330381317fd87.png'
+                }
+                preload="auto"></video>
+            </div>
+          </div>
+
           <div
             class={[
               styles.studentSection,
               styles.studentSectionForm,
-              styles.noSendDay
+              forms.giftVipDay <= 0 && styles.noSendDay
             ]}
             // style={{ display: 'none' }}
           >
-            <div class={styles.title3}></div>
+            <div class={styles.title1}></div>
 
             <Form labelAlign="left" class={styles.registerForm}>
               <Field
@@ -1400,22 +1743,8 @@ export default defineComponent({
                     )
                 }}
               </Field>
-            </Form>
-          </div>
-
-          <div
-            class={[
-              styles.studentSection,
-              styles.studentSectionForm,
-              forms.giftVipDay <= 0 && styles.noSendDay
-            ]}
-            // style={{ display: 'none' }}
-          >
-            <div class={styles.title1}></div>
-
-            <Form labelAlign="left" class={styles.registerForm}>
               {/* 大于等于2,则可以切换学生 */}
-              {forms.studentList.length > 1 && (
+              {/* {forms.studentList.length > 1 && (
                 <div
                   class={[
                     styles.selectStudentGroup,
@@ -1433,7 +1762,7 @@ export default defineComponent({
                       : '新增学生'}
                   </span>
                 </div>
-              )}
+              )} */}
 
               <Field
                 clearable={false}
@@ -1443,8 +1772,24 @@ export default defineComponent({
                 placeholder="请输入学生姓名"
                 autocomplete="off"
                 maxlength={14}
-                v-model={studentInfo.extra.nickname}
-              />
+                v-model={studentInfo.extra.nickname}>
+                {{
+                  extra: () =>
+                    forms.studentList.length > 1 && (
+                      <div
+                        class={[
+                          styles.selectStudentGroup,
+                          forms.showSelectStudent &&
+                            styles.selectStudentGroupChecked
+                        ]}
+                        onClick={() => (forms.showSelectStudent = true)}>
+                        <span>
+                          {forms.studentItem.userId ? '切换' : '新增'}
+                        </span>
+                      </div>
+                    )
+                }}
+              </Field>
               <Field
                 clearable={false}
                 required
@@ -1594,9 +1939,35 @@ export default defineComponent({
                 <div class={styles.goodsInner}>传统方式</div>
               </div>
             </div>
+
+            {forms.joinType && (
+              <div class={styles.goodsTypeGroup}>
+                {forms.joinType === 'digitalize' && (
+                  <>
+                    <img src={icon10} class={styles.showImg} />
+
+                    {forms.detailVip.membershipDays ? (
+                      <div class={styles.memberNumer}>
+                        <img src={iconGift} class={styles.iconGift} />
+                        <p>
+                          首次购买赠送乐器AI学练工具
+                          <span>{forms.detailVip.membershipDays || 0}</span>
+                          天有效期
+                        </p>
+                      </div>
+                    ) : (
+                      ''
+                    )}
+                  </>
+                )}
+                {forms.joinType === 'tradition' && (
+                  <img src={icon11} class={styles.showImg} />
+                )}
+              </div>
+            )}
           </div>
 
-          {forms.joinType === 'digitalize' && (
+          {/* {forms.joinType === 'digitalize' && (
             <div class={[styles.goodsExtra]}>
               <i class={styles.iconArrow}></i>
               <Cell border={false} class={styles.goodsCell}>
@@ -1611,16 +1982,13 @@ export default defineComponent({
                     <div class={styles.section}>
                       <div class={styles.sectionContent}>
                         <h2>
-                          {/* {forms.detailVip.goodsName} */}
                           <img src={icon5} class={styles.goodsName} />
                           <Tag class={styles.brandName}>
-                            {/* {forms.detailVip.brandName} */}
+
                             12个月
                           </Tag>
                         </h2>
                         <p class={[styles.model]}>
-                          {/* 解决学生不会练、不知练的对错、家长无法辅导、无需再额外请老师 */}
-                          {/* {forms.detailVip.description} */}
                           <p>
                             <i></i>解决学生不会练、不知练的对错
                           </p>
@@ -1628,7 +1996,6 @@ export default defineComponent({
                             <i></i>家长无法辅导、无需再额外请老师
                           </p>
                         </p>
-                        {/* <span class={styles.sendInstrument}>赠送课堂乐器</span> */}
                         <img src={icon6} class={styles.sendInstrument} />
                       </div>
                     </div>
@@ -1688,13 +2055,12 @@ export default defineComponent({
                         'C调、木质、高音德式八孔;'}
                       {forms.instrumentCode === 'Woodwind' &&
                         'C调、红木色、树脂或木质;'}
-                      {/* 管数不限,建议20管以上C调加嘴排箫(音域宽,能演奏更多复杂乐曲,不需要重复更换),黑色,要选择单一原调(调性多学生很难掌握),价格由学生根据自身情况确定。 */}
                     </div>
                   </div>
                 )}
               </div>
             </div>
-          )}
+          )} */}
 
           {forms.joinType && (
             <MSticky position="bottom" ref={mstickyRef}>

+ 71 - 0
src/views/student-register/timer.ts

@@ -0,0 +1,71 @@
+export class Timer {
+  private startTime: number;
+  private elapsedTime: number;
+  private timerId: number | null;
+  private isRunning: boolean;
+
+  constructor() {
+    this.startTime = 0;
+    this.elapsedTime = 0;
+    this.timerId = null;
+    this.isRunning = false;
+  }
+
+  start(): void {
+    if (!this.isRunning) {
+      this.isRunning = true;
+      this.startTime = Date.now() - this.elapsedTime;
+      this.timerId = window.setInterval(() => {
+        this.elapsedTime = Date.now() - this.startTime;
+      }, 10); // Update every 10 milliseconds
+    }
+  }
+
+  pause(): void {
+    if (this.isRunning) {
+      this.isRunning = false;
+      if (this.timerId !== null) {
+        clearInterval(this.timerId);
+      }
+    }
+  }
+
+  resume(): void {
+    if (!this.isRunning) {
+      this.start();
+    }
+  }
+
+  resetTime(): void {
+    this.elapsedTime = 0;
+    this.startTime = Date.now() - this.elapsedTime
+  }
+
+  reset(): void {
+    this.isRunning = false;
+    if (this.timerId !== null) {
+      clearInterval(this.timerId);
+    }
+    this.elapsedTime = 0;
+  }
+
+  getTime(): Number {
+    return this.elapsedTime / 1000
+  }
+
+
+
+  private formatTime(ms: number): Object {
+    const milliseconds = ms % 1000;
+    const seconds = Math.floor(ms / 1000) % 60;
+    const minutes = Math.floor(ms / 1000 / 60) % 60;
+    const hours = Math.floor(ms / 1000 / 60 / 60);
+
+    // console.log(`${this.pad(hours)}:${this.pad(minutes)}:${this.pad(seconds)}.${this.pad(milliseconds, 3)}`)
+    return ms / 1000
+  }
+
+  private pad(number: number, digits: number = 2): string {
+    return number.toString().padStart(digits, '0');
+  }
+}

Some files were not shown because too many files changed in this diff