Browse Source

Merge branch 'iteration-20240408-stat' into online

lex 1 year ago
parent
commit
72554877e5

+ 17 - 0
src/App.tsx

@@ -150,8 +150,25 @@ export default defineComponent({
         history.go(0);
       }
     };
+
     onMounted(() => {
       window.addEventListener('message', handleOpen);
+
+      // 禁用右键菜单
+      document.addEventListener('contextmenu', function (event) {
+        event.preventDefault();
+      });
+      // 禁用浏览器快捷键
+      document.addEventListener('keydown', function (event) {
+        // 屏蔽 F12 和 Ctrl+Shift+I
+        if (
+          event.key === 'F12' ||
+          (event.ctrlKey && event.shiftKey && event.key === 'I') ||
+          (event.metaKey && event.altKey && event.key === 'I')
+        ) {
+          event.preventDefault();
+        }
+      });
     });
     onUnmounted(() => {
       window.removeEventListener('message', handleOpen);

+ 3 - 1
src/components/card-preview/listen-modal/index.tsx

@@ -2,6 +2,7 @@ import { defineComponent, ref, watch } from 'vue';
 import styles from './index.module.less';
 import { useUserStore } from '/src/store/modules/users';
 import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
+import { iframeDislableKeyboard } from '/src/utils';
 
 export default defineComponent({
   name: 'song-modal',
@@ -51,9 +52,10 @@ export default defineComponent({
       <div class={styles.musicScore}>
         <iframe
           ref={iframeRef}
-          onLoad={() => {
+          onLoad={(val: any) => {
             emit('setIframe', iframeRef.value);
             isLoaded.value = true;
+            iframeDislableKeyboard(val.target);
           }}
           class={[styles.container, 'musicIframe']}
           frameborder="0"

+ 3 - 1
src/components/card-preview/music-modal/index.tsx

@@ -2,6 +2,7 @@ import { defineComponent, ref } from 'vue';
 import styles from './index.module.less';
 import { useUserStore } from '/src/store/modules/users';
 import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
+import { iframeDislableKeyboard } from '/src/utils';
 
 export default defineComponent({
   name: 'song-modal',
@@ -25,9 +26,10 @@ export default defineComponent({
       <div class={styles.musicScore}>
         <iframe
           ref={iframeRef}
-          onLoad={() => {
+          onLoad={(val: any) => {
             // emit('setIframe', iframeRef.value);
             isLoaded.value = true;
+            iframeDislableKeyboard(val.target);
           }}
           class={[styles.container, 'musicIframe']}
           frameborder="0"

+ 3 - 1
src/components/card-preview/rhythm-modal/index.tsx

@@ -2,6 +2,7 @@ import { defineComponent, ref } from 'vue';
 import styles from './index.module.less';
 import { useUserStore } from '/src/store/modules/users';
 import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
+import { iframeDislableKeyboard } from '/src/utils';
 
 export default defineComponent({
   name: 'song-modal',
@@ -34,9 +35,10 @@ export default defineComponent({
       <div class={styles.musicScore}>
         <iframe
           ref={iframeRef}
-          onLoad={() => {
+          onLoad={(val: any) => {
             // emit('setIframe', iframeRef.value);
             isLoaded.value = true;
+            iframeDislableKeyboard(val.target);
           }}
           class={[styles.container, 'musicIframe']}
           frameborder="0"

+ 90 - 3
src/components/card-preview/video-modal/index.module.less

@@ -58,6 +58,7 @@
 
     .actionWrap {
       display: flex;
+      align-items: center;
     }
 
     .actionBtn {
@@ -75,9 +76,36 @@
     }
 
 
+    .actionBtnSpeed {
+      width: 40px;
+      height: 40px;
+      background-color: transparent;
+      cursor: pointer;
+
+      &>img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+
     .iconReplay {
-      width: 31px;
-      height: 29px;
+      width: 40px;
+      height: 40px;
+      background-color: transparent;
+      cursor: pointer;
+      margin: 0 22px;
+
+      &>img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .iconDownload {
+      width: 40px;
+      height: 40px;
+      margin-left: 5px;
       background-color: transparent;
       cursor: pointer;
 
@@ -90,7 +118,7 @@
 
   .slider {
     width: 100%;
-    padding: 0 20px 0 12px;
+    padding: 0 0 0 12px;
 
     :global {
 
@@ -110,6 +138,65 @@
 
 }
 
+.sliderPopup {
+  position: absolute;
+  z-index: 9999;
+  left: 144px;
+  bottom: 82px;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  height: 252px;
+  width: 59Px;
+  padding: 12Px 0 15Px;
+  background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAI4AAAJcCAMAAAAYSmw3AAAAaVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnbPKNAAAAI3RSTlOzAAUJiqaplqF6V0c4nSevpJN+GAZtX1EQGpqDPTQVc2QhqyTybJ0AAAKuSURBVHja7NRJVsJQAAXRRxMg9BwgGWX/2xT0KCKII5M/uHcFNaqMntm25+mm2uVf7KrN9NxuR8885ozX9Sw9mNXr8Z85p2WV3lTL0+uc1SK9Wqxe5HTz9G7e/ZbTZBDN05xJnYHUk8ec4yGDORx/5hz3GdD+eJ8zOWRQh8ldTp2B1d9zmgyuueV0KUD3lTNPAeafOasUYfWRc1qkCIvTe84yhVhec8ZVClGNLznrFGM9yvAHvKkvObMUYzbKNgXZpk1B2pxTkHOmKcg0mxRkk2ImeFVll4IUFQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAG3twIAAAAAAA5P/aCKqqqqqqqqqqqqqqqtJ+nSQ3CgVRFH2XjwCjHnWoCTXe/yJrUlEljCz7R9riD/Ks4E7yRaRzzjnnnHPOOeecc845zZWQuUZKyEhTJWSqXAnJtVFCNtopITvNlJCZKJSMAvGmZLwhJkrGBJElM4SjDEGtRNQgCJWSUAUQMFYSxoAASiWgBBCQxnFN/uWw1eC2/M+h0cAa7nNCqUGVoZPDaqEBLVZ0c9iXGky552MOodFAmkA/B64axBUe5jA56OUOE+6IjvFCL7UY0yG6Ql3oZYo68DwH1qfmXS/w3pzWfCQeyGb1Ja8K/ZKiyi/1LOMB8UTWFzcH4+wBnhCRsmNEDbFErPW3e3ZEE9FCrm+p6THkWHu29BlyjD1XIthyCEt9YUMEaw7tFz0XIthzaMvnz3YUew7tQZ86ZsSx53Ce6hP5mqcsOfE9eSCePYdVpQeWhhqEwX6hnrLFQFjcej2HFgthchupY3rGRNjcCt2pVtgIo1lxV7PHSFjtl/orP2Ml7E7HuTRvTtiJH9G2/AiRlD84lRqqt/0KYQAAAABJRU5ErkJggg==') no-repeat top center;
+  background-size: contain;
+
+  .iconAdd,
+  .iconCut {
+    display: inline-block;
+    width: 24Px;
+    height: 24Px;
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAABMlBMVEUAAAAkqP8ckv8zu/8op/81v/8ajv8elP82v/8rrv8elP4npv8yuf8elP8elf81v/8sr/8elf8vuv8dlv8srv8jn/8zu/8wtf82wP8oqP8npf8Zjf8mpP8Zjf8hmv8bj/40vP8wtP81wP8Zjf41vv8sr/8jnf8aj/0zuf8dlf81vv8rq/4kof4vsf8imP8aj/////8usv8qrP4jn/4inP4dlP4yuf8el/4srv8pqv4gmf4bkf40vf8ssP4wtP8lov4koP4xt/8npv4mpP4zu/8hmv4ajv4ckv4wtv4op/58x/5+y/6Cxv5/zf57xP6Dx/6BxP7u+P/t9/6a1f6Bz/56w/5iuP5zwP5Bpv4yoP6x3/6w3f6d2/6Wzv5tyf5pwv5nvv5svP5Rsv5Pr/5Mq/43p/49/opPAAAAMHRSTlMACoRW+Pf38dTUurGDWEhHR0cjI/b28fHr6+vr19fQ0Lq6sbGoqKiolZWGhoZSUlKk1yinAAAB+UlEQVQ4y43S6VbaQACG4S9E9h3c933fatISI4sWYxUjAVQQ99r2/m+hGQfGMJNEHw4M+XhPTn4AjpQNLM1MhUJTM0uBrAQ/qbXQmUNoLQUve3Nngrk9uMnHjlzF8hAkw0cewklwAr98BDBg47uvjYH72oNx1/WuHfdOFgqFu3qj8bfgiT13Pmy3DbNhtr3jcB5UTNe7DdM0f6s69dRqPemcGG13dV3t2G2737b3bW2+3gUxaxh/LHJfo+eWxLcGZxa2lKqqHcsyuyph2G8aq71rdpL/yar9tW5ZbypTJ3Fd5a0C0oiiKG9W51hhaKzwRiRkFeJfVRFiQRbbx4IaiWvivo14RUBjcY9julKlKtXXGvVA4ofexWuV/T6NyWrf9b6raxZMYuhnX8s9brFgyBE33eOmI5646PN6DBZMIHpIXJCPl0vqnjT3vYuXj9+jiB8KLkl8Je5xbB0Irt5jcd9C5utxBtKwph1oBDtprPH7sASsaFpJs5XYSeMSv68AyJR4NyS+EeYMbNFvHBrzaxTEDj8/k/iZX3fwboGby4/N5mOZGxdA5cbKnxrLoUcunxMn596nDCZx8okEHNZ/+FrHgIRfmwBHHj31MCpDkJsvntqvov0eOOdzcCNHioKIDC/p5aCzDC6n4UdKby5GxoPB8cjiZlrCoP+meld2tFTGwgAAAABJRU5ErkJggg==') no-repeat center;
+    background-size: contain;
+    flex-shrink: 0;
+    cursor: pointer;
+
+    &.disabled {
+      opacity: 0.7;
+    }
+  }
+
+  .iconCut {
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAABGlBMVEUAAAAkqP8zu/8op/8ajv8aj/8yuP8elP4npv4dlP8elP8yuP8elP81v/8sr/8jnf8Zjf81v/81v/8jn/8zu/8wtf8gl/82wP8oqP8npf83wP8qqv8mpP8Zjf81vf8tsv8hmv8bj/41wP8qqv8lof8Zjf41vv8sr/8jnf8aj/0zuf8dlf8bjf00vP8xt/8zvf8stv8dmf8dkv8srv8trf8qq/7///8tsP4jn/4us/8srv8gmf4lov4yuf8dlf4wt/8inf4hm/4ck/4bkf4wtP8koP4mpf4fl/4op/40vP8ajv4pqf4zuv98x/5+y/6Bxf40vv80u/6Dx/6a1f5owP5PsP4/p/5zwP4oo/4yoP7u+P7u9/5svP5it/4TfkURAAAANXRSTlMACoT49+66uoaDWFJSR0dHR/j29vHx8evr69fX19fQ0NDQsbGxsaioqKiVlYZYWCMjIyP29l0yDtwAAAGnSURBVDjLjc6HVsIwGAXg27I37r33nkilWloXCGK17OH7v4YpSKSQtPnS5v7n5J6cYIyUPt1bC/j9gbW907QEN+kjf2mE/ygNnvhWacJWHCzXoWem0DUmRAPPHIEoxkSeXETgcPJEaGSx88Rxr6ZpaqOtcUVARTWt0DBNs06yoBGTSd99NV8gXeKnwDV/hYGQqrZNoqerfCH0xVXVaNr3GqqbOGybhlG3LKunG642QaQMXW9altkmaRhk42UKwKGu6xXLquteDgFpRlGUutV8VbzMSEj1h0ZL8ZZC+FVYGLsPwnaxSudWrcJQa9HCKpbehqp3TFVaWIKPzl12uUsLPviKQx12uUMLPiwWi+XBzHsGOR9YxEqZqn0y1MrUCnYy5Uyfd+4gnBEWRlK8nIR0kxN0KwEHuVzWnrNeeQAgmRWUBLEh1t2ALXYvJIa+oEg3iIHLuRdPc5f4c/6Sz+fd/3NQct6DjBHHj66O4SC7dWWMOZv94Jg9w4SL4Ps3WeRzZvACLLH1d8Iu/Od6DDyJ/emvEdP7CbiREvL28sLU1MLytpyQ4PQLtc9vYI2HRk0AAAAASUVORK5CYII=') no-repeat center;
+    background-size: contain;
+  }
+
+  .sliderPoint {
+    background: #FFFFFF;
+    box-shadow: 0px 2px 4px 0px rgba(102, 102, 102, 0.77);
+    border-radius: 14px;
+    font-size: 14Px;
+    font-weight: 500;
+    height: 22Px;
+    color: #198CFE;
+    min-width: 40Px;
+    text-align: center;
+    vertical-align: text-bottom;
+
+    span {
+      font-size: 12Px;
+    }
+  }
+
+  :global {
+    .n-slider {
+      margin: 7px 0;
+      padding: 0;
+    }
+  }
+}
+
 // .videoWrap {
 //   width: 100%;
 //   height: 100%;

+ 103 - 24
src/components/card-preview/video-modal/index.tsx

@@ -9,6 +9,7 @@ import iconplay from '@views/attend-class/image/icon-pause.png';
 import iconpause from '@views/attend-class/image/icon-play.png';
 import iconReplay from '@views/attend-class/image/icon-replay.png';
 import iconPreviewDownload from '@views/attend-class/image/icon-preivew-download.png';
+import iconSpeed from '@views/attend-class/image/icon-speed.png';
 import { NSlider, useMessage } from 'naive-ui';
 import { saveAs } from 'file-saver';
 
@@ -46,7 +47,12 @@ export default defineComponent({
       currentTime: '00:00',
       durationNum: 0,
       duration: '00:00',
-      showBar: true
+      showBar: true,
+      speedControl: false,
+      speedStyle: {
+        left: '1px'
+      },
+      defaultSpeed: 1 // 默认速度
     });
     const videoRef = ref();
     const videoItem = ref();
@@ -66,15 +72,18 @@ export default defineComponent({
     //
     const toggleHideControl = (isShow: false) => {
       videoFroms.showBar = isShow;
+      videoFroms.speedControl = false;
     };
 
     const onReplay = () => {
+      videoFroms.speedControl = false;
       if (!videoItem.value) return;
       videoItem.value.currentTime(0);
     };
 
     // 切换音频播放
     const onToggleVideo = (e?: MouseEvent) => {
+      videoFroms.speedControl = false;
       e?.stopPropagation();
       if (videoFroms.paused) {
         videoItem.value.play();
@@ -104,11 +113,7 @@ export default defineComponent({
         });
     };
 
-    onMounted(() => {
-      videoItem.value = TCPlayer(videoID, {
-        appID: '',
-        controls: false
-      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
+    const __init = () => {
       if (videoItem.value) {
         videoItem.value.poster(poster.value); // 封面
         videoItem.value.src(isEmtry.value ? '' : src.value); // url 播放地址
@@ -140,6 +145,14 @@ export default defineComponent({
           emit('ended');
         });
       }
+    };
+
+    onMounted(() => {
+      videoItem.value = TCPlayer(videoID, {
+        appID: '',
+        controls: false
+      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
+      __init();
     });
     expose({
       // changePlayBtn,
@@ -175,18 +188,17 @@ export default defineComponent({
                   <img class={styles.playIcon} src={iconpause} />
                 )}
               </button>
-            </div>
-            <div class={styles.time}>
-              <div
-                class="plyr__time plyr__time--current"
-                aria-label="Current time">
-                {videoFroms.currentTime}
-              </div>
-              <span class={styles.line}>/</span>
+
+              <button class={styles.iconReplay} onClick={onReplay}>
+                <img src={iconReplay} />
+              </button>
+
               <div
-                class="plyr__time plyr__time--duration"
-                aria-label="Duration">
-                {videoFroms.duration}
+                class={styles.actionBtnSpeed}
+                onClick={() => {
+                  videoFroms.speedControl = !videoFroms.speedControl;
+                }}>
+                <img src={iconSpeed} />
               </div>
             </div>
           </div>
@@ -198,6 +210,7 @@ export default defineComponent({
               max={videoFroms.durationNum}
               tooltip={false}
               onUpdate:value={(val: number) => {
+                videoFroms.speedControl = false;
                 videoItem.value.currentTime(val);
                 videoFroms.currentTimeNum = val;
                 videoFroms.currentTime = timeFormat(Math.round(val || 0));
@@ -206,21 +219,87 @@ export default defineComponent({
           </div>
 
           <div class={styles.actions}>
+            <div class={styles.time}>
+              <div
+                class="plyr__time plyr__time--current"
+                aria-label="Current time">
+                {videoFroms.currentTime}
+              </div>
+              <span class={styles.line}>/</span>
+              <div
+                class="plyr__time plyr__time--duration"
+                aria-label="Duration">
+                {videoFroms.duration}
+              </div>
+            </div>
             <div class={styles.actionWrap}>
-              <button class={styles.iconReplay} onClick={onReplay}>
-                <img src={iconReplay} />
-              </button>
               {props.isDownload && (
-                <button
-                  class={styles.iconReplay}
-                  onClick={onDownload}
-                  style={{ marginLeft: '15px' }}>
+                <button class={styles.iconDownload} onClick={onDownload}>
                   <img src={iconPreviewDownload} />
                 </button>
               )}
             </div>
           </div>
         </div>
+
+        <div
+          style={{
+            display: videoFroms.speedControl ? 'block' : 'none'
+          }}>
+          <div
+            class={styles.sliderPopup}
+            onClick={(e: Event) => {
+              e.stopPropagation();
+            }}>
+            <i
+              class={styles.iconAdd}
+              onClick={() => {
+                if (videoFroms.defaultSpeed >= 1.5) {
+                  return;
+                }
+
+                if (videoItem.value) {
+                  videoFroms.defaultSpeed =
+                    (videoFroms.defaultSpeed * 10 + 1) / 10;
+                  videoItem.value.playbackRate(videoFroms.defaultSpeed);
+                }
+              }}></i>
+            <NSlider
+              value={videoFroms.defaultSpeed}
+              step={0.1}
+              max={1.5}
+              min={0.6}
+              vertical
+              tooltip={false}
+              onUpdate:value={(val: number) => {
+                videoFroms.defaultSpeed = val;
+                if (videoItem.value) {
+                  videoItem.value.playbackRate(videoFroms.defaultSpeed);
+                }
+              }}>
+              {{
+                thumb: () => (
+                  <div class={styles.sliderPoint}>
+                    {videoFroms.defaultSpeed}
+                    <span>x</span>
+                  </div>
+                )
+              }}
+            </NSlider>
+            <i
+              class={[styles.iconCut]}
+              onClick={() => {
+                if (videoFroms.defaultSpeed <= 0.6) {
+                  return;
+                }
+                if (videoItem.value) {
+                  videoFroms.defaultSpeed =
+                    (videoFroms.defaultSpeed * 10 - 1) / 10;
+                  videoItem.value.playbackRate(videoFroms.defaultSpeed);
+                }
+              }}></i>
+          </div>
+        </div>
       </div>
     );
   }

+ 3 - 1
src/components/layout/imGroup.tsx

@@ -2,6 +2,7 @@ import { defineComponent, ref } from 'vue';
 import { useUserStore } from '/src/store/modules/users';
 import styles from './index.module.less';
 import { NSpin } from 'naive-ui';
+import { iframeDislableKeyboard } from '/src/utils';
 
 export default defineComponent({
   name: 'imGroup',
@@ -26,8 +27,9 @@ export default defineComponent({
         <NSpin show={!isLoaded.value}>
           <iframe
             ref={iframeRef}
-            onLoad={() => {
+            onLoad={(val: any) => {
               isLoaded.value = true;
+              iframeDislableKeyboard(val.target);
             }}
             class={[styles.container]}
             frameborder="0"

+ 9 - 1
src/components/layout/index.tsx

@@ -29,7 +29,12 @@ import TimerMeter from '../timerMeter';
 import { useRoute, useRouter } from 'vue-router';
 import { vaildUrl } from '/src/utils/urlUtils';
 import ChioseModal from '/src/views/home/modals/chioseModal';
-import { eventGlobal, px2vw, px2vwH } from '@/utils/index';
+import {
+  eventGlobal,
+  iframeDislableKeyboard,
+  px2vw,
+  px2vwH
+} from '@/utils/index';
 import PlaceholderTone from './modals/placeholderTone';
 import { state } from '/src/state';
 import PreviewWindow from '/src/views/preview-window';
@@ -766,6 +771,9 @@ export default defineComponent({
               scrolling="no"
               frameborder="0"
               width="100%"
+              onLoad={(val: any) => {
+                iframeDislableKeyboard(val.target);
+              }}
               height={'650px'}></iframe>
           </div>
         </NModal>

+ 90 - 68
src/utils/index.ts

@@ -374,7 +374,6 @@ export function checkUrlType(urlType: string) {
   return 'video';
 }
 
-
 const instruments: any = {
   'Acoustic Grand Piano': '大钢琴',
   'Bright Acoustic Piano': '明亮的钢琴',
@@ -581,13 +580,13 @@ const instruments: any = {
 export const getInstrumentName = (name = '') => {
   name = name.toLocaleLowerCase().replace(/ /g, '');
   if (!name) return '';
-  for (let key in instruments) {
+  for (const key in instruments) {
     const _key = key.toLocaleLowerCase().replace(/ /g, '');
     if (_key.includes(name)) {
       return instruments[key];
     }
   }
-  for (let key in instruments) {
+  for (const key in instruments) {
     const _key = key.toLocaleLowerCase().replace(/ /g, '');
     if (name.includes(_key)) {
       return instruments[key];
@@ -651,72 +650,95 @@ export const sortMusical = (name: string, index: number) => {
 
 // 课堂乐器声轨名称集合
 const trackNames: any = {
-  "Piccolo": "Tenor Recorder",
-  "flute": "Flute",
-  "Flute": "Flute",
-  "Flute 1": "Flute",
-  "Flute 2": "Flute",
-  "Oboe": "Clarinet",
-  "oboe": "Clarinet",
-  "clarinet": "Clarinet",
-  "Clarinet in Bb": "Clarinet",
-  "Clarinet in Bb 1": "Clarinet",
-  "Clarinet in Bb 2": "Clarinet",
-  "Alto Clarinet in Eb": "Clarinet",
-  "Bass Clarinet in Bb": "Clarinet",
-  "Bassoon": "Bassoon",
-  "Alto Saxophone": "Alto Saxophone",
-  "Tenor Saxophone": "Alto Saxophone",
-  "Baritone Saxophone": "Alto Saxophone",
-  "altosaxophone": "Alto Saxophone",
-  "tenorsaxophone": "Alto Saxophone",
-  "saxophone": "Alto Saxophone",
-  "Trumpet in Bb 1": "Trumpet",
-  "Trumpet in Bb 2": "Trumpet",
-  "trumpet": "Trumpet",
-  "Horn in F": "Horn",
-  "Horn in F 1": "Horn",
-  "Horn in F 2": "Horn",
-  "horn": "Horn",
-  "trombone": "Trombone",
-  "Trombone 1": "Trombone",
-  "Trombone 2": "Trombone",
-  "Trombone 3": "Trombone",
-  "Euphonium": "Baritone",
-  "upbasshorn": "Baritone",
-  "Tuba": "Tuba",
-  "tuba": "Tuba",
-  "Chimes": "Chimes",
-  "Bells": "Bells",
-  "Xylophone": "Xylophone",
-  "Snare Drum": "Snare Drum",
-  "Bass Drum": "Bass Drum",
-  "Triangle": "Triangle",
-  "Suspended Cymbal": "Suspended Cymbal",
-  "Crash Cymbals": "Crash Cymbals",
-  "Concert Toms": "Concert Toms",
-  "Timpani": "Timpani",
-  "Drum Set": "Drum Set",
-  "Marimba": "Marimba",
-  "Vibraphone": "Vibraphone",
-  "Tubular Bells": "Tubular Bells",
-  "Mallets": "Mallets",
-  "recorder": "Piccolo",
-  "tenorrecorder": "piccolo",
-  "melodica": "melodica",
-  "hulusiFlute": "hulusiFlute",
-  "panflute": "panflute",
-  "ukulele": "ukulele",
-  "mouthorgan": "mouthorgan",
-  "piano": "piano",
-  "woodwind": "Woodwind",
-  "panpipes": "Panpipes",
-  "ocarina": "Ocarina",
-  "nai": "Nai",
-  "BaroqueRecorder": "Baroque Recorder",
+  Piccolo: 'Tenor Recorder',
+  flute: 'Flute',
+  Flute: 'Flute',
+  'Flute 1': 'Flute',
+  'Flute 2': 'Flute',
+  Oboe: 'Clarinet',
+  oboe: 'Clarinet',
+  clarinet: 'Clarinet',
+  'Clarinet in Bb': 'Clarinet',
+  'Clarinet in Bb 1': 'Clarinet',
+  'Clarinet in Bb 2': 'Clarinet',
+  'Alto Clarinet in Eb': 'Clarinet',
+  'Bass Clarinet in Bb': 'Clarinet',
+  Bassoon: 'Bassoon',
+  'Alto Saxophone': 'Alto Saxophone',
+  'Tenor Saxophone': 'Alto Saxophone',
+  'Baritone Saxophone': 'Alto Saxophone',
+  altosaxophone: 'Alto Saxophone',
+  tenorsaxophone: 'Alto Saxophone',
+  saxophone: 'Alto Saxophone',
+  'Trumpet in Bb 1': 'Trumpet',
+  'Trumpet in Bb 2': 'Trumpet',
+  trumpet: 'Trumpet',
+  'Horn in F': 'Horn',
+  'Horn in F 1': 'Horn',
+  'Horn in F 2': 'Horn',
+  horn: 'Horn',
+  trombone: 'Trombone',
+  'Trombone 1': 'Trombone',
+  'Trombone 2': 'Trombone',
+  'Trombone 3': 'Trombone',
+  Euphonium: 'Baritone',
+  upbasshorn: 'Baritone',
+  Tuba: 'Tuba',
+  tuba: 'Tuba',
+  Chimes: 'Chimes',
+  Bells: 'Bells',
+  Xylophone: 'Xylophone',
+  'Snare Drum': 'Snare Drum',
+  'Bass Drum': 'Bass Drum',
+  Triangle: 'Triangle',
+  'Suspended Cymbal': 'Suspended Cymbal',
+  'Crash Cymbals': 'Crash Cymbals',
+  'Concert Toms': 'Concert Toms',
+  Timpani: 'Timpani',
+  'Drum Set': 'Drum Set',
+  Marimba: 'Marimba',
+  Vibraphone: 'Vibraphone',
+  'Tubular Bells': 'Tubular Bells',
+  Mallets: 'Mallets',
+  recorder: 'Piccolo',
+  tenorrecorder: 'piccolo',
+  melodica: 'melodica',
+  hulusiFlute: 'hulusiFlute',
+  panflute: 'panflute',
+  ukulele: 'ukulele',
+  mouthorgan: 'mouthorgan',
+  piano: 'piano',
+  woodwind: 'Woodwind',
+  panpipes: 'Panpipes',
+  ocarina: 'Ocarina',
+  nai: 'Nai',
+  BaroqueRecorder: 'Baroque Recorder'
 };
 
 /** 声轨track转换成乐器code */
 export const trackToCode = (track: any) => {
-  return trackNames[track] || track
-}
+  return trackNames[track] || track;
+};
+export const iframeDislableKeyboard = (iframeDom: any) => {
+  // 在 iframe 内部注入脚本禁用右键菜单
+  const script = document.createElement('script');
+  script.innerHTML = `
+      document.addEventListener('contextmenu', function(e) {
+          e.preventDefault();
+      });
+
+      document.addEventListener('keydown', function (event) {
+        // 屏蔽 F12 和 Ctrl+Shift+I
+        if (
+          event.key === 'F12' ||
+          (event.ctrlKey && event.shiftKey && event.key === 'I') ||
+          (event.metaKey && event.altKey && event.key === 'I')
+        ) {
+          event.preventDefault();
+        }
+      });
+            `;
+  if (iframeDom.contentWindow.document.body) {
+    iframeDom?.contentDocument?.body.appendChild(script);
+  }
+};

+ 3 - 1
src/views/attend-class/component/musicScore.tsx

@@ -4,6 +4,7 @@ import styles from './musicScore.module.less';
 import { usePageVisibility } from '@vant/use';
 import { useUserStore } from '/src/store/modules/users';
 import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
+import { iframeDislableKeyboard } from '/src/utils';
 
 export default defineComponent({
   name: 'musicScore',
@@ -118,9 +119,10 @@ export default defineComponent({
       <div class={styles.musicScore}>
         <iframe
           ref={iframeRef}
-          onLoad={() => {
+          onLoad={(val: any) => {
             emit('setIframe', iframeRef.value);
             isLoaded.value = true;
+            iframeDislableKeyboard(val.target);
           }}
           class={[styles.container, 'musicIframe']}
           frameborder="0"

+ 3 - 1
src/views/attend-class/component/rhythm-modal/index.tsx

@@ -2,6 +2,7 @@ import { defineComponent, ref, watch } from 'vue';
 import styles from './index.module.less';
 import { useUserStore } from '/src/store/modules/users';
 import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
+import { iframeDislableKeyboard } from '/src/utils';
 
 export default defineComponent({
   name: 'song-modal',
@@ -44,9 +45,10 @@ export default defineComponent({
       <div class={styles.musicScore}>
         <iframe
           ref={iframeRef}
-          onLoad={() => {
+          onLoad={(val: any) => {
             emit('setIframe', iframeRef.value);
             isLoaded.value = true;
+            iframeDislableKeyboard(val.target);
           }}
           class={[styles.container, 'musicIframe']}
           frameborder="0"

+ 3 - 1
src/views/attend-class/component/roll-call/pen.tsx

@@ -2,6 +2,7 @@ import { defineComponent, toRefs, ref, PropType, reactive } from 'vue';
 import styles from './pen.module.less';
 import { ToolType } from '../../index';
 import { NButton, NModal, NSpace } from 'naive-ui';
+import { iframeDislableKeyboard } from '/src/utils';
 
 export default defineComponent({
   name: 'pen-page',
@@ -53,8 +54,9 @@ export default defineComponent({
           width="100vw"
           height="100vh"
           src={src}
-          onLoad={() => {
+          onLoad={(val: any) => {
             firstRender.value = false;
+            iframeDislableKeyboard(val.target);
           }}></iframe>
         <div class={styles.rightItem} onClick={() => (modal.status = true)}>
           <svg

+ 3 - 1
src/views/attend-class/component/tools/pen.tsx

@@ -2,6 +2,7 @@ import { defineComponent, toRefs, ref, PropType, reactive } from 'vue';
 import styles from './pen.module.less';
 import { ToolType } from '../../index';
 import { NButton, NModal, NSpace } from 'naive-ui';
+import { iframeDislableKeyboard } from '/src/utils';
 
 export default defineComponent({
   name: 'pen-page',
@@ -59,7 +60,7 @@ export default defineComponent({
           width="100vw"
           height="100vh"
           src={src}
-          onLoad={() => {
+          onLoad={(val: any) => {
             firstRender.value = false;
             if (props.type === 'call') {
               const iframeRef: any = document.getElementById(penIframeRefId);
@@ -67,6 +68,7 @@ export default defineComponent({
                 iframeRef.contentWindow.renderData(props.callStudents);
               }
             }
+            iframeDislableKeyboard(val.target);
           }}></iframe>
         {props.type !== 'call' && (
           <div class={styles.rightItem} onClick={() => (modal.status = true)}>

+ 121 - 27
src/views/attend-class/component/video-play.tsx

@@ -15,7 +15,10 @@ import { ref } from 'vue';
 import styles from './video.module.less';
 import iconplay from '../image/icon-pause.png';
 import iconpause from '../image/icon-play.png';
-import iconReplay from '../image/icon-replay.png';
+// import iconReplay from '../image/icon-replay.png';
+import iconLoop from '../image/icon-loop.svg';
+import iconLoopActive from '../image/icon-loop-active.svg';
+import iconSpeed from '../image/icon-speed.png';
 import { NSlider } from 'naive-ui';
 
 export default defineComponent({
@@ -55,11 +58,18 @@ export default defineComponent({
       durationNum: 0,
       duration: '00:00',
       showBar: true,
-      showAction: true
+      showAction: true,
+      loop: false,
+      speedControl: false,
+      speedStyle: {
+        left: '1px'
+      },
+      defaultSpeed: 1 // 默认速度
     });
     const videoRef = ref();
     const videoItem = ref();
     const videoID = ref('video' + Date.now() + Math.floor(Math.random() * 100));
+    const speedBtnId = 'speed' + Date.now() + Math.floor(Math.random() * 100);
 
     // 对时间进行格式化
     const timeFormat = (num: number) => {
@@ -83,9 +93,11 @@ export default defineComponent({
     //
     const toggleHideControl = (isShow: false) => {
       videoFroms.showBar = isShow;
+      videoFroms.speedControl = false;
     };
 
     const onReplay = () => {
+      videoFroms.speedControl = false;
       if (!videoItem.value) return;
       videoItem.value.currentTime(0);
     };
@@ -103,18 +115,15 @@ export default defineComponent({
       emit('togglePlay', videoFroms.paused);
     };
 
-    onMounted(() => {
-      videoItem.value = TCPlayer(videoID.value, {
-        appID: '',
-        controls: false
-      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
+    const __init = () => {
       if (videoItem.value) {
         videoItem.value.poster(props.item.coverImg); // 封面
         videoItem.value.src(item.value.content); // url 播放地址
-
+        videoItem.value.playbackRate(videoFroms.defaultSpeed);
         // 初步加载时
         videoItem.value.one('loadedmetadata', () => {
-          console.log(' Loading metadata');
+          // console.log(' Loading metadata');
+          videoItem.value.playbackRate(videoFroms.defaultSpeed);
 
           // 获取时长
           videoFroms.duration = timeFormat(
@@ -171,6 +180,20 @@ export default defineComponent({
           console.log(e, 'error');
         });
       }
+    };
+
+    onMounted(() => {
+      videoItem.value = TCPlayer(videoID.value, {
+        appID: '',
+        controls: false
+      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
+
+      __init();
+
+      document.getElementById(speedBtnId)?.addEventListener('click', e => {
+        e.stopPropagation();
+        videoFroms.speedControl = !videoFroms.speedControl;
+      });
     });
     const stop = () => {
       videoItem.value.currentTime(0);
@@ -191,11 +214,13 @@ export default defineComponent({
 
     watch(
       () => props.item,
-      (val, oldVal) => {
+      () => {
         videoItem.value.pause();
         videoItem.value.currentTime(0);
-        videoItem.value.poster(props.item.coverImg); // 封面
-        videoItem.value.src(item.value.content); // url 播放地址
+        // videoItem.value.poster(props.item.coverImg); // 封面
+        // videoItem.value.src(item.value.content); // url 播放地址
+        __init();
+
         videoFroms.paused = true;
       }
     );
@@ -204,6 +229,7 @@ export default defineComponent({
       () => {
         // console.log(props.showModel, 'props.showModel')
         videoFroms.showAction = props.showModel;
+        videoFroms.speedControl = false;
       }
     );
     expose({
@@ -223,6 +249,7 @@ export default defineComponent({
           playsinline
           webkit-playsinline
           x5-video-player-type="h5"></video>
+        <div class={styles.videoPop}></div>
 
         <div
           class={[
@@ -240,6 +267,7 @@ export default defineComponent({
               <div
                 class={styles.actionBtn}
                 onClick={() => {
+                  videoFroms.speedControl = false;
                   onToggleVideo();
                 }}>
                 {videoFroms.paused ? (
@@ -248,18 +276,13 @@ export default defineComponent({
                   <img class={styles.playIcon} src={iconpause} />
                 )}
               </div>
-            </div>
-            <div class={styles.time}>
-              <div
-                class="plyr__time plyr__time--current"
-                aria-label="Current time">
-                {videoFroms.currentTime}
-              </div>
-              <span class={styles.line}>/</span>
-              <div
-                class="plyr__time plyr__time--duration"
-                aria-label="Duration">
-                {videoFroms.duration}
+
+              <button class={styles.iconReplay} onClick={onReplay}>
+                <img src={iconLoop} />
+              </button>
+
+              <div class={styles.actionBtnSpeed} id={speedBtnId}>
+                <img src={iconSpeed} />
               </div>
             </div>
           </div>
@@ -271,6 +294,7 @@ export default defineComponent({
               max={videoFroms.durationNum}
               tooltip={false}
               onUpdate:value={(val: number) => {
+                videoFroms.speedControl = false;
                 videoItem.value.currentTime(val);
                 videoFroms.currentTimeNum = val;
                 videoFroms.currentTime = timeFormat(Math.round(val || 0));
@@ -280,12 +304,82 @@ export default defineComponent({
 
           <div class={styles.actions}>
             <div class={styles.actionWrap}>
-              <button class={styles.iconReplay} onClick={onReplay}>
-                <img src={iconReplay} />
-              </button>
+              <div class={styles.time}>
+                <div
+                  class="plyr__time plyr__time--current"
+                  aria-label="Current time">
+                  {videoFroms.currentTime}
+                </div>
+                <span class={styles.line}>/</span>
+                <div
+                  class="plyr__time plyr__time--duration"
+                  aria-label="Duration">
+                  {videoFroms.duration}
+                </div>
+              </div>
             </div>
           </div>
         </div>
+
+        <div
+          style={{
+            display: videoFroms.speedControl ? 'block' : 'none'
+          }}>
+          <div
+            class={styles.sliderPopup}
+            onClick={(e: Event) => {
+              e.stopPropagation();
+            }}>
+            <i
+              class={styles.iconAdd}
+              onClick={() => {
+                if (videoFroms.defaultSpeed >= 1.5) {
+                  return;
+                }
+
+                if (videoItem.value) {
+                  videoFroms.defaultSpeed =
+                    (videoFroms.defaultSpeed * 10 + 1) / 10;
+                  videoItem.value.playbackRate(videoFroms.defaultSpeed);
+                }
+              }}></i>
+            <NSlider
+              value={videoFroms.defaultSpeed}
+              step={0.1}
+              max={1.5}
+              min={0.6}
+              vertical
+              tooltip={false}
+              onUpdate:value={(val: number) => {
+                videoFroms.defaultSpeed = val;
+
+                if (videoItem.value) {
+                  videoItem.value.playbackRate(videoFroms.defaultSpeed);
+                }
+              }}>
+              {{
+                thumb: () => (
+                  <div class={styles.sliderPoint}>
+                    {videoFroms.defaultSpeed}
+                    <span>x</span>
+                  </div>
+                )
+              }}
+            </NSlider>
+            <i
+              class={[styles.iconCut]}
+              onClick={() => {
+                if (videoFroms.defaultSpeed <= 0.6) {
+                  return;
+                }
+                if (videoItem.value) {
+                  videoFroms.defaultSpeed =
+                    (videoFroms.defaultSpeed * 10 - 1) / 10;
+                  videoItem.value.playbackRate(videoFroms.defaultSpeed);
+                }
+              }}></i>
+          </div>
+        </div>
       </div>
     );
   }

+ 82 - 2
src/views/attend-class/component/video.module.less

@@ -1,6 +1,12 @@
 .videoWrap {
   width: 100%;
   height: 100%;
+  position: relative;
+
+  .videoPop {
+    position: absolute;
+    inset: 0;
+  }
 
   :global {
 
@@ -30,6 +36,7 @@
     display: flex;
     align-items: center;
     transition: all 0.301s;
+    z-index: 9;
 
     .time {
       display: flex;
@@ -69,6 +76,7 @@
 
     .actionWrap {
       display: flex;
+      align-items: center;
     }
 
     .actionBtn {
@@ -85,12 +93,25 @@
       }
     }
 
+    .actionBtnSpeed {
+      width: 40px;
+      height: 40px;
+      background-color: transparent;
+      cursor: pointer;
+
+      &>img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
 
     .iconReplay {
       width: 40px;
-      height: 39px;
+      height: 40px;
       background-color: transparent;
       cursor: pointer;
+      margin: 0 22px;
 
       &>img {
         width: 100%;
@@ -101,7 +122,7 @@
 
   .slider {
     width: 100%;
-    padding: 0 20px 0 12px;
+    padding: 0 4px 0 16px;
 
     :global {
 
@@ -116,6 +137,65 @@
 }
 
 
+.sliderPopup {
+  position: absolute;
+  z-index: 9999;
+  left: 184px;
+  bottom: 90px;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  height: 252px;
+  width: 59Px;
+  padding: 12Px 0 15Px;
+  background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAI4AAAJcCAMAAAAYSmw3AAAAaVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnbPKNAAAAI3RSTlOzAAUJiqaplqF6V0c4nSevpJN+GAZtX1EQGpqDPTQVc2QhqyTybJ0AAAKuSURBVHja7NRJVsJQAAXRRxMg9BwgGWX/2xT0KCKII5M/uHcFNaqMntm25+mm2uVf7KrN9NxuR8885ozX9Sw9mNXr8Z85p2WV3lTL0+uc1SK9Wqxe5HTz9G7e/ZbTZBDN05xJnYHUk8ec4yGDORx/5hz3GdD+eJ8zOWRQh8ldTp2B1d9zmgyuueV0KUD3lTNPAeafOasUYfWRc1qkCIvTe84yhVhec8ZVClGNLznrFGM9yvAHvKkvObMUYzbKNgXZpk1B2pxTkHOmKcg0mxRkk2ImeFVll4IUFQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAG3twIAAAAAAA5P/aCKqqqqqqqqqqqqqqqtJ+nSQ3CgVRFH2XjwCjHnWoCTXe/yJrUlEljCz7R9riD/Ks4E7yRaRzzjnnnHPOOeecc845zZWQuUZKyEhTJWSqXAnJtVFCNtopITvNlJCZKJSMAvGmZLwhJkrGBJElM4SjDEGtRNQgCJWSUAUQMFYSxoAASiWgBBCQxnFN/uWw1eC2/M+h0cAa7nNCqUGVoZPDaqEBLVZ0c9iXGky552MOodFAmkA/B64axBUe5jA56OUOE+6IjvFCL7UY0yG6Ql3oZYo68DwH1qfmXS/w3pzWfCQeyGb1Ja8K/ZKiyi/1LOMB8UTWFzcH4+wBnhCRsmNEDbFErPW3e3ZEE9FCrm+p6THkWHu29BlyjD1XIthyCEt9YUMEaw7tFz0XIthzaMvnz3YUew7tQZ86ZsSx53Ce6hP5mqcsOfE9eSCePYdVpQeWhhqEwX6hnrLFQFjcej2HFgthchupY3rGRNjcCt2pVtgIo1lxV7PHSFjtl/orP2Ml7E7HuTRvTtiJH9G2/AiRlD84lRqqt/0KYQAAAABJRU5ErkJggg==') no-repeat top center;
+  background-size: contain;
+
+  .iconAdd,
+  .iconCut {
+    display: inline-block;
+    width: 24Px;
+    height: 24Px;
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAABMlBMVEUAAAAkqP8ckv8zu/8op/81v/8ajv8elP82v/8rrv8elP4npv8yuf8elP8elf81v/8sr/8elf8vuv8dlv8srv8jn/8zu/8wtf82wP8oqP8npf8Zjf8mpP8Zjf8hmv8bj/40vP8wtP81wP8Zjf41vv8sr/8jnf8aj/0zuf8dlf81vv8rq/4kof4vsf8imP8aj/////8usv8qrP4jn/4inP4dlP4yuf8el/4srv8pqv4gmf4bkf40vf8ssP4wtP8lov4koP4xt/8npv4mpP4zu/8hmv4ajv4ckv4wtv4op/58x/5+y/6Cxv5/zf57xP6Dx/6BxP7u+P/t9/6a1f6Bz/56w/5iuP5zwP5Bpv4yoP6x3/6w3f6d2/6Wzv5tyf5pwv5nvv5svP5Rsv5Pr/5Mq/43p/49/opPAAAAMHRSTlMACoRW+Pf38dTUurGDWEhHR0cjI/b28fHr6+vr19fQ0Lq6sbGoqKiolZWGhoZSUlKk1yinAAAB+UlEQVQ4y43S6VbaQACG4S9E9h3c933fatISI4sWYxUjAVQQ99r2/m+hGQfGMJNEHw4M+XhPTn4AjpQNLM1MhUJTM0uBrAQ/qbXQmUNoLQUve3Nngrk9uMnHjlzF8hAkw0cewklwAr98BDBg47uvjYH72oNx1/WuHfdOFgqFu3qj8bfgiT13Pmy3DbNhtr3jcB5UTNe7DdM0f6s69dRqPemcGG13dV3t2G2737b3bW2+3gUxaxh/LHJfo+eWxLcGZxa2lKqqHcsyuyph2G8aq71rdpL/yar9tW5ZbypTJ3Fd5a0C0oiiKG9W51hhaKzwRiRkFeJfVRFiQRbbx4IaiWvivo14RUBjcY9julKlKtXXGvVA4ofexWuV/T6NyWrf9b6raxZMYuhnX8s9brFgyBE33eOmI5646PN6DBZMIHpIXJCPl0vqnjT3vYuXj9+jiB8KLkl8Je5xbB0Irt5jcd9C5utxBtKwph1oBDtprPH7sASsaFpJs5XYSeMSv68AyJR4NyS+EeYMbNFvHBrzaxTEDj8/k/iZX3fwboGby4/N5mOZGxdA5cbKnxrLoUcunxMn596nDCZx8okEHNZ/+FrHgIRfmwBHHj31MCpDkJsvntqvov0eOOdzcCNHioKIDC/p5aCzDC6n4UdKby5GxoPB8cjiZlrCoP+meld2tFTGwgAAAABJRU5ErkJggg==') no-repeat center;
+    background-size: contain;
+    flex-shrink: 0;
+    cursor: pointer;
+
+    &.disabled {
+      opacity: 0.7;
+    }
+  }
+
+  .iconCut {
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAABGlBMVEUAAAAkqP8zu/8op/8ajv8aj/8yuP8elP4npv4dlP8elP8yuP8elP81v/8sr/8jnf8Zjf81v/81v/8jn/8zu/8wtf8gl/82wP8oqP8npf83wP8qqv8mpP8Zjf81vf8tsv8hmv8bj/41wP8qqv8lof8Zjf41vv8sr/8jnf8aj/0zuf8dlf8bjf00vP8xt/8zvf8stv8dmf8dkv8srv8trf8qq/7///8tsP4jn/4us/8srv8gmf4lov4yuf8dlf4wt/8inf4hm/4ck/4bkf4wtP8koP4mpf4fl/4op/40vP8ajv4pqf4zuv98x/5+y/6Bxf40vv80u/6Dx/6a1f5owP5PsP4/p/5zwP4oo/4yoP7u+P7u9/5svP5it/4TfkURAAAANXRSTlMACoT49+66uoaDWFJSR0dHR/j29vHx8evr69fX19fQ0NDQsbGxsaioqKiVlYZYWCMjIyP29l0yDtwAAAGnSURBVDjLjc6HVsIwGAXg27I37r33nkilWloXCGK17OH7v4YpSKSQtPnS5v7n5J6cYIyUPt1bC/j9gbW907QEN+kjf2mE/ygNnvhWacJWHCzXoWem0DUmRAPPHIEoxkSeXETgcPJEaGSx88Rxr6ZpaqOtcUVARTWt0DBNs06yoBGTSd99NV8gXeKnwDV/hYGQqrZNoqerfCH0xVXVaNr3GqqbOGybhlG3LKunG642QaQMXW9altkmaRhk42UKwKGu6xXLquteDgFpRlGUutV8VbzMSEj1h0ZL8ZZC+FVYGLsPwnaxSudWrcJQa9HCKpbehqp3TFVaWIKPzl12uUsLPviKQx12uUMLPiwWi+XBzHsGOR9YxEqZqn0y1MrUCnYy5Uyfd+4gnBEWRlK8nIR0kxN0KwEHuVzWnrNeeQAgmRWUBLEh1t2ALXYvJIa+oEg3iIHLuRdPc5f4c/6Sz+fd/3NQct6DjBHHj66O4SC7dWWMOZv94Jg9w4SL4Ps3WeRzZvACLLH1d8Iu/Od6DDyJ/emvEdP7CbiREvL28sLU1MLytpyQ4PQLtc9vYI2HRk0AAAAASUVORK5CYII=') no-repeat center;
+    background-size: contain;
+  }
+
+  .sliderPoint {
+    background: #FFFFFF;
+    box-shadow: 0px 2px 4px 0px rgba(102, 102, 102, 0.77);
+    border-radius: 14px;
+    font-size: 14Px;
+    font-weight: 500;
+    height: 22Px;
+    color: #198CFE;
+    min-width: 40Px;
+    text-align: center;
+    vertical-align: text-bottom;
+
+    span {
+      font-size: 12Px;
+    }
+  }
+
+  :global {
+    .n-slider {
+      margin: 7px 0;
+      padding: 0;
+    }
+  }
+}
+
 // .videoWrap {
 //   width: 100%;
 //   height: 100%;

+ 25 - 0
src/views/attend-class/image/icon-loop-active.svg

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
+            <stop stop-color="#36C0FF" offset="0%"></stop>
+            <stop stop-color="#198CFE" offset="100%"></stop>
+        </linearGradient>
+    </defs>
+    <g id="(最新版)上课页面" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="上课页面-视频" transform="translate(-132.000000, -1006.000000)">
+            <g id="编组-4" transform="translate(132.000000, 1006.000000)">
+                <path d="M20.0364629,2.64780703 C23.5119013,2.64780703 26.6636469,3.60966843 29.4746633,5.51621514 L29.4746633,5.51621514 L27.2769596,8.05827742 C25.3859122,6.46090045 22.5408229,5.82538488 20.0194264,5.82538488 C12.4382004,5.82538488 6.13470916,12.1977167 6.13470916,19.8239035 C6.13470916,27.4672665 12.4552369,33.8224222 20.0194264,33.8224222 C21.2373516,33.8224222 22.4223003,33.657962 23.5534353,33.3502636 C24.0828084,34.3781373 24.7949378,35.2963492 25.6463878,36.0628017 C23.8930535,36.670292 22.0060049,37 20.0364629,37 C10.5641895,37 3,29.3738132 3,19.8239035 C3,10.2739939 10.5641895,2.64780703 20.0364629,2.64780703 Z M35.4885347,18.2265265 C36.4425766,18.2265265 37.0729257,18.8620421 37.0729257,19.8239035 C37.0729257,20.2009806 37.0611327,20.5750585 37.0378949,20.9457862 C36.0936072,20.3525212 35.0376237,19.9220828 33.9088104,19.6918272 C33.9633543,18.8044181 34.5787732,18.2265265 35.4885347,18.2265265 Z" id="形状结合" stroke="#FFFFFF" stroke-width="0.530184528" fill="#FFFFFF" fill-rule="nonzero"></path>
+                <g id="编组-8" transform="translate(3.000000, 1.000000)">
+                    <g id="编组" fill="#FFFFFF">
+                        <path d="M19.9945582,15.7497809 L23.6145156,21.7339958 C24.0691834,22.485615 23.8284567,23.4635038 23.0768375,23.9181715 C22.8285105,24.0683889 22.5438155,24.1477982 22.2535887,24.1477982 L15.0136738,24.1477982 C14.1352353,24.1477982 13.4231202,23.4356831 13.4231202,22.5572446 C13.4231202,22.2670178 13.5025294,21.9823228 13.6527468,21.7339958 L17.2727043,15.7497809 C17.7273721,14.9981617 18.7052608,14.757435 19.45688,15.2121028 C19.6769413,15.3452217 19.8614393,15.5297197 19.9945582,15.7497809 Z" id="三角形" transform="translate(18.633631, 18.823904) rotate(-270.000000) translate(-18.633631, -18.823904) "></path>
+                        <path d="M30.3312059,5.24809907 L33.9186551,11.2349874 C34.3701744,11.9885021 34.1253588,12.9653753 33.371844,13.4168946 C33.1266024,13.5638477 32.8462852,13.6419824 32.5603875,13.6430774 L25.3393967,13.6707329 C24.4609646,13.6740972 23.7461275,12.9647146 23.7427632,12.0862825 C23.7416449,11.7943024 23.8209199,11.5076482 23.971901,11.2577315 L27.6054427,5.24318768 C28.0596745,4.49130496 29.0374235,4.25001123 29.7893062,4.70424305 C30.0116384,4.83855966 30.1976914,5.02528433 30.3312059,5.24809907 Z" id="三角形备份" transform="translate(28.931402, 8.331027) rotate(125.000000) translate(-28.931402, -8.331027) "></path>
+                    </g>
+                    <path d="M29,36 C33.418278,36 37,32.418278 37,28 C37,23.581722 33.418278,20 29,20 C24.581722,20 21,23.581722 21,28 C21,32.418278 24.581722,36 29,36 Z" id="形状结合" fill="url(#linearGradient-1)"></path>
+                    <path d="M25.8351688,23.8111688 C26.278259,23.8111688 26.643447,24.1288248 26.6933561,24.5380634 L26.6991688,24.634026 L26.6983048,27.9252788 L32.7471688,27.9254545 C33.190259,27.9254545 33.555447,28.2431105 33.6053561,28.6523492 L33.6111688,28.7483117 C33.6111688,29.1703023 33.2776301,29.5181004 32.8479295,29.5656329 L32.7471688,29.5711688 L25.8351688,29.5711688 C25.3920787,29.5711688 25.0268906,29.2535129 24.9769816,28.8442742 L24.9711688,28.7483117 L24.9711688,24.634026 C24.9711688,24.1795745 25.3579948,23.8111688 25.8351688,23.8111688 Z" id="路径" fill="#FFFFFF" transform="translate(29.291169, 26.691169) rotate(-45.000000) translate(-29.291169, -26.691169) "></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 15 - 0
src/views/attend-class/image/icon-loop.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="(最新版)上课页面" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="上课页面-视频" transform="translate(-132.000000, -932.000000)" fill="#FFFFFF">
+            <g id="编组-4备份" transform="translate(132.000000, 932.000000)">
+                <g id="编组" transform="translate(3.000000, 1.000000)">
+                    <path d="M17.0364629,1.64780703 C7.56418951,1.64780703 0,9.27399387 0,18.8239035 C0,28.3738132 7.56418951,36 17.0364629,36 C26.5087362,36 34.0729257,28.3738132 34.0729257,18.8239035 C34.0729257,17.8620421 33.4425766,17.2265265 32.4885347,17.2265265 C31.5344927,17.2265265 30.9041436,17.8620421 30.9041436,18.8239035 C30.9041436,26.4500904 24.6006524,32.8224222 17.0194264,32.8224222 C9.45523688,32.8224222 3.13470916,26.4672665 3.13470916,18.8239035 C3.13470916,11.1977167 9.43820042,4.82538488 17.0194264,4.82538488 C19.5408229,4.82538488 22.3859122,5.46090045 24.2769596,7.05827742 L26.4746633,4.51621514 C23.6636469,2.60966843 20.5119013,1.64780703 17.0364629,1.64780703 Z" id="路径" stroke="#FFFFFF" stroke-width="0.530184528" fill-rule="nonzero"></path>
+                    <path d="M19.9945582,15.7497809 L23.6145156,21.7339958 C24.0691834,22.485615 23.8284567,23.4635038 23.0768375,23.9181715 C22.8285105,24.0683889 22.5438155,24.1477982 22.2535887,24.1477982 L15.0136738,24.1477982 C14.1352353,24.1477982 13.4231202,23.4356831 13.4231202,22.5572446 C13.4231202,22.2670178 13.5025294,21.9823228 13.6527468,21.7339958 L17.2727043,15.7497809 C17.7273721,14.9981617 18.7052608,14.757435 19.45688,15.2121028 C19.6769413,15.3452217 19.8614393,15.5297197 19.9945582,15.7497809 Z" id="三角形" transform="translate(18.633631, 18.823904) rotate(-270.000000) translate(-18.633631, -18.823904) "></path>
+                    <path d="M30.3312059,5.24809907 L33.9186551,11.2349874 C34.3701744,11.9885021 34.1253588,12.9653753 33.371844,13.4168946 C33.1266024,13.5638477 32.8462852,13.6419824 32.5603875,13.6430774 L25.3393967,13.6707329 C24.4609646,13.6740972 23.7461275,12.9647146 23.7427632,12.0862825 C23.7416449,11.7943024 23.8209199,11.5076482 23.971901,11.2577315 L27.6054427,5.24318768 C28.0596745,4.49130496 29.0374235,4.25001123 29.7893062,4.70424305 C30.0116384,4.83855966 30.1976914,5.02528433 30.3312059,5.24809907 Z" id="三角形备份" transform="translate(28.931402, 8.331027) rotate(125.000000) translate(-28.931402, -8.331027) "></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/views/attend-class/image/icon-speed.png


+ 8 - 5
src/views/attend-class/index.tsx

@@ -50,7 +50,7 @@ import {
 } from '../prepare-lessons/api';
 import { vaildUrl } from '/src/utils/urlUtils';
 import TimerMeter from '/src/components/timerMeter';
-import { px2vw } from '/src/utils';
+import { iframeDislableKeyboard, px2vw } from '/src/utils';
 import PlaceholderTone from '/src/components/layout/modals/placeholderTone';
 import { state as globalState } from '/src/state';
 import Chapter from './model/chapter';
@@ -1474,10 +1474,10 @@ export default defineComponent({
                       activeData.model = true;
                     }}
                     onEnded={() => {
-                      const _index = popupData.activeIndex + 1;
-                      if (_index < data.itemList.length) {
-                        handleSwipeChange(_index);
-                      }
+                      // const _index = popupData.activeIndex + 1;
+                      // if (_index < data.itemList.length) {
+                      //   handleSwipeChange(_index);
+                      // }
                     }}
                   />
                 </div>
@@ -1967,6 +1967,9 @@ export default defineComponent({
               scrolling="no"
               frameborder="0"
               width="100%"
+              onLoad={(val: any) => {
+                iframeDislableKeyboard(val.target);
+              }}
               height={'650px'}></iframe>
           </div>
         </NModal>

+ 3 - 0
src/views/attend-class/model/train-type/index.tsx

@@ -418,6 +418,9 @@ export default defineComponent({
               width={'100%'}
               height={'450px'}
               frameborder="0"
+              onLoad={(val: any) => {
+                iframeDislableKeyboard(val.target);
+              }}
               src={reportSrc.value}></iframe>
           </div>
         </NModal>

+ 4 - 1
src/views/home/index copy.tsx

@@ -38,7 +38,7 @@ import TheEmpty from '/src/components/TheEmpty';
 import HomeGuide from '/src/custom-plugins/guide-page/home-guide';
 import TimerMeter from '/src/components/timerMeter';
 import { vaildUrl } from '/src/utils/urlUtils';
-import { px2vw } from '/src/utils';
+import { iframeDislableKeyboard, px2vw } from '/src/utils';
 import PlaceholderTone from '@/components/layout/modals/placeholderTone';
 import PreviewWindow from '../preview-window';
 import UpdatePassword from '/src/components/layout/modals/update-password';
@@ -532,6 +532,9 @@ export default defineComponent({
               scrolling="no"
               frameborder="0"
               width="100%"
+              onLoad={(val: any) => {
+                iframeDislableKeyboard(val.target);
+              }}
               height={'650px'}></iframe>
           </div>
         </NModal>

+ 1 - 1
src/views/home/index2.module.less

@@ -718,7 +718,7 @@
 
   .teachGroupTitle {
     position: relative;
-    left: -10px;
+    left: -4px;
     font-size: max(14px, 12Px);
     font-weight: 400;
     color: #aaaaaa;

+ 3 - 1
src/views/home/modals/class-modal/index.tsx

@@ -216,7 +216,9 @@ export default defineComponent({
               ' | ' +
               lessonCourseware.lessonCoursewareDetailName +
               ' | ' +
-              lessonCourseware.lessonCoursewareKnowledgeDetailName,
+              lessonCourseware.lessonCoursewareKnowledgeDetailName +
+              ' | ' +
+              lessonCourseware.useChapterLessonCoursewareName,
             image: item.teacherAvatar,
             subjectName: item.subjectName
           });

+ 1 - 1
src/views/notation/index.tsx

@@ -3,7 +3,7 @@ import { useUserStore } from '/src/store/modules/users';
 import styles from './index.module.less';
 import { state } from '/src/state';
 import { NButton, NModal, NSpace, NSpin } from 'naive-ui';
-import { exitFullscreen } from '/src/utils';
+import { exitFullscreen, iframeDislableKeyboard } from '/src/utils';
 import { useRouter } from 'vue-router';
 export default defineComponent({
   name: 'notation-a',

+ 3 - 1
src/views/prepare-lessons/model/source-rhythm/index.tsx

@@ -2,6 +2,7 @@ import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
 import styles from './index.module.less';
 import { useUserStore } from '/src/store/modules/users';
 import { NButton, NSpace, NSpin } from 'naive-ui';
+import { iframeDislableKeyboard } from '/src/utils';
 
 export default defineComponent({
   name: 'source-rhythm',
@@ -49,10 +50,11 @@ export default defineComponent({
         <NSpin show={loading.value}>
           <iframe
             ref={iframeRef}
-            onLoad={() => {
+            onLoad={(val: any) => {
               // emit('setIframe', iframeRef.value);
               // isLoaded.value = true;
               loading.value = false;
+              iframeDislableKeyboard(val.target);
             }}
             class={[styles.container, 'musicIframe']}
             frameborder="0"

+ 9 - 1
src/views/preview-window/index.tsx

@@ -10,6 +10,7 @@ import {
 import styles from './index.module.less';
 import { NModal } from 'naive-ui';
 import AttendClass from '../attend-class';
+import { iframeDislableKeyboard } from '/src/utils';
 
 export default defineComponent({
   name: 'preview-window',
@@ -88,10 +89,17 @@ export default defineComponent({
                   onClose={() => emit('update:show', false)}
                 />
               ) : type.value == 'notation' ? (
-                <iframe src={params.value.src}></iframe>
+                <iframe
+                  src={params.value.src}
+                  onLoad={(val: any) => {
+                    iframeDislableKeyboard(val.target);
+                  }}></iframe>
               ) : type.value == 'music' ? (
                 <iframe
                   src={params.value.src}
+                  onLoad={(val: any) => {
+                    iframeDislableKeyboard(val.target);
+                  }}
                   style={{ height: '100vh' }}></iframe>
               ) : (
                 ''

+ 4 - 0
src/views/studentList/components/evaluationRecords.tsx

@@ -23,6 +23,7 @@ import CDatePicker from '/src/components/CDatePicker';
 import { useUserStore } from '/src/store/modules/users';
 import TheEmpty from '/src/components/TheEmpty';
 import { initCache, setCache } from '/src/hooks/use-async';
+import { iframeDislableKeyboard } from '/src/utils';
 export default defineComponent({
   name: 'student-practiceData',
   props: {
@@ -246,6 +247,9 @@ export default defineComponent({
               height={'450px'}
               ref={iframeRef}
               frameborder="0"
+              onLoad={(val: any) => {
+                iframeDislableKeyboard(val.target);
+              }}
               src={reportSrc.value}></iframe>
           </div>
         </NModal>