Browse Source

Merge branch 'iteration-20240520' into jenkins

lex 1 year ago
parent
commit
afc65b927a
24 changed files with 4178 additions and 3463 deletions
  1. 210 200
      src/components/card-preview/index.tsx
  2. 157 144
      src/components/card-preview/song-modal/index.module.less
  3. 82 6
      src/components/card-preview/song-modal/index.tsx
  4. 382 346
      src/components/card-preview/video-modal/index.module.less
  5. 388 306
      src/components/card-preview/video-modal/index.tsx
  6. 536 517
      src/components/card-type/index.tsx
  7. 743 744
      src/utils/index.ts
  8. BIN
      src/views/attend-class/image/icon-fullscreen-exit.png
  9. BIN
      src/views/attend-class/image/icon-fullscreen.png
  10. 0 1
      src/views/attend-class/model/train-type/index.tsx
  11. 49 10
      src/views/attend-class/model/train-update/index.tsx
  12. 251 0
      src/views/classList/components/afterWorkDetail.module.less
  13. 247 77
      src/views/classList/components/afterWorkDetail.tsx
  14. 33 14
      src/views/classList/work-item/index.tsx
  15. 1 0
      src/views/natural-resources/components/my-collect/index.tsx
  16. 477 476
      src/views/natural-resources/components/my-resources/index.tsx
  17. 174 173
      src/views/natural-resources/components/share-resources/index.tsx
  18. 1 0
      src/views/prepare-lessons/components/directory-main/index.module.less
  19. 73 68
      src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx
  20. 11 12
      src/views/prepare-lessons/components/lesson-main/train/index.tsx
  21. 1 0
      src/views/prepare-lessons/components/resource-main/components/resource-item/index.tsx
  22. 360 369
      src/views/prepare-lessons/components/resource-main/components/select-music/index.tsx
  23. 1 0
      src/views/prepare-lessons/model/select-music/select-item/index.tsx
  24. 1 0
      src/views/prepare-lessons/model/select-resources/select-item/index.tsx

+ 210 - 200
src/components/card-preview/index.tsx

@@ -1,200 +1,210 @@
-import { NModal, NSpin } from 'naive-ui';
-import { defineComponent, ref, toRef, watch } from 'vue';
-import styles from './index.module.less';
-import VideoModal from './video-modal';
-import MusicModal from './music-modal';
-import SongModal from './song-modal';
-import TheEmpty from '../TheEmpty';
-import RhythmModal from './rhythm-modal';
-import InstruemntDetail from '/src/views/prepare-lessons/model/source-instrument/detail';
-import TheoryDetail from '/src/views/prepare-lessons/model/source-knowledge/detail';
-import MusicDetail from '/src/views/prepare-lessons/model/source-music/detail';
-import ListenModal from './listen-modal';
-import useDrag from '@/hooks/useDrag';
-import Dragbom from '@/hooks/useDrag/dragbom';
-import { useUserStore } from '@/store/modules/users';
-
-export default defineComponent({
-  name: 'card-preview',
-  props: {
-    show: {
-      type: Boolean,
-      default: false
-    },
-    item: {
-      type: Object,
-      default: () => ({})
-    },
-    size: {
-      type: String,
-      default: 'default'
-    },
-    /** 是否下载 只支持 video audio */
-    isDownload: {
-      type: Boolean,
-      default: false
-    },
-    /** 从哪里使用 */
-    from: {
-      type: String,
-      default: ''
-    }
-  },
-  emit: ['update:show'],
-  setup(props, { emit }) {
-    const show = toRef(props.show);
-    const item = toRef(props.item);
-    const pptLoading = ref(true);
-
-    watch(
-      () => props.show,
-      () => {
-        show.value = props.show;
-      }
-    );
-
-    watch(
-      () => props.item,
-      () => {
-        item.value = props.item;
-      }
-    );
-    // 拖动
-    let cardPreviewBoxDragData: any;
-    let cardPreviewBoxClass: string;
-    if (props.from === 'class') {
-      const users = useUserStore();
-      cardPreviewBoxClass = 'cardPreviewBoxClass_drag';
-      cardPreviewBoxDragData = useDrag(
-        [
-          `${cardPreviewBoxClass}>.n-card-header`,
-          `${cardPreviewBoxClass} .bom_drag`
-        ],
-        cardPreviewBoxClass,
-        show,
-        users.info.id
-      );
-    }
-    return () => (
-      <>
-        <NModal
-          style={
-            props.from === 'class' ? cardPreviewBoxDragData.styleDrag.value : {}
-          }
-          v-model:show={show.value}
-          onUpdate:show={() => {
-            emit('update:show', show.value);
-            if (!show.value) {
-              pptLoading.value = true;
-            }
-          }}
-          preset="card"
-          showIcon={false}
-          class={[
-            'modalTitle background',
-            cardPreviewBoxClass,
-            props.from === 'class' && styles.classCard,
-            styles.cardPreview,
-            item.value.type === 'PPT' && styles.maxCard,
-            props.size === 'large' && styles.cardLarge
-          ]}
-          title={item.value.type === 'MUSIC' ? '曲目预览' : item.value.title}
-          blockScroll={false}>
-          {item.value.type === 'VIDEO' && (
-            <VideoModal
-              title={item.value.title}
-              poster={item.value.url}
-              src={item.value.content}
-              isDownload={props.isDownload}
-            />
-          )}
-          {item.value.type === 'MUSIC' && (
-            <MusicModal
-              class={styles.musicPreview}
-              item={item.value}
-              from={props.from}
-            />
-          )}
-          {item.value.type === 'SONG' && (
-            <SongModal item={item.value} isDownload={props.isDownload} />
-          )}
-          {item.value.type === 'PPT' && (
-            <NSpin show={pptLoading.value}>
-              <iframe
-                class={styles.pptBox}
-                src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
-                  item.value.content
-                )}`}
-                onLoad={() => {
-                  console.log('loading end');
-                  pptLoading.value = false;
-                }}
-                width="100%"
-                height="100%"
-                frameborder="1"></iframe>
-            </NSpin>
-          )}
-          {item.value.type === 'RHYTHM' && (
-            <RhythmModal class={styles.musicPreview} item={item.value} />
-          )}
-
-          {item.value.type === 'LISTEN' && (
-            <ListenModal class={styles.musicPreview} item={item.value} />
-          )}
-
-          {(item.value.type === 'INSTRUMENT' ||
-            item.value.type === 'MUSICIAN') && (
-            <div class={styles.instrumentGroup}>
-              <InstruemntDetail
-                type="modal"
-                contentType={item.value.type}
-                id={item.value.content}
-              />
-            </div>
-          )}
-          {item.value.type === 'MUSIC_WIKI' && (
-            <div class={styles.instrumentGroup}>
-              <MusicDetail
-                type="modal"
-                contentType={item.value.type}
-                id={item.value.content}
-              />
-            </div>
-          )}
-
-          {item.value.type === 'THEORY' && (
-            <div>
-              <TheoryDetail type="modal" id={item.value.content} />
-            </div>
-          )}
-
-          {/* LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC_WIKI:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家) */}
-          {/*  VIDEO("视频"),
-    MUSIC("曲目"),
-    IMG("图片"),
-    SONG("音频"),
-    PPT("ppt"),
-    LISTEN("听音练习"),
-    RHYTHM("节奏练习"),
-    THEORY("乐理知识"),
-    MUSIC_WIKI("名曲鉴赏"),
-    INSTRUMENT("乐器"),
-    MUSICIAN("音乐家"), */}
-          {![
-            'VIDEO',
-            'MUSIC',
-            'SONG',
-            'PPT',
-            'RHYTHM',
-            'INSTRUMENT',
-            'THEORY',
-            'MUSICIAN',
-            'MUSIC_WIKI',
-            'LISTEN'
-          ].includes(item.value.type) && <TheEmpty />}
-          {props.from === 'class' && <Dragbom></Dragbom>}
-        </NModal>
-      </>
-    );
-  }
-});
+import { NModal, NSpin } from 'naive-ui';
+import { defineComponent, ref, toRef, watch } from 'vue';
+import styles from './index.module.less';
+import VideoModal from './video-modal';
+import MusicModal from './music-modal';
+import SongModal from './song-modal';
+import TheEmpty from '../TheEmpty';
+import RhythmModal from './rhythm-modal';
+import InstruemntDetail from '/src/views/prepare-lessons/model/source-instrument/detail';
+import TheoryDetail from '/src/views/prepare-lessons/model/source-knowledge/detail';
+import MusicDetail from '/src/views/prepare-lessons/model/source-music/detail';
+import ListenModal from './listen-modal';
+import useDrag from '@/hooks/useDrag';
+import Dragbom from '@/hooks/useDrag/dragbom';
+import { useUserStore } from '@/store/modules/users';
+
+export default defineComponent({
+  name: 'card-preview',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    item: {
+      type: Object,
+      default: () => ({})
+    },
+    size: {
+      type: String,
+      default: 'default'
+    },
+    /** 是否下载 只支持 video audio */
+    isDownload: {
+      type: Boolean,
+      default: true
+    },
+    /** 是否下载 只支持 video audio */
+    fullscreen: {
+      type: Boolean,
+      default: true
+    },
+    /** 从哪里使用 */
+    from: {
+      type: String,
+      default: ''
+    }
+  },
+  emit: ['update:show'],
+  setup(props, { emit }) {
+    const show = toRef(props.show);
+    const item = toRef(props.item);
+    const pptLoading = ref(true);
+
+    watch(
+      () => props.show,
+      () => {
+        show.value = props.show;
+      }
+    );
+
+    watch(
+      () => props.item,
+      () => {
+        item.value = props.item;
+      }
+    );
+    // 拖动
+    let cardPreviewBoxDragData: any;
+    let cardPreviewBoxClass: string;
+    if (props.from === 'class') {
+      const users = useUserStore();
+      cardPreviewBoxClass = 'cardPreviewBoxClass_drag';
+      cardPreviewBoxDragData = useDrag(
+        [
+          `${cardPreviewBoxClass}>.n-card-header`,
+          `${cardPreviewBoxClass} .bom_drag`
+        ],
+        cardPreviewBoxClass,
+        show,
+        users.info.id
+      );
+    }
+    return () => (
+      <>
+        <NModal
+          style={
+            props.from === 'class' ? cardPreviewBoxDragData.styleDrag.value : {}
+          }
+          v-model:show={show.value}
+          onUpdate:show={() => {
+            emit('update:show', show.value);
+            if (!show.value) {
+              pptLoading.value = true;
+            }
+          }}
+          preset="card"
+          showIcon={false}
+          class={[
+            'modalTitle background',
+            cardPreviewBoxClass,
+            props.from === 'class' && styles.classCard,
+            styles.cardPreview,
+            item.value.type === 'PPT' && styles.maxCard,
+            props.size === 'large' && styles.cardLarge
+          ]}
+          title={item.value.type === 'MUSIC' ? '曲目预览' : item.value.title}
+          blockScroll={false}>
+          {item.value.type === 'VIDEO' && (
+            <VideoModal
+              title={item.value.title}
+              poster={item.value.url}
+              src={item.value.content}
+              isDownload={props.isDownload}
+              fullscreen={props.fullscreen}
+            />
+          )}
+          {item.value.type === 'MUSIC' && (
+            <MusicModal
+              class={styles.musicPreview}
+              item={item.value}
+              from={props.from}
+            />
+          )}
+          {item.value.type === 'SONG' && (
+            <SongModal
+              item={item.value}
+              isDownload={props.isDownload}
+              fullscreen={props.fullscreen}
+            />
+          )}
+          {item.value.type === 'PPT' && (
+            <NSpin show={pptLoading.value}>
+              <iframe
+                class={styles.pptBox}
+                src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
+                  item.value.content
+                )}`}
+                onLoad={() => {
+                  console.log('loading end');
+                  pptLoading.value = false;
+                }}
+                width="100%"
+                height="100%"
+                frameborder="1"></iframe>
+            </NSpin>
+          )}
+          {item.value.type === 'RHYTHM' && (
+            <RhythmModal class={styles.musicPreview} item={item.value} />
+          )}
+
+          {item.value.type === 'LISTEN' && (
+            <ListenModal class={styles.musicPreview} item={item.value} />
+          )}
+
+          {(item.value.type === 'INSTRUMENT' ||
+            item.value.type === 'MUSICIAN') && (
+            <div class={styles.instrumentGroup}>
+              <InstruemntDetail
+                type="modal"
+                contentType={item.value.type}
+                id={item.value.content}
+              />
+            </div>
+          )}
+          {item.value.type === 'MUSIC_WIKI' && (
+            <div class={styles.instrumentGroup}>
+              <MusicDetail
+                type="modal"
+                contentType={item.value.type}
+                id={item.value.content}
+              />
+            </div>
+          )}
+
+          {item.value.type === 'THEORY' && (
+            <div>
+              <TheoryDetail type="modal" id={item.value.content} />
+            </div>
+          )}
+
+          {/* LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC_WIKI:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家) */}
+          {/*  VIDEO("视频"),
+    MUSIC("曲目"),
+    IMG("图片"),
+    SONG("音频"),
+    PPT("ppt"),
+    LISTEN("听音练习"),
+    RHYTHM("节奏练习"),
+    THEORY("乐理知识"),
+    MUSIC_WIKI("名曲鉴赏"),
+    INSTRUMENT("乐器"),
+    MUSICIAN("音乐家"), */}
+          {![
+            'VIDEO',
+            'MUSIC',
+            'SONG',
+            'PPT',
+            'RHYTHM',
+            'INSTRUMENT',
+            'THEORY',
+            'MUSICIAN',
+            'MUSIC_WIKI',
+            'LISTEN'
+          ].includes(item.value.type) && <TheEmpty />}
+          {props.from === 'class' && <Dragbom></Dragbom>}
+        </NModal>
+      </>
+    );
+  }
+});

+ 157 - 144
src/components/card-preview/song-modal/index.module.less

@@ -1,144 +1,157 @@
-.audioWrap {
-  width: 100%;
-  height: 518px;
-  background-color: #fff;
-}
-
-.audioContainer {
-  position: relative;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  height: 100%;
-  padding: 0 0 78px 0;
-
-  &>div {
-    flex: 1;
-  }
-
-  .audio {
-    position: absolute;
-    top: 0;
-    opacity: 0;
-  }
-
-  .tempVudio {
-    position: absolute;
-    top: 0;
-    right: 0;
-    bottom: 80px;
-    left: 0;
-    padding: 0;
-  }
-
-  canvas {
-    width: 100%;
-    height: 100%;
-  }
-}
-
-.controls {
-  border-radius: 0 0 16px 16px !important;
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  width: 100%;
-  background: rgba(0, 0, 0, 0.6);
-  backdrop-filter: blur(26px);
-  height: 80px;
-  padding: 0 26px 0 26px !important;
-  transition: all 0.5s;
-  display: flex;
-  align-items: center;
-  transition: all .5s;
-
-  .time {
-    display: flex;
-    justify-content: space-between;
-    color: #fff;
-    padding: 4px 12px 4px;
-    font-size: 24px;
-    font-weight: 600;
-    line-height: 33px;
-    min-width: 150px;
-
-    .line {
-      font-size: 12px;
-    }
-
-    :global {
-      .plyr__time {
-        font-size: max(22px, 12Px);
-      }
-
-      .plyr__time+.plyr__time:before {
-        content: '';
-        margin-right: 0;
-      }
-    }
-  }
-}
-
-.actions {
-  display: flex;
-  justify-content: space-between;
-  height: 100%;
-  color: #fff;
-  font-size: 12px;
-  align-items: center;
-
-  .actionWrap {
-    display: flex;
-    align-items: center;
-  }
-
-  .actionBtn {
-    display: flex;
-    width: 40px;
-    height: 40px;
-    padding: 0;
-    background: transparent;
-    cursor: pointer;
-
-    &>img {
-      width: 100%;
-      height: 100%;
-    }
-  }
-
-
-  .iconReplay {
-    width: 40px;
-    height: 40px;
-    background-color: transparent;
-    cursor: pointer;
-    margin-left: 22px;
-    margin-right: 10px;
-
-    &>img {
-      width: 100%;
-      height: 100%;
-    }
-  }
-}
-
-.slider {
-  width: 100%;
-  padding: 0 8px 0 12px;
-
-  :global {
-
-    .n-slider .n-slider-rail .n-slider-rail__fill,
-    .n-slider .n-slider-handles .n-slider-handle-wrapper {
-      transition: all .2s;
-    }
-  }
-}
-
-.sectionAnimate {
-  opacity: 0;
-  pointer-events: none;
-  transform: translateY(100%);
-  transition: all .5s;
-}
+.audioWrap {
+  width: 100%;
+  height: 518px;
+  background-color: #fff;
+}
+
+.audioContainer {
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  padding: 0 0 78px 0;
+
+  &>div {
+    flex: 1;
+  }
+
+  .audio {
+    position: absolute;
+    top: 0;
+    opacity: 0;
+  }
+
+  .tempVudio {
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 80px;
+    left: 0;
+    padding: 0;
+  }
+
+  canvas {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.controls {
+  // border-radius: 0 0 16px 16px !important;
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  width: 100%;
+  background: rgba(0, 0, 0, 0.6);
+  backdrop-filter: blur(26px);
+  height: 80px;
+  padding: 0 26px 0 26px !important;
+  transition: all 0.3s;
+  display: flex;
+  align-items: center;
+
+  .time {
+    display: flex;
+    justify-content: space-between;
+    color: #fff;
+    padding: 4px 12px 4px;
+    font-size: 24px;
+    font-weight: 600;
+    line-height: 33px;
+    min-width: 150px;
+
+    .line {
+      font-size: 12px;
+    }
+
+    :global {
+      .plyr__time {
+        font-size: max(22px, 12Px);
+      }
+
+      .plyr__time+.plyr__time:before {
+        content: '';
+        margin-right: 0;
+      }
+    }
+  }
+}
+
+.actions {
+  display: flex;
+  justify-content: space-between;
+  height: 100%;
+  color: #fff;
+  font-size: 12px;
+  align-items: center;
+
+  .actionWrap {
+    display: flex;
+    align-items: center;
+  }
+
+  .actionBtn {
+    display: flex;
+    width: 36px;
+    height: 36px;
+    padding: 0;
+    background: transparent;
+    cursor: pointer;
+
+    &>img {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+
+  .iconReplay {
+    width: 36px;
+    height: 36px;
+    background-color: transparent;
+    cursor: pointer;
+    margin-left: 22px;
+    margin-right: 10px;
+
+    &>img {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  .iconDownload {
+    width: 36px;
+    height: 36px;
+    margin-left: 22px;
+    background-color: transparent;
+    cursor: pointer;
+
+    &>img {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+
+.slider {
+  width: 100%;
+  padding: 0 8px 0 12px;
+
+  :global {
+
+    .n-slider .n-slider-rail .n-slider-rail__fill,
+    .n-slider .n-slider-handles .n-slider-handle-wrapper {
+      transition: all .2s;
+    }
+  }
+}
+
+.sectionAnimate {
+  // opacity: 0;
+  pointer-events: none;
+  margin-bottom: -80px;
+  // transform: translateY(100%);
+  transition: all .3s;
+}

+ 82 - 6
src/components/card-preview/song-modal/index.tsx

@@ -4,10 +4,13 @@ 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 iconFullscreen from '@views/attend-class/image/icon-fullscreen.png';
+import iconFullscreenExit from '@views/attend-class/image/icon-fullscreen-exit.png';
 import { NSlider, useMessage } from 'naive-ui';
 import Vudio from 'vudio.js';
 import tickMp3 from '@views/attend-class/image/tick.mp3';
 import { saveAs } from 'file-saver';
+import { exitFullscreen } from '/src/utils';
 
 export default defineComponent({
   name: 'audio-play',
@@ -25,18 +28,26 @@ export default defineComponent({
     isDownload: {
       type: Boolean,
       default: false
+    },
+    fullscreen: {
+      type: Boolean,
+      default: false
     }
   },
   setup(props) {
+    const videoId =
+      'vFullscreen' + Date.now() + Math.floor(Math.random() * 100);
     const message = useMessage();
     const audioForms = reactive({
+      isFullScreen: false, // 是否全屏
       paused: true,
       currentTimeNum: 0,
       currentTime: '00:00',
       durationNum: 0,
       duration: '00:00',
       showBar: true,
-      afterMa3: true
+      afterMa3: true,
+      timer: null as any
     });
     const canvas: any = ref();
     const audio: any = ref();
@@ -49,8 +60,13 @@ export default defineComponent({
         onInit(audio.value, canvas.value);
         audio.value.play();
         audioForms.afterMa3 = false;
+
+        setModelOpen();
       } else {
         audio.value.pause();
+
+        clearTimeout(audioForms.timer);
+        audioForms.showBar = true;
       }
       audioForms.paused = audio.value.paused;
     };
@@ -137,8 +153,55 @@ export default defineComponent({
       vudio1.dance();
     });
 
+    /** 全屏 */
+    const isElementFullscreen = (element: any) => {
+      const dom: any = document;
+      return (
+        element ===
+        (dom.fullscreenElement ||
+          dom.webkitFullscreenElement ||
+          dom.mozFullScreenElement ||
+          dom.msFullscreenElement)
+      );
+    };
+    const onFullScreen = () => {
+      const el: any = document.querySelector('#' + videoId);
+
+      //   //进入全屏
+      if (el) {
+        if (isElementFullscreen(el)) {
+          exitFullscreen();
+          audioForms.isFullScreen = false;
+        } else {
+          (el.requestFullscreen && el.requestFullscreen()) ||
+            (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
+            (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
+            (el.msRequestFullscreen && el.msRequestFullscreen());
+          audioForms.isFullScreen = true;
+        }
+        // audioForms.isFullScreen = isElementFullscreen(el);
+      }
+    };
+
+    /** 延迟收起模态框 */
+    const setModelOpen2 = () => {
+      clearTimeout(audioForms.timer);
+      audioForms.showBar = !audioForms.showBar;
+      audioForms.timer = setTimeout(() => {
+        audioForms.showBar = false;
+      }, 3000);
+    };
+    /** 延迟收起模态框 */
+    const setModelOpen = () => {
+      clearTimeout(audioForms.timer);
+      audioForms.showBar = true;
+      audioForms.timer = setTimeout(() => {
+        audioForms.showBar = false;
+      }, 3000);
+    };
+
     return () => (
-      <div class={styles.audioWrap}>
+      <div class={styles.audioWrap} id={videoId} onClick={setModelOpen2}>
         <div class={styles.audioContainer}>
           <audio
             ref={audio}
@@ -147,6 +210,10 @@ export default defineComponent({
             onEnded={() => {
               audioForms.paused = true;
             }}
+            onPause={() => {
+              clearTimeout(audioForms.timer);
+              audioForms.showBar = true;
+            }}
             onTimeupdate={() => {
               audioForms.currentTime = timeFormat(
                 Math.floor(audio.value?.currentTime || 0)
@@ -177,6 +244,7 @@ export default defineComponent({
           ]}
           onClick={(e: MouseEvent) => {
             e.stopPropagation();
+            setModelOpen();
           }}>
           <div class={styles.actions}>
             <div class={styles.actionWrap}>
@@ -223,13 +291,21 @@ export default defineComponent({
             </div>
             <div class={styles.actionWrap}>
               {props.isDownload && (
-                <button
-                  class={styles.iconReplay}
-                  onClick={onDownload}
-                  style={{ marginLeft: '15px' }}>
+                <button class={styles.iconDownload} onClick={onDownload}>
                   <img src={iconPreviewDownload} />
                 </button>
               )}
+              {props.fullscreen && (
+                <button class={styles.iconDownload} onClick={onFullScreen}>
+                  <img
+                    src={
+                      audioForms.isFullScreen
+                        ? iconFullscreenExit
+                        : iconFullscreen
+                    }
+                  />
+                </button>
+              )}
             </div>
           </div>
         </div>

+ 382 - 346
src/components/card-preview/video-modal/index.module.less

@@ -1,346 +1,382 @@
-.videoWrap {
-  width: 100%;
-  height: 100%;
-  height: 518px;
-
-  .controls {
-    border-radius: 0 0 16px 16px !important;
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    right: 0;
-    width: 100%;
-    background: rgba(0, 0, 0, 0.6);
-    backdrop-filter: blur(26px);
-    height: 80px;
-    padding: 0 26px 0 26px !important;
-    transition: all 0.5s;
-    display: flex;
-    align-items: center;
-    transition: all .5s;
-
-    .time {
-      display: flex;
-      justify-content: space-between;
-      color: #fff;
-      padding: 4px 0 4px 12px;
-      font-size: max(24px, 14Px);
-      font-weight: 600;
-      line-height: 33px;
-      // min-width: 150px;
-      flex-shrink: 0;
-
-      .line {
-        font-size: 20px;
-      }
-
-      :global {
-        .plyr__time {
-          font-size: max(22px, 12Px);
-        }
-
-        .plyr__time+.plyr__time:before {
-          content: '';
-          margin-right: 0;
-        }
-      }
-    }
-  }
-
-  .actions {
-    display: flex;
-    justify-content: space-between;
-    height: 100%;
-    color: #fff;
-    font-size: 12px;
-    align-items: center;
-    flex-shrink: 0;
-
-    .actionWrap {
-      display: flex;
-      align-items: center;
-    }
-
-    .actionBtn {
-      display: flex;
-      width: 40px;
-      height: 40px;
-      padding: 0;
-      background: transparent;
-      cursor: pointer;
-
-      &>img {
-        width: 100%;
-        height: 100%;
-      }
-    }
-
-
-    .actionBtnSpeed {
-      position: relative;
-      width: 40px;
-      height: 40px;
-      background-color: transparent;
-      cursor: pointer;
-
-      &>img {
-        width: 100%;
-        height: 100%;
-      }
-    }
-
-
-    .iconReplay {
-      width: 40px;
-      height: 40px;
-      background-color: transparent;
-      cursor: pointer;
-      margin: 0 22px;
-
-      &>img {
-        width: 100%;
-        height: 100%;
-      }
-    }
-
-    .iconDownload {
-      width: 40px;
-      height: 40px;
-      margin-left: 12px;
-      background-color: transparent;
-      cursor: pointer;
-
-      &>img {
-        width: 100%;
-        height: 100%;
-      }
-    }
-  }
-
-  .slider {
-    width: 100%;
-    padding: 0 0 0 12px;
-
-    :global {
-
-      .n-slider .n-slider-rail .n-slider-rail__fill,
-      .n-slider .n-slider-handles .n-slider-handle-wrapper {
-        transition: all .2s;
-      }
-    }
-  }
-
-  .sectionAnimate {
-    opacity: 0;
-    pointer-events: none;
-    transform: translateY(100%);
-    transition: all .5s;
-  }
-
-}
-
-.sliderPopup {
-  position: absolute;
-  z-index: 9999;
-  left: -13px;
-  bottom: 35px;
-  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: 0 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%;
-//   --plyr-color-main: #198CFE;
-//   --plyr-range-track-height: 6px;
-//   --plyr-tooltip-radius: 3px;
-//   --plyr-range-thumb-height: 24px;
-//   --plyr-video-controls-background: #000;
-
-
-//   :global {
-//     .plyr--video {
-//       width: 100%;
-//       height: 100%;
-//     }
-
-//     .plyr__time {
-//       display: block !important;
-//     }
-
-//     .plyr__video-wrapper {
-//       pointer-events: none;
-//     }
-
-//   }
-// }
-
-// :global(.bottomFixed).controls {
-//   width: 100% !important;
-//   background: rgba(0, 0, 0, 0.6) !important;
-//   // backdrop-filter: blur(26px);
-//   height: 80px !important;
-//   min-height: 80px !important;
-//   padding: 0px 40px !important;
-//   z-index: 999;
-
-//   .time {
-//     display: flex;
-//     justify-content: space-between;
-//     color: #fff;
-//     padding: 4px 12px 4px;
-//     font-size: 24px;
-//     font-weight: 600;
-//     line-height: 33px;
-//     min-width: 140px;
-
-//     .line {
-//       font-size: 12px;
-//     }
-
-//     :global {
-//       .plyr__time+.plyr__time:before {
-//         content: '';
-//         margin-right: 0;
-//       }
-//     }
-//   }
-
-//   .slider {
-//     width: 100%;
-//     padding: 0 20px 0 12px;
-
-//     :global {
-//       .van-slider__button {
-//         background: var(--van-primary);
-//       }
-
-//       .van-loading {
-//         width: 100%;
-//         height: 100%;
-//       }
-//     }
-//   }
-
-//   .actions {
-//     display: flex;
-//     justify-content: space-between;
-//     color: #fff;
-//     font-size: 12px;
-//     align-items: center;
-
-//     .actionWrap {
-//       display: flex;
-//     }
-
-//     .actionBtn {
-//       display: flex;
-
-//       padding: 0;
-//       width: 52px;
-//       height: 52px;
-//       background: transparent;
-//     }
-
-//     .actionBtn>img {
-//       width: 100%;
-//       height: 100%;
-//     }
-
-//     :global {
-//       .van-loading__circular {
-//         width: 100%;
-//         height: 100%;
-//       }
-//     }
-
-//     .playIcon {
-//       display: none;
-//     }
-
-//     .btnPlay img:nth-child(2) {
-//       display: block;
-//     }
-
-//     .btnPause img:nth-child(3) {
-//       display: block;
-//     }
-
-//     .btnPlay,
-//     .btnPause {
-//       :global {
-//         .van-loading {
-//           display: none;
-//         }
-//       }
-//     }
-
-//     .loopBtn {
-//       background-color: transparent;
-//       width: 31px !important;
-//       height: 29px !important;
-//       padding: 0;
-//       cursor: pointer;
-
-//       :global {
-//         .loop {
-//           display: block;
-//         }
-
-//         .loopActive {
-//           display: none;
-//         }
-//       }
-//     }
-//   }
-// }
+/* 全屏模式下的样式 */
+// :fullscreen .videoContent,
+// :-webkit-full-screen .videoContent,
+// :-moz-full-screen .videoContent,
+// :-ms-fullscreen .videoContent {
+//   width: 100% !important;
+//   height: 100% !important;
+
+// }
+
+.videoWrap {
+  width: 100%;
+  height: 100%;
+  height: 518px;
+
+  // &:-webkit-full-screen {
+  //   width: 100% !important;
+  //   height: 100% !important;
+  // }
+
+  .videoContent {
+    height: 100%;
+
+    // &.fullScreen {
+    //   width: 100% !important;
+    //   height: 100% !important;
+    // }
+
+    // &:-webkit-full-screen {
+    //   width: 100% !important;
+    //   height: 100% !important;
+    // }
+
+    :global {
+      .video-js .vjs-tech {
+        object-fit: cover;
+      }
+    }
+  }
+
+
+  .controls {
+    border-radius: 0 0 16px 16px !important;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    background: rgba(0, 0, 0, 0.6);
+    backdrop-filter: blur(26px);
+    height: 80px;
+    padding: 0 26px 0 26px !important;
+    transition: all 0.3s;
+    display: flex;
+    align-items: center;
+
+    .time {
+      display: flex;
+      justify-content: space-between;
+      color: #fff;
+      padding: 4px 0 4px 12px;
+      font-size: max(24px, 14Px);
+      font-weight: 600;
+      line-height: 33px;
+      // min-width: 150px;
+      flex-shrink: 0;
+
+      .line {
+        font-size: 20px;
+      }
+
+      :global {
+        .plyr__time {
+          font-size: max(22px, 12Px);
+        }
+
+        .plyr__time+.plyr__time:before {
+          content: '';
+          margin-right: 0;
+        }
+      }
+    }
+  }
+
+  .actions {
+    display: flex;
+    justify-content: space-between;
+    height: 100%;
+    color: #fff;
+    font-size: 12px;
+    align-items: center;
+    flex-shrink: 0;
+
+    .actionWrap {
+      display: flex;
+      align-items: center;
+    }
+
+    .actionBtn {
+      display: flex;
+      width: 36px;
+      height: 36px;
+      padding: 0;
+      background: transparent;
+      cursor: pointer;
+
+      &>img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+
+    .actionBtnSpeed {
+      position: relative;
+      width: 36px;
+      height: 36px;
+      background-color: transparent;
+      cursor: pointer;
+
+      &>img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+
+    .iconReplay {
+      width: 36px;
+      height: 36px;
+      background-color: transparent;
+      cursor: pointer;
+      margin: 0 22px;
+
+      &>img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .iconDownload {
+      width: 36px;
+      height: 36px;
+      margin-left: 22px;
+      background-color: transparent;
+      cursor: pointer;
+
+      &>img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+
+  .slider {
+    width: 100%;
+    padding: 0 0 0 12px;
+
+    :global {
+
+      .n-slider .n-slider-rail .n-slider-rail__fill,
+      .n-slider .n-slider-handles .n-slider-handle-wrapper {
+        transition: all .2s;
+      }
+    }
+  }
+
+  .sectionAnimate {
+    // opacity: 0;
+    pointer-events: none;
+    // transform: translateY(100%);
+    margin-bottom: -80px;
+    transition: all .3s;
+  }
+
+}
+
+.sliderPopup {
+  position: absolute;
+  z-index: 9999;
+  left: -13px;
+  bottom: 35px;
+  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: 0 2px 4px 0px rgba(102, 102, 102, 0.77);
+    border-radius: 14px;
+    font-size: 14Px;
+    font-weight: 500;
+    height: 22Px;
+    color: #198CFE;
+    min-width: 36px;
+    text-align: center;
+    vertical-align: text-bottom;
+
+    span {
+      font-size: 12Px;
+    }
+  }
+
+  :global {
+    .n-slider {
+      margin: 7px 0;
+      padding: 0;
+    }
+  }
+}
+
+// .videoWrap {
+//   width: 100%;
+//   height: 100%;
+//   --plyr-color-main: #198CFE;
+//   --plyr-range-track-height: 6px;
+//   --plyr-tooltip-radius: 3px;
+//   --plyr-range-thumb-height: 24px;
+//   --plyr-video-controls-background: #000;
+
+
+//   :global {
+//     .plyr--video {
+//       width: 100%;
+//       height: 100%;
+//     }
+
+//     .plyr__time {
+//       display: block !important;
+//     }
+
+//     .plyr__video-wrapper {
+//       pointer-events: none;
+//     }
+
+//   }
+// }
+
+// :global(.bottomFixed).controls {
+//   width: 100% !important;
+//   background: rgba(0, 0, 0, 0.6) !important;
+//   // backdrop-filter: blur(26px);
+//   height: 80px !important;
+//   min-height: 80px !important;
+//   padding: 0px 36px !important;
+//   z-index: 999;
+
+//   .time {
+//     display: flex;
+//     justify-content: space-between;
+//     color: #fff;
+//     padding: 4px 12px 4px;
+//     font-size: 24px;
+//     font-weight: 600;
+//     line-height: 33px;
+//     min-width: 136px;
+
+//     .line {
+//       font-size: 12px;
+//     }
+
+//     :global {
+//       .plyr__time+.plyr__time:before {
+//         content: '';
+//         margin-right: 0;
+//       }
+//     }
+//   }
+
+//   .slider {
+//     width: 100%;
+//     padding: 0 20px 0 12px;
+
+//     :global {
+//       .van-slider__button {
+//         background: var(--van-primary);
+//       }
+
+//       .van-loading {
+//         width: 100%;
+//         height: 100%;
+//       }
+//     }
+//   }
+
+//   .actions {
+//     display: flex;
+//     justify-content: space-between;
+//     color: #fff;
+//     font-size: 12px;
+//     align-items: center;
+
+//     .actionWrap {
+//       display: flex;
+//     }
+
+//     .actionBtn {
+//       display: flex;
+
+//       padding: 0;
+//       width: 52px;
+//       height: 52px;
+//       background: transparent;
+//     }
+
+//     .actionBtn>img {
+//       width: 100%;
+//       height: 100%;
+//     }
+
+//     :global {
+//       .van-loading__circular {
+//         width: 100%;
+//         height: 100%;
+//       }
+//     }
+
+//     .playIcon {
+//       display: none;
+//     }
+
+//     .btnPlay img:nth-child(2) {
+//       display: block;
+//     }
+
+//     .btnPause img:nth-child(3) {
+//       display: block;
+//     }
+
+//     .btnPlay,
+//     .btnPause {
+//       :global {
+//         .van-loading {
+//           display: none;
+//         }
+//       }
+//     }
+
+//     .loopBtn {
+//       background-color: transparent;
+//       width: 31px !important;
+//       height: 29px !important;
+//       padding: 0;
+//       cursor: pointer;
+
+//       :global {
+//         .loop {
+//           display: block;
+//         }
+
+//         .loopActive {
+//           display: none;
+//         }
+//       }
+//     }
+//   }
+// }

+ 388 - 306
src/components/card-preview/video-modal/index.tsx

@@ -1,306 +1,388 @@
-import { defineComponent, nextTick, onMounted, reactive, toRefs } from 'vue';
-// import 'plyr/dist/plyr.css';
-// import Plyr from 'plyr';
-import { ref } from 'vue';
-import TCPlayer from 'tcplayer.js';
-import 'tcplayer.js/dist/tcplayer.min.css';
-import styles from './index.module.less';
-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';
-
-export default defineComponent({
-  name: 'video-play',
-  props: {
-    src: {
-      type: String,
-      default: ''
-    },
-    title: {
-      type: String,
-      default: ''
-    },
-    poster: {
-      type: String,
-      default: ''
-    },
-    isEmtry: {
-      type: Boolean,
-      default: false
-    },
-    isDownload: {
-      type: Boolean,
-      default: false
-    }
-  },
-  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
-  setup(props, { emit, expose }) {
-    const message = useMessage();
-    const { src, poster, isEmtry } = toRefs(props);
-    const videoFroms = reactive({
-      paused: true,
-      currentTimeNum: 0,
-      currentTime: '00:00',
-      durationNum: 0,
-      duration: '00:00',
-      showBar: true,
-      speedControl: false,
-      speedStyle: {
-        left: '1px'
-      },
-      defaultSpeed: 1 // 默认速度
-    });
-    const videoRef = ref();
-    const videoItem = ref();
-    const videoID = 'video' + Date.now() + Math.floor(Math.random() * 100);
-
-    // 对时间进行格式化
-    const timeFormat = (num: number) => {
-      if (num > 0) {
-        const m = Math.floor(num / 60);
-        const s = num % 60;
-        return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
-      } else {
-        return '00:00';
-      }
-    };
-
-    //
-    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();
-        videoFroms.paused = false;
-      } else {
-        videoItem.value.pause();
-        videoFroms.paused = true;
-      }
-      emit('togglePlay', videoFroms.paused);
-    };
-
-    // 下载资源
-    const onDownload = () => {
-      if (!props.src) {
-        message.error('下载失败');
-        return;
-      }
-      const fileUrl = props.src;
-      // 发起Fetch请求
-      fetch(fileUrl)
-        .then(response => response.blob())
-        .then(blob => {
-          saveAs(blob, props.title || new Date().getTime() + '');
-        })
-        .catch(() => {
-          message.error('下载失败');
-        });
-    };
-
-    const __init = () => {
-      if (videoItem.value) {
-        videoItem.value.poster(poster.value); // 封面
-        videoItem.value.src(isEmtry.value ? '' : src.value); // url 播放地址
-
-        // 初步加载时
-        videoItem.value.one('loadedmetadata', () => {
-          console.log(' Loading metadata');
-
-          // 获取时长
-          videoFroms.duration = timeFormat(
-            Math.round(videoItem.value.duration())
-          );
-          videoFroms.durationNum = videoItem.value.duration();
-
-          emit('loadedmetadata', videoItem.value);
-        });
-
-        // 视频播放时加载
-        videoItem.value.on('timeupdate', () => {
-          videoFroms.currentTime = timeFormat(
-            Math.round(videoItem.value?.currentTime() || 0)
-          );
-          videoFroms.currentTimeNum = videoItem.value.currentTime();
-        });
-
-        // 视频播放结束
-        videoItem.value.on('ended', () => {
-          videoFroms.paused = true;
-          emit('ended');
-        });
-      }
-    };
-
-    onMounted(() => {
-      videoItem.value = TCPlayer(videoID, {
-        appID: '',
-        controls: false
-      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
-      __init();
-    });
-    expose({
-      // changePlayBtn,
-      toggleHideControl
-    });
-    return () => (
-      <div class={styles.videoWrap}>
-        <video
-          style={{ width: '100%', height: '100%' }}
-          src={isEmtry.value ? '' : src.value}
-          poster={poster.value}
-          ref={videoRef}
-          id={videoID}
-          preload="auto"
-          playsinline
-          webkit-playsinline></video>
-
-        <div
-          class={[
-            styles.controls,
-            videoFroms.showBar ? '' : styles.sectionAnimate
-          ]}
-          onClick={(e: MouseEvent) => {
-            e.stopPropagation();
-            emit('reset');
-          }}>
-          <div class={styles.actions}>
-            <div class={styles.actionWrap}>
-              <button class={styles.actionBtn} onClick={onToggleVideo}>
-                {videoFroms.paused ? (
-                  <img class={styles.playIcon} src={iconplay} />
-                ) : (
-                  <img class={styles.playIcon} src={iconpause} />
-                )}
-              </button>
-
-              <button class={styles.iconReplay} onClick={onReplay}>
-                <img src={iconReplay} />
-              </button>
-
-              <div
-                class={styles.actionBtnSpeed}
-                onClick={() => {
-                  videoFroms.speedControl = !videoFroms.speedControl;
-                }}>
-                <img src={iconSpeed} />
-
-                <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.5}
-                      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.5) {
-                          return;
-                        }
-                        if (videoItem.value) {
-                          videoFroms.defaultSpeed =
-                            (videoFroms.defaultSpeed * 10 - 1) / 10;
-                          videoItem.value.playbackRate(videoFroms.defaultSpeed);
-                        }
-                      }}></i>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <div class={styles.slider}>
-            <NSlider
-              value={videoFroms.currentTimeNum}
-              step={0.01}
-              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));
-              }}
-            />
-          </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}>
-              {props.isDownload && (
-                <button class={styles.iconDownload} onClick={onDownload}>
-                  <img src={iconPreviewDownload} />
-                </button>
-              )}
-            </div>
-          </div>
-        </div>
-      </div>
-    );
-  }
-});
+import { defineComponent, nextTick, onMounted, reactive, toRefs } from 'vue';
+// import 'plyr/dist/plyr.css';
+// import Plyr from 'plyr';
+import { ref } from 'vue';
+import TCPlayer from 'tcplayer.js';
+import 'tcplayer.js/dist/tcplayer.min.css';
+import styles from './index.module.less';
+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 iconFullscreen from '@views/attend-class/image/icon-fullscreen.png';
+import iconFullscreenExit from '@views/attend-class/image/icon-fullscreen-exit.png';
+import iconSpeed from '@views/attend-class/image/icon-speed.png';
+import { NSlider, useMessage } from 'naive-ui';
+import { saveAs } from 'file-saver';
+import { exitFullscreen } from '/src/utils';
+
+export default defineComponent({
+  name: 'video-play',
+  props: {
+    src: {
+      type: String,
+      default: ''
+    },
+    title: {
+      type: String,
+      default: ''
+    },
+    poster: {
+      type: String,
+      default: ''
+    },
+    isEmtry: {
+      type: Boolean,
+      default: false
+    },
+    isDownload: {
+      type: Boolean,
+      default: false
+    },
+    fullscreen: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
+  setup(props, { emit, expose }) {
+    const message = useMessage();
+    const videoId =
+      'vFullscreen' + Date.now() + Math.floor(Math.random() * 100);
+    const { src, poster, isEmtry } = toRefs(props);
+    const videoFroms = reactive({
+      isFullScreen: false, // 是否全屏
+      paused: true,
+      currentTimeNum: 0,
+      currentTime: '00:00',
+      durationNum: 0,
+      duration: '00:00',
+      showBar: true,
+      timer: null as any,
+      speedControl: false,
+      speedStyle: {
+        left: '1px'
+      },
+      defaultSpeed: 1 // 默认速度
+    });
+    const videoRef = ref();
+    const videoItem = ref();
+    const videoID = 'video' + Date.now() + Math.floor(Math.random() * 100);
+
+    // 对时间进行格式化
+    const timeFormat = (num: number) => {
+      if (num > 0) {
+        const m = Math.floor(num / 60);
+        const s = num % 60;
+        return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
+      } else {
+        return '00:00';
+      }
+    };
+
+    //
+    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();
+        videoFroms.paused = false;
+      } else {
+        videoItem.value.pause();
+        videoFroms.paused = true;
+      }
+      emit('togglePlay', videoFroms.paused);
+    };
+
+    // 下载资源
+    const onDownload = () => {
+      if (!props.src) {
+        message.error('下载失败');
+        return;
+      }
+      const fileUrl = props.src;
+      // 发起Fetch请求
+      fetch(fileUrl)
+        .then(response => response.blob())
+        .then(blob => {
+          saveAs(blob, props.title || new Date().getTime() + '');
+        })
+        .catch(() => {
+          message.error('下载失败');
+        });
+    };
+
+    /** 延迟收起模态框 */
+    const setModelOpen = (status = true) => {
+      clearTimeout(videoFroms.timer);
+      toggleHideControl(status);
+      videoFroms.timer = setTimeout(() => {
+        toggleHideControl(false);
+      }, 3000);
+    };
+
+    const __init = () => {
+      if (videoItem.value) {
+        videoItem.value.poster(poster.value); // 封面
+        videoItem.value.src(isEmtry.value ? '' : src.value); // url 播放地址
+
+        // 初步加载时
+        videoItem.value.one('loadedmetadata', () => {
+          console.log(' Loading metadata');
+
+          // 获取时长
+          videoFroms.duration = timeFormat(
+            Math.round(videoItem.value.duration())
+          );
+          videoFroms.durationNum = videoItem.value.duration();
+
+          emit('loadedmetadata', videoItem.value);
+        });
+
+        // 视频播放时加载
+        videoItem.value.on('timeupdate', () => {
+          videoFroms.currentTime = timeFormat(
+            Math.round(videoItem.value?.currentTime() || 0)
+          );
+          videoFroms.currentTimeNum = videoItem.value.currentTime();
+        });
+
+        // 视频播放结束
+        videoItem.value.on('ended', () => {
+          videoFroms.paused = true;
+          emit('ended');
+        });
+
+        videoItem.value.on('play', () => {
+          setModelOpen();
+        });
+
+        videoItem.value.on('pause', () => {
+          clearTimeout(videoFroms.timer);
+          videoFroms.showBar = true;
+        });
+      }
+    };
+
+    /** 全屏 */
+    const isElementFullscreen = (element: any) => {
+      const dom: any = document;
+      return (
+        element ===
+        (dom.fullscreenElement ||
+          dom.webkitFullscreenElement ||
+          dom.mozFullScreenElement ||
+          dom.msFullscreenElement)
+      );
+    };
+    const onFullScreen = () => {
+      const el: any = document.querySelector('#' + videoId);
+
+      //   //进入全屏
+      if (el) {
+        if (isElementFullscreen(el)) {
+          exitFullscreen();
+          videoFroms.isFullScreen = false;
+        } else {
+          (el.requestFullscreen && el.requestFullscreen()) ||
+            (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
+            (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
+            (el.msRequestFullscreen && el.msRequestFullscreen());
+          videoFroms.isFullScreen = true;
+        }
+        // videoFroms.isFullScreen = isElementFullscreen(el);
+      }
+    };
+
+    onMounted(() => {
+      videoItem.value = TCPlayer(videoID, {
+        appID: '',
+        controls: false
+      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
+      __init();
+    });
+    expose({
+      // changePlayBtn,
+      toggleHideControl
+    });
+    return () => (
+      <div
+        class={[styles.videoWrap]}
+        id={videoId}
+        onClick={() => setModelOpen(!videoFroms.showBar)}>
+        <div class={[styles.videoContent]}>
+          <video
+            style={{ width: '100%', height: '100%' }}
+            src={isEmtry.value ? '' : src.value}
+            poster={poster.value}
+            ref={videoRef}
+            id={videoID}
+            preload="auto"
+            playsinline
+            webkit-playsinline></video>
+
+          <div
+            class={[
+              styles.controls,
+              videoFroms.showBar ? '' : styles.sectionAnimate
+            ]}
+            onClick={(e: MouseEvent) => {
+              e.stopPropagation();
+              emit('reset');
+              setModelOpen();
+            }}>
+            <div class={styles.actions}>
+              <div class={styles.actionWrap}>
+                <button class={styles.actionBtn} onClick={onToggleVideo}>
+                  {videoFroms.paused ? (
+                    <img class={styles.playIcon} src={iconplay} />
+                  ) : (
+                    <img class={styles.playIcon} src={iconpause} />
+                  )}
+                </button>
+
+                <button class={styles.iconReplay} onClick={onReplay}>
+                  <img src={iconReplay} />
+                </button>
+
+                <div
+                  class={styles.actionBtnSpeed}
+                  onClick={() => {
+                    videoFroms.speedControl = !videoFroms.speedControl;
+                  }}>
+                  <img src={iconSpeed} />
+
+                  <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.5}
+                        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.5) {
+                            return;
+                          }
+                          if (videoItem.value) {
+                            videoFroms.defaultSpeed =
+                              (videoFroms.defaultSpeed * 10 - 1) / 10;
+                            videoItem.value.playbackRate(
+                              videoFroms.defaultSpeed
+                            );
+                          }
+                        }}></i>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <div class={styles.slider}>
+              <NSlider
+                value={videoFroms.currentTimeNum}
+                step={0.01}
+                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));
+                }}
+              />
+            </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}>
+                {props.isDownload && (
+                  <button class={styles.iconDownload} onClick={onDownload}>
+                    <img src={iconPreviewDownload} />
+                  </button>
+                )}
+                {props.fullscreen && (
+                  <button class={styles.iconDownload} onClick={onFullScreen}>
+                    <img
+                      src={
+                        videoFroms.isFullScreen
+                          ? iconFullscreenExit
+                          : iconFullscreen
+                      }
+                    />
+                  </button>
+                )}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

+ 536 - 517
src/components/card-type/index.tsx

@@ -1,517 +1,536 @@
-import { PropType, Transition, defineComponent, ref } from 'vue';
-import styles from './index.module.less';
-import { NButton, NCard, NImage, NModal, NSpin, useMessage } from 'naive-ui';
-import iconImage from '@common/images/icon-image.png';
-import iconVideo from '@common/images/icon-video.png';
-import iconAudio from '@common/images/icon-audio.png';
-import iconMusic from '@common/images/icon-music.png';
-import iconPPT from '@common/images/icon-ppt.png';
-import iconOther from '@common/images/icon-other.png';
-import iconCollectDefault from '@common/images/icon-collect-default.png';
-import iconCollectActive from '@common/images/icon-collect-active.png';
-import iconDownload from '@common/images/icon-download.png';
-import TheNoticeBar from '../TheNoticeBar';
-import AudioPlayer from './audio-player';
-import VideoPlayer from './video-player';
-import { PageEnum } from '/src/enums/pageEnum';
-import { api_musicSheetDetail } from '/src/api/user';
-import JSZip, { file } from 'jszip';
-import { saveAs } from 'file-saver';
-
-// LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC_WIKI:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家)
-type itemType = {
-  id: string | number;
-  type:
-    | 'IMG'
-    | 'VIDEO'
-    | 'SONG'
-    | 'MUSIC'
-    | 'PPT'
-    | 'LISTEN'
-    | 'RHYTHM'
-    | 'THEORY'
-    | 'MUSIC_WIKI'
-    | 'INSTRUMENT'
-    | 'MUSICIAN';
-  coverImg: string;
-  content?: string;
-  title: string;
-  isCollect: boolean;
-  isSelected: boolean; // 精选
-  exist?: boolean; // 是否已经选
-};
-
-export default defineComponent({
-  name: 'card-type',
-  props: {
-    // 是否是选中状态
-    isActive: {
-      type: Boolean,
-      default: false
-    },
-    /** 是否可以拖拽 */
-    draggable: {
-      type: Boolean,
-      default: false
-    },
-    // 是否可以收藏
-    isCollect: {
-      type: Boolean,
-      default: true
-    },
-    // 是否显示收藏
-    isShowCollect: {
-      type: Boolean,
-      default: true
-    },
-    // 是否显示添加按钮
-    isShowAdd: {
-      type: Boolean,
-      default: false
-    },
-    // 是否禁用添加按钮
-    isShowAddDisabled: {
-      type: Boolean,
-      default: false
-    },
-    // 鼠标移动上面的时候是否自动播放,或者可以点击
-    disabledMouseHover: {
-      type: Boolean,
-      default: true
-    },
-    // 是否预览
-    isPreview: {
-      type: Boolean,
-      default: true
-    },
-    item: {
-      type: Object as PropType<itemType>,
-      default: () => ({})
-    },
-    /** 是否下架 */
-    offShelf: {
-      type: Boolean,
-      default: false
-    },
-    /** 是否可以下载 */
-    isDownload: {
-      type: Boolean,
-      default: false
-    }
-  },
-  /**
-   * @type {string} click 点击事件
-   * @type {string} collect 收藏
-   * @type {string} add 添加
-   * @type {string} offShelf 下架
-   */
-  emits: ['click', 'collect', 'add', 'offShelf'],
-  setup(props, { emit }) {
-    const message = useMessage();
-    const isAnimation = ref(false);
-    const downloadStatus = ref(false);
-    const formatType = (type: string) => {
-      let typeImg = iconOther;
-      switch (type) {
-        case 'IMG':
-          typeImg = iconImage;
-          break;
-        case 'VIDEO':
-          typeImg = iconVideo;
-          break;
-        case 'SONG':
-          typeImg = iconAudio;
-          break;
-        case 'MUSIC':
-          typeImg = iconMusic;
-          break;
-        case 'PPT':
-          typeImg = iconPPT;
-          break;
-      }
-      return typeImg;
-    };
-
-    // 获取文件blob格式
-    const getFileBlob = (url: string) => {
-      return new Promise((resolve, reject) => {
-        const request = new XMLHttpRequest();
-        request.open('GET', url, true);
-        request.responseType = 'blob';
-        request.onload = (res: any) => {
-          if (res.target.status == 200) {
-            resolve(res.target.response);
-          } else {
-            reject(res);
-          }
-        };
-        request.send();
-      });
-    };
-
-    // 多个文件下载
-    const downLoadMultiFile = (files: any, filesName: string) => {
-      const zip = new JSZip();
-      const result = [];
-      for (const i in files) {
-        const promise = getFileBlob(files[i].url).then((res: any) => {
-          zip.file(files[i].name, res, { binary: true });
-        });
-        result.push(promise);
-      }
-      Promise.all(result)
-        .then(() => {
-          zip.generateAsync({ type: 'blob' }).then(res => {
-            saveAs(
-              res,
-              filesName
-                ? filesName + Date.now() + '.zip'
-                : `文件夹${Date.now()}.zip`
-            );
-          });
-        })
-        .catch(() => {
-          message.error('下载失败');
-        });
-
-      downloadStatus.value = false;
-    };
-
-    const downloadFile = (filename: string, fileUrl: string) => {
-      // 发起Fetch请求
-      fetch(fileUrl)
-        .then(response => response.blob())
-        .then(blob => {
-          saveAs(blob, filename);
-          setTimeout(() => {
-            downloadStatus.value = false;
-          }, 100);
-        })
-        .catch(() => {
-          message.error('下载失败');
-        });
-
-      downloadStatus.value = false;
-    };
-
-    const getFileName = (url: any) => {
-      // 使用正则表达式获取文件名
-      const tempUrl = url.split('?');
-      const fileNameRegex = /\/([^\\/]+)$/; // 匹配最后一个斜杠后的内容
-      const match = tempUrl[0].match(fileNameRegex);
-
-      if (match) {
-        return match[1];
-      } else {
-        return '';
-      }
-    };
-    const onDownload = async (e: MouseEvent) => {
-      e.stopPropagation();
-      e.preventDefault();
-      const item = props.item;
-      if (!item.content) {
-        message.error('下载失败');
-        return;
-      }
-      if (downloadStatus.value) return false;
-      downloadStatus.value = true;
-      const suffix: any = item.content?.split('.');
-      const fileName = item.title + '.' + suffix[suffix?.length - 1];
-      if (item.type === 'MUSIC') {
-        const { data } = await api_musicSheetDetail(item.content);
-        const urls = [];
-        if (data.xmlFileUrl) {
-          urls.push({
-            url: data.xmlFileUrl,
-            name: getFileName(data.xmlFileUrl)
-          });
-        }
-        if (data.background && data.background.length > 0) {
-          data.background.forEach((item: any) => {
-            urls.push({
-              url: item.audioFileUrl,
-              name: getFileName(item.audioFileUrl)
-            });
-          });
-        }
-        downLoadMultiFile(urls, item.title);
-
-        // setTimeout(() => {
-        //   downloadStatus.value = false;
-        // }, 1000);
-      } else {
-        downloadFile(fileName, item.content);
-      }
-    };
-
-    return () => (
-      <div
-        onClick={() => emit('click', props.item)}
-        key={props.item.id}
-        draggable={!props.draggable ? false : props.item.exist ? false : true}
-        class={[
-          styles['card-section'],
-          'card-section-container',
-          !props.draggable ? '' : props.item.exist ? '' : styles.cardDrag
-        ]}
-        onMouseenter={() => {
-          isAnimation.value = true;
-        }}
-        onMouseleave={() => {
-          isAnimation.value = false;
-        }}
-        onDragstart={(e: any) => {
-          console.log('dragstart', Date.now());
-          e.dataTransfer.setData('text', JSON.stringify(props.item));
-        }}>
-        {/* 判断是否下架 */}
-        {props.offShelf && (
-          <div class={styles.offShelfBg}>
-            <p class={styles.offShelfTips}>该资源已被下架</p>
-            <NButton
-              type="primary"
-              class={styles.offShelfBtn}
-              onClick={(e: MouseEvent) => {
-                e.stopPropagation();
-                emit('offShelf');
-              }}>
-              确认
-            </NButton>
-          </div>
-        )}
-        <NCard
-          class={[
-            styles['card-section-content'],
-            props.isShowAdd ? '' : styles.course,
-            props.isActive ? styles.isActive : '',
-            props.item.exist ? styles.showAddBtn : '' // 是否已添加
-          ]}
-          style={{ cursor: 'pointer' }}>
-          {{
-            cover: () => (
-              <>
-                {/* 图片 */}
-                {props.item.type === 'IMG' && (
-                  <NImage
-                    class={[styles.cover, styles.image]}
-                    lazy
-                    previewDisabled={props.disabledMouseHover}
-                    objectFit="cover"
-                    src={props.item.coverImg}
-                    previewSrc={props.item.content}
-                  />
-                )}
-                {/* 乐谱 */}
-                {props.item.type === 'MUSIC' && (
-                  <NImage
-                    class={[styles.cover, styles.image]}
-                    lazy
-                    previewDisabled={true}
-                    objectFit="contain"
-                    src={props.item.coverImg}
-                  />
-                )}
-                {/* 音频 */}
-                {props.item.type === 'SONG' && (
-                  <AudioPlayer
-                    content={props.item.content}
-                    cover={props.item.coverImg}
-                    previewDisabled={props.disabledMouseHover}
-                  />
-                )}
-                {/* 视频 */}
-                {props.item.type === 'VIDEO' && (
-                  <VideoPlayer
-                    cover={props.item.coverImg}
-                    content={props.item.content}
-                    previewDisabled={props.disabledMouseHover}
-                  />
-                )}
-                {/* ppt */}
-                {props.item.type === 'PPT' && (
-                  <NImage
-                    class={[styles.cover, styles.image]}
-                    lazy
-                    previewDisabled={true}
-                    objectFit="cover"
-                    src={props.item.coverImg || PageEnum.PPT_DEFAULT_COVER}
-                  />
-                )}
-                {/* 节奏练习 */}
-                {props.item.type === 'RHYTHM' && (
-                  <NImage
-                    class={[styles.cover, styles.image]}
-                    lazy
-                    previewDisabled={true}
-                    objectFit="cover"
-                    src={props.item.coverImg || PageEnum.RHYTHM_DEFAULT_COVER}
-                  />
-                )}
-
-                {/* 听音练习 */}
-                {props.item.type === 'LISTEN' && (
-                  <NImage
-                    class={[styles.cover, styles.image]}
-                    lazy
-                    previewDisabled={true}
-                    objectFit="cover"
-                    src={props.item.coverImg}
-                  />
-                )}
-                {/* 乐理 */}
-                {props.item.type === 'THEORY' && (
-                  <NImage
-                    class={[styles.cover, styles.image]}
-                    lazy
-                    previewDisabled={true}
-                    objectFit="cover"
-                    src={props.item.coverImg || PageEnum.THEORY_DEFAULT_COVER}
-                  />
-                )}
-                {/* 名曲 */}
-                {props.item.type === 'MUSIC_WIKI' && (
-                  <NImage
-                    class={[styles.cover, styles.image]}
-                    lazy
-                    previewDisabled={true}
-                    objectFit="cover"
-                    src={props.item.coverImg || PageEnum.MUSIC_DEFAULT_COVER}
-                  />
-                )}
-                {/* 乐器 */}
-                {props.item.type === 'INSTRUMENT' && (
-                  <NImage
-                    class={[styles.cover, styles.image]}
-                    lazy
-                    previewDisabled={true}
-                    objectFit="cover"
-                    src={
-                      props.item.coverImg || PageEnum.INSTRUMENT_DEFAULT_COVER
-                    }
-                  />
-                )}
-                {/* 音乐家 */}
-                {props.item.type === 'MUSICIAN' && (
-                  <NImage
-                    class={[styles.cover, styles.image]}
-                    lazy
-                    previewDisabled={true}
-                    objectFit="cover"
-                    src={props.item.coverImg || PageEnum.MUSICIAN_DEFAULT_COVER}
-                  />
-                )}
-              </>
-            ),
-            footer: () => (
-              <div class={styles.footer}>
-                <div class={[styles.title, 'footerTitle']}>
-                  <NImage
-                    class={[styles.titleType]}
-                    src={formatType(props.item.type)}
-                    objectFit="cover"
-                  />
-                  <span class={[styles.titleContent, 'titleContent']}>
-                    <TheNoticeBar
-                      isAnimation={isAnimation.value}
-                      text={props.item.title}
-                    />
-                  </span>
-                </div>
-                {/* 收藏 */}
-                <div class={styles.btnGroup}>
-                  {props.isDownload && (
-                    <div class={styles.btnItem} onClick={onDownload}>
-                      <NSpin show={downloadStatus.value} size={'small'}>
-                        <img
-                          src={iconDownload}
-                          key="3"
-                          class={[styles.iconCollect]}
-                        />
-                      </NSpin>
-                    </div>
-                  )}
-                  {props.isShowCollect && (
-                    <div
-                      class={[styles.iconCollect, styles.btnItem]}
-                      onClick={(e: MouseEvent) => {
-                        e.stopPropagation();
-                        e.preventDefault();
-                        // 判断是否可以收藏
-                        if (props.isCollect) {
-                          emit('collect', props.item);
-                        }
-                      }}>
-                      <Transition name="favitor" mode="out-in">
-                        {props.item.isCollect ? (
-                          <img
-                            src={iconCollectActive}
-                            key="1"
-                            class={[
-                              styles.iconCollect,
-                              props.isCollect ? styles.isCollect : ''
-                            ]}
-                          />
-                        ) : (
-                          <img
-                            src={iconCollectDefault}
-                            key="2"
-                            class={[
-                              styles.iconCollect,
-                              props.isCollect ? styles.isCollect : ''
-                            ]}
-                          />
-                        )}
-                      </Transition>
-                    </div>
-                  )}
-                </div>
-
-                {/* 精选 */}
-                {props.item.isSelected && (
-                  <span class={styles.iconSelected}></span>
-                )}
-
-                {/* 添加按钮 */}
-                {props.isShowAdd &&
-                  (props.item.exist ? (
-                    <NButton
-                      type="primary"
-                      class={[
-                        styles.addBtn,
-                        props.item.exist ? styles.addBtnDisabled : ''
-                      ]}
-                      disabled={props.item.exist || props.isShowAddDisabled}
-                      onClick={(e: MouseEvent) => {
-                        e.stopPropagation();
-                        e.preventDefault();
-                        emit('add', props.item);
-                      }}>
-                      {props.item.exist ? '已添加' : '添加'}
-                    </NButton>
-                  ) : (
-                    !props.isShowAddDisabled && (
-                      <NButton
-                        type="primary"
-                        class={[
-                          styles.addBtn,
-                          props.item.exist ? styles.addBtnDisabled : ''
-                        ]}
-                        disabled={props.item.exist || props.isShowAddDisabled}
-                        onClick={(e: MouseEvent) => {
-                          e.stopPropagation();
-                          e.preventDefault();
-                          emit('add', props.item);
-                        }}>
-                        {props.item.exist ? '已添加' : '添加'}
-                      </NButton>
-                    )
-                  ))}
-              </div>
-            )
-          }}
-        </NCard>
-      </div>
-    );
-  }
-});
+import { PropType, Transition, defineComponent, ref } from 'vue';
+import styles from './index.module.less';
+import {
+  ImageRenderToolbarProps,
+  NButton,
+  NCard,
+  NImage,
+  NModal,
+  NSpin,
+  useMessage
+} from 'naive-ui';
+import iconImage from '@common/images/icon-image.png';
+import iconVideo from '@common/images/icon-video.png';
+import iconAudio from '@common/images/icon-audio.png';
+import iconMusic from '@common/images/icon-music.png';
+import iconPPT from '@common/images/icon-ppt.png';
+import iconOther from '@common/images/icon-other.png';
+import iconCollectDefault from '@common/images/icon-collect-default.png';
+import iconCollectActive from '@common/images/icon-collect-active.png';
+import iconDownload from '@common/images/icon-download.png';
+import TheNoticeBar from '../TheNoticeBar';
+import AudioPlayer from './audio-player';
+import VideoPlayer from './video-player';
+import { PageEnum } from '/src/enums/pageEnum';
+import { api_musicSheetDetail } from '/src/api/user';
+import JSZip, { file } from 'jszip';
+import { saveAs } from 'file-saver';
+
+// LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC_WIKI:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家)
+type itemType = {
+  id: string | number;
+  type:
+    | 'IMG'
+    | 'VIDEO'
+    | 'SONG'
+    | 'MUSIC'
+    | 'PPT'
+    | 'LISTEN'
+    | 'RHYTHM'
+    | 'THEORY'
+    | 'MUSIC_WIKI'
+    | 'INSTRUMENT'
+    | 'MUSICIAN';
+  coverImg: string;
+  content?: string;
+  title: string;
+  isCollect: boolean;
+  isSelected: boolean; // 精选
+  exist?: boolean; // 是否已经选
+};
+
+export default defineComponent({
+  name: 'card-type',
+  props: {
+    // 是否是选中状态
+    isActive: {
+      type: Boolean,
+      default: false
+    },
+    /** 是否可以拖拽 */
+    draggable: {
+      type: Boolean,
+      default: false
+    },
+    // 是否可以收藏
+    isCollect: {
+      type: Boolean,
+      default: true
+    },
+    // 是否显示收藏
+    isShowCollect: {
+      type: Boolean,
+      default: true
+    },
+    // 是否显示添加按钮
+    isShowAdd: {
+      type: Boolean,
+      default: false
+    },
+    // 是否禁用添加按钮
+    isShowAddDisabled: {
+      type: Boolean,
+      default: false
+    },
+    // 鼠标移动上面的时候是否自动播放,或者可以点击
+    disabledMouseHover: {
+      type: Boolean,
+      default: true
+    },
+    // 是否预览
+    isPreview: {
+      type: Boolean,
+      default: true
+    },
+    item: {
+      type: Object as PropType<itemType>,
+      default: () => ({})
+    },
+    /** 是否下架 */
+    offShelf: {
+      type: Boolean,
+      default: false
+    },
+    /** 是否可以下载 */
+    isDownload: {
+      type: Boolean,
+      default: false
+    }
+  },
+  /**
+   * @type {string} click 点击事件
+   * @type {string} collect 收藏
+   * @type {string} add 添加
+   * @type {string} offShelf 下架
+   */
+  emits: ['click', 'collect', 'add', 'offShelf'],
+  setup(props, { emit }) {
+    const message = useMessage();
+    const isAnimation = ref(false);
+    const downloadStatus = ref(false);
+    const formatType = (type: string) => {
+      let typeImg = iconOther;
+      switch (type) {
+        case 'IMG':
+          typeImg = iconImage;
+          break;
+        case 'VIDEO':
+          typeImg = iconVideo;
+          break;
+        case 'SONG':
+          typeImg = iconAudio;
+          break;
+        case 'MUSIC':
+          typeImg = iconMusic;
+          break;
+        case 'PPT':
+          typeImg = iconPPT;
+          break;
+      }
+      return typeImg;
+    };
+
+    // 获取文件blob格式
+    const getFileBlob = (url: string) => {
+      return new Promise((resolve, reject) => {
+        const request = new XMLHttpRequest();
+        request.open('GET', url, true);
+        request.responseType = 'blob';
+        request.onload = (res: any) => {
+          if (res.target.status == 200) {
+            resolve(res.target.response);
+          } else {
+            reject(res);
+          }
+        };
+        request.send();
+      });
+    };
+
+    // 多个文件下载
+    const downLoadMultiFile = (files: any, filesName: string) => {
+      const zip = new JSZip();
+      const result = [];
+      for (const i in files) {
+        const promise = getFileBlob(files[i].url).then((res: any) => {
+          zip.file(files[i].name, res, { binary: true });
+        });
+        result.push(promise);
+      }
+      Promise.all(result)
+        .then(() => {
+          zip.generateAsync({ type: 'blob' }).then(res => {
+            saveAs(
+              res,
+              filesName
+                ? filesName + Date.now() + '.zip'
+                : `文件夹${Date.now()}.zip`
+            );
+          });
+        })
+        .catch(() => {
+          message.error('下载失败');
+        });
+
+      downloadStatus.value = false;
+    };
+
+    const downloadFile = (filename: string, fileUrl: string) => {
+      // 发起Fetch请求
+      fetch(fileUrl)
+        .then(response => response.blob())
+        .then(blob => {
+          saveAs(blob, filename);
+          setTimeout(() => {
+            downloadStatus.value = false;
+          }, 100);
+        })
+        .catch(() => {
+          message.error('下载失败');
+        });
+
+      downloadStatus.value = false;
+    };
+
+    const getFileName = (url: any) => {
+      // 使用正则表达式获取文件名
+      const tempUrl = url.split('?');
+      const fileNameRegex = /\/([^\\/]+)$/; // 匹配最后一个斜杠后的内容
+      const match = tempUrl[0].match(fileNameRegex);
+
+      if (match) {
+        return match[1];
+      } else {
+        return '';
+      }
+    };
+    const onDownload = async (e: MouseEvent) => {
+      e.stopPropagation();
+      e.preventDefault();
+      const item = props.item;
+      if (!item.content) {
+        message.error('下载失败');
+        return;
+      }
+      if (downloadStatus.value) return false;
+      downloadStatus.value = true;
+      const suffix: any = item.content?.split('.');
+      const fileName = item.title + '.' + suffix[suffix?.length - 1];
+      if (item.type === 'MUSIC') {
+        const { data } = await api_musicSheetDetail(item.content);
+        const urls = [];
+        if (data.xmlFileUrl) {
+          urls.push({
+            url: data.xmlFileUrl,
+            name: getFileName(data.xmlFileUrl)
+          });
+        }
+        if (data.background && data.background.length > 0) {
+          data.background.forEach((item: any) => {
+            urls.push({
+              url: item.audioFileUrl,
+              name: getFileName(item.audioFileUrl)
+            });
+          });
+        }
+        downLoadMultiFile(urls, item.title);
+
+        // setTimeout(() => {
+        //   downloadStatus.value = false;
+        // }, 1000);
+      } else {
+        downloadFile(fileName, item.content);
+      }
+    };
+
+    return () => (
+      <div
+        onClick={() => emit('click', props.item)}
+        key={props.item.id}
+        draggable={!props.draggable ? false : props.item.exist ? false : true}
+        class={[
+          styles['card-section'],
+          'card-section-container',
+          !props.draggable ? '' : props.item.exist ? '' : styles.cardDrag
+        ]}
+        onMouseenter={() => {
+          isAnimation.value = true;
+        }}
+        onMouseleave={() => {
+          isAnimation.value = false;
+        }}
+        onDragstart={(e: any) => {
+          console.log('dragstart', Date.now());
+          e.dataTransfer.setData('text', JSON.stringify(props.item));
+        }}>
+        {/* 判断是否下架 */}
+        {props.offShelf && (
+          <div class={styles.offShelfBg}>
+            <p class={styles.offShelfTips}>该资源已被下架</p>
+            <NButton
+              type="primary"
+              class={styles.offShelfBtn}
+              onClick={(e: MouseEvent) => {
+                e.stopPropagation();
+                emit('offShelf');
+              }}>
+              确认
+            </NButton>
+          </div>
+        )}
+        <NCard
+          class={[
+            styles['card-section-content'],
+            props.isShowAdd ? '' : styles.course,
+            props.isActive ? styles.isActive : '',
+            props.item.exist ? styles.showAddBtn : '' // 是否已添加
+          ]}
+          style={{ cursor: 'pointer' }}>
+          {{
+            cover: () => (
+              <>
+                {/* 图片 */}
+                {props.item.type === 'IMG' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={props.disabledMouseHover}
+                    objectFit="cover"
+                    src={props.item.coverImg}
+                    previewSrc={props.item.content}
+                    renderToolbar={({ nodes }: ImageRenderToolbarProps) => {
+                      return [
+                        nodes.prev,
+                        nodes.next,
+                        nodes.rotateCounterclockwise,
+                        nodes.rotateClockwise,
+                        nodes.resizeToOriginalSize,
+                        nodes.zoomOut,
+                        nodes.close
+                      ];
+                    }}
+                  />
+                )}
+                {/* 乐谱 */}
+                {props.item.type === 'MUSIC' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="contain"
+                    src={props.item.coverImg}
+                  />
+                )}
+                {/* 音频 */}
+                {props.item.type === 'SONG' && (
+                  <AudioPlayer
+                    content={props.item.content}
+                    cover={props.item.coverImg}
+                    previewDisabled={props.disabledMouseHover}
+                  />
+                )}
+                {/* 视频 */}
+                {props.item.type === 'VIDEO' && (
+                  <VideoPlayer
+                    cover={props.item.coverImg}
+                    content={props.item.content}
+                    previewDisabled={props.disabledMouseHover}
+                  />
+                )}
+                {/* ppt */}
+                {props.item.type === 'PPT' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg || PageEnum.PPT_DEFAULT_COVER}
+                  />
+                )}
+                {/* 节奏练习 */}
+                {props.item.type === 'RHYTHM' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg || PageEnum.RHYTHM_DEFAULT_COVER}
+                  />
+                )}
+
+                {/* 听音练习 */}
+                {props.item.type === 'LISTEN' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg}
+                  />
+                )}
+                {/* 乐理 */}
+                {props.item.type === 'THEORY' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg || PageEnum.THEORY_DEFAULT_COVER}
+                  />
+                )}
+                {/* 名曲 */}
+                {props.item.type === 'MUSIC_WIKI' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg || PageEnum.MUSIC_DEFAULT_COVER}
+                  />
+                )}
+                {/* 乐器 */}
+                {props.item.type === 'INSTRUMENT' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={
+                      props.item.coverImg || PageEnum.INSTRUMENT_DEFAULT_COVER
+                    }
+                  />
+                )}
+                {/* 音乐家 */}
+                {props.item.type === 'MUSICIAN' && (
+                  <NImage
+                    class={[styles.cover, styles.image]}
+                    lazy
+                    previewDisabled={true}
+                    objectFit="cover"
+                    src={props.item.coverImg || PageEnum.MUSICIAN_DEFAULT_COVER}
+                  />
+                )}
+              </>
+            ),
+            footer: () => (
+              <div class={styles.footer}>
+                <div class={[styles.title, 'footerTitle']}>
+                  <NImage
+                    class={[styles.titleType]}
+                    src={formatType(props.item.type)}
+                    objectFit="cover"
+                  />
+                  <span class={[styles.titleContent, 'titleContent']}>
+                    <TheNoticeBar
+                      isAnimation={isAnimation.value}
+                      text={props.item.title}
+                    />
+                  </span>
+                </div>
+                {/* 收藏 */}
+                <div class={styles.btnGroup}>
+                  {props.isDownload && (
+                    <div class={styles.btnItem} onClick={onDownload}>
+                      <NSpin show={downloadStatus.value} size={'small'}>
+                        <img
+                          src={iconDownload}
+                          key="3"
+                          class={[styles.iconCollect]}
+                        />
+                      </NSpin>
+                    </div>
+                  )}
+                  {props.isShowCollect && (
+                    <div
+                      class={[styles.iconCollect, styles.btnItem]}
+                      onClick={(e: MouseEvent) => {
+                        e.stopPropagation();
+                        e.preventDefault();
+                        // 判断是否可以收藏
+                        if (props.isCollect) {
+                          emit('collect', props.item);
+                        }
+                      }}>
+                      <Transition name="favitor" mode="out-in">
+                        {props.item.isCollect ? (
+                          <img
+                            src={iconCollectActive}
+                            key="1"
+                            class={[
+                              styles.iconCollect,
+                              props.isCollect ? styles.isCollect : ''
+                            ]}
+                          />
+                        ) : (
+                          <img
+                            src={iconCollectDefault}
+                            key="2"
+                            class={[
+                              styles.iconCollect,
+                              props.isCollect ? styles.isCollect : ''
+                            ]}
+                          />
+                        )}
+                      </Transition>
+                    </div>
+                  )}
+                </div>
+
+                {/* 精选 */}
+                {props.item.isSelected && (
+                  <span class={styles.iconSelected}></span>
+                )}
+
+                {/* 添加按钮 */}
+                {props.isShowAdd &&
+                  (props.item.exist ? (
+                    <NButton
+                      type="primary"
+                      class={[
+                        styles.addBtn,
+                        props.item.exist ? styles.addBtnDisabled : ''
+                      ]}
+                      disabled={props.item.exist || props.isShowAddDisabled}
+                      onClick={(e: MouseEvent) => {
+                        e.stopPropagation();
+                        e.preventDefault();
+                        emit('add', props.item);
+                      }}>
+                      {props.item.exist ? '已添加' : '添加'}
+                    </NButton>
+                  ) : (
+                    !props.isShowAddDisabled && (
+                      <NButton
+                        type="primary"
+                        class={[
+                          styles.addBtn,
+                          props.item.exist ? styles.addBtnDisabled : ''
+                        ]}
+                        disabled={props.item.exist || props.isShowAddDisabled}
+                        onClick={(e: MouseEvent) => {
+                          e.stopPropagation();
+                          e.preventDefault();
+                          emit('add', props.item);
+                        }}>
+                        {props.item.exist ? '已添加' : '添加'}
+                      </NButton>
+                    )
+                  ))}
+              </div>
+            )
+          }}
+        </NCard>
+      </div>
+    );
+  }
+});

+ 743 - 744
src/utils/index.ts

@@ -1,744 +1,743 @@
-import { h, unref } from 'vue';
-import type { App, Plugin } from 'vue';
-import { NIcon, NTag } from 'naive-ui';
-import { PageEnum } from '@/enums/pageEnum';
-import { isObject } from './is/index';
-import { cloneDeep } from 'lodash';
-import dayjs from 'dayjs';
-
-import EventEmitter from 'eventemitter3';
-
-export const eventGlobal = new EventEmitter();
-
-/**
- * render 图标
- * */
-export function renderIcon(icon: any) {
-  return () => h(NIcon, null, { default: () => h(icon) });
-}
-/**
- * font 图标(Font class)
- * */
-export function renderFontClassIcon(icon: string, iconName = 'iconfont') {
-  return () => h('span', { class: [iconName, icon] });
-}
-/**
- * font 图标(Unicode)
- * */
-export function renderUnicodeIcon(icon: string, iconName = 'iconfont') {
-  return () => h('span', { class: [iconName], innerHTML: icon });
-}
-/**
- * font svg 图标
- * */
-export function renderfontsvg(icon: any) {
-  return () =>
-    h(NIcon, null, {
-      default: () =>
-        h(
-          'svg',
-          { class: `icon`, 'aria-hidden': 'true' },
-          h('use', { 'xlink:href': `#${icon}` })
-        )
-    });
-}
-
-/**
- * render new Tag
- * */
-const newTagColors = { color: '#f90', textColor: '#fff', borderColor: '#f90' };
-export function renderNew(
-  type = 'warning',
-  text = 'New',
-  color: object = newTagColors
-) {
-  return () =>
-    h(
-      NTag as any,
-      {
-        type,
-        round: true,
-        size: 'small',
-        color
-      },
-      { default: () => text }
-    );
-}
-
-/**
- * 递归组装菜单格式
- */
-export function generatorMenu(routerMap: Array<any>) {
-  return filterRouter(routerMap).map(item => {
-    const isRoot = isRootRouter(item);
-    if (isRoot) {
-      console.log(item.children[0], 'isRoot');
-    }
-    const info = isRoot ? item.children[0] : item;
-    // console.log(item)
-    const currentMenu = {
-      ...info,
-      ...info.meta,
-      label: info.meta?.title,
-      key: info.key,
-      icon: info.meta?.icon
-    };
-    // 是否有子菜单,并递归处理
-    if (info.children && info.children.length > 0) {
-      // Recursion
-      currentMenu.children = generatorMenu(info.children);
-    }
-    return currentMenu;
-  });
-}
-
-/**
- * 混合菜单
- * */
-export function generatorMenuMix(
-  routerMap: Array<any>,
-  routerName: string,
-  location: string
-) {
-  const cloneRouterMap = cloneDeep(routerMap);
-  const newRouter = filterRouter(cloneRouterMap);
-  if (location === 'header') {
-    const firstRouter: any[] = [];
-    newRouter.forEach(item => {
-      const isRoot = isRootRouter(item);
-      const info = isRoot ? item.children[0] : item;
-      info.children = undefined;
-      const currentMenu = {
-        ...info,
-        ...info.meta,
-        label: info.meta?.title,
-        key: info.name
-      };
-      firstRouter.push(currentMenu);
-    });
-    return firstRouter;
-  } else {
-    // 混合菜单
-    console.log('混合菜单');
-    return getChildrenRouter(
-      newRouter.filter(item => item.name === routerName)
-    );
-  }
-}
-
-/**
- * 递归组装子菜单
- * */
-export function getChildrenRouter(routerMap: Array<any>) {
-  return filterRouter(routerMap).map(item => {
-    const isRoot = isRootRouter(item);
-    const info = isRoot ? item.children[0] : item;
-    const currentMenu = {
-      ...info,
-      ...info.meta,
-      label: info.meta?.title,
-      key: info.name
-    };
-    // 是否有子菜单,并递归处理
-    if (info.children && info.children.length > 0) {
-      // Recursion
-      currentMenu.children = getChildrenRouter(info.children);
-    }
-    return currentMenu;
-  });
-}
-
-/**
- * 判断根路由 Router
- * */
-export function isRootRouter(item: any) {
-  return item.meta?.isRoot === true;
-}
-
-/**
- * 排除Router
- * */
-export function filterRouter(routerMap: Array<any>) {
-  return routerMap.filter(item => {
-    // console.log(
-    //   (item.meta?.hidden || false) != true &&
-    //   !['/:path(.*)*', PageEnum.REDIRECT, PageEnum.BASE_LOGIN].includes(item.path),
-    //   '过滤完之后的路由'
-    // )
-    return (
-      (item.meta?.hidden || false) != true &&
-      !['/:path(.*)*', PageEnum.REDIRECT, PageEnum.BASE_LOGIN].includes(
-        item.path
-      )
-    );
-  });
-}
-
-export const withInstall = <T>(component: T, alias?: string) => {
-  const comp = component as any;
-  comp.install = (app: App) => {
-    app.component(comp.name || comp.displayName, component as any);
-    if (alias) {
-      app.config.globalProperties[alias] = component;
-    }
-  };
-  return component as T & Plugin;
-};
-
-/**
- *  找到对应的节点
- * */
-let result = null as any;
-export function getTreeItem(data: any[], key?: string | number): any {
-  data.map(item => {
-    if (item.key === key) {
-      result = item;
-    } else {
-      if (item.children && item.children.length) {
-        getTreeItem(item.children, key);
-      }
-    }
-  });
-  return result;
-}
-
-/**
- *  找到所有节点
- * */
-const treeAll: any[] = [];
-export function getTreeAll(data: any[]): any[] {
-  data.map(item => {
-    treeAll.push(item.key);
-    if (item.children && item.children.length) {
-      getTreeAll(item.children);
-    }
-  });
-  return treeAll;
-}
-
-export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
-  let key: string;
-  for (key in target) {
-    src[key] = isObject(src[key])
-      ? deepMerge(src[key], target[key])
-      : (src[key] = target[key]);
-  }
-  return src;
-}
-
-/**
- * Sums the passed percentage to the R, G or B of a HEX color
- * @param {string} color The color to change
- * @param {number} amount The amount to change the color by
- * @returns {string} The processed part of the color
- */
-function addLight(color: string, amount: number) {
-  const cc = parseInt(color, 16) + amount;
-  const c = cc > 255 ? 255 : cc;
-  return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`;
-}
-
-/**
- * Lightens a 6 char HEX color according to the passed percentage
- * @param {string} color The color to change
- * @param {number} amount The amount to change the color by
- * @returns {string} The processed color represented as HEX
- */
-export function lighten(color: string, amount: number) {
-  color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color;
-  amount = Math.trunc((255 * amount) / 100);
-  return `#${addLight(color.substring(0, 2), amount)}${addLight(
-    color.substring(2, 4),
-    amount
-  )}${addLight(color.substring(4, 6), amount)}`;
-}
-
-/**
- * 判断是否 url
- * */
-export function isUrl(url: string) {
-  return /^(http|https):\/\//g.test(url);
-}
-
-/** 递归清除空数据 */
-export function clearEmtryData(list: any[], key: string) {
-  for (let i = 0; i < list.length; i++) {
-    if (Array.isArray(list[i][key]) && !list[i][key].length) {
-      list[i][key] = '';
-    } else {
-      list[i][key] = clearEmtryData(list[i][key], key);
-    }
-  }
-  return list;
-}
-
-// 秒转分
-export const getSecondRPM = (second: number, type?: string) => {
-  if (isNaN(second)) return '00:00';
-  const mm = Math.floor(second / 60)
-    .toString()
-    .padStart(2, '0');
-  const dd = Math.floor(second % 60)
-    .toString()
-    .padStart(2, '0');
-  if (type === 'cn') {
-    return mm + '分' + dd + '秒';
-  } else {
-    return mm + ':' + dd;
-  }
-};
-
-// 秒转分
-export const getSecond = (second: number, type?: string) => {
-  if (isNaN(second)) return '0000';
-  const mm = Math.floor(second / 60)
-    .toString()
-    .padStart(2, '0');
-  const dd = Math.floor(second % 60)
-    .toString()
-    .padStart(2, '0');
-  return `${mm}${dd}`;
-};
-
-/** 滚动到表单填写错误的地方 */
-export function scrollToErrorForm() {
-  const isError =
-    document.querySelector('.n-input--error-status') ||
-    document.querySelector('.n-base-selection--error-status');
-  isError?.scrollIntoView({
-    block: 'center',
-    behavior: 'smooth'
-  });
-}
-
-export const getTimes = (
-  times: any,
-  keys: Array<string> = [],
-  format = 'YYYY-MM-DD'
-) => {
-  if (times && times.length) {
-    return format == 'YYYY-MM-DD'
-      ? {
-          [keys[0] || 'start']: dayjs(times[0]).isValid()
-            ? dayjs(times[0]).format(format) + ' 00:00:00'
-            : '',
-          [keys[1] || 'end']: dayjs(times[1]).isValid()
-            ? dayjs(times[1]).format(format) + ' 23:59:59'
-            : ''
-        }
-      : {
-          [keys[0] || 'start']: dayjs(times[0]).isValid()
-            ? dayjs(times[0]).format(format)
-            : '',
-          [keys[1] || 'end']: dayjs(times[1]).isValid()
-            ? dayjs(times[1]).format(format)
-            : ''
-        };
-  }
-  return {};
-};
-
-export const px2vw = (px: number): string => `${(px / 1920) * 100}vw`;
-
-export const px2vwH = (px: number): string =>
-  `${(((px / 1920) * 1920) / 1188) * 100}vw`;
-
-export const fscreen = () => {
-  const el = document.documentElement;
-
-  //进入全屏
-  (el.requestFullscreen && el.requestFullscreen()) ||
-    (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
-    (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
-    (el.msRequestFullscreen && el.msRequestFullscreen());
-
-  //退出全屏
-};
-
-export const exitFullscreen = () => {
-  document.exitFullscreen
-    ? document.exitFullscreen()
-    : document.mozCancelFullScreen
-    ? document.mozCancelFullScreen()
-    : document.webkitExitFullscreen
-    ? document.webkitExitFullscreen()
-    : '';
-};
-
-/** 检测链接格式 */
-export function checkUrlType(urlType: string) {
-  const subfix = (urlType || '').split('.').pop();
-  if (subfix === 'wav' || subfix === 'mp3' || subfix === 'm4a') {
-    return 'audio';
-  }
-  return 'video';
-}
-
-const instruments: any = {
-  'Acoustic Grand Piano': '大钢琴',
-  'Bright Acoustic Piano': '明亮的钢琴',
-  'Electric Grand Piano': '电钢琴',
-  'Rhodes Piano': '柔和的电钢琴',
-  'Chorused Piano': '加合唱效果的电钢琴',
-  Harpsichord: '羽管键琴',
-  Clavichord: '科拉维科特琴',
-  Celesta: '钢片琴',
-  Glockenspiel: '钢片琴',
-  'Music box': '八音盒',
-  Vibraphone: '颤音琴',
-  Marimba: '马林巴',
-  Xylophone: '木琴',
-  'Tubular Bells': '管钟',
-  Dulcimer: '大扬琴',
-  'Hammond Organ': '击杆风琴',
-  'Percussive Organ': '打击式风琴',
-  'Rock Organ': '摇滚风琴',
-  'Church Organ': '教堂风琴',
-  'Reed Organ': '簧管风琴',
-  Accordian: '手风琴',
-  Harmonica: '口琴',
-  'Tango Accordian': '探戈手风琴',
-  'Acoustic Guitar': '钢弦吉他',
-  'Electric Guitar': '闷音电吉他',
-  'Overdriven Guitar': '加驱动效果的电吉他',
-  'Distortion Guitar': '加失真效果的电吉他',
-  'Guitar Harmonics': '吉他和音',
-  'Acoustic Bass': '大贝司',
-  'Electric Bass': '电贝司',
-  'Fretless Bass': '无品贝司',
-  'Slap Bass': '掌击',
-  'Synth Bass': '电子合成',
-  Violin: '小提琴',
-  Viola: '中提琴',
-  Cello: '大提琴',
-  Contrabass: '低音大提琴',
-  'Tremolo Strings': '弦乐群颤音音色',
-  'Pizzicato Strings': '弦乐群拨弦音色',
-  'Orchestral Harp': '竖琴',
-  Timpani: '定音鼓',
-  'String Ensemble': '弦乐合奏音色',
-  'Synth Strings': '合成弦乐合奏音色',
-  'Choir Aahs': '人声合唱',
-  'Voice Oohs': '人声',
-  'Synth Voice': '合成人声',
-  'Orchestra Hit': '管弦乐敲击齐奏',
-  Trumpet: '小号',
-  Trombone: '长号',
-  Tuba: '大号',
-  'Muted Trumpet': '加弱音器小号',
-  'French Horn': '法国号',
-  'Brass Section': '铜管组',
-  'Synth Brass': '合成铜管音色',
-  'Soprano Sax': '高音萨克斯管',
-  'Alto Sax': '中音萨克斯管',
-  'Tenor Sax': '次中音萨克斯管',
-  'Baritone Sax': '低音萨克斯管',
-  Oboe: '双簧管',
-  'English Horn': '英国管',
-  Bassoon: '巴松',
-  Clarinet: '单簧管',
-  'Soprano Saxophone': '高音萨克斯管',
-  'Alto Saxophone': '中音萨克斯管',
-  'Tenor Saxophone': '次中音萨克斯管',
-  'Baritone Saxophone': '低音萨克斯管',
-  Piccolo: '短笛',
-  Flute: '长笛',
-  Recorder: '竖笛',
-  'Soprano Recorder': '高音竖笛',
-  'Pan Flute': '排箫',
-  'Bottle Blow': '瓶木管',
-  Whistle: '口哨声',
-  Ocarina: '陶笛',
-  Lead: '合成主音',
-  'Lead lead': '合成主音',
-  'Pad age': '合成音色',
-  Pad: '合成音色',
-  FX: '合成效果  科幻',
-  Sitar: '西塔尔',
-  Banjo: '班卓琴',
-  Shamisen: '三昧线',
-  Koto: '十三弦筝',
-  Kalimba: '卡林巴',
-  Bagpipe: '风笛',
-  Fiddle: '民族提琴',
-  Shanai: '山奈',
-  'Tinkle Bell': '叮当铃',
-  Agogos: '阿戈戈铃',
-  'Steel Drums': '钢鼓',
-  'Taiko Drum': '太鼓',
-  'Melodic Toms': '嗵嗵鼓',
-  'Synth Drums': '合成鼓',
-  'Reverse Cymbals': '反向镲',
-  'Agogo Bells': '阿戈戈铃',
-  'Taiko Drums': '太鼓',
-  Bongos: '邦戈鼓',
-  'Bongo Bell': '邦戈铃',
-  Congas: '康加鼓',
-  Guiro: '刮壶',
-  'Guitar Fret Noise': '吉他换把杂音',
-  'Breath Noise': '呼吸声',
-  Seashore: '海浪声',
-  'Bird Tweet': '鸟鸣',
-  'Telephone Ring': '电话铃',
-  Helicopter: '直升机',
-  Applause: '鼓掌声',
-  Gunshot: '枪声',
-  'Acoustic Bass Drum': '大鼓',
-  'Bass Drum': '大鼓',
-  'Side Drum': '小鼓鼓边',
-  'Acoustic Snare': '小鼓',
-  'Hand Claps': '拍手',
-  'Electric Snare': '小鼓',
-  'Low Floor Tom': '低音嗵鼓',
-  'Closed Hi-Hat': '闭合踩镲',
-  'High Floor Tom': '高音落地嗵鼓',
-  'Pedal Hi-Hat': '脚踏踩镲',
-  'Low Tom': '低音嗵鼓',
-  'Open Hi-Hat': '开音踩镲',
-  'Low-Mid Tom': '中低音嗵鼓',
-  'Hi Mid Tom': '高音鼓',
-  'Crash Cymbals': '对镲',
-  'High Tom': '高音嗵鼓',
-  'Ride Cymbals': '叮叮镲',
-  'Chinese Cymbals': '中国镲',
-  'Ride Bell': '圆铃',
-  Tambourine: '铃鼓',
-  'Splash Cymbal': '溅音镲',
-  Cowbell: '牛铃',
-  'Crash Cymbal': '强音钹',
-  'Vibra-Slap': '颤音器',
-  'Ride Cymbal': '打点钹',
-  'Hi Bongo': '高音邦戈鼓',
-  'Low Bongo': '低音邦戈鼓',
-  'Mute Hi Conga': '弱音高音康加鼓',
-  'Open Hi Conga': '强音高音康加鼓',
-  'Low Conga': '低音康加鼓',
-  'High Timbale': '高音天巴鼓',
-  'Low Timbale': '低音天巴鼓',
-  'High Agogo': '高音阿戈戈铃',
-  'Low Agogo': '低音阿戈戈铃',
-  Cabasa: '卡巴萨',
-  Maracas: '沙锤',
-  'Short Whistle': '短口哨',
-  'Long Whistle': '长口哨',
-  'Short Guiro': '短刮壶',
-  'Long Guiro': '长刮壶',
-  Claves: '响棒',
-  'Hi Wood Block': '高音木鱼',
-  'Low Wood Block': '低音木鱼',
-  'Mute Triangle': '弱音三角铁',
-  'Open Triangle': '强音三角铁',
-  'Drum Set': '架子鼓',
-  'Hulusi flute': '葫芦丝',
-  Melodica: '口风琴',
-  Nai: '口风琴',
-  'Snare Drum': '小军鼓',
-  Cymbal: '镲',
-  Cymbals: '镲',
-  'Horn in F': '圆号',
-  Triangle: '三角铁',
-  Vibrato: '颤音琴',
-  'Suspend Cymbals': '吊镲',
-  'Suspended Cymbals': '吊镲',
-  'Tom-Toms': '嗵嗵鼓',
-  Bell: '铃铛',
-  Bells: '铃铛',
-  'Alto Clarinet': '中音单簧管',
-  'Bass Clarinet': '低音单簧管',
-  Cornet: '短号',
-  Euphonium: '上低音号',
-  'crash cymbals': '对镲',
-  Castanets: '响板',
-  Shaker: '沙锤',
-  'Mark tree': '音树',
-  Chimes: '管钟',
-  'Mark Tree': '音树',
-  'Tom-toms': '嗵嗵鼓',
-  'Hi-Hat': '踩镲',
-  'Sleigh Bells': '雪橇铃',
-  Flexatone: '弹音器',
-  'Brake drum': '闸鼓',
-  Gong: '锣',
-  'concert tom': '音乐会嗵嗵鼓',
-  'brake drum': '车轮鼓',
-  'finger cymbal': '指钹',
-  'ride cymbal': '叮叮镲',
-  'Concert Toms': '音乐会嗵嗵鼓',
-  Vibraslap: '弹音器',
-  'Wood Blocks': '木鱼',
-  'Temple Blocks': '木鱼',
-  'Wood Block': '木鱼',
-  'Field Drum': '军鼓',
-  'Quad-Toms': '筒鼓',
-  Quads: '筒鼓',
-  'Drums set': '架子鼓',
-  'High Bongo': '邦戈',
-  Timbales: '天巴鼓'
-};
-
-/** 获取分轨名称 */
-export const getInstrumentName = (name = '') => {
-  name = name.toLocaleLowerCase().replace(/ /g, '');
-  if (!name) return '';
-  for (const key in instruments) {
-    const _key = key.toLocaleLowerCase().replace(/ /g, '');
-    if (_key.includes(name)) {
-      return instruments[key];
-    }
-  }
-  for (const key in instruments) {
-    const _key = key.toLocaleLowerCase().replace(/ /g, '');
-    if (name.includes(_key)) {
-      return instruments[key];
-    }
-  }
-  return '';
-};
-
-/**
- * 乐器排序
- * 排序顺序:长笛、单簧管、中音单簧管、低音单簧管、高音萨克斯风、中音萨克斯风、次中音萨克斯风、低音萨克斯风、小号、长号、圆号、大号、上低音号
- * */
-export const sortMusical = (name: string, index: number) => {
-  let sortId = 0;
-  switch (name) {
-    case '长笛':
-      sortId = 1;
-      break;
-    case '单簧管':
-      sortId = 2;
-      break;
-    case '中音单簧管':
-      sortId = 3;
-      break;
-    case '低音单簧管':
-      sortId = 4;
-      break;
-    case '高音萨克斯风':
-      sortId = 5;
-      break;
-    case '中音萨克斯风':
-      sortId = 6;
-      break;
-    case '次中音萨克斯风':
-      sortId = 7;
-      break;
-    case '低音萨克斯风':
-      sortId = 8;
-      break;
-    case '小号':
-      sortId = 9;
-      break;
-    case '长号':
-      sortId = 10;
-      break;
-    case '圆号':
-      sortId = 11;
-      break;
-    case '大号':
-      sortId = 12;
-      break;
-    case '上低音号':
-      sortId = 13;
-      break;
-    default:
-      sortId = index + 14;
-      break;
-  }
-  return sortId;
-};
-
-// 课堂乐器声轨名称集合
-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'
-};
-
-/** 声轨track转换成乐器code */
-export const trackToCode = (track: any) => {
-  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);
-  }
-};
+import { h, unref } from 'vue';
+import type { App, Plugin } from 'vue';
+import { NIcon, NTag } from 'naive-ui';
+import { PageEnum } from '@/enums/pageEnum';
+import { isObject } from './is/index';
+import { cloneDeep } from 'lodash';
+import dayjs from 'dayjs';
+
+import EventEmitter from 'eventemitter3';
+
+export const eventGlobal = new EventEmitter();
+
+/**
+ * render 图标
+ * */
+export function renderIcon(icon: any) {
+  return () => h(NIcon, null, { default: () => h(icon) });
+}
+/**
+ * font 图标(Font class)
+ * */
+export function renderFontClassIcon(icon: string, iconName = 'iconfont') {
+  return () => h('span', { class: [iconName, icon] });
+}
+/**
+ * font 图标(Unicode)
+ * */
+export function renderUnicodeIcon(icon: string, iconName = 'iconfont') {
+  return () => h('span', { class: [iconName], innerHTML: icon });
+}
+/**
+ * font svg 图标
+ * */
+export function renderfontsvg(icon: any) {
+  return () =>
+    h(NIcon, null, {
+      default: () =>
+        h(
+          'svg',
+          { class: `icon`, 'aria-hidden': 'true' },
+          h('use', { 'xlink:href': `#${icon}` })
+        )
+    });
+}
+
+/**
+ * render new Tag
+ * */
+const newTagColors = { color: '#f90', textColor: '#fff', borderColor: '#f90' };
+export function renderNew(
+  type = 'warning',
+  text = 'New',
+  color: object = newTagColors
+) {
+  return () =>
+    h(
+      NTag as any,
+      {
+        type,
+        round: true,
+        size: 'small',
+        color
+      },
+      { default: () => text }
+    );
+}
+
+/**
+ * 递归组装菜单格式
+ */
+export function generatorMenu(routerMap: Array<any>) {
+  return filterRouter(routerMap).map(item => {
+    const isRoot = isRootRouter(item);
+    if (isRoot) {
+      console.log(item.children[0], 'isRoot');
+    }
+    const info = isRoot ? item.children[0] : item;
+    // console.log(item)
+    const currentMenu = {
+      ...info,
+      ...info.meta,
+      label: info.meta?.title,
+      key: info.key,
+      icon: info.meta?.icon
+    };
+    // 是否有子菜单,并递归处理
+    if (info.children && info.children.length > 0) {
+      // Recursion
+      currentMenu.children = generatorMenu(info.children);
+    }
+    return currentMenu;
+  });
+}
+
+/**
+ * 混合菜单
+ * */
+export function generatorMenuMix(
+  routerMap: Array<any>,
+  routerName: string,
+  location: string
+) {
+  const cloneRouterMap = cloneDeep(routerMap);
+  const newRouter = filterRouter(cloneRouterMap);
+  if (location === 'header') {
+    const firstRouter: any[] = [];
+    newRouter.forEach(item => {
+      const isRoot = isRootRouter(item);
+      const info = isRoot ? item.children[0] : item;
+      info.children = undefined;
+      const currentMenu = {
+        ...info,
+        ...info.meta,
+        label: info.meta?.title,
+        key: info.name
+      };
+      firstRouter.push(currentMenu);
+    });
+    return firstRouter;
+  } else {
+    // 混合菜单
+    console.log('混合菜单');
+    return getChildrenRouter(
+      newRouter.filter(item => item.name === routerName)
+    );
+  }
+}
+
+/**
+ * 递归组装子菜单
+ * */
+export function getChildrenRouter(routerMap: Array<any>) {
+  return filterRouter(routerMap).map(item => {
+    const isRoot = isRootRouter(item);
+    const info = isRoot ? item.children[0] : item;
+    const currentMenu = {
+      ...info,
+      ...info.meta,
+      label: info.meta?.title,
+      key: info.name
+    };
+    // 是否有子菜单,并递归处理
+    if (info.children && info.children.length > 0) {
+      // Recursion
+      currentMenu.children = getChildrenRouter(info.children);
+    }
+    return currentMenu;
+  });
+}
+
+/**
+ * 判断根路由 Router
+ * */
+export function isRootRouter(item: any) {
+  return item.meta?.isRoot === true;
+}
+
+/**
+ * 排除Router
+ * */
+export function filterRouter(routerMap: Array<any>) {
+  return routerMap.filter(item => {
+    // console.log(
+    //   (item.meta?.hidden || false) != true &&
+    //   !['/:path(.*)*', PageEnum.REDIRECT, PageEnum.BASE_LOGIN].includes(item.path),
+    //   '过滤完之后的路由'
+    // )
+    return (
+      (item.meta?.hidden || false) != true &&
+      !['/:path(.*)*', PageEnum.REDIRECT, PageEnum.BASE_LOGIN].includes(
+        item.path
+      )
+    );
+  });
+}
+
+export const withInstall = <T>(component: T, alias?: string) => {
+  const comp = component as any;
+  comp.install = (app: App) => {
+    app.component(comp.name || comp.displayName, component as any);
+    if (alias) {
+      app.config.globalProperties[alias] = component;
+    }
+  };
+  return component as T & Plugin;
+};
+
+/**
+ *  找到对应的节点
+ * */
+let result = null as any;
+export function getTreeItem(data: any[], key?: string | number): any {
+  data.map(item => {
+    if (item.key === key) {
+      result = item;
+    } else {
+      if (item.children && item.children.length) {
+        getTreeItem(item.children, key);
+      }
+    }
+  });
+  return result;
+}
+
+/**
+ *  找到所有节点
+ * */
+const treeAll: any[] = [];
+export function getTreeAll(data: any[]): any[] {
+  data.map(item => {
+    treeAll.push(item.key);
+    if (item.children && item.children.length) {
+      getTreeAll(item.children);
+    }
+  });
+  return treeAll;
+}
+
+export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
+  let key: string;
+  for (key in target) {
+    src[key] = isObject(src[key])
+      ? deepMerge(src[key], target[key])
+      : (src[key] = target[key]);
+  }
+  return src;
+}
+
+/**
+ * Sums the passed percentage to the R, G or B of a HEX color
+ * @param {string} color The color to change
+ * @param {number} amount The amount to change the color by
+ * @returns {string} The processed part of the color
+ */
+function addLight(color: string, amount: number) {
+  const cc = parseInt(color, 16) + amount;
+  const c = cc > 255 ? 255 : cc;
+  return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`;
+}
+
+/**
+ * Lightens a 6 char HEX color according to the passed percentage
+ * @param {string} color The color to change
+ * @param {number} amount The amount to change the color by
+ * @returns {string} The processed color represented as HEX
+ */
+export function lighten(color: string, amount: number) {
+  color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color;
+  amount = Math.trunc((255 * amount) / 100);
+  return `#${addLight(color.substring(0, 2), amount)}${addLight(
+    color.substring(2, 4),
+    amount
+  )}${addLight(color.substring(4, 6), amount)}`;
+}
+
+/**
+ * 判断是否 url
+ * */
+export function isUrl(url: string) {
+  return /^(http|https):\/\//g.test(url);
+}
+
+/** 递归清除空数据 */
+export function clearEmtryData(list: any[], key: string) {
+  for (let i = 0; i < list.length; i++) {
+    if (Array.isArray(list[i][key]) && !list[i][key].length) {
+      list[i][key] = '';
+    } else {
+      list[i][key] = clearEmtryData(list[i][key], key);
+    }
+  }
+  return list;
+}
+
+// 秒转分
+export const getSecondRPM = (second: number, type?: string) => {
+  if (isNaN(second)) return '00:00';
+  const mm = Math.floor(second / 60)
+    .toString()
+    .padStart(2, '0');
+  const dd = Math.floor(second % 60)
+    .toString()
+    .padStart(2, '0');
+  if (type === 'cn') {
+    return mm + '分' + dd + '秒';
+  } else {
+    return mm + ':' + dd;
+  }
+};
+
+// 秒转分
+export const getSecond = (second: number, type?: string) => {
+  if (isNaN(second)) return '0000';
+  const mm = Math.floor(second / 60)
+    .toString()
+    .padStart(2, '0');
+  const dd = Math.floor(second % 60)
+    .toString()
+    .padStart(2, '0');
+  return `${mm}${dd}`;
+};
+
+/** 滚动到表单填写错误的地方 */
+export function scrollToErrorForm() {
+  const isError =
+    document.querySelector('.n-input--error-status') ||
+    document.querySelector('.n-base-selection--error-status');
+  isError?.scrollIntoView({
+    block: 'center',
+    behavior: 'smooth'
+  });
+}
+
+export const getTimes = (
+  times: any,
+  keys: Array<string> = [],
+  format = 'YYYY-MM-DD'
+) => {
+  if (times && times.length) {
+    return format == 'YYYY-MM-DD'
+      ? {
+        [keys[0] || 'start']: dayjs(times[0]).isValid()
+          ? dayjs(times[0]).format(format) + ' 00:00:00'
+          : '',
+        [keys[1] || 'end']: dayjs(times[1]).isValid()
+          ? dayjs(times[1]).format(format) + ' 23:59:59'
+          : ''
+      }
+      : {
+        [keys[0] || 'start']: dayjs(times[0]).isValid()
+          ? dayjs(times[0]).format(format)
+          : '',
+        [keys[1] || 'end']: dayjs(times[1]).isValid()
+          ? dayjs(times[1]).format(format)
+          : ''
+      };
+  }
+  return {};
+};
+
+export const px2vw = (px: number): string => `${(px / 1920) * 100}vw`;
+
+export const px2vwH = (px: number): string =>
+  `${(((px / 1920) * 1920) / 1188) * 100}vw`;
+
+export const fscreen = () => {
+  const el = document.documentElement;
+
+  //进入全屏
+  (el.requestFullscreen && el.requestFullscreen()) ||
+    (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
+    (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
+    (el.msRequestFullscreen && el.msRequestFullscreen());
+};
+
+//退出全屏
+export const exitFullscreen = () => {
+  document.exitFullscreen
+    ? document.exitFullscreen()
+    : document.mozCancelFullScreen
+      ? document.mozCancelFullScreen()
+      : document.webkitExitFullscreen
+        ? document.webkitExitFullscreen()
+        : '';
+};
+
+/** 检测链接格式 */
+export function checkUrlType(urlType: string) {
+  const subfix = (urlType || '').split('.').pop();
+  if (subfix === 'wav' || subfix === 'mp3' || subfix === 'm4a') {
+    return 'audio';
+  }
+  return 'video';
+}
+
+const instruments: any = {
+  'Acoustic Grand Piano': '大钢琴',
+  'Bright Acoustic Piano': '明亮的钢琴',
+  'Electric Grand Piano': '电钢琴',
+  'Rhodes Piano': '柔和的电钢琴',
+  'Chorused Piano': '加合唱效果的电钢琴',
+  Harpsichord: '羽管键琴',
+  Clavichord: '科拉维科特琴',
+  Celesta: '钢片琴',
+  Glockenspiel: '钢片琴',
+  'Music box': '八音盒',
+  Vibraphone: '颤音琴',
+  Marimba: '马林巴',
+  Xylophone: '木琴',
+  'Tubular Bells': '管钟',
+  Dulcimer: '大扬琴',
+  'Hammond Organ': '击杆风琴',
+  'Percussive Organ': '打击式风琴',
+  'Rock Organ': '摇滚风琴',
+  'Church Organ': '教堂风琴',
+  'Reed Organ': '簧管风琴',
+  Accordian: '手风琴',
+  Harmonica: '口琴',
+  'Tango Accordian': '探戈手风琴',
+  'Acoustic Guitar': '钢弦吉他',
+  'Electric Guitar': '闷音电吉他',
+  'Overdriven Guitar': '加驱动效果的电吉他',
+  'Distortion Guitar': '加失真效果的电吉他',
+  'Guitar Harmonics': '吉他和音',
+  'Acoustic Bass': '大贝司',
+  'Electric Bass': '电贝司',
+  'Fretless Bass': '无品贝司',
+  'Slap Bass': '掌击',
+  'Synth Bass': '电子合成',
+  Violin: '小提琴',
+  Viola: '中提琴',
+  Cello: '大提琴',
+  Contrabass: '低音大提琴',
+  'Tremolo Strings': '弦乐群颤音音色',
+  'Pizzicato Strings': '弦乐群拨弦音色',
+  'Orchestral Harp': '竖琴',
+  Timpani: '定音鼓',
+  'String Ensemble': '弦乐合奏音色',
+  'Synth Strings': '合成弦乐合奏音色',
+  'Choir Aahs': '人声合唱',
+  'Voice Oohs': '人声',
+  'Synth Voice': '合成人声',
+  'Orchestra Hit': '管弦乐敲击齐奏',
+  Trumpet: '小号',
+  Trombone: '长号',
+  Tuba: '大号',
+  'Muted Trumpet': '加弱音器小号',
+  'French Horn': '法国号',
+  'Brass Section': '铜管组',
+  'Synth Brass': '合成铜管音色',
+  'Soprano Sax': '高音萨克斯管',
+  'Alto Sax': '中音萨克斯管',
+  'Tenor Sax': '次中音萨克斯管',
+  'Baritone Sax': '低音萨克斯管',
+  Oboe: '双簧管',
+  'English Horn': '英国管',
+  Bassoon: '巴松',
+  Clarinet: '单簧管',
+  'Soprano Saxophone': '高音萨克斯管',
+  'Alto Saxophone': '中音萨克斯管',
+  'Tenor Saxophone': '次中音萨克斯管',
+  'Baritone Saxophone': '低音萨克斯管',
+  Piccolo: '短笛',
+  Flute: '长笛',
+  Recorder: '竖笛',
+  'Soprano Recorder': '高音竖笛',
+  'Pan Flute': '排箫',
+  'Bottle Blow': '瓶木管',
+  Whistle: '口哨声',
+  Ocarina: '陶笛',
+  Lead: '合成主音',
+  'Lead lead': '合成主音',
+  'Pad age': '合成音色',
+  Pad: '合成音色',
+  FX: '合成效果  科幻',
+  Sitar: '西塔尔',
+  Banjo: '班卓琴',
+  Shamisen: '三昧线',
+  Koto: '十三弦筝',
+  Kalimba: '卡林巴',
+  Bagpipe: '风笛',
+  Fiddle: '民族提琴',
+  Shanai: '山奈',
+  'Tinkle Bell': '叮当铃',
+  Agogos: '阿戈戈铃',
+  'Steel Drums': '钢鼓',
+  'Taiko Drum': '太鼓',
+  'Melodic Toms': '嗵嗵鼓',
+  'Synth Drums': '合成鼓',
+  'Reverse Cymbals': '反向镲',
+  'Agogo Bells': '阿戈戈铃',
+  'Taiko Drums': '太鼓',
+  Bongos: '邦戈鼓',
+  'Bongo Bell': '邦戈铃',
+  Congas: '康加鼓',
+  Guiro: '刮壶',
+  'Guitar Fret Noise': '吉他换把杂音',
+  'Breath Noise': '呼吸声',
+  Seashore: '海浪声',
+  'Bird Tweet': '鸟鸣',
+  'Telephone Ring': '电话铃',
+  Helicopter: '直升机',
+  Applause: '鼓掌声',
+  Gunshot: '枪声',
+  'Acoustic Bass Drum': '大鼓',
+  'Bass Drum': '大鼓',
+  'Side Drum': '小鼓鼓边',
+  'Acoustic Snare': '小鼓',
+  'Hand Claps': '拍手',
+  'Electric Snare': '小鼓',
+  'Low Floor Tom': '低音嗵鼓',
+  'Closed Hi-Hat': '闭合踩镲',
+  'High Floor Tom': '高音落地嗵鼓',
+  'Pedal Hi-Hat': '脚踏踩镲',
+  'Low Tom': '低音嗵鼓',
+  'Open Hi-Hat': '开音踩镲',
+  'Low-Mid Tom': '中低音嗵鼓',
+  'Hi Mid Tom': '高音鼓',
+  'Crash Cymbals': '对镲',
+  'High Tom': '高音嗵鼓',
+  'Ride Cymbals': '叮叮镲',
+  'Chinese Cymbals': '中国镲',
+  'Ride Bell': '圆铃',
+  Tambourine: '铃鼓',
+  'Splash Cymbal': '溅音镲',
+  Cowbell: '牛铃',
+  'Crash Cymbal': '强音钹',
+  'Vibra-Slap': '颤音器',
+  'Ride Cymbal': '打点钹',
+  'Hi Bongo': '高音邦戈鼓',
+  'Low Bongo': '低音邦戈鼓',
+  'Mute Hi Conga': '弱音高音康加鼓',
+  'Open Hi Conga': '强音高音康加鼓',
+  'Low Conga': '低音康加鼓',
+  'High Timbale': '高音天巴鼓',
+  'Low Timbale': '低音天巴鼓',
+  'High Agogo': '高音阿戈戈铃',
+  'Low Agogo': '低音阿戈戈铃',
+  Cabasa: '卡巴萨',
+  Maracas: '沙锤',
+  'Short Whistle': '短口哨',
+  'Long Whistle': '长口哨',
+  'Short Guiro': '短刮壶',
+  'Long Guiro': '长刮壶',
+  Claves: '响棒',
+  'Hi Wood Block': '高音木鱼',
+  'Low Wood Block': '低音木鱼',
+  'Mute Triangle': '弱音三角铁',
+  'Open Triangle': '强音三角铁',
+  'Drum Set': '架子鼓',
+  'Hulusi flute': '葫芦丝',
+  Melodica: '口风琴',
+  Nai: '口风琴',
+  'Snare Drum': '小军鼓',
+  Cymbal: '镲',
+  Cymbals: '镲',
+  'Horn in F': '圆号',
+  Triangle: '三角铁',
+  Vibrato: '颤音琴',
+  'Suspend Cymbals': '吊镲',
+  'Suspended Cymbals': '吊镲',
+  'Tom-Toms': '嗵嗵鼓',
+  Bell: '铃铛',
+  Bells: '铃铛',
+  'Alto Clarinet': '中音单簧管',
+  'Bass Clarinet': '低音单簧管',
+  Cornet: '短号',
+  Euphonium: '上低音号',
+  'crash cymbals': '对镲',
+  Castanets: '响板',
+  Shaker: '沙锤',
+  'Mark tree': '音树',
+  Chimes: '管钟',
+  'Mark Tree': '音树',
+  'Tom-toms': '嗵嗵鼓',
+  'Hi-Hat': '踩镲',
+  'Sleigh Bells': '雪橇铃',
+  Flexatone: '弹音器',
+  'Brake drum': '闸鼓',
+  Gong: '锣',
+  'concert tom': '音乐会嗵嗵鼓',
+  'brake drum': '车轮鼓',
+  'finger cymbal': '指钹',
+  'ride cymbal': '叮叮镲',
+  'Concert Toms': '音乐会嗵嗵鼓',
+  Vibraslap: '弹音器',
+  'Wood Blocks': '木鱼',
+  'Temple Blocks': '木鱼',
+  'Wood Block': '木鱼',
+  'Field Drum': '军鼓',
+  'Quad-Toms': '筒鼓',
+  Quads: '筒鼓',
+  'Drums set': '架子鼓',
+  'High Bongo': '邦戈',
+  Timbales: '天巴鼓'
+};
+
+/** 获取分轨名称 */
+export const getInstrumentName = (name = '') => {
+  name = name.toLocaleLowerCase().replace(/ /g, '');
+  if (!name) return '';
+  for (const key in instruments) {
+    const _key = key.toLocaleLowerCase().replace(/ /g, '');
+    if (_key.includes(name)) {
+      return instruments[key];
+    }
+  }
+  for (const key in instruments) {
+    const _key = key.toLocaleLowerCase().replace(/ /g, '');
+    if (name.includes(_key)) {
+      return instruments[key];
+    }
+  }
+  return '';
+};
+
+/**
+ * 乐器排序
+ * 排序顺序:长笛、单簧管、中音单簧管、低音单簧管、高音萨克斯风、中音萨克斯风、次中音萨克斯风、低音萨克斯风、小号、长号、圆号、大号、上低音号
+ * */
+export const sortMusical = (name: string, index: number) => {
+  let sortId = 0;
+  switch (name) {
+    case '长笛':
+      sortId = 1;
+      break;
+    case '单簧管':
+      sortId = 2;
+      break;
+    case '中音单簧管':
+      sortId = 3;
+      break;
+    case '低音单簧管':
+      sortId = 4;
+      break;
+    case '高音萨克斯风':
+      sortId = 5;
+      break;
+    case '中音萨克斯风':
+      sortId = 6;
+      break;
+    case '次中音萨克斯风':
+      sortId = 7;
+      break;
+    case '低音萨克斯风':
+      sortId = 8;
+      break;
+    case '小号':
+      sortId = 9;
+      break;
+    case '长号':
+      sortId = 10;
+      break;
+    case '圆号':
+      sortId = 11;
+      break;
+    case '大号':
+      sortId = 12;
+      break;
+    case '上低音号':
+      sortId = 13;
+      break;
+    default:
+      sortId = index + 14;
+      break;
+  }
+  return sortId;
+};
+
+// 课堂乐器声轨名称集合
+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'
+};
+
+/** 声轨track转换成乐器code */
+export const trackToCode = (track: any) => {
+  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);
+  }
+};

BIN
src/views/attend-class/image/icon-fullscreen-exit.png


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


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

@@ -417,7 +417,6 @@ export default defineComponent({
           from={props.from}
           v-model:show={previewShow.value}
           item={preivewItem.value}
-          isDownload={isDownload.value}
         />
 
         <NModal

+ 49 - 10
src/views/attend-class/model/train-update/index.tsx

@@ -1,4 +1,11 @@
-import { PropType, defineComponent, onMounted, reactive, ref } from 'vue';
+import {
+  PropType,
+  computed,
+  defineComponent,
+  onMounted,
+  reactive,
+  ref
+} from 'vue';
 import styles from './index.module.less';
 import {
   NButton,
@@ -8,6 +15,7 @@ import {
   NInputGroupLabel,
   NInputNumber,
   NSpace,
+  NTooltip,
   useMessage
 } from 'naive-ui';
 import {
@@ -102,8 +110,25 @@ export default defineComponent({
       });
     };
 
+    // 是否可以评测
+    const isDisabledEvaluated = computed(() => {
+      const item = props.item;
+      if (item.background && item.background.length > 0) {
+        let status = false;
+        item.background.forEach((item: any) => {
+          if (!item.audioFileUrl) {
+            status = true;
+          }
+        });
+        return status;
+      } else {
+        return true;
+      }
+    });
+
     onMounted(() => {
       const item = props.item;
+      console.log(item, 'item');
       if (item.trainId) {
         forms.id = item.trainId;
         forms.practiceSpeed = item.practiceSpeed;
@@ -149,15 +174,29 @@ export default defineComponent({
                 onClick={() => (forms.type = 'PRACTICE')}>
                 练习
               </NButton>
-              <NButton
-                secondary
-                class={[
-                  styles.switch,
-                  forms.type === 'EVALUATION' ? styles.active : ''
-                ]}
-                onClick={() => (forms.type = 'EVALUATION')}>
-                评测
-              </NButton>
+
+              {isDisabledEvaluated.value ? (
+                <NTooltip showArrow={false}>
+                  {{
+                    trigger: () => (
+                      <NButton disabled secondary class={[styles.switch]}>
+                        评测
+                      </NButton>
+                    ),
+                    default: () => '该曲目暂不支持评测'
+                  }}
+                </NTooltip>
+              ) : (
+                <NButton
+                  secondary
+                  class={[
+                    styles.switch,
+                    forms.type === 'EVALUATION' ? styles.active : ''
+                  ]}
+                  onClick={() => (forms.type = 'EVALUATION')}>
+                  评测
+                </NButton>
+              )}
             </NSpace>
           </NFormItem>
           <div class={styles.scoreGroup}>

+ 251 - 0
src/views/classList/components/afterWorkDetail.module.less

@@ -0,0 +1,251 @@
+.listWrap {
+  // min-height: 100%;
+  padding: 32px;
+  background-color: #fff;
+  border-radius: 20px;
+  min-height: calc(100vh - 7.8125vw) !important
+}
+
+.teacherSection {
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid #E9E9E9;
+  margin-bottom: 30px;
+  padding-bottom: 24px;
+
+  .tTemp {
+    display: flex;
+    align-content: center;
+  }
+
+  .infos {
+    margin-top: 8px;
+    padding: 13px;
+    background: #FFFFFF;
+    border-radius: 10px;
+
+    .homeTitle {
+      font-size: max(17px, 14Px);
+      font-family: PingFangSC, PingFang SC;
+      font-weight: 600;
+      color: #000000;
+      padding-bottom: 8px;
+    }
+
+    .homeContent {
+      padding-bottom: 5px;
+    }
+
+    .homeworkText {
+      display: flex;
+      align-items: flex-start;
+
+      .pSection {
+        max-width: 790px;
+      }
+
+      .p1,
+      .p2 {
+        // white-space: nowrap;
+        // overflow: hidden;
+        // text-overflow: ellipsis;
+
+        &>div {
+          display: flex;
+          align-items: flex-start;
+          color: #838383;
+
+          span {
+            color: #313131;
+            flex-shrink: 0;
+          }
+        }
+      }
+
+      .p1::before,
+      .p2::before {
+        content: '';
+        display: inline-block;
+        width: 5px;
+        height: 5px;
+        background: #198CFE;
+        margin-right: 7px;
+        border-radius: 50%;
+        flex-shrink: 0;
+        transform: translateY(-3px);
+      }
+
+      .p2 {
+        padding-top: 6px;
+      }
+
+      .p2::before {
+        background: #F44040;
+      }
+    }
+
+    .title {
+      font-size: max(13px, 12Px);
+      color: #777777;
+      flex-shrink: 0;
+    }
+
+    .text {
+      font-size: max(13px, 12Px);
+      font-weight: 500;
+      color: #333333;
+      line-height: 22px;
+      display: flex;
+      align-items: baseline;
+    }
+
+  }
+
+  .stitcTitle {
+    display: flex;
+    align-items: center;
+    font-size: max(20px, 16Px);
+    font-family: PingFangSC, PingFang SC;
+    font-weight: 600;
+    color: #000000;
+    line-height: 28px;
+    padding-bottom: 30px;
+
+    &::before {
+      content: '';
+      display: inline-block;
+      width: 4px;
+      height: 14px;
+      background: #198CFE;
+      border-radius: 2px;
+      margin-right: 8px;
+    }
+  }
+
+  .stitcConent {
+    :global {
+      .n-progress {
+        width: 116Px;
+      }
+    }
+
+    .contentRect {
+      text-align: center;
+
+      .text {
+        padding-top: 5px;
+        font-size: 12Px;
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 400;
+        color: #777777;
+        line-height: 17px;
+      }
+    }
+
+    .nums {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: max(26px, 18Px);
+      font-family: DINAlternate, DINAlternate;
+      font-weight: bold;
+      color: #000000;
+      line-height: 30px;
+
+      i {
+        font-style: normal;
+        font-size: max(20px, 14Px);
+      }
+
+      span {
+        font-size: 12Px;
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 500;
+        color: #333333;
+        line-height: 17px;
+      }
+    }
+  }
+}
+
+.teacherList {
+  display: flex;
+  // align-items: center;
+  flex-direction: column;
+  // margin-bottom: 32px;
+  flex: 1;
+  margin-right: 60px;
+  position: relative;
+
+  &::after {
+    content: '';
+    position: absolute;
+    right: 0;
+    width: 1px;
+    height: 55%;
+    background: #E9E9E9;
+    top: 50%;
+    margin-top: -60px;
+  }
+
+
+
+  .teacherHeader {
+    width: 100px;
+    height: 100px;
+    padding: 4px;
+    border-radius: 99px;
+    background: linear-gradient(228deg,
+        rgba(2, 186, 255, 1),
+        rgba(0, 122, 254, 1));
+    margin-right: 20px;
+
+    .teacherHeaderBorder {
+      width: 100%;
+      height: 100%;
+      background: #fff;
+      border-radius: 99px;
+      overflow: hidden;
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: center;
+      padding: 4px;
+    }
+  }
+
+  .teacherHeaderImg {
+    width: 84px;
+    height: 84px;
+    border-radius: 50%;
+    overflow: hidden;
+  }
+
+  .workafterInfo {
+    display: flex;
+    justify-content: center;
+    flex-direction: column;
+
+    h4 {
+      font-size: 22px;
+      line-height: 30px;
+      font-weight: 600;
+      color: #131415;
+      margin-bottom: 12px;
+    }
+
+    p {
+      font-size: max(16px, 12Px);
+      line-height: 22px;
+      color: #777;
+
+      span {
+        color: #ea4132;
+      }
+    }
+  }
+}
+
+.wordDetailModel {
+  width: 1012px;
+}

+ 247 - 77
src/views/classList/components/afterWorkDetail.tsx

@@ -1,5 +1,5 @@
 import { computed, defineComponent, onMounted, reactive, ref } from 'vue';
-import styles from '../index.module.less';
+import styles from './afterWorkDetail.module.less';
 import {
   NButton,
   NDataTable,
@@ -7,6 +7,7 @@ import {
   NFormItem,
   NImage,
   NModal,
+  NProgress,
   NSelect,
   NSpace
 } from 'naive-ui';
@@ -14,47 +15,48 @@ import SearchInput from '@/components/searchInput';
 import CSelect from '@/components/CSelect';
 import Pagination from '@/components/pagination';
 import { getWorkDetail, getTrainingStudentList } from '../api';
-import add from './images/add.png';
+// import add from './images/add.png';
 import { useRoute } from 'vue-router';
 import CBreadcrumb from '/src/components/CBreadcrumb';
-import CDatePicker from '/src/components/CDatePicker';
+// import CDatePicker from '/src/components/CDatePicker';
 import defultHeade from '@/components/layout/images/teacherIcon.png';
-import {
-  getNowDateAndMonday,
-  getNowDateAndSunday,
-  getTimes
-} from '/src/utils/dateFormat';
+// import {
+//   getNowDateAndMonday,
+//   getNowDateAndSunday,
+//   getTimes
+// } from '/src/utils/dateFormat';
 import { trainingStatusArray } from '@/utils/searchArray';
 import TrainingDetails from '../modals/TrainingDetails';
 import dayjs from 'dayjs';
-import { lookup } from 'dns';
 import TheEmpty from '/src/components/TheEmpty';
+import { evaluateDifficult } from '/src/utils/contants';
 export default defineComponent({
   name: 'student-studentList',
-  setup(props, { emit }) {
+  setup() {
     const state = reactive({
-      searchForm: { keyword: '', trainingStatus: '' as any },
+      searchForm: {
+        keyword: '',
+        vipFlag: null as any,
+        trainingStatus: '' as any
+        // classGroupId: '' as any
+      },
       loading: false,
       pagination: {
         page: 1,
         rows: 10,
         pageTotal: 4
       },
+      studentClassList: [] as any,
       tableList: [] as any,
-      workInfo: {
-        createTime: '',
-        expireDate: '',
-        teacherAvatar: '',
-        teacherName: ''
-      },
+      workInfo: {} as any,
       detailVisiable: false,
       activeRow: null as any,
       index: 0
     });
-    const timer = ref<[number, number]>([
-      getNowDateAndMonday(new Date().getTime()),
-      getNowDateAndSunday(new Date().getTime())
-    ]);
+    // const timer = ref<[number, number]>([
+    //   getNowDateAndMonday(new Date().getTime()),
+    //   getNowDateAndSunday(new Date().getTime())
+    // ]);
     const TrainingDetailsRef = ref();
     const route = useRoute();
     const routerList = ref([
@@ -70,18 +72,22 @@ export default defineComponent({
     };
 
     const onReset = () => {
-      state.searchForm = { keyword: '', trainingStatus: '' as any };
-      timer.value = [
-        getNowDateAndMonday(new Date().getTime()),
-        getNowDateAndSunday(new Date().getTime())
-      ];
+      state.searchForm = {
+        keyword: '',
+        trainingStatus: '' as any,
+        vipFlag: null
+      };
+      // timer.value = [
+      //   getNowDateAndMonday(new Date().getTime()),
+      //   getNowDateAndSunday(new Date().getTime())
+      // ];
       search();
     };
     const getList = async (type?: string, page?: number) => {
       state.loading = true;
       try {
         const res = await getTrainingStudentList({
-          classGroupId: route.query.classGroupId || '',
+          classGroupId: (route.query.classGroupId as any) || '',
           trainingId: route.query.trainingId,
           ...state.searchForm,
           ...state.pagination,
@@ -109,7 +115,56 @@ export default defineComponent({
     const getWorkInfo = async () => {
       try {
         const res = await getWorkDetail({ trainingId: route.query.trainingId });
-        state.workInfo = { ...res.data };
+        // state.workInfo = { ...res.data };
+        const result = res.data || {};
+        let pTitle = '';
+        let eTitle = '';
+        if (
+          result.studentLessonTrainingDetails &&
+          result.studentLessonTrainingDetails.length > 0
+        ) {
+          result.studentLessonTrainingDetails.forEach((child: any) => {
+            // if (child.trainingType === 'PRACTICE' && child.musicName) {
+            //   pTitle += pTitle ? '、' + child.musicName : child.musicName;
+            // }
+            // if (child.trainingType === 'EVALUATION' && child.musicName) {
+            //   eTitle += eTitle ? '、' + child.musicName : child.musicName;
+            // }
+            const trainingContent = child.trainingContent
+              ? JSON.parse(child.trainingContent)
+              : null;
+
+            if (child.trainingType === 'PRACTICE' && child.musicName) {
+              pTitle += '《' + child.musicName + '》';
+
+              if (trainingContent) {
+                const tempList = [
+                  `${trainingContent.practiceChapterBegin}-${trainingContent.practiceChapterEnd}小节`,
+                  `速度${trainingContent.practiceSpeed}`,
+                  `${trainingContent.trainingTimes}分钟`
+                ];
+                pTitle += tempList.join(' | ') + ';';
+              }
+            }
+            if (child.trainingType === 'EVALUATION' && child.musicName) {
+              eTitle += '《' + child.musicName + '》';
+              if (trainingContent) {
+                const tempList = [
+                  `${evaluateDifficult[trainingContent.evaluateDifficult]}`,
+                  trainingContent.practiceChapterBegin ||
+                  trainingContent.practiceChapterEnd
+                    ? `${trainingContent.practiceChapterBegin}-${trainingContent.practiceChapterEnd}小节`
+                    : '全部小节',
+                  `${trainingContent.trainingTimes}分合格`
+                ];
+                eTitle += tempList.join(' | ') + ';';
+              }
+            }
+          });
+        }
+        result.pTitle = pTitle;
+        result.eTitle = eTitle;
+        state.workInfo = result;
       } catch (e) {
         console.log(e);
       }
@@ -139,9 +194,13 @@ export default defineComponent({
               : '--';
           }
         },
+        // {
+        //   title: '所属班级',
+        //   key: 'classGroupName'
+        // },
         {
           title: '作业状态',
-          key: 'sex',
+          key: 'trainingStatus',
           render(row: any) {
             return (
               <div>
@@ -159,6 +218,13 @@ export default defineComponent({
           }
         },
         {
+          title: '是否会员',
+          key: 'vipFlag',
+          render(row: any) {
+            return row.vipFlag ? '是' : '否';
+          }
+        },
+        {
           title: '操作',
           key: 'id',
           render(row: any, index: number) {
@@ -207,36 +273,129 @@ export default defineComponent({
       <div>
         <CBreadcrumb list={routerList.value}></CBreadcrumb>
         <div class={styles.listWrap}>
-          <div class={styles.teacherList}>
-            <div class={styles.teacherHeader}>
-              <div class={styles.teacherHeaderBorder}>
-                <NImage
-                  class={styles.teacherHeaderImg}
-                  src={
-                    state.workInfo.teacherAvatar
-                      ? state.workInfo.teacherAvatar
-                      : defultHeade
-                  }
-                  previewDisabled></NImage>
+          <div class={styles.teacherSection}>
+            <div class={styles.teacherList}>
+              <div class={styles.tTemp}>
+                <div class={styles.teacherHeader}>
+                  <div class={styles.teacherHeaderBorder}>
+                    <NImage
+                      class={styles.teacherHeaderImg}
+                      src={state.workInfo.teacherAvatar || defultHeade}
+                      previewDisabled></NImage>
+                  </div>
+                </div>
+                <div class={styles.workafterInfo}>
+                  <h4>{state.workInfo.teacherName}</h4>
+                  {state.workInfo.createTime && (
+                    <p>
+                      布置时间:
+                      {state.workInfo.createTime &&
+                        dayjs(state.workInfo.createTime).format(
+                          'YYYY-MM-DD HH:mm'
+                        )}{' '}
+                      |{' '}
+                      <span>
+                        截止时间:
+                        {state.workInfo.expireDate &&
+                          dayjs(state.workInfo.expireDate).format(
+                            'YYYY-MM-DD HH:mm'
+                          )}
+                      </span>
+                    </p>
+                  )}
+                </div>
+              </div>
+              <div class={styles.infos}>
+                <div class={styles.homeTitle}>{state.workInfo.name}</div>
+                <div class={[styles.homeContent, styles.homeworkText]}>
+                  <div class={styles.pSection}>
+                    {state.workInfo.pTitle && (
+                      <p class={[styles.text, styles.p1]}>
+                        <div>
+                          <span>练习曲目:</span>
+                          <p>{state.workInfo.pTitle}</p>
+                        </div>
+                      </p>
+                    )}
+                    {state.workInfo.eTitle && (
+                      <p class={[styles.text, styles.p2]}>
+                        <div>
+                          <span>评测曲目:</span>
+                          <p>{state.workInfo.eTitle}</p>
+                        </div>
+                      </p>
+                    )}
+                  </div>
+                </div>
               </div>
             </div>
-            <div class={styles.workafterInfo}>
-              <h4>{state.workInfo.teacherName}</h4>
-              <p>
-                布置时间:
-                {state.workInfo.createTime
-                  ? dayjs(state.workInfo.createTime).format('YYYY-MM-DD HH:mm')
-                  : '--'}{' '}
-                |{' '}
-                <span>
-                  截止时间:
-                  {state.workInfo.expireDate
-                    ? dayjs(state.workInfo.expireDate).format(
-                        'YYYY-MM-DD HH:mm'
-                      )
-                    : '--'}
-                </span>
-              </p>
+            <div>
+              <div class={styles.stitcTitle}>作业完成情况</div>
+              <div class={styles.stitcConent}>
+                <NSpace size={[38, 0]}>
+                  <NProgress
+                    percentage={state.workInfo.trainingRate || 0}
+                    // percentage={20}
+                    offset-degree={180}
+                    type="circle"
+                    strokeWidth={6}
+                    rail-color={'EDEFFA'}
+                    color={'#64A5FF'}>
+                    <div class={styles.contentRect}>
+                      <div class={styles.nums}>
+                        {state.workInfo.trainingNum || 0}
+                        <i>/</i>
+                        {state.workInfo.expectNum || 0}
+                        <span>人</span>
+                      </div>
+                      <div class={styles.text}>已提交</div>
+                    </div>
+                  </NProgress>
+                  <NProgress
+                    percentage={state.workInfo.trainingRate || 0}
+                    offset-degree={180}
+                    strokeWidth={6}
+                    type="circle"
+                    rail-color={'EDEFFA'}
+                    color={'#64A5FF'}>
+                    <div class={styles.contentRect}>
+                      <div class={styles.nums}>
+                        {state.workInfo.trainingRate || 0}%
+                      </div>
+                      <div class={styles.text}>提交率</div>
+                    </div>
+                  </NProgress>
+                  <NProgress
+                    percentage={state.workInfo.qualifiedRate || 0}
+                    offset-degree={180}
+                    strokeWidth={6}
+                    type="circle"
+                    rail-color={'EDEFFA'}
+                    color={'#40CEAE'}>
+                    <div class={styles.contentRect}>
+                      <div class={styles.nums}>
+                        {state.workInfo.standardNum || 0}
+                        <span>人</span>
+                      </div>
+                      <div class={styles.text}>合格人数</div>
+                    </div>
+                  </NProgress>
+                  <NProgress
+                    percentage={state.workInfo.qualifiedRate || 0}
+                    offset-degree={180}
+                    strokeWidth={6}
+                    type="circle"
+                    rail-color={'EDEFFA'}
+                    color={'#40CEAE'}>
+                    <div class={styles.contentRect}>
+                      <div class={styles.nums}>
+                        {state.workInfo.qualifiedRate || 0}%
+                      </div>
+                      <div class={styles.text}>合格率</div>
+                    </div>
+                  </NProgress>
+                </NSpace>
+              </div>
             </div>
           </div>
           <div class={styles.searchList}>
@@ -251,6 +410,22 @@ export default defineComponent({
                   }></SearchInput>
               </NFormItem>
 
+              {/* <NFormItem>
+                <CSelect
+                  {...({
+                    options: [
+                      {
+                        label: '全部班级',
+                        value: ''
+                      },
+                      ...state.studentClassList
+                    ],
+                    placeholder: '全部班级',
+                    clearable: true,
+                    inline: true
+                  } as any)}
+                  v-model:value={state.searchForm.classGroupId}></CSelect>
+              </NFormItem> */}
               <NFormItem>
                 <CSelect
                   {...({
@@ -267,13 +442,20 @@ export default defineComponent({
                   } as any)}
                   v-model:value={state.searchForm.trainingStatus}></CSelect>
               </NFormItem>
-              {/* <NFormItem>
-                <CDatePicker
-                  v-model:value={timer.value}
-                  separator={'至'}
-                  type="daterange"
-                  timerValue={timer.value}></CDatePicker>
-              </NFormItem> */}
+
+              <NFormItem>
+                <CSelect
+                  {...({
+                    options: [
+                      { label: '是', value: true },
+                      { label: '否', value: false }
+                    ],
+                    placeholder: '是否会员',
+                    clearable: true,
+                    inline: true
+                  } as any)}
+                  v-model:value={state.searchForm.vipFlag}></CSelect>
+              </NFormItem>
 
               <NFormItem>
                 <NSpace justify="end">
@@ -291,18 +473,6 @@ export default defineComponent({
               </NFormItem>
             </NForm>
           </div>
-          {/* <NButton
-          class={styles.addBtn}
-          type="primary"
-          v-slots={{
-            icon: () => (
-              <>
-                <NImage class={styles.addBtnIcon} src={add}></NImage>
-              </>
-            )
-          }}>
-          新增学生
-        </NButton> */}
           <div class={styles.tableWrap}>
             <NDataTable
               v-slots={{
@@ -317,7 +487,7 @@ export default defineComponent({
               v-model:pageSize={state.pagination.rows}
               v-model:pageTotal={state.pagination.pageTotal}
               onList={getList}
-              sync
+              // sync
             />
           </div>
         </div>

+ 33 - 14
src/views/classList/work-item/index.tsx

@@ -1,5 +1,12 @@
 import { computed, defineComponent, ref } from 'vue';
-import { NImage, NDivider, NButton, NModal, useMessage } from 'naive-ui';
+import {
+  NImage,
+  NDivider,
+  NButton,
+  NModal,
+  useMessage,
+  ImageRenderToolbarProps
+} from 'naive-ui';
 import TheNoticeBar from '/src/components/TheNoticeBar';
 import styles from './index.module.less';
 import { PageEnum } from '/src/enums/pageEnum';
@@ -29,17 +36,16 @@ export default defineComponent({
     const reportSrc = ref('');
     const detailVisiable = ref(false);
 
-    const isDownload = computed(() => {
-      if (
-        props.item.fileList?.expireFlag &&
-        props.item.fileList?.fileType === 'EVALUATION'
-      ) {
-        return true;
-      } else {
-        return false;
-      }
-    });
-
+    // const isDownload = computed(() => {
+    //   if (
+    //     props.item.fileList?.expireFlag &&
+    //     props.item.fileList?.fileType === 'EVALUATION'
+    //   ) {
+    //     return true;
+    //   } else {
+    //     return false;
+    //   }
+    // });
     return () => (
       <div
         class={[
@@ -64,7 +70,21 @@ export default defineComponent({
           )}
 
           {props.item.fileList?.fileType === 'IMG' && (
-            <NImage src={props.item.fileList?.filePath} objectFit="contain" />
+            <NImage
+              src={props.item.fileList?.filePath}
+              objectFit="contain"
+              // renderToolbar={({ nodes }: ImageRenderToolbarProps) => {
+              //   return [
+              //     nodes.prev,
+              //     nodes.next,
+              //     nodes.rotateCounterclockwise,
+              //     nodes.rotateClockwise,
+              //     nodes.resizeToOriginalSize,
+              //     nodes.zoomOut,
+              //     nodes.close
+              //   ];
+              // }}
+            />
           )}
           {props.item.fileList?.fileType === 'SOUND' && (
             <div
@@ -200,7 +220,6 @@ export default defineComponent({
         <CardPreview
           v-model:show={previewShow.value}
           item={preivewItem.value}
-          isDownload={isDownload.value}
         />
 
         <NModal

+ 1 - 0
src/views/natural-resources/components/my-collect/index.tsx

@@ -64,6 +64,7 @@ export default defineComponent({
             refFlag: row.refFlag,
             content: row.content,
             subjectId: row.subjectIds,
+            background: row.background,
             enableFlag: row.enableFlag ? 1 : 0,
             openFlag: row.openFlag
           });

+ 477 - 476
src/views/natural-resources/components/my-resources/index.tsx

@@ -1,476 +1,477 @@
-import { computed, defineComponent, onMounted, reactive, ref } from 'vue';
-import styles from './index.module.less';
-import CardType from '/src/components/card-type';
-import Pagination from '/src/components/pagination';
-import SearchGroupResources from './search-group-resources';
-import {
-  favorite,
-  materialQueryPage,
-  materialRemove,
-  materialRemoveAll,
-  materialRemoveMusic,
-  materialUpdateAll
-} from '../../api';
-import {
-  NButton,
-  NModal,
-  NSpace,
-  NSpin,
-  useDialog,
-  useMessage
-} from 'naive-ui';
-import TheEmpty from '/src/components/TheEmpty';
-import UploadModal, { formatUrlType } from './upload-modal';
-import CardPreview from '@/components/card-preview';
-import resourceDefault from '../../images/resource-default.png';
-import resourceChecked from '../../images/resource-checked.png';
-import MyResourcesGuide from '@/custom-plugins/guide-page/myResources-guide';
-import SaveModal from './save-modal';
-import deepClone from '/src/helpers/deep-clone';
-import UploadCover from './upload-cover';
-export default defineComponent({
-  name: 'share-resources',
-  setup() {
-    const message = useMessage();
-    const dialog = useDialog();
-    const state = reactive({
-      searchWord: '',
-      loading: false,
-      pageTotal: 0,
-      pagination: {
-        page: 1,
-        rows: 20
-      },
-      searchGroup: {
-        type: 'MUSIC', //
-        name: '',
-        bookVersionId: null,
-        subjectId: null,
-        sourceType: 3
-      },
-      tableList: [] as any,
-      uploadStatus: false,
-      saveStatus: false,
-      show: false,
-      item: {} as any,
-      editStatus: false, // 是否编辑
-      isAdd: false,
-      editList: [] as any, // TOD
-      editIds: [] as any, // 编辑的
-      editOverIds: [] as any, // 确认修改的数据
-      removeVisiable: false,
-      removeContent: '是否删除该资源?',
-      type: 'remove',
-      removeItem: {} as any
-    });
-    const showGuide = ref(false);
-    const getList = async () => {
-      try {
-        state.loading = true;
-        const { data } = await materialQueryPage({
-          ...state.searchGroup,
-          ...state.pagination
-        });
-        state.loading = false;
-        state.pageTotal = Number(data.total);
-        const tempRows = data.rows || [];
-        const temp: any = [];
-        tempRows.forEach((row: any) => {
-          temp.push({
-            id: row.id,
-            coverImg: row.coverImg,
-            type: row.type,
-            title: row.name,
-            isCollect: !!row.favoriteFlag,
-            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
-            refFlag: row.refFlag,
-            content: row.content,
-            // subjectId: row.subjectIds,
-            instrumentIds: row.instrumentIds,
-            sourceFrom: row.sourceFrom,
-            enableFlag: row.enableFlag ? 1 : 0,
-            openFlag: row.openFlag
-          });
-        });
-        state.tableList = temp || [];
-        setTimeout(() => {
-          showGuide.value = true;
-        }, 500);
-      } catch {
-        state.loading = false;
-      }
-    };
-
-    const isEmpty = computed(() => {
-      const list = state.tableList || [];
-      let num = 0;
-      list.forEach((item: any) => {
-        if (!item.delFlag) {
-          num++;
-        }
-      });
-      return num > 0 ? false : true;
-    });
-
-    // 收藏
-    const onCollect = async (item: any) => {
-      try {
-        await favorite({
-          materialId: item.id,
-          favoriteFlag: item.isCollect ? 0 : 1,
-          type: item.type
-        });
-        item.isCollect = !item.isCollect;
-      } catch {
-        //
-      }
-    };
-
-    const onSearch = async (item: any) => {
-      state.pagination.page = 1;
-
-      const { subjectId, ...res } = item;
-      state.searchGroup = Object.assign(state.searchGroup, {
-        ...res,
-        musicalInstrumentId: subjectId,
-        subjectId: null
-      });
-
-      getList();
-    };
-
-    // 批量删除
-    const onDelete = async () => {
-      try {
-        if (state.searchGroup.type === 'MUSIC') {
-          await materialRemoveMusic(state.editIds);
-        } else {
-          await materialRemoveAll(state.editIds);
-        }
-        // message.success('删除成功');
-        // state.pagination.page = 1;
-        // getList();
-        // state.editIds = [];
-      } catch {
-        //
-      }
-    };
-
-    // 单个删除
-    const onRemove = async () => {
-      try {
-        // 如果是乐谱类型则使用其它删除接口
-        if (state.searchGroup.type === 'MUSIC') {
-          await materialRemoveMusic([state.removeItem.id]);
-        } else {
-          await materialRemove({ ids: state.removeItem.id });
-        }
-
-        message.success('删除成功');
-        onSearch(state.searchGroup);
-      } catch {
-        //
-      }
-    };
-    const searchGroupResourcesRef = ref();
-    onMounted(() => {
-      getList();
-    });
-    return () => (
-      <>
-        <SearchGroupResources
-          ref={searchGroupResourcesRef}
-          onSearch={(item: any) => onSearch(item)}
-          onUpload={() => {
-            state.editList = [];
-            // state.uploadStatus = true;
-            state.saveStatus = true;
-          }}
-          onUpdate={() => {
-            // 修改
-            const list: any[] = [];
-            state.tableList.forEach((item: any) => {
-              if (state.editIds.indexOf(item.id) > -1 && item.delFlag !== 1) {
-                list.push(item);
-              }
-            });
-            state.editList = list || [];
-            if (state.editList.length <= 0) {
-              message.error('至少选择一条资源进行编辑');
-              return;
-            }
-            state.uploadStatus = true;
-            state.isAdd = false;
-          }}
-          onEditOver={async (status: boolean) => {
-            state.editStatus = status;
-            try {
-              // 修改
-              if (state.editOverIds.length > 0) {
-                const body = [] as any;
-                state.tableList.forEach((item: any) => {
-                  if (state.editOverIds.includes(item.id)) {
-                    body.push({
-                      instrumentIds: item.instrumentIds,
-                      openFlag: item.openFlag,
-                      coverImg: item.coverImg,
-                      name: item.title,
-                      type: item.type,
-                      enableFlag: 1,
-                      content: item.content,
-                      id: item.id || null,
-                      delFlag: item.delFlag
-                    });
-                  }
-                });
-                //
-                if (body.length > 0) {
-                  if (state.searchGroup.type === 'MUSIC') {
-                    await materialRemoveMusic(state.editIds);
-                  } else {
-                    await materialUpdateAll(body);
-                  }
-                }
-              }
-              message.success('修改成功');
-              state.pagination.page = 1;
-              getList();
-              state.editIds = [];
-              state.editOverIds = [];
-            } catch {
-              //
-            }
-          }}
-          onCancel={status => {
-            state.editStatus = status;
-            state.pagination.page = 1;
-            state.editIds = [];
-            state.editOverIds = [];
-            getList();
-          }}
-          onEdit={async (status: boolean) => {
-            // 点击编辑
-            state.editStatus = status;
-
-            if (!state.editStatus) {
-              state.editIds = [];
-              state.editOverIds = [];
-            }
-          }}
-          onSelectAll={(status: boolean) => {
-            // 全选
-            if (status) {
-              const tempIds: any[] = [];
-              state.tableList.forEach((item: any) => {
-                tempIds.push(item.id);
-              });
-              state.editIds = tempIds;
-            } else {
-              state.editIds = [];
-            }
-          }}
-          onDelete={() => {
-            if (state.editIds.length <= 0) {
-              message.error('至少选择一条资源进行删除');
-              return;
-            }
-            state.type = 'delete';
-            state.removeContent = '是否删除该资源?';
-            state.removeVisiable = true;
-          }}
-        />
-
-        <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
-          <div class={styles.list}>
-            {state.tableList.map(
-              (item: any) =>
-                item.delFlag !== 1 && (
-                  <div class={styles.itemWrap}>
-                    <div class={styles.itemWrapBox}>
-                      <CardType
-                        item={item}
-                        isDownload
-                        disabledMouseHover={false}
-                        offShelf={item.enableFlag ? false : true}
-                        onOffShelf={() => {
-                          state.type = 'remove';
-                          state.removeContent = '该资源已下架,是否删除?';
-                          state.removeVisiable = true;
-                          state.removeItem = item;
-                        }} // 下架
-                        onClick={(val: any) => {
-                          if (val.type === 'IMG' || !item.enableFlag) return;
-                          state.show = true;
-                          state.item = val;
-                        }}
-                        onCollect={(item: any) => onCollect(item)}
-                      />
-                      {/* 编辑模式 */}
-                      {state.editStatus && (
-                        <div
-                          class={[
-                            styles.itemBg,
-                            state.editIds.includes(item.id)
-                              ? styles.itemBgChecked
-                              : ''
-                          ]}
-                          onClick={() => {
-                            const index = state.editIds.indexOf(item.id);
-                            if (index > -1) {
-                              state.editIds.splice(index, 1);
-                            } else {
-                              state.editIds.push(item.id);
-                            }
-                          }}>
-                          <img
-                            src={
-                              state.editIds.includes(item.id)
-                                ? resourceChecked
-                                : resourceDefault
-                            }
-                            class={styles.resourceDefault}
-                          />
-                        </div>
-                      )}
-                    </div>
-                  </div>
-                )
-            )}
-
-            {!state.loading && isEmpty.value && (
-              <TheEmpty style={{ minHeight: '50vh' }} description="暂无资源" />
-            )}
-          </div>
-        </NSpin>
-
-        <Pagination
-          disabled={state.editStatus}
-          v-model:page={state.pagination.page}
-          v-model:pageSize={state.pagination.rows}
-          v-model:pageTotal={state.pageTotal}
-          onList={getList}
-        />
-
-        {/* 弹窗查看 */}
-        <CardPreview v-model:show={state.show} item={state.item} />
-
-        <NModal
-          v-model:show={state.uploadStatus}
-          preset="card"
-          showIcon={false}
-          class={['modalTitle background', styles.attendClassModal]}
-          title={state.editStatus ? '修改资源' : '上传资源'}
-          blockScroll={false}>
-          <UploadModal
-            editStatus={state.editStatus}
-            onClose={() => {
-              state.uploadStatus = false;
-            }}
-            onConfirm={() => {
-              state.editIds = [];
-              state.editList = [];
-              state.editOverIds = [];
-              state.saveStatus = false;
-              searchGroupResourcesRef.value?.resetStatus();
-              onSearch(state.searchGroup);
-            }}
-            list={state.editList}
-            showDelete={state.isAdd}
-            onEditAll={(list: any) => {
-              try {
-                state.tableList.forEach((table: any) => {
-                  const item = list.find((item: any) => item.id === table.id);
-                  if (item) {
-                    table.openFlag = item.openFlag;
-                    table.title = item.name;
-                    table.instrumentIds = item.instrumentIds;
-                    table.content = item.content;
-                    table.coverImg = item.coverImg;
-
-                    if (!state.editOverIds.includes(table.id)) {
-                      state.editOverIds.push(table.id);
-                    }
-                  }
-                });
-                state.uploadStatus = false;
-              } catch (e: any) {
-                console.log(e);
-              }
-            }}
-          />
-        </NModal>
-
-        <NModal
-          v-model:show={state.saveStatus}
-          preset="card"
-          showIcon={false}
-          class={['modalTitle background', styles.attendClassSaveModal]}
-          title={'上传资源'}
-          blockScroll={false}>
-          <SaveModal
-            onClose={() => (state.saveStatus = false)}
-            onConfrim={(val: any) => {
-              const list = val || [];
-              const temp: any = [];
-              list.forEach((item: any) => {
-                temp.push({
-                  instrumentIds: null,
-                  openFlag: false,
-                  coverImg: item.coverImg,
-                  title: item.name || '',
-                  type: formatUrlType(item.content),
-                  enableFlag: 1,
-                  content: item.content,
-                  id: null
-                });
-              });
-              state.editList = [...temp];
-              state.uploadStatus = true;
-              state.isAdd = true;
-              state.editStatus = false;
-            }}
-          />
-        </NModal>
-
-        {showGuide.value ? <MyResourcesGuide></MyResourcesGuide> : null}
-
-        <NModal
-          v-model:show={state.removeVisiable}
-          preset="card"
-          class={['modalTitle', styles.removeVisiable]}
-          title={'提示'}>
-          <div class={styles.studentRemove}>
-            <p>{state.removeContent}</p>
-
-            <NSpace class={styles.btnGroupModal} justify="center">
-              <NButton
-                round
-                type="primary"
-                onClick={() => {
-                  if (state.type === 'remove') {
-                    onRemove();
-                  } else {
-                    state.tableList.forEach((item: any) => {
-                      if (state.editIds.includes(item.id)) {
-                        item.delFlag = 1;
-
-                        if (!state.editOverIds.includes(item.id)) {
-                          state.editOverIds.push(item.id);
-                        }
-                      }
-                    });
-                  }
-                  state.removeVisiable = false;
-                }}>
-                确定
-              </NButton>
-              <NButton round onClick={() => (state.removeVisiable = false)}>
-                取消
-              </NButton>
-            </NSpace>
-          </div>
-        </NModal>
-      </>
-    );
-  }
-});
+import { computed, defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import CardType from '/src/components/card-type';
+import Pagination from '/src/components/pagination';
+import SearchGroupResources from './search-group-resources';
+import {
+  favorite,
+  materialQueryPage,
+  materialRemove,
+  materialRemoveAll,
+  materialRemoveMusic,
+  materialUpdateAll
+} from '../../api';
+import {
+  NButton,
+  NModal,
+  NSpace,
+  NSpin,
+  useDialog,
+  useMessage
+} from 'naive-ui';
+import TheEmpty from '/src/components/TheEmpty';
+import UploadModal, { formatUrlType } from './upload-modal';
+import CardPreview from '@/components/card-preview';
+import resourceDefault from '../../images/resource-default.png';
+import resourceChecked from '../../images/resource-checked.png';
+import MyResourcesGuide from '@/custom-plugins/guide-page/myResources-guide';
+import SaveModal from './save-modal';
+import deepClone from '/src/helpers/deep-clone';
+import UploadCover from './upload-cover';
+export default defineComponent({
+  name: 'share-resources',
+  setup() {
+    const message = useMessage();
+    const dialog = useDialog();
+    const state = reactive({
+      searchWord: '',
+      loading: false,
+      pageTotal: 0,
+      pagination: {
+        page: 1,
+        rows: 20
+      },
+      searchGroup: {
+        type: 'MUSIC', //
+        name: '',
+        bookVersionId: null,
+        subjectId: null,
+        sourceType: 3
+      },
+      tableList: [] as any,
+      uploadStatus: false,
+      saveStatus: false,
+      show: false,
+      item: {} as any,
+      editStatus: false, // 是否编辑
+      isAdd: false,
+      editList: [] as any, // TOD
+      editIds: [] as any, // 编辑的
+      editOverIds: [] as any, // 确认修改的数据
+      removeVisiable: false,
+      removeContent: '是否删除该资源?',
+      type: 'remove',
+      removeItem: {} as any
+    });
+    const showGuide = ref(false);
+    const getList = async () => {
+      try {
+        state.loading = true;
+        const { data } = await materialQueryPage({
+          ...state.searchGroup,
+          ...state.pagination
+        });
+        state.loading = false;
+        state.pageTotal = Number(data.total);
+        const tempRows = data.rows || [];
+        const temp: any = [];
+        tempRows.forEach((row: any) => {
+          temp.push({
+            id: row.id,
+            coverImg: row.coverImg,
+            type: row.type,
+            title: row.name,
+            isCollect: !!row.favoriteFlag,
+            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
+            refFlag: row.refFlag,
+            content: row.content,
+            // subjectId: row.subjectIds,
+            instrumentIds: row.instrumentIds,
+            sourceFrom: row.sourceFrom,
+            background: row.background,
+            enableFlag: row.enableFlag ? 1 : 0,
+            openFlag: row.openFlag
+          });
+        });
+        state.tableList = temp || [];
+        setTimeout(() => {
+          showGuide.value = true;
+        }, 500);
+      } catch {
+        state.loading = false;
+      }
+    };
+
+    const isEmpty = computed(() => {
+      const list = state.tableList || [];
+      let num = 0;
+      list.forEach((item: any) => {
+        if (!item.delFlag) {
+          num++;
+        }
+      });
+      return num > 0 ? false : true;
+    });
+
+    // 收藏
+    const onCollect = async (item: any) => {
+      try {
+        await favorite({
+          materialId: item.id,
+          favoriteFlag: item.isCollect ? 0 : 1,
+          type: item.type
+        });
+        item.isCollect = !item.isCollect;
+      } catch {
+        //
+      }
+    };
+
+    const onSearch = async (item: any) => {
+      state.pagination.page = 1;
+
+      const { subjectId, ...res } = item;
+      state.searchGroup = Object.assign(state.searchGroup, {
+        ...res,
+        musicalInstrumentId: subjectId,
+        subjectId: null
+      });
+
+      getList();
+    };
+
+    // 批量删除
+    const onDelete = async () => {
+      try {
+        if (state.searchGroup.type === 'MUSIC') {
+          await materialRemoveMusic(state.editIds);
+        } else {
+          await materialRemoveAll(state.editIds);
+        }
+        // message.success('删除成功');
+        // state.pagination.page = 1;
+        // getList();
+        // state.editIds = [];
+      } catch {
+        //
+      }
+    };
+
+    // 单个删除
+    const onRemove = async () => {
+      try {
+        // 如果是乐谱类型则使用其它删除接口
+        if (state.searchGroup.type === 'MUSIC') {
+          await materialRemoveMusic([state.removeItem.id]);
+        } else {
+          await materialRemove({ ids: state.removeItem.id });
+        }
+
+        message.success('删除成功');
+        onSearch(state.searchGroup);
+      } catch {
+        //
+      }
+    };
+    const searchGroupResourcesRef = ref();
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <>
+        <SearchGroupResources
+          ref={searchGroupResourcesRef}
+          onSearch={(item: any) => onSearch(item)}
+          onUpload={() => {
+            state.editList = [];
+            // state.uploadStatus = true;
+            state.saveStatus = true;
+          }}
+          onUpdate={() => {
+            // 修改
+            const list: any[] = [];
+            state.tableList.forEach((item: any) => {
+              if (state.editIds.indexOf(item.id) > -1 && item.delFlag !== 1) {
+                list.push(item);
+              }
+            });
+            state.editList = list || [];
+            if (state.editList.length <= 0) {
+              message.error('至少选择一条资源进行编辑');
+              return;
+            }
+            state.uploadStatus = true;
+            state.isAdd = false;
+          }}
+          onEditOver={async (status: boolean) => {
+            state.editStatus = status;
+            try {
+              // 修改
+              if (state.editOverIds.length > 0) {
+                const body = [] as any;
+                state.tableList.forEach((item: any) => {
+                  if (state.editOverIds.includes(item.id)) {
+                    body.push({
+                      instrumentIds: item.instrumentIds,
+                      openFlag: item.openFlag,
+                      coverImg: item.coverImg,
+                      name: item.title,
+                      type: item.type,
+                      enableFlag: 1,
+                      content: item.content,
+                      id: item.id || null,
+                      delFlag: item.delFlag
+                    });
+                  }
+                });
+                //
+                if (body.length > 0) {
+                  if (state.searchGroup.type === 'MUSIC') {
+                    await materialRemoveMusic(state.editIds);
+                  } else {
+                    await materialUpdateAll(body);
+                  }
+                }
+              }
+              message.success('修改成功');
+              state.pagination.page = 1;
+              getList();
+              state.editIds = [];
+              state.editOverIds = [];
+            } catch {
+              //
+            }
+          }}
+          onCancel={status => {
+            state.editStatus = status;
+            state.pagination.page = 1;
+            state.editIds = [];
+            state.editOverIds = [];
+            getList();
+          }}
+          onEdit={async (status: boolean) => {
+            // 点击编辑
+            state.editStatus = status;
+
+            if (!state.editStatus) {
+              state.editIds = [];
+              state.editOverIds = [];
+            }
+          }}
+          onSelectAll={(status: boolean) => {
+            // 全选
+            if (status) {
+              const tempIds: any[] = [];
+              state.tableList.forEach((item: any) => {
+                tempIds.push(item.id);
+              });
+              state.editIds = tempIds;
+            } else {
+              state.editIds = [];
+            }
+          }}
+          onDelete={() => {
+            if (state.editIds.length <= 0) {
+              message.error('至少选择一条资源进行删除');
+              return;
+            }
+            state.type = 'delete';
+            state.removeContent = '是否删除该资源?';
+            state.removeVisiable = true;
+          }}
+        />
+
+        <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
+          <div class={styles.list}>
+            {state.tableList.map(
+              (item: any) =>
+                item.delFlag !== 1 && (
+                  <div class={styles.itemWrap}>
+                    <div class={styles.itemWrapBox}>
+                      <CardType
+                        item={item}
+                        isDownload
+                        disabledMouseHover={false}
+                        offShelf={item.enableFlag ? false : true}
+                        onOffShelf={() => {
+                          state.type = 'remove';
+                          state.removeContent = '该资源已下架,是否删除?';
+                          state.removeVisiable = true;
+                          state.removeItem = item;
+                        }} // 下架
+                        onClick={(val: any) => {
+                          if (val.type === 'IMG' || !item.enableFlag) return;
+                          state.show = true;
+                          state.item = val;
+                        }}
+                        onCollect={(item: any) => onCollect(item)}
+                      />
+                      {/* 编辑模式 */}
+                      {state.editStatus && (
+                        <div
+                          class={[
+                            styles.itemBg,
+                            state.editIds.includes(item.id)
+                              ? styles.itemBgChecked
+                              : ''
+                          ]}
+                          onClick={() => {
+                            const index = state.editIds.indexOf(item.id);
+                            if (index > -1) {
+                              state.editIds.splice(index, 1);
+                            } else {
+                              state.editIds.push(item.id);
+                            }
+                          }}>
+                          <img
+                            src={
+                              state.editIds.includes(item.id)
+                                ? resourceChecked
+                                : resourceDefault
+                            }
+                            class={styles.resourceDefault}
+                          />
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                )
+            )}
+
+            {!state.loading && isEmpty.value && (
+              <TheEmpty style={{ minHeight: '50vh' }} description="暂无资源" />
+            )}
+          </div>
+        </NSpin>
+
+        <Pagination
+          disabled={state.editStatus}
+          v-model:page={state.pagination.page}
+          v-model:pageSize={state.pagination.rows}
+          v-model:pageTotal={state.pageTotal}
+          onList={getList}
+        />
+
+        {/* 弹窗查看 */}
+        <CardPreview v-model:show={state.show} item={state.item} />
+
+        <NModal
+          v-model:show={state.uploadStatus}
+          preset="card"
+          showIcon={false}
+          class={['modalTitle background', styles.attendClassModal]}
+          title={state.editStatus ? '修改资源' : '上传资源'}
+          blockScroll={false}>
+          <UploadModal
+            editStatus={state.editStatus}
+            onClose={() => {
+              state.uploadStatus = false;
+            }}
+            onConfirm={() => {
+              state.editIds = [];
+              state.editList = [];
+              state.editOverIds = [];
+              state.saveStatus = false;
+              searchGroupResourcesRef.value?.resetStatus();
+              onSearch(state.searchGroup);
+            }}
+            list={state.editList}
+            showDelete={state.isAdd}
+            onEditAll={(list: any) => {
+              try {
+                state.tableList.forEach((table: any) => {
+                  const item = list.find((item: any) => item.id === table.id);
+                  if (item) {
+                    table.openFlag = item.openFlag;
+                    table.title = item.name;
+                    table.instrumentIds = item.instrumentIds;
+                    table.content = item.content;
+                    table.coverImg = item.coverImg;
+
+                    if (!state.editOverIds.includes(table.id)) {
+                      state.editOverIds.push(table.id);
+                    }
+                  }
+                });
+                state.uploadStatus = false;
+              } catch (e: any) {
+                console.log(e);
+              }
+            }}
+          />
+        </NModal>
+
+        <NModal
+          v-model:show={state.saveStatus}
+          preset="card"
+          showIcon={false}
+          class={['modalTitle background', styles.attendClassSaveModal]}
+          title={'上传资源'}
+          blockScroll={false}>
+          <SaveModal
+            onClose={() => (state.saveStatus = false)}
+            onConfrim={(val: any) => {
+              const list = val || [];
+              const temp: any = [];
+              list.forEach((item: any) => {
+                temp.push({
+                  instrumentIds: null,
+                  openFlag: false,
+                  coverImg: item.coverImg,
+                  title: item.name || '',
+                  type: formatUrlType(item.content),
+                  enableFlag: 1,
+                  content: item.content,
+                  id: null
+                });
+              });
+              state.editList = [...temp];
+              state.uploadStatus = true;
+              state.isAdd = true;
+              state.editStatus = false;
+            }}
+          />
+        </NModal>
+
+        {showGuide.value ? <MyResourcesGuide></MyResourcesGuide> : null}
+
+        <NModal
+          v-model:show={state.removeVisiable}
+          preset="card"
+          class={['modalTitle', styles.removeVisiable]}
+          title={'提示'}>
+          <div class={styles.studentRemove}>
+            <p>{state.removeContent}</p>
+
+            <NSpace class={styles.btnGroupModal} justify="center">
+              <NButton
+                round
+                type="primary"
+                onClick={() => {
+                  if (state.type === 'remove') {
+                    onRemove();
+                  } else {
+                    state.tableList.forEach((item: any) => {
+                      if (state.editIds.includes(item.id)) {
+                        item.delFlag = 1;
+
+                        if (!state.editOverIds.includes(item.id)) {
+                          state.editOverIds.push(item.id);
+                        }
+                      }
+                    });
+                  }
+                  state.removeVisiable = false;
+                }}>
+                确定
+              </NButton>
+              <NButton round onClick={() => (state.removeVisiable = false)}>
+                取消
+              </NButton>
+            </NSpace>
+          </div>
+        </NModal>
+      </>
+    );
+  }
+});

+ 174 - 173
src/views/natural-resources/components/share-resources/index.tsx

@@ -1,173 +1,174 @@
-import { defineComponent, onMounted, reactive, ref } from 'vue';
-import styles from './index.module.less';
-import CardType from '/src/components/card-type';
-import Pagination from '/src/components/pagination';
-import SearchGroupResources from './search-group-resources';
-import { favorite, materialQueryPage } from '../../api';
-import { NModal, NSpin } from 'naive-ui';
-import TheEmpty from '/src/components/TheEmpty';
-import CardPreview from '/src/components/card-preview';
-import AddTeaching from '../../model/add-teaching';
-import ShareResourcesGuide from '@/custom-plugins/guide-page/shareResources-guide';
-
-export default defineComponent({
-  name: 'share-resources',
-  setup() {
-    const state = reactive({
-      searchWord: '',
-      loading: false,
-      pageTotal: 0,
-      pagination: {
-        page: 1,
-        rows: 20
-      },
-      searchGroup: {
-        type: 'MUSIC', //
-        name: '',
-        bookVersionId: null,
-        subjectId: null,
-        sourceType: 2
-      },
-      tableList: [] as any,
-      teachingStatus: false,
-      show: false,
-      item: {} as any
-    });
-    const showGuide = ref(false);
-    const SearchGroupResourcesRef = ref();
-    const getList = async () => {
-      try {
-        state.loading = true;
-        const { data } = await materialQueryPage({
-          ...state.searchGroup,
-          ...state.pagination
-        });
-        state.loading = false;
-        state.pageTotal = Number(data.total);
-        const tempRows = data.rows || [];
-        const temp: any = [];
-        tempRows.forEach((row: any) => {
-          temp.push({
-            id: row.id,
-            coverImg: row.coverImg,
-            type: row.type,
-            title: row.name,
-            isCollect: !!row.favoriteFlag,
-            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
-            refFlag: row.refFlag,
-            content: row.content
-          });
-        });
-        state.tableList = temp || [];
-        setTimeout(() => {
-          showGuide.value = true;
-        }, 500);
-      } catch {
-        state.loading = false;
-      }
-    };
-
-    const onSearch = async (item: any) => {
-      state.pagination.page = 1;
-
-      const { subjectId, ...res } = item;
-      state.searchGroup = Object.assign(state.searchGroup, {
-        ...res,
-        musicalInstrumentId: subjectId,
-        subjectId: null
-      });
-
-      getList();
-    };
-
-    // 收藏
-    const onCollect = async (item: any) => {
-      try {
-        await favorite({
-          materialId: item.id,
-          favoriteFlag: item.isCollect ? 0 : 1,
-          type: item.type
-        });
-        item.isCollect = !item.isCollect;
-      } catch {
-        //
-      }
-    };
-
-    onMounted(() => {
-      getList();
-    });
-    return () => (
-      <>
-        <SearchGroupResources
-          ref={SearchGroupResourcesRef}
-          onSearch={(item: any) => onSearch(item)}
-          onAdd={() => (state.teachingStatus = true)}
-        />
-        <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
-          <div class={styles.list}>
-            {state.tableList.map((item: any, index: number) => (
-              <div class={styles.itemWrap}>
-                <div class={styles.itemWrapBox}>
-                  {index == 0 ? (
-                    <CardType
-                      {...{ id: 'shareResources-1' }}
-                      item={item}
-                      disabledMouseHover={false}
-                      onClick={(val: any) => {
-                        if (val.type === 'IMG') return;
-                        state.show = true;
-                        state.item = val;
-                      }}
-                      onCollect={(item: any) => onCollect(item)}
-                    />
-                  ) : (
-                    <CardType
-                      item={item}
-                      disabledMouseHover={false}
-                      onClick={(val: any) => {
-                        if (val.type === 'IMG') return;
-                        state.show = true;
-                        state.item = val;
-                      }}
-                      onCollect={(item: any) => onCollect(item)}
-                    />
-                  )}
-                </div>
-              </div>
-            ))}
-
-            {!state.loading && state.tableList.length <= 0 && (
-              <TheEmpty
-                style={{ minHeight: '50vh' }}
-                description="暂无共享资源"
-              />
-            )}
-          </div>
-        </NSpin>
-
-        <Pagination
-          v-model:page={state.pagination.page}
-          v-model:pageSize={state.pagination.rows}
-          v-model:pageTotal={state.pageTotal}
-          onList={getList}
-        />
-
-        {/* 弹窗查看 */}
-        <CardPreview v-model:show={state.show} item={state.item} />
-
-        {/* 添加自定义教材 */}
-        <NModal
-          v-model:show={state.teachingStatus}
-          preset="card"
-          showIcon={false}
-          class={['modalTitle background', styles.teachingModal]}
-          title={'自定义教材'}
-          blockScroll={false}>
-          <AddTeaching onClose={() => (state.teachingStatus = false)} />
-        </NModal>
-        {showGuide.value ? <ShareResourcesGuide></ShareResourcesGuide> : null}
-      </>
-    );
-  }
-});
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import CardType from '/src/components/card-type';
+import Pagination from '/src/components/pagination';
+import SearchGroupResources from './search-group-resources';
+import { favorite, materialQueryPage } from '../../api';
+import { NModal, NSpin } from 'naive-ui';
+import TheEmpty from '/src/components/TheEmpty';
+import CardPreview from '/src/components/card-preview';
+import AddTeaching from '../../model/add-teaching';
+import ShareResourcesGuide from '@/custom-plugins/guide-page/shareResources-guide';
+
+export default defineComponent({
+  name: 'share-resources',
+  setup() {
+    const state = reactive({
+      searchWord: '',
+      loading: false,
+      pageTotal: 0,
+      pagination: {
+        page: 1,
+        rows: 20
+      },
+      searchGroup: {
+        type: 'MUSIC', //
+        name: '',
+        bookVersionId: null,
+        subjectId: null,
+        sourceType: 2
+      },
+      tableList: [] as any,
+      teachingStatus: false,
+      show: false,
+      item: {} as any
+    });
+    const showGuide = ref(false);
+    const SearchGroupResourcesRef = ref();
+    const getList = async () => {
+      try {
+        state.loading = true;
+        const { data } = await materialQueryPage({
+          ...state.searchGroup,
+          ...state.pagination
+        });
+        state.loading = false;
+        state.pageTotal = Number(data.total);
+        const tempRows = data.rows || [];
+        const temp: any = [];
+        tempRows.forEach((row: any) => {
+          temp.push({
+            id: row.id,
+            coverImg: row.coverImg,
+            type: row.type,
+            title: row.name,
+            isCollect: !!row.favoriteFlag,
+            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
+            refFlag: row.refFlag,
+            background: row.background,
+            content: row.content
+          });
+        });
+        state.tableList = temp || [];
+        setTimeout(() => {
+          showGuide.value = true;
+        }, 500);
+      } catch {
+        state.loading = false;
+      }
+    };
+
+    const onSearch = async (item: any) => {
+      state.pagination.page = 1;
+
+      const { subjectId, ...res } = item;
+      state.searchGroup = Object.assign(state.searchGroup, {
+        ...res,
+        musicalInstrumentId: subjectId,
+        subjectId: null
+      });
+
+      getList();
+    };
+
+    // 收藏
+    const onCollect = async (item: any) => {
+      try {
+        await favorite({
+          materialId: item.id,
+          favoriteFlag: item.isCollect ? 0 : 1,
+          type: item.type
+        });
+        item.isCollect = !item.isCollect;
+      } catch {
+        //
+      }
+    };
+
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <>
+        <SearchGroupResources
+          ref={SearchGroupResourcesRef}
+          onSearch={(item: any) => onSearch(item)}
+          onAdd={() => (state.teachingStatus = true)}
+        />
+        <NSpin v-model:show={state.loading} style={{ 'min-height': '50vh' }}>
+          <div class={styles.list}>
+            {state.tableList.map((item: any, index: number) => (
+              <div class={styles.itemWrap}>
+                <div class={styles.itemWrapBox}>
+                  {index == 0 ? (
+                    <CardType
+                      {...{ id: 'shareResources-1' }}
+                      item={item}
+                      disabledMouseHover={false}
+                      onClick={(val: any) => {
+                        if (val.type === 'IMG') return;
+                        state.show = true;
+                        state.item = val;
+                      }}
+                      onCollect={(item: any) => onCollect(item)}
+                    />
+                  ) : (
+                    <CardType
+                      item={item}
+                      disabledMouseHover={false}
+                      onClick={(val: any) => {
+                        if (val.type === 'IMG') return;
+                        state.show = true;
+                        state.item = val;
+                      }}
+                      onCollect={(item: any) => onCollect(item)}
+                    />
+                  )}
+                </div>
+              </div>
+            ))}
+
+            {!state.loading && state.tableList.length <= 0 && (
+              <TheEmpty
+                style={{ minHeight: '50vh' }}
+                description="暂无共享资源"
+              />
+            )}
+          </div>
+        </NSpin>
+
+        <Pagination
+          v-model:page={state.pagination.page}
+          v-model:pageSize={state.pagination.rows}
+          v-model:pageTotal={state.pageTotal}
+          onList={getList}
+        />
+
+        {/* 弹窗查看 */}
+        <CardPreview v-model:show={state.show} item={state.item} />
+
+        {/* 添加自定义教材 */}
+        <NModal
+          v-model:show={state.teachingStatus}
+          preset="card"
+          showIcon={false}
+          class={['modalTitle background', styles.teachingModal]}
+          title={'自定义教材'}
+          blockScroll={false}>
+          <AddTeaching onClose={() => (state.teachingStatus = false)} />
+        </NModal>
+        {showGuide.value ? <ShareResourcesGuide></ShareResourcesGuide> : null}
+      </>
+    );
+  }
+});

+ 1 - 0
src/views/prepare-lessons/components/directory-main/index.module.less

@@ -196,6 +196,7 @@
   cursor: pointer;
   border-radius: 10px;
   font-size: max(17px, 12Px);
+  font-weight: 600;
   margin-bottom: 8px;
 
   &.childItem:hover {

+ 73 - 68
src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx

@@ -791,80 +791,85 @@ export default defineComponent({
                     </NTooltip>
                   </NSpace>
 
-                  {item.list.length > 0 && (
-                    <Draggable
-                      v-model:modelValue={item.list}
-                      itemKey="id"
-                      componentData={{
-                        itemKey: 'id',
-                        tag: 'div',
-                        animation: 200,
-                        group: 'description',
-                        disabled: false
-                      }}
-                      class={styles.list}>
-                      {{
-                        item: (element: any) => {
-                          const item = element.element;
-                          return (
-                            <div
-                              data-id={item.id}
-                              class={[
-                                styles.itemWrap,
-                                styles.itemBlock,
-                                'row-nav'
-                              ]}>
-                              <div class={styles.itemWrapBox}>
-                                <CardType
-                                  class={[styles.itemContent]}
-                                  isShowCollect={false}
-                                  offShelf={item.removeFlag ? true : false}
-                                  // onOffShelf={() => onRemove(item)}
-                                  item={item}
-                                  disabledMouseHover={false}
-                                  onClick={() => {
-                                    if (item.type === 'IMG') return;
-                                    forms.show = true;
-                                    forms.item = item;
+                  {/* {item.list.length > 0 && ( */}
+                  <Draggable
+                    v-model:modelValue={item.list}
+                    itemKey="id"
+                    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+                    // @ts-ignore
+                    group="description"
+                    componentData={{
+                      itemKey: 'id',
+                      tag: 'div',
+                      animation: 200,
+                      pull: true,
+                      put: true,
+                      group: 'description'
+                      // disabled: false
+                    }}
+                    class={styles.list}>
+                    {{
+                      item: (element: any) => {
+                        const item = element.element;
+                        return (
+                          <div
+                            data-id={item.id}
+                            class={[
+                              styles.itemWrap,
+                              styles.itemBlock,
+                              'row-nav'
+                            ]}>
+                            <div class={styles.itemWrapBox}>
+                              <CardType
+                                class={[styles.itemContent]}
+                                isShowCollect={false}
+                                offShelf={item.removeFlag ? true : false}
+                                // onOffShelf={() => onRemove(item)}
+                                item={item}
+                                disabledMouseHover={false}
+                                onClick={() => {
+                                  if (item.type === 'IMG') return;
+                                  forms.show = true;
+                                  forms.item = item;
+                                }}
+                              />
+                              <div class={styles.itemOperation}>
+                                <img
+                                  src={iconDelete}
+                                  class={styles.iconDelete}
+                                  onClick={(e: MouseEvent) => {
+                                    e.stopPropagation();
+                                    onDelete(element.index, index);
                                   }}
                                 />
-                                <div class={styles.itemOperation}>
-                                  <img
-                                    src={iconDelete}
-                                    class={styles.iconDelete}
-                                    onClick={(e: MouseEvent) => {
-                                      e.stopPropagation();
-                                      onDelete(element.index, index);
-                                    }}
-                                  />
-                                </div>
                               </div>
                             </div>
-                          );
-                        },
-                        footer: () => (
-                          <div class={styles.itemWrap}>
-                            <div class={styles.itemWrapBox}>
-                              <div
-                                class={[
-                                  styles.itemContent,
-                                  styles.addMusicItem,
-                                  'handle'
-                                ]}
-                                onClick={() => {
-                                  forms.addOtherSource = true;
-                                  forms.addOtherIndex = index;
-                                }}>
-                                <img src={iconAddMusic} />
+                          </div>
+                        );
+                      },
+                      footer: () => (
+                        <div class={styles.itemWrap}>
+                          <div class={styles.itemWrapBox}>
+                            <div
+                              class={[
+                                styles.itemContent,
+                                styles.addMusicItem,
+                                'handle'
+                              ]}
+                              onClick={() => {
+                                forms.addOtherSource = true;
+                                forms.addOtherIndex = index;
+                              }}>
+                              <img src={iconAddMusic} />
 
-                                <p class={styles.addMusicName}>添加资源</p>
-                              </div>
+                              <p class={styles.addMusicName}>添加资源</p>
                             </div>
                           </div>
-                        )
-                      }}
-                    </Draggable>
-                  )}
+                        </div>
+                      )
+                    }}
+                  </Draggable>
+                  {/* )}
                   {item.list <= 0 && (
                     <div class={styles.list}>
                       <div class={styles.itemWrap}>
@@ -886,7 +891,7 @@ export default defineComponent({
                         </div>
                       </div>
                     </div>
-                  )}
+                  )} */}
                 </div>
               ))}
 

+ 11 - 12
src/views/prepare-lessons/components/lesson-main/train/index.tsx

@@ -361,22 +361,21 @@ export default defineComponent({
               <NButton
                 bordered={false}
                 type="default"
-                disabled={forms.trainList.length <= 0}
                 onClick={() => {
                   if (!forms.title) {
                     message.error('请输入标题');
                     return;
                   }
-                  let count = 0;
-                  forms.trainList.forEach((item: any) => {
-                    if (!item.removeFlag) {
-                      count++;
-                    }
-                  });
-                  if (count <= 0) {
-                    message.error('作业内容不能为空');
-                    return;
-                  }
+                  // let count = 0;
+                  // forms.trainList.forEach((item: any) => {
+                  //   if (!item.removeFlag) {
+                  //     count++;
+                  //   }
+                  // });
+                  // if (count <= 0) {
+                  //   message.error('作业内容不能为空');
+                  //   return;
+                  // }
 
                   forms.preSaveVisiable = true;
                 }}
@@ -436,7 +435,7 @@ export default defineComponent({
               }}
               onDrop={(e: any) => {
                 let dropItem = e.dataTransfer.getData('text');
-                console.log(dropItem, 'dropItem', dropItem);
+                // console.log(dropItem, 'dropItem', dropItem);
                 dropItem = dropItem ? JSON.parse(dropItem) : {};
                 // 判断是否有数据
                 if (dropItem.id) {

+ 1 - 0
src/views/prepare-lessons/components/resource-main/components/resource-item/index.tsx

@@ -94,6 +94,7 @@ export default defineComponent({
             isCollect: !!row.favoriteFlag,
             refFlag: row.refFlag,
             isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
+            background: row.background,
             content: row.content
             // exist: index !== -1 ? true : false // 是否存在
           });

+ 360 - 369
src/views/prepare-lessons/components/resource-main/components/select-music/index.tsx

@@ -1,369 +1,360 @@
-import {
-  PropType,
-  defineComponent,
-  onMounted,
-  reactive,
-  watch,
-  toRef
-} from 'vue';
-import ResourceSearchGroup from './resource-search-group';
-import { NModal, NScrollbar, NSpin } from 'naive-ui';
-import styles from './index.module.less';
-import CardType from '/src/components/card-type';
-import TheEmpty from '/src/components/TheEmpty';
-import { useThrottleFn } from '@vueuse/core';
-import { usePrepareStore } from '/src/store/modules/prepareLessons';
-import { musicSheetPage } from '/src/views/prepare-lessons/api';
-import TrainUpdate from '/src/views/attend-class/model/train-update';
-import requestOrigin from 'umi-request';
-import CardPreview from '/src/components/card-preview';
-import { evaluateDifficult } from '/src/utils/contants';
-import { eventGlobal } from '/src/utils';
-import { favorite, materialQueryPage } from '/src/views/natural-resources/api';
-import useDrag from '@/hooks/useDrag';
-import Dragbom from '@/hooks/useDrag/dragbom';
-import { useUserStore } from '@/store/modules/users';
-
-const formatType = (type: string) => {
-  if (type === 'sahreMusic') {
-    return 2;
-  } else if (type === 'myMusic') {
-    return 3;
-  } else if (type === 'collectMusic') {
-    return 4;
-  }
-};
-
-export const typeFormat = (trainingType: string, configJson: any) => {
-  let tList: string[] = [];
-
-  if (trainingType === 'EVALUATION') {
-    tList = [
-      `${evaluateDifficult[configJson.evaluateDifficult]}`,
-      configJson.practiceChapterBegin || configJson.practiceChapterEnd
-        ? `${configJson.practiceChapterBegin}-${configJson.practiceChapterEnd}小节`
-        : '全部小节',
-      // `速度${configJson.evaluateSpeed}`,
-      `${configJson.trainingTimes}分合格`
-    ];
-  } else {
-    tList = [
-      `${configJson.practiceChapterBegin}-${configJson.practiceChapterEnd}小节`,
-      `速度${configJson.practiceSpeed}`,
-      `${configJson.trainingTimes}分钟`
-    ];
-  }
-  return tList;
-};
-
-export default defineComponent({
-  name: 'share-resources',
-  props: {
-    type: {
-      type: String as PropType<'myMusic' | 'sahreMusic' | 'collectMusic'>,
-      default: 'myMusic'
-    },
-    /** 类型 */
-    cardType: {
-      type: String as PropType<'' | 'homerowk-record' | 'prepare'>,
-      default: ''
-    },
-    from: {
-      // 来自哪里
-      type: String,
-      default: ''
-    }
-  },
-  setup(props) {
-    const prepareStore = usePrepareStore();
-    const state = reactive({
-      loading: false,
-      finshed: false, // 是否加载完
-      pagination: {
-        page: 1,
-        rows: 20
-      },
-      searchGroup: {
-        name: '',
-        type: 'MUSIC', //
-        musicSheetCategoriesId: '',
-        musicalInstrumentId: '',
-        sourceType: formatType(props.type),
-        status: 1,
-        versionFlag: false,
-        musicSubject: null
-      },
-      tableList: [] as any,
-      editStatus: false,
-      editItem: {} as any,
-      show: false,
-      item: {} as any
-    });
-    const getList = async () => {
-      try {
-        if (
-          !prepareStore.getSubjectId &&
-          !['homerowk-record', 'prepare'].includes(props.cardType)
-        )
-          return;
-        if (state.pagination.page === 1) {
-          state.loading = true;
-        }
-        // const { data } = await musicSheetPage({
-        //   ...state.searchGroup,
-        //   ...state.pagination,
-        //   musicSubject: prepareStore.getSubjectId
-        // });
-        const { data } = await materialQueryPage({
-          ...state.searchGroup,
-          ...state.pagination
-          // subjectId: prepareStore.getSubjectId
-        });
-        state.loading = false;
-        const tempRows = data.rows || [];
-        const temp: any = [];
-
-        tempRows.forEach((row: any) => {
-          const index = prepareStore.getTrainList.findIndex(
-            (course: any) => course.musicId === row.id
-          );
-          temp.push({
-            id: row.id,
-            coverImg: row.coverImg || row.musicSvg,
-            type: 'MUSIC',
-            title: row.name,
-            isCollect: !!row.favoriteFlag,
-            refFlag: row.refFlag,
-            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
-            content: row.id,
-            xmlFileUrl: row.xmlFileUrl
-            // exist: index !== -1 ? true : false // 是否存在
-          });
-        });
-        state.tableList.push(...temp);
-        state.finshed = data.pages <= data.current ? true : false;
-      } catch {
-        state.loading = false;
-      }
-    };
-
-    const onSearch = async (item: any) => {
-      state.pagination.page = 1;
-      state.tableList = [];
-      state.searchGroup = Object.assign(state.searchGroup, item);
-      getList();
-    };
-
-    // 声部变化时
-    watch(
-      () => prepareStore.getSubjectId,
-      () => {
-        onSearch(state.searchGroup);
-      }
-    );
-    // watch(
-    //   () => prepareStore.trainList,
-    //   () => {
-    //     state.tableList.forEach((item: any) => {
-    //       const index = prepareStore.getTrainList.findIndex(
-    //         (course: any) => course.musicId === item.id
-    //       );
-    //       item.exist = index !== -1 ? true : false; // 是否存在
-    //     });
-    //   },
-    //   {
-    //     deep: true,
-    //     immediate: true
-    //   }
-    // );
-
-    const throttledFn = useThrottleFn(() => {
-      state.pagination.page = state.pagination.page + 1;
-      getList();
-    }, 500);
-
-    // 添加资源
-    const onAdd = async (item: any) => {
-      let xmlStatus = 'init';
-      // 第一个声部小节
-      let firstMeasures: any = null;
-      try {
-        // 获取文件
-        const res = await requestOrigin.get(item.xmlFileUrl, {
-          mode: 'cors'
-        });
-        const xmlParse = new DOMParser().parseFromString(res, 'text/xml');
-        /**
-         * 妙极客的曲子,systems中含有part标签,需过滤掉systems标签,xmlParse.querySelectorAll('systems')
-         */
-        const systems = Array.from(xmlParse?.querySelectorAll('systems')) || [];
-        systems.forEach((system: any) => {
-          const childs = Array.from(system?.querySelectorAll('system')) || [];
-          childs.forEach((child: any) => {
-            system?.removeChild(child);
-          })
-        });    
-        const parts = xmlParse.getElementsByTagName('part');
-        firstMeasures = parts[0]?.getElementsByTagName('measure');
-        xmlStatus = 'success';
-      } catch (error) {
-        xmlStatus = 'error';
-      }
-
-      // 判断读取小节数
-      if (xmlStatus == 'success') {
-        item.practiceChapterMax = firstMeasures.length;
-      } else {
-        item.practiceChapterMax = 0;
-      }
-      item.coursewareKnowledgeDetailId = prepareStore.getSelectKey;
-      item.subjectId = prepareStore.getSubjectId;
-
-      state.editItem = item;
-      state.editStatus = true;
-    };
-
-    // 收藏
-    const onCollect = async (item: any) => {
-      try {
-        await favorite({
-          materialId: item.id,
-          favoriteFlag: item.isCollect ? 0 : 1,
-          type: item.type
-        });
-        item.isCollect = !item.isCollect;
-      } catch {
-        //
-      }
-    };
-
-    onMounted(() => {
-      getList();
-
-      eventGlobal.on('onTrainDragItem', (item: any, point?: any) => {
-        onAdd(item);
-      });
-    });
-    // 弹窗拖动
-    // 作业设置
-    let workSetingBoxDragData: any;
-    let workSetingBoxClass: string;
-    if (props.from === 'class') {
-      const users = useUserStore();
-      workSetingBoxClass = 'workSetingBoxClass_drag';
-      workSetingBoxDragData = useDrag(
-        [
-          `${workSetingBoxClass}>.n-card-header`,
-          `${workSetingBoxClass} .bom_drag`
-        ],
-        workSetingBoxClass,
-        toRef(state, 'editStatus'),
-        users.info.id
-      );
-    }
-    return () => (
-      <div>
-        <ResourceSearchGroup
-          type={props.type}
-          onSearch={(item: any) => onSearch(item)}
-        />
-        <NScrollbar
-          class={[
-            styles.listContainer,
-            props.type === 'myMusic' ? styles.listNoMusic : ''
-          ]}
-          onScroll={(e: any) => {
-            const clientHeight = e.target?.clientHeight;
-            const scrollTop = e.target?.scrollTop;
-            const scrollHeight = e.target?.scrollHeight;
-            // 是否到底,是否加载完
-            if (
-              clientHeight + scrollTop + 20 >= scrollHeight &&
-              !state.finshed &&
-              !state.loading
-            ) {
-              throttledFn();
-            }
-          }}>
-          <NSpin show={state.loading} size={'small'}>
-            <div
-              class={[
-                styles.listSection,
-                !state.loading && state.tableList.length <= 0
-                  ? styles.emptySection
-                  : ''
-              ]}>
-              {state.tableList.length > 0 && (
-                <div class={styles.list}>
-                  {state.tableList.map((item: any) => (
-                    <CardType
-                      isShowAdd
-                      item={item}
-                      draggable
-                      isShowCollect
-                      // isShowAddDisabled={!prepareStore.getIsEditTrain}
-                      disabledMouseHover={false}
-                      onClick={() => {
-                        if (item.type === 'IMG') return;
-                        state.show = true;
-                        state.item = item;
-                      }}
-                      onAdd={(child: any) => onAdd(child)}
-                      onCollect={(item: any) => onCollect(item)}
-                    />
-                  ))}
-                </div>
-              )}
-              {!state.loading && state.tableList.length <= 0 && <TheEmpty />}
-            </div>
-          </NSpin>
-        </NScrollbar>
-
-        {/* 弹窗查看 */}
-        <CardPreview
-          from={props.from}
-          v-model:show={state.show}
-          item={state.item}
-        />
-
-        <NModal
-          v-model:show={state.editStatus}
-          style={
-            props.from === 'class' ? workSetingBoxDragData.styleDrag.value : {}
-          }
-          class={[
-            'modalTitle background',
-            styles.trainEditModal,
-            workSetingBoxClass
-          ]}
-          preset="card"
-          title="作业设置">
-          <TrainUpdate
-            item={state.editItem}
-            type="homework"
-            onClose={() => (state.editStatus = false)}
-            onConfirm={(item: any) => {
-              // state.editItem = {};
-              // prepareStore.setIsAddTrain(true);
-
-              const tList = typeFormat(
-                item.trainingType,
-                item.trainingConfigJson
-              );
-              //
-              const train = {
-                ...item,
-                id: null,
-                musicName: state.editItem.title,
-                typeList: tList
-              };
-
-              eventGlobal.emit('onTrainAddItem', train);
-            }}
-          />
-          {props.from === 'class' && <Dragbom></Dragbom>}
-        </NModal>
-      </div>
-    );
-  }
-});
+import {
+  PropType,
+  defineComponent,
+  onMounted,
+  reactive,
+  watch,
+  toRef
+} from 'vue';
+import ResourceSearchGroup from './resource-search-group';
+import { NModal, NScrollbar, NSpin } from 'naive-ui';
+import styles from './index.module.less';
+import CardType from '/src/components/card-type';
+import TheEmpty from '/src/components/TheEmpty';
+import { useThrottleFn } from '@vueuse/core';
+import { usePrepareStore } from '/src/store/modules/prepareLessons';
+import { musicSheetPage } from '/src/views/prepare-lessons/api';
+import TrainUpdate from '/src/views/attend-class/model/train-update';
+import requestOrigin from 'umi-request';
+import CardPreview from '/src/components/card-preview';
+import { evaluateDifficult } from '/src/utils/contants';
+import { eventGlobal } from '/src/utils';
+import { favorite, materialQueryPage } from '/src/views/natural-resources/api';
+import useDrag from '@/hooks/useDrag';
+import Dragbom from '@/hooks/useDrag/dragbom';
+import { useUserStore } from '@/store/modules/users';
+
+const formatType = (type: string) => {
+  if (type === 'sahreMusic') {
+    return 2;
+  } else if (type === 'myMusic') {
+    return 3;
+  } else if (type === 'collectMusic') {
+    return 4;
+  }
+};
+
+export const typeFormat = (trainingType: string, configJson: any) => {
+  let tList: string[] = [];
+
+  if (trainingType === 'EVALUATION') {
+    tList = [
+      `${evaluateDifficult[configJson.evaluateDifficult]}`,
+      configJson.practiceChapterBegin || configJson.practiceChapterEnd
+        ? `${configJson.practiceChapterBegin}-${configJson.practiceChapterEnd}小节`
+        : '全部小节',
+      // `速度${configJson.evaluateSpeed}`,
+      `${configJson.trainingTimes}分合格`
+    ];
+  } else {
+    tList = [
+      `${configJson.practiceChapterBegin}-${configJson.practiceChapterEnd}小节`,
+      `速度${configJson.practiceSpeed}`,
+      `${configJson.trainingTimes}分钟`
+    ];
+  }
+  return tList;
+};
+
+export default defineComponent({
+  name: 'share-resources',
+  props: {
+    type: {
+      type: String as PropType<'myMusic' | 'sahreMusic' | 'collectMusic'>,
+      default: 'myMusic'
+    },
+    /** 类型 */
+    cardType: {
+      type: String as PropType<'' | 'homerowk-record' | 'prepare'>,
+      default: ''
+    },
+    from: {
+      // 来自哪里
+      type: String,
+      default: ''
+    }
+  },
+  setup(props) {
+    const prepareStore = usePrepareStore();
+    const state = reactive({
+      loading: false,
+      finshed: false, // 是否加载完
+      pagination: {
+        page: 1,
+        rows: 20
+      },
+      searchGroup: {
+        name: '',
+        type: 'MUSIC', //
+        musicSheetCategoriesId: '',
+        musicalInstrumentId: '',
+        sourceType: formatType(props.type),
+        status: 1,
+        versionFlag: false,
+        musicSubject: null
+      },
+      tableList: [] as any,
+      editStatus: false,
+      editItem: {} as any,
+      show: false,
+      item: {} as any
+    });
+    const getList = async () => {
+      try {
+        if (
+          !prepareStore.getSubjectId &&
+          !['homerowk-record', 'prepare'].includes(props.cardType)
+        )
+          return;
+        if (state.pagination.page === 1) {
+          state.loading = true;
+        }
+        // const { data } = await musicSheetPage({
+        //   ...state.searchGroup,
+        //   ...state.pagination,
+        //   musicSubject: prepareStore.getSubjectId
+        // });
+        const { data } = await materialQueryPage({
+          ...state.searchGroup,
+          ...state.pagination
+          // subjectId: prepareStore.getSubjectId
+        });
+        state.loading = false;
+        const tempRows = data.rows || [];
+        const temp: any = [];
+
+        tempRows.forEach((row: any) => {
+          const index = prepareStore.getTrainList.findIndex(
+            (course: any) => course.musicId === row.id
+          );
+          temp.push({
+            id: row.id,
+            coverImg: row.coverImg || row.musicSvg,
+            type: 'MUSIC',
+            title: row.name,
+            isCollect: !!row.favoriteFlag,
+            refFlag: row.refFlag,
+            isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
+            content: row.id,
+            background: row.background,
+            xmlFileUrl: row.xmlFileUrl
+            // exist: index !== -1 ? true : false // 是否存在
+          });
+        });
+        state.tableList.push(...temp);
+        state.finshed = data.pages <= data.current ? true : false;
+      } catch {
+        state.loading = false;
+      }
+    };
+
+    const onSearch = async (item: any) => {
+      state.pagination.page = 1;
+      state.tableList = [];
+      state.searchGroup = Object.assign(state.searchGroup, item);
+      getList();
+    };
+
+    // 声部变化时
+    watch(
+      () => prepareStore.getSubjectId,
+      () => {
+        onSearch(state.searchGroup);
+      }
+    );
+    // watch(
+    //   () => prepareStore.trainList,
+    //   () => {
+    //     state.tableList.forEach((item: any) => {
+    //       const index = prepareStore.getTrainList.findIndex(
+    //         (course: any) => course.musicId === item.id
+    //       );
+    //       item.exist = index !== -1 ? true : false; // 是否存在
+    //     });
+    //   },
+    //   {
+    //     deep: true,
+    //     immediate: true
+    //   }
+    // );
+
+    const throttledFn = useThrottleFn(() => {
+      state.pagination.page = state.pagination.page + 1;
+      getList();
+    }, 500);
+
+    // 添加资源
+    const onAdd = async (item: any) => {
+      let xmlStatus = 'init';
+      // 第一个声部小节
+      let firstMeasures: any = null;
+      try {
+        // 获取文件
+        const res = await requestOrigin.get(item.xmlFileUrl, {
+          mode: 'cors'
+        });
+        const xmlParse = new DOMParser().parseFromString(res, 'text/xml');
+        const parts = xmlParse.getElementsByTagName('part');
+        firstMeasures = parts[0]?.getElementsByTagName('measure');
+        xmlStatus = 'success';
+      } catch (error) {
+        xmlStatus = 'error';
+      }
+
+      // 判断读取小节数
+      if (xmlStatus == 'success') {
+        item.practiceChapterMax = firstMeasures.length;
+      } else {
+        item.practiceChapterMax = 0;
+      }
+      item.coursewareKnowledgeDetailId = prepareStore.getSelectKey;
+      item.subjectId = prepareStore.getSubjectId;
+
+      state.editItem = item;
+      state.editStatus = true;
+    };
+
+    // 收藏
+    const onCollect = async (item: any) => {
+      try {
+        await favorite({
+          materialId: item.id,
+          favoriteFlag: item.isCollect ? 0 : 1,
+          type: item.type
+        });
+        item.isCollect = !item.isCollect;
+      } catch {
+        //
+      }
+    };
+
+    onMounted(() => {
+      getList();
+
+      eventGlobal.on('onTrainDragItem', (item: any, point?: any) => {
+        onAdd(item);
+      });
+    });
+    // 弹窗拖动
+    // 作业设置
+    let workSetingBoxDragData: any;
+    let workSetingBoxClass: string;
+    if (props.from === 'class') {
+      const users = useUserStore();
+      workSetingBoxClass = 'workSetingBoxClass_drag';
+      workSetingBoxDragData = useDrag(
+        [
+          `${workSetingBoxClass}>.n-card-header`,
+          `${workSetingBoxClass} .bom_drag`
+        ],
+        workSetingBoxClass,
+        toRef(state, 'editStatus'),
+        users.info.id
+      );
+    }
+    return () => (
+      <div>
+        <ResourceSearchGroup
+          type={props.type}
+          onSearch={(item: any) => onSearch(item)}
+        />
+        <NScrollbar
+          class={[
+            styles.listContainer,
+            props.type === 'myMusic' ? styles.listNoMusic : ''
+          ]}
+          onScroll={(e: any) => {
+            const clientHeight = e.target?.clientHeight;
+            const scrollTop = e.target?.scrollTop;
+            const scrollHeight = e.target?.scrollHeight;
+            // 是否到底,是否加载完
+            if (
+              clientHeight + scrollTop + 20 >= scrollHeight &&
+              !state.finshed &&
+              !state.loading
+            ) {
+              throttledFn();
+            }
+          }}>
+          <NSpin show={state.loading} size={'small'}>
+            <div
+              class={[
+                styles.listSection,
+                !state.loading && state.tableList.length <= 0
+                  ? styles.emptySection
+                  : ''
+              ]}>
+              {state.tableList.length > 0 && (
+                <div class={styles.list}>
+                  {state.tableList.map((item: any) => (
+                    <CardType
+                      isShowAdd
+                      item={item}
+                      draggable
+                      isShowCollect
+                      // isShowAddDisabled={!prepareStore.getIsEditTrain}
+                      disabledMouseHover={false}
+                      onClick={() => {
+                        if (item.type === 'IMG') return;
+                        state.show = true;
+                        state.item = item;
+                      }}
+                      onAdd={(child: any) => onAdd(child)}
+                      onCollect={(item: any) => onCollect(item)}
+                    />
+                  ))}
+                </div>
+              )}
+              {!state.loading && state.tableList.length <= 0 && <TheEmpty />}
+            </div>
+          </NSpin>
+        </NScrollbar>
+
+        {/* 弹窗查看 */}
+        <CardPreview
+          from={props.from}
+          v-model:show={state.show}
+          item={state.item}
+        />
+
+        <NModal
+          v-model:show={state.editStatus}
+          style={
+            props.from === 'class' ? workSetingBoxDragData.styleDrag.value : {}
+          }
+          class={[
+            'modalTitle background',
+            styles.trainEditModal,
+            workSetingBoxClass
+          ]}
+          preset="card"
+          title="作业设置">
+          <TrainUpdate
+            item={state.editItem}
+            type="homework"
+            onClose={() => (state.editStatus = false)}
+            onConfirm={(item: any) => {
+              // state.editItem = {};
+              // prepareStore.setIsAddTrain(true);
+
+              const tList = typeFormat(
+                item.trainingType,
+                item.trainingConfigJson
+              );
+              //
+              const train = {
+                ...item,
+                id: null,
+                musicName: state.editItem.title,
+                typeList: tList
+              };
+
+              eventGlobal.emit('onTrainAddItem', train);
+            }}
+          />
+          {props.from === 'class' && <Dragbom></Dragbom>}
+        </NModal>
+      </div>
+    );
+  }
+});

+ 1 - 0
src/views/prepare-lessons/model/select-music/select-item/index.tsx

@@ -86,6 +86,7 @@ export default defineComponent({
             refFlag: row.refFlag,
             content: row.id,
             xmlFileUrl: row.xmlFileUrl,
+            background: row.background,
             exist: index !== -1 ? true : false // 是否存在
           });
         });

+ 1 - 0
src/views/prepare-lessons/model/select-resources/select-item/index.tsx

@@ -102,6 +102,7 @@ export default defineComponent({
             isCollect: !!row.favoriteFlag,
             isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
             refFlag: row.refFlag,
+            background: row.background,
             content: row.content
           });
         });