瀏覽代碼

Merge branch 'iteration-20240520' into jenkins-dev

lex 1 年之前
父節點
當前提交
c10c4e5af3

+ 72 - 73
src/components/CBreadcrumb/index.tsx

@@ -1,73 +1,72 @@
-import { defineComponent, ref, watch } from 'vue';
-import styles from './index.module.less';
-import {
-  NIcon,
-  NImage,
-  NDatePicker,
-  NSelect,
-  NSpace,
-  NBreadcrumb,
-  NBreadcrumbItem
-} from 'naive-ui';
-import icon_back from './images/icon_back.png';
-import icon_separator from './images/icon_separator.png';
-import activeArrow from './images/activeArrow.png';
-import arrow from './images/arrow.png';
-import { useRoute, useRouter } from 'vue-router';
-export default defineComponent({
-  props: {
-    list: {
-      type: Array,
-      required: true,
-      default: [] as any
-    }
-  },
-  name: 'CBreadcrumb',
-  setup(props, { emit, attrs }) {
-    const router = useRouter();
-    const route = useRoute();
-    const lastNum = ref(props.list?.length || 0);
-    const list = ref(props.list as any);
-    watch(
-      () => props.list,
-      (value: any) => {
-        console.log('list', value);
-        list.value = value;
-      },
-      { deep: true, immediate: true }
-    );
-    return () => (
-      <>
-        <div class={styles.CBreadcrumb}>
-          <NSpace align="center" wrapItem={false} size={16}>
-            <img
-              style={{ cursor: 'pointer' }}
-              src={icon_back}
-              class={styles.icon_back}
-              onClick={() => router.go(-1)}
-            />
-            <NBreadcrumb separator="">
-              {list.value &&
-                list.value.map((item: any, index: number) => (
-                  <>
-                    <NBreadcrumbItem
-                      onClick={() =>
-                        router.push({
-                          path: item.path,
-                          query: { ...route.query }
-                        })
-                      }>
-                      {item.name}
-                    </NBreadcrumbItem>
-                    {index != lastNum.value - 1 ? (
-                      <img class={styles.separator} src={icon_separator} />
-                    ) : null}
-                  </>
-                ))}
-            </NBreadcrumb>
-          </NSpace>
-        </div>
-      </>
-    );
-  }
-});
+import { defineComponent, ref, watch } from 'vue';
+import styles from './index.module.less';
+import {
+  NIcon,
+  NImage,
+  NDatePicker,
+  NSelect,
+  NSpace,
+  NBreadcrumb,
+  NBreadcrumbItem
+} from 'naive-ui';
+import icon_back from './images/icon_back.png';
+import icon_separator from './images/icon_separator.png';
+import activeArrow from './images/activeArrow.png';
+import arrow from './images/arrow.png';
+import { useRoute, useRouter } from 'vue-router';
+export default defineComponent({
+  props: {
+    list: {
+      type: Array,
+      required: true,
+      default: [] as any
+    }
+  },
+  name: 'CBreadcrumb',
+  setup(props, { emit, attrs }) {
+    const router = useRouter();
+    const route = useRoute();
+    const lastNum = ref(props.list?.length || 0);
+    const list = ref(props.list as any);
+    watch(
+      () => props.list,
+      (value: any) => {
+        list.value = value;
+      },
+      { deep: true, immediate: true }
+    );
+    return () => (
+      <>
+        <div class={styles.CBreadcrumb}>
+          <NSpace align="center" wrapItem={false} size={16}>
+            <img
+              style={{ cursor: 'pointer' }}
+              src={icon_back}
+              class={styles.icon_back}
+              onClick={() => router.go(-1)}
+            />
+            <NBreadcrumb separator="">
+              {list.value &&
+                list.value.map((item: any, index: number) => (
+                  <>
+                    <NBreadcrumbItem
+                      onClick={() =>
+                        router.push({
+                          path: item.path,
+                          query: { ...route.query }
+                        })
+                      }>
+                      {item.name}
+                    </NBreadcrumbItem>
+                    {index != lastNum.value - 1 ? (
+                      <img class={styles.separator} src={icon_separator} />
+                    ) : null}
+                  </>
+                ))}
+            </NBreadcrumb>
+          </NSpace>
+        </div>
+      </>
+    );
+  }
+});

+ 7 - 2
src/components/card-preview/index.tsx

@@ -50,7 +50,6 @@ export default defineComponent({
     const show = toRef(props.show);
     const item = toRef(props.item);
     const pptLoading = ref(true);
-
     watch(
       () => props.show,
       () => {
@@ -62,6 +61,8 @@ export default defineComponent({
       () => props.item,
       () => {
         item.value = props.item;
+
+        console.log(item.value, 'item.value  value');
       }
     );
     // 拖动
@@ -80,6 +81,7 @@ export default defineComponent({
         users.info.id
       );
     }
+
     return () => (
       <>
         <NModal
@@ -107,7 +109,10 @@ export default defineComponent({
           blockScroll={false}>
           {item.value.type === 'VIDEO' && (
             <VideoModal
-              title={item.value.title}
+              title={
+                item.value.title +
+                (props.item.studentName ? '-' + props.item.studentName : '')
+              }
               poster={item.value.url}
               src={item.value.content}
               isDownload={props.isDownload}

+ 41 - 9
src/components/card-preview/song-modal/index.module.less

@@ -47,7 +47,7 @@
   background: rgba(0, 0, 0, 0.6);
   backdrop-filter: blur(26px);
   height: 80px;
-  padding: 0 26px 0 26px !important;
+  padding: 0 24px 0 24px !important;
   transition: all 0.3s;
   display: flex;
   align-items: center;
@@ -56,7 +56,7 @@
     display: flex;
     justify-content: space-between;
     color: #fff;
-    padding: 4px 12px 4px;
+    padding: 4px 12px 4px 18px;
     font-size: 24px;
     font-weight: 600;
     line-height: 33px;
@@ -112,8 +112,8 @@
     height: 36px;
     background-color: transparent;
     cursor: pointer;
-    margin-left: 22px;
-    margin-right: 10px;
+    margin-left: 12px;
+    margin-right: 12px;
 
     &>img {
       width: 100%;
@@ -121,16 +121,48 @@
     }
   }
 
+  .actionBtn,
+  .actionBtnSpeed,
+  .iconReplay {
+    width: 48px;
+    height: 48px;
+    flex-shrink: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 6px;
+
+    &>img {
+      width: 36px;
+      height: 36px;
+    }
+
+    &:hover {
+      transition: background .1s ease;
+      background-color: rgba(255, 255, 255, 0.15);
+    }
+  }
+
   .iconDownload {
-    width: 36px;
-    height: 36px;
-    margin-left: 22px;
+    width: 48px;
+    height: 48px;
+    margin-left: 14px;
+    border-radius: 6px;
     background-color: transparent;
     cursor: pointer;
+    transition: background .1s ease;
+    display: flex;
+    align-items: center;
+    justify-content: center;
 
     &>img {
-      width: 100%;
-      height: 100%;
+      width: 36px;
+      height: 36px;
+    }
+
+    &:hover {
+      transition: background .1s ease;
+      background-color: rgba(255, 255, 255, 0.15);
     }
   }
 }

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

@@ -1,9 +1,16 @@
-import { defineComponent, reactive, ref, nextTick } from 'vue';
+import {
+  defineComponent,
+  reactive,
+  ref,
+  nextTick,
+  onMounted,
+  onUnmounted
+} from 'vue';
 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 iconPreviewDownload from '@views/attend-class/image/icon-preivew-download2.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';
@@ -118,12 +125,15 @@ export default defineComponent({
         return;
       }
       const fileUrl = props.item.content;
-      const filename = props.item.title;
+      const filename =
+        props.item.title +
+        (props.item.studentName ? '-' + props.item.studentName : '');
+      const sfixx = fileUrl.substring(fileUrl.lastIndexOf('.'));
       // 发起Fetch请求
       fetch(fileUrl)
         .then(response => response.blob())
         .then(blob => {
-          saveAs(blob, filename || new Date().getTime() + '.mp3');
+          saveAs(blob, (filename || new Date().getTime()) + sfixx);
         })
         .catch(() => {
           message.error('下载失败');
@@ -171,13 +181,11 @@ export default defineComponent({
       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);
       }
@@ -200,6 +208,23 @@ export default defineComponent({
       }, 3000);
     };
 
+    const onFullScreenChange = () => {
+      if (document.fullscreenElement) {
+        audioForms.isFullScreen = true;
+      } else {
+        audioForms.isFullScreen = false;
+      }
+    };
+
+    onMounted(() => {
+      console.log(props.item, 'props.item');
+      window.addEventListener('fullscreenchange', onFullScreenChange);
+    });
+
+    onUnmounted(() => {
+      window.removeEventListener('fullscreenchange', onFullScreenChange);
+    });
+
     return () => (
       <div class={styles.audioWrap} id={videoId} onClick={setModelOpen2}>
         <div class={styles.audioContainer}>

+ 45 - 13
src/components/card-preview/video-modal/index.module.less

@@ -33,14 +33,14 @@
 
     :global {
       .video-js .vjs-tech {
-        object-fit: cover;
+        // object-fit: cover;
       }
     }
   }
 
 
   .controls {
-    border-radius: 0 0 16px 16px !important;
+    // border-radius: 0 0 16px 16px !important;
     position: absolute;
     bottom: 0;
     left: 0;
@@ -58,7 +58,7 @@
       display: flex;
       justify-content: space-between;
       color: #fff;
-      padding: 4px 0 4px 12px;
+      padding: 4px 12px 4px 26px;
       font-size: max(24px, 14Px);
       font-weight: 600;
       line-height: 33px;
@@ -130,7 +130,7 @@
       height: 36px;
       background-color: transparent;
       cursor: pointer;
-      margin: 0 22px;
+      margin: 0 14px 0 12px;
 
       &>img {
         width: 100%;
@@ -138,16 +138,48 @@
       }
     }
 
+    .actionBtn,
+    .actionBtnSpeed,
+    .iconReplay {
+      width: 48px;
+      height: 48px;
+      flex-shrink: 0;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border-radius: 6px;
+
+      &>img {
+        width: 36px;
+        height: 36px;
+      }
+
+      &:hover {
+        transition: background .1s ease;
+        background-color: rgba(255, 255, 255, 0.15);
+      }
+    }
+
     .iconDownload {
-      width: 36px;
-      height: 36px;
-      margin-left: 22px;
+      width: 48px;
+      height: 48px;
+      margin-left: 14px;
+      border-radius: 6px;
       background-color: transparent;
       cursor: pointer;
+      transition: background .1s ease;
+      display: flex;
+      align-items: center;
+      justify-content: center;
 
       &>img {
-        width: 100%;
-        height: 100%;
+        width: 36px;
+        height: 36px;
+      }
+
+      &:hover {
+        transition: background .1s ease;
+        background-color: rgba(255, 255, 255, 0.15);
       }
     }
   }
@@ -178,8 +210,8 @@
 .sliderPopup {
   position: absolute;
   z-index: 9999;
-  left: -13px;
-  bottom: 35px;
+  left: -8px;
+  bottom: 55px;
   display: flex;
   align-items: center;
   flex-direction: column;
@@ -213,11 +245,11 @@
     background: #FFFFFF;
     box-shadow: 0 2px 4px 0px rgba(102, 102, 102, 0.77);
     border-radius: 14px;
-    font-size: 14Px;
+    font-size: 13Px;
     font-weight: 500;
     height: 22Px;
     color: #198CFE;
-    min-width: 36px;
+    min-width: 42px;
     text-align: center;
     vertical-align: text-bottom;
 

+ 30 - 5
src/components/card-preview/video-modal/index.tsx

@@ -1,4 +1,11 @@
-import { defineComponent, nextTick, onMounted, reactive, toRefs } from 'vue';
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  onUnmounted,
+  reactive,
+  toRefs
+} from 'vue';
 // import 'plyr/dist/plyr.css';
 // import Plyr from 'plyr';
 import { ref } from 'vue';
@@ -8,7 +15,7 @@ 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 iconPreviewDownload from '@views/attend-class/image/icon-preivew-download2.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';
@@ -194,25 +201,36 @@ export default defineComponent({
       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);
       }
     };
 
+    const onFullScreenChange = () => {
+      if (document.fullscreenElement) {
+        videoFroms.isFullScreen = true;
+      } else {
+        videoFroms.isFullScreen = false;
+      }
+    };
+
     onMounted(() => {
       videoItem.value = TCPlayer(videoID, {
         appID: '',
         controls: false
       }); // player-container-id 为播放器容器 ID,必须与 html 中一致
       __init();
+      window.addEventListener('fullscreenchange', onFullScreenChange);
+    });
+    onUnmounted(() => {
+      window.removeEventListener('fullscreenchange', onFullScreenChange);
     });
+
     expose({
       // changePlayBtn,
       toggleHideControl
@@ -259,8 +277,15 @@ export default defineComponent({
 
                 <div
                   class={styles.actionBtnSpeed}
-                  onClick={() => {
+                  onClick={(e: any) => {
+                    e.stopPropagation();
                     videoFroms.speedControl = !videoFroms.speedControl;
+                    if (videoFroms.speedControl) {
+                      clearTimeout(videoFroms.timer);
+                      videoFroms.showBar = true;
+                    } else {
+                      setModelOpen(true);
+                    }
                   }}>
                   <img src={iconSpeed} />
 

+ 394 - 395
src/components/upload-file/index.tsx

@@ -1,395 +1,394 @@
-import {
-  NButton,
-  NModal,
-  NUpload,
-  UploadCustomRequestOptions,
-  UploadFileInfo,
-  useMessage
-} from 'naive-ui';
-import { defineComponent, watch, PropType, reactive, ref } from 'vue';
-import { policy } from './api';
-import Copper from './copper';
-import axios from 'axios';
-import {
-  getUploadSign,
-  onFileUpload,
-  onOnlyFileUpload
-} from '/src/helpers/oss-file-upload';
-
-export default defineComponent({
-  name: 'upload-file',
-  props: {
-    fileList: {
-      type: String,
-      default: ''
-    },
-    imageList: {
-      type: Array,
-      default: () => []
-    },
-    accept: {
-      // 支持类型
-      type: String,
-      default: '.jpg,.png,.jpeg,.gif'
-    },
-    listType: {
-      type: String as PropType<'image' | 'image-card'>,
-      default: 'image-card'
-    },
-    showType: {
-      type: String as PropType<'default' | 'custom'>,
-      default: 'default'
-    },
-    showFileList: {
-      type: Boolean,
-      default: true
-    },
-    // width: {
-    //   type: Number,
-    //   default: 96
-    // },
-    // height: {
-    //   type: Number,
-    //   default: 96
-    // },
-    text: {
-      type: String as PropType<string>,
-      default: '上传文件'
-    },
-    size: {
-      // 文件大小
-      type: Number as PropType<number>,
-      default: 5
-    },
-    max: {
-      type: Number as PropType<number>,
-      default: 1
-    },
-    multiple: {
-      type: Boolean as PropType<boolean>,
-      default: false
-    },
-    disabled: {
-      type: Boolean as PropType<boolean>,
-      default: false
-    },
-    tips: {
-      type: String as PropType<string>,
-      default: ''
-    },
-    bucketName: {
-      type: String,
-      default: 'gyt'
-    },
-    path: {
-      type: String,
-      default: ''
-    },
-    fileName: {
-      type: String,
-      default: ''
-    },
-    cropper: {
-      // 是否裁切, 只有图片才支持
-      type: Boolean as PropType<boolean>,
-      default: false
-    },
-    options: {
-      type: Object,
-      default: () => {
-        return {
-          viewMode: 0,
-          autoCrop: true, //是否默认生成截图框
-          enlarge: 1, //  图片放大倍数
-          autoCropWidth: 200, //默认生成截图框宽度
-          autoCropHeight: 200, //默认生成截图框高度
-          fixedBox: false, //是否固定截图框大小 不允许改变
-          previewsCircle: true, //预览图是否是原图形
-          title: '上传图片'
-        };
-      }
-    }
-  },
-  // readFileInputEventAsArrayBuffer 只会在文件的时间回调
-  emits: [
-    'update:fileList',
-    'close',
-    'readFileInputEventAsArrayBuffer',
-    'remove'
-  ],
-  setup(props, { emit, expose, slots }) {
-    const ossUploadUrl = `https://${props.bucketName}.ks3-cn-beijing.ksyuncs.com/`;
-    const message = useMessage();
-    const visiable = ref<boolean>(false);
-    const btnLoading = ref<boolean>(false);
-    const tempFiileBuffer = ref();
-    const uploadRef = ref();
-    // const state = reactive({
-    //   policy: '',
-    //   signature: '',
-    //   key: '',
-    //   KSSAccessKeyId: '',
-    //   acl: 'public-read',
-    //   name: ''
-    // }) as any;
-    const state = reactive([]) as any;
-
-    const fileListRef = ref<UploadFileInfo[]>([]);
-    const initFileList = () => {
-      if (props.fileList) {
-        const splitName = props.fileList.split('/');
-        fileListRef.value = [
-          {
-            id: new Date().getTime().toString(),
-            name: splitName[splitName.length - 1],
-            status: 'finished',
-            url: props.fileList
-          }
-        ];
-      } else if (Array.isArray(props.imageList)) {
-        const list: any = [];
-        props.imageList.forEach((n: any) => {
-          const splitName = n.split('/');
-          list.push({
-            id: Date.now().toString(),
-            name: splitName[splitName.length - 1],
-            status: 'finished',
-            url: n
-          });
-        });
-        fileListRef.value = list;
-      } else {
-        fileListRef.value = [];
-      }
-    };
-    initFileList();
-    watch(
-      () => props.imageList,
-      () => {
-        initFileList();
-      }
-    );
-    watch(
-      () => props.fileList,
-      () => {
-        console.log('list');
-        initFileList();
-      }
-    );
-    const handleClearFile = () => {
-      uploadRef.value?.clear();
-      console.log('清空', uploadRef.value);
-    };
-    expose({
-      handleClearFile
-    });
-
-    const CropperModal = ref();
-    const onBeforeUpload = async (options: any) => {
-      const file = options.file;
-      // 文件大小
-      let isLt2M = true;
-      if (props.size) {
-        isLt2M = file.file.size / 1024 / 1024 < props.size;
-        if (!isLt2M) {
-          message.error(`文件大小不能超过${props.size}M`);
-          return false;
-        }
-      }
-
-      if (!isLt2M) {
-        return isLt2M;
-      }
-      // 是否裁切
-      if (props.cropper) {
-        getBase64(file.file, (imageUrl: any) => {
-          const target = Object.assign({}, props.options, {
-            img: imageUrl,
-            name: file.file.name // 上传文件名
-          });
-          visiable.value = true;
-
-          setTimeout(() => {
-            CropperModal.value?.edit(target);
-            console.log(CropperModal.value, 'cropper');
-          }, 100);
-        });
-        return false;
-      }
-      try {
-        btnLoading.value = true;
-        console.log(props.path, file.file);
-        const name = file.file.name;
-        const suffix = name.slice(name.lastIndexOf('.'));
-        // const months = dayjs().format('MM')
-        const fileName = `${props.path}${
-          props.fileName || Date.now() + suffix
-        }`;
-        const obj = {
-          filename: fileName,
-          bucketName: props.bucketName,
-          postData: {
-            filename: fileName,
-            acl: 'public-read',
-            key: fileName,
-            unknowValueField: []
-          }
-        };
-        // const { data } = await policy(obj);
-
-        // state.policy = data.policy;
-        // state.signature = data.signature;
-        // state.key = fileName;
-        // state.KSSAccessKeyId = data.kssAccessKeyId;
-        // state.name = fileName;
-
-        // tempFiileBuffer.value = file.file;
-        const { data } = await getUploadSign(obj);
-        state.push({
-          id: file.id,
-          tempFiileBuffer: file.file,
-          policy: data.policy,
-          signature: data.signature,
-          acl: 'public-read',
-          key: fileName,
-          KSSAccessKeyId: data.kssAccessKeyId,
-          name: fileName
-        });
-      } catch {
-        //
-        // message.error('上传失败')
-        btnLoading.value = false;
-        return false;
-      }
-      return true;
-    };
-    const getBase64 = async (img: any, callback: any) => {
-      const reader = new FileReader();
-      reader.addEventListener('load', () => callback(reader.result));
-      reader.readAsDataURL(img);
-    };
-    const onFinish = (options: any) => {
-      const item = state.find((c: any) => c.id == options.file.id);
-      // const url = ossUploadUrl + state.key;
-      emit('update:fileList', options.file.url);
-      emit('readFileInputEventAsArrayBuffer', item.tempFiileBuffer);
-      // options.file.url = url;
-      visiable.value = false;
-      btnLoading.value = false;
-    };
-    const onRemove = async (options: any) => {
-      console.log('🚀 ~ options', options);
-      emit('update:fileList', '');
-      emit('remove');
-      btnLoading.value = false;
-    };
-
-    const onCustomRequest = ({
-      file,
-      // data,
-      // headers,
-      // withCredentials,
-      action,
-      onFinish,
-      onError,
-      onProgress
-    }: UploadCustomRequestOptions) => {
-      const item = state.find((c: any) => {
-        return c.id == file.id;
-      });
-
-      item.file = file;
-      onFileUpload({ file, action, data: item, onProgress, onFinish, onError });
-    };
-
-    // 裁切失败
-    // const cropperNo = () => {}
-    // 裁切成功
-    const cropperOk = async (blob: any) => {
-      try {
-        // const months = dayjs().format('MM')
-        const fileName = `${props.path}${
-          props.fileName || new Date().getTime() + '.png'
-        }`;
-        const obj = {
-          filename: fileName,
-          bucketName: props.bucketName,
-          postData: {
-            filename: fileName,
-            acl: 'public-read',
-            key: fileName,
-            unknowValueField: []
-          }
-        };
-        // const { data } = await policy(obj);
-        const { data } = await getUploadSign(obj);
-        const formData = {
-          policy: data.policy,
-          signature: data.signature,
-          acl: 'public-read',
-          key: fileName,
-          KSSAccessKeyId: data.kssAccessKeyId,
-          name: fileName,
-          file: blob
-        };
-
-        const res = await onOnlyFileUpload(ossUploadUrl, formData);
-        console.log(res, 'upload');
-        emit('update:fileList', res);
-        visiable.value = false;
-      } catch {
-        //
-        // message.error('上传失败');
-        return false;
-      }
-    };
-    return () => (
-      <div>
-        <NUpload
-          ref={uploadRef}
-          action={ossUploadUrl}
-          // data={state}
-          customRequest={onCustomRequest}
-          v-model:fileList={fileListRef.value}
-          listType={props.listType}
-          accept={props.accept}
-          multiple={props.multiple}
-          max={props.max}
-          disabled={props.disabled}
-          showFileList={props.showFileList}
-          showPreviewButton
-          onBeforeUpload={(options: any) => onBeforeUpload(options)}
-          onFinish={(options: any) => onFinish(options)}
-          onRemove={(options: any) => onRemove(options)}>
-          {props.showType === 'default' && props.listType === 'image' && (
-            <NButton loading={btnLoading.value} type="primary">
-              {props.text}
-            </NButton>
-          )}
-          {props.showType === 'custom' && slots.custom && slots.custom()}
-        </NUpload>
-        {props.tips && (
-          <p style="font-size: 13px; color: #666; padding-top: 4px;">
-            {props.tips}
-          </p>
-        )}
-
-        <NModal
-          v-model:show={visiable.value}
-          preset="dialog"
-          showIcon={false}
-          class={['modalTitle background']}
-          title="上传图片"
-          style={{ width: '800px' }}>
-          {/* @cropper-no="error" @cropper-ok="success" */}
-          <Copper
-            // ref="CropperModal"
-            ref={CropperModal}
-            onClose={() => (visiable.value = false)}
-            onCropperOk={cropperOk}
-          />
-        </NModal>
-      </div>
-    );
-  }
-});
+import {
+  NButton,
+  NModal,
+  NUpload,
+  UploadCustomRequestOptions,
+  UploadFileInfo,
+  useMessage
+} from 'naive-ui';
+import { defineComponent, watch, PropType, reactive, ref } from 'vue';
+import { policy } from './api';
+import Copper from './copper';
+import axios from 'axios';
+import {
+  getUploadSign,
+  onFileUpload,
+  onOnlyFileUpload
+} from '/src/helpers/oss-file-upload';
+
+export default defineComponent({
+  name: 'upload-file',
+  props: {
+    fileList: {
+      type: String,
+      default: ''
+    },
+    imageList: {
+      type: Array,
+      default: () => []
+    },
+    accept: {
+      // 支持类型
+      type: String,
+      default: '.jpg,.png,.jpeg,.gif'
+    },
+    listType: {
+      type: String as PropType<'image' | 'image-card'>,
+      default: 'image-card'
+    },
+    showType: {
+      type: String as PropType<'default' | 'custom'>,
+      default: 'default'
+    },
+    showFileList: {
+      type: Boolean,
+      default: true
+    },
+    // width: {
+    //   type: Number,
+    //   default: 96
+    // },
+    // height: {
+    //   type: Number,
+    //   default: 96
+    // },
+    text: {
+      type: String as PropType<string>,
+      default: '上传文件'
+    },
+    size: {
+      // 文件大小
+      type: Number as PropType<number>,
+      default: 5
+    },
+    max: {
+      type: Number as PropType<number>,
+      default: 1
+    },
+    multiple: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    disabled: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    tips: {
+      type: String as PropType<string>,
+      default: ''
+    },
+    bucketName: {
+      type: String,
+      default: 'gyt'
+    },
+    path: {
+      type: String,
+      default: ''
+    },
+    fileName: {
+      type: String,
+      default: ''
+    },
+    cropper: {
+      // 是否裁切, 只有图片才支持
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    options: {
+      type: Object,
+      default: () => {
+        return {
+          viewMode: 0,
+          autoCrop: true, //是否默认生成截图框
+          enlarge: 1, //  图片放大倍数
+          autoCropWidth: 200, //默认生成截图框宽度
+          autoCropHeight: 200, //默认生成截图框高度
+          fixedBox: false, //是否固定截图框大小 不允许改变
+          previewsCircle: true, //预览图是否是原图形
+          title: '上传图片'
+        };
+      }
+    }
+  },
+  // readFileInputEventAsArrayBuffer 只会在文件的时间回调
+  emits: [
+    'update:fileList',
+    'close',
+    'readFileInputEventAsArrayBuffer',
+    'remove'
+  ],
+  setup(props, { emit, expose, slots }) {
+    const ossUploadUrl = `https://${props.bucketName}.ks3-cn-beijing.ksyuncs.com/`;
+    const message = useMessage();
+    const visiable = ref<boolean>(false);
+    const btnLoading = ref<boolean>(false);
+    const tempFiileBuffer = ref();
+    const uploadRef = ref();
+    // const state = reactive({
+    //   policy: '',
+    //   signature: '',
+    //   key: '',
+    //   KSSAccessKeyId: '',
+    //   acl: 'public-read',
+    //   name: ''
+    // }) as any;
+    const state = reactive([]) as any;
+
+    const fileListRef = ref<UploadFileInfo[]>([]);
+    const initFileList = () => {
+      if (props.fileList) {
+        const splitName = props.fileList.split('/');
+        fileListRef.value = [
+          {
+            id: new Date().getTime().toString(),
+            name: splitName[splitName.length - 1],
+            status: 'finished',
+            url: props.fileList
+          }
+        ];
+      } else if (Array.isArray(props.imageList)) {
+        const list: any = [];
+        props.imageList.forEach((n: any) => {
+          const splitName = n.split('/');
+          list.push({
+            id: Date.now().toString(),
+            name: splitName[splitName.length - 1],
+            status: 'finished',
+            url: n
+          });
+        });
+        fileListRef.value = list;
+      } else {
+        fileListRef.value = [];
+      }
+    };
+    initFileList();
+    watch(
+      () => props.imageList,
+      () => {
+        initFileList();
+      }
+    );
+    watch(
+      () => props.fileList,
+      () => {
+        initFileList();
+      }
+    );
+    const handleClearFile = () => {
+      uploadRef.value?.clear();
+      console.log('清空', uploadRef.value);
+    };
+    expose({
+      handleClearFile
+    });
+
+    const CropperModal = ref();
+    const onBeforeUpload = async (options: any) => {
+      const file = options.file;
+      // 文件大小
+      let isLt2M = true;
+      if (props.size) {
+        isLt2M = file.file.size / 1024 / 1024 < props.size;
+        if (!isLt2M) {
+          message.error(`文件大小不能超过${props.size}M`);
+          return false;
+        }
+      }
+
+      if (!isLt2M) {
+        return isLt2M;
+      }
+      // 是否裁切
+      if (props.cropper) {
+        getBase64(file.file, (imageUrl: any) => {
+          const target = Object.assign({}, props.options, {
+            img: imageUrl,
+            name: file.file.name // 上传文件名
+          });
+          visiable.value = true;
+
+          setTimeout(() => {
+            CropperModal.value?.edit(target);
+            console.log(CropperModal.value, 'cropper');
+          }, 100);
+        });
+        return false;
+      }
+      try {
+        btnLoading.value = true;
+        console.log(props.path, file.file);
+        const name = file.file.name;
+        const suffix = name.slice(name.lastIndexOf('.'));
+        // const months = dayjs().format('MM')
+        const fileName = `${props.path}${
+          props.fileName || Date.now() + suffix
+        }`;
+        const obj = {
+          filename: fileName,
+          bucketName: props.bucketName,
+          postData: {
+            filename: fileName,
+            acl: 'public-read',
+            key: fileName,
+            unknowValueField: []
+          }
+        };
+        // const { data } = await policy(obj);
+
+        // state.policy = data.policy;
+        // state.signature = data.signature;
+        // state.key = fileName;
+        // state.KSSAccessKeyId = data.kssAccessKeyId;
+        // state.name = fileName;
+
+        // tempFiileBuffer.value = file.file;
+        const { data } = await getUploadSign(obj);
+        state.push({
+          id: file.id,
+          tempFiileBuffer: file.file,
+          policy: data.policy,
+          signature: data.signature,
+          acl: 'public-read',
+          key: fileName,
+          KSSAccessKeyId: data.kssAccessKeyId,
+          name: fileName
+        });
+      } catch {
+        //
+        // message.error('上传失败')
+        btnLoading.value = false;
+        return false;
+      }
+      return true;
+    };
+    const getBase64 = async (img: any, callback: any) => {
+      const reader = new FileReader();
+      reader.addEventListener('load', () => callback(reader.result));
+      reader.readAsDataURL(img);
+    };
+    const onFinish = (options: any) => {
+      const item = state.find((c: any) => c.id == options.file.id);
+      // const url = ossUploadUrl + state.key;
+      emit('update:fileList', options.file.url);
+      emit('readFileInputEventAsArrayBuffer', item.tempFiileBuffer);
+      // options.file.url = url;
+      visiable.value = false;
+      btnLoading.value = false;
+    };
+    const onRemove = async (options: any) => {
+      console.log('🚀 ~ options', options);
+      emit('update:fileList', '');
+      emit('remove');
+      btnLoading.value = false;
+    };
+
+    const onCustomRequest = ({
+      file,
+      // data,
+      // headers,
+      // withCredentials,
+      action,
+      onFinish,
+      onError,
+      onProgress
+    }: UploadCustomRequestOptions) => {
+      const item = state.find((c: any) => {
+        return c.id == file.id;
+      });
+
+      item.file = file;
+      onFileUpload({ file, action, data: item, onProgress, onFinish, onError });
+    };
+
+    // 裁切失败
+    // const cropperNo = () => {}
+    // 裁切成功
+    const cropperOk = async (blob: any) => {
+      try {
+        // const months = dayjs().format('MM')
+        const fileName = `${props.path}${
+          props.fileName || new Date().getTime() + '.png'
+        }`;
+        const obj = {
+          filename: fileName,
+          bucketName: props.bucketName,
+          postData: {
+            filename: fileName,
+            acl: 'public-read',
+            key: fileName,
+            unknowValueField: []
+          }
+        };
+        // const { data } = await policy(obj);
+        const { data } = await getUploadSign(obj);
+        const formData = {
+          policy: data.policy,
+          signature: data.signature,
+          acl: 'public-read',
+          key: fileName,
+          KSSAccessKeyId: data.kssAccessKeyId,
+          name: fileName,
+          file: blob
+        };
+
+        const res = await onOnlyFileUpload(ossUploadUrl, formData);
+        console.log(res, 'upload');
+        emit('update:fileList', res);
+        visiable.value = false;
+      } catch {
+        //
+        // message.error('上传失败');
+        return false;
+      }
+    };
+    return () => (
+      <div>
+        <NUpload
+          ref={uploadRef}
+          action={ossUploadUrl}
+          // data={state}
+          customRequest={onCustomRequest}
+          v-model:fileList={fileListRef.value}
+          listType={props.listType}
+          accept={props.accept}
+          multiple={props.multiple}
+          max={props.max}
+          disabled={props.disabled}
+          showFileList={props.showFileList}
+          showPreviewButton
+          onBeforeUpload={(options: any) => onBeforeUpload(options)}
+          onFinish={(options: any) => onFinish(options)}
+          onRemove={(options: any) => onRemove(options)}>
+          {props.showType === 'default' && props.listType === 'image' && (
+            <NButton loading={btnLoading.value} type="primary">
+              {props.text}
+            </NButton>
+          )}
+          {props.showType === 'custom' && slots.custom && slots.custom()}
+        </NUpload>
+        {props.tips && (
+          <p style="font-size: 13px; color: #666; padding-top: 4px;">
+            {props.tips}
+          </p>
+        )}
+
+        <NModal
+          v-model:show={visiable.value}
+          preset="dialog"
+          showIcon={false}
+          class={['modalTitle background']}
+          title="上传图片"
+          style={{ width: '800px' }}>
+          {/* @cropper-no="error" @cropper-ok="success" */}
+          <Copper
+            // ref="CropperModal"
+            ref={CropperModal}
+            onClose={() => (visiable.value = false)}
+            onCropperOk={cropperOk}
+          />
+        </NModal>
+      </div>
+    );
+  }
+});

二進制
src/views/attend-class/image/icon-preivew-download2.png


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

@@ -159,7 +159,7 @@ export default defineComponent({
                 练习
               </NButton>
 
-              {props.item.containAccompaniment ? (
+              {!props.item.containAccompaniment ? (
                 <NTooltip showArrow={false}>
                   {{
                     trigger: () => (

+ 265 - 250
src/views/classList/components/afterWorkDetail.module.less

@@ -1,251 +1,266 @@
-.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;
+.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;
+}
+
+.isok {
+  font-weight: 600;
+  color: #333333;
+}
+
+.ison {
+  font-weight: 600;
+  color: #ea4132;
+}
+
+.nosub {
+  font-weight: 600;
+  color: #aaa;
 }

+ 6 - 1
src/views/classList/modals/TrainingDetails.tsx

@@ -206,7 +206,12 @@ export default defineComponent({
             <div class={styles.workList}>
               {studnetInfo.value.studentLessonTrainingDetails.map(
                 (item: any) => (
-                  <WorkItem item={item} />
+                  <WorkItem
+                    item={{
+                      ...item,
+                      studentName: studnetInfo.value.studentName
+                    }}
+                  />
                 )
               )}
             </div>

+ 8 - 0
src/views/classList/work-item/index.module.less

@@ -36,6 +36,14 @@
   align-items: center;
   justify-content: center;
 
+  .videoSection {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
   .expireBg {
     position: absolute;
     inset: 0;

+ 94 - 40
src/views/classList/work-item/index.tsx

@@ -15,6 +15,7 @@ import CardPreview from '/src/components/card-preview';
 import { checkUrlType, iframeDislableKeyboard } from '/src/utils';
 import { useUserStore } from '/src/store/modules/users';
 import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
+import { saveAs } from 'file-saver';
 
 export default defineComponent({
   name: 'work-item',
@@ -31,32 +32,49 @@ export default defineComponent({
     const preivewItem = ref({
       type: 'MUSIC',
       content: props.item.musicId,
-      title: props.item.musicName
+      title: props.item.musicName,
+      studentName: props.item.studentName
     });
     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 onDownload = (src: any) => {
+      if (!src) {
+        message.error('下载失败');
+        return;
+      }
+      const fileUrl = src;
+      // props.item.studentName
+      const title =
+        props.item.musicName +
+        (props.item.studentName ? '-' + props.item.studentName : '');
+      const suffix = src.substring(src.lastIndexOf('.'));
+      // 发起Fetch请求
+      fetch(fileUrl)
+        .then(response => response.blob())
+        .then(blob => {
+          saveAs(blob, (title || new Date().getTime() + '') + suffix);
+        })
+        .catch(() => {
+          message.error('下载失败');
+        });
+    };
     return () => (
       <div
         class={[
           styles.workItem,
-          (props.item.fileList?.expireFlag || !props.item.fileList?.fileType) &&
+          (props.item.fileList?.expireFlag ||
+            props.item.trainingStatus === 'UNSUBMITTED') &&
             styles['work-content-disabled']
         ]}>
         <div
           class={[styles['work-content']]}
           style={{
-            cursor: !props.item.fileList?.fileType ? 'default' : 'pointer'
+            cursor:
+              props.item.trainingStatus === 'UNSUBMITTED'
+                ? 'default'
+                : 'pointer'
           }}>
           {/* ("文件类型:评测:EVALUATION,IMG:图片,SOUND:音频,VIDEO:视频")
         private String fileType; */}
@@ -73,17 +91,35 @@ export default defineComponent({
             <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
-              //   ];
-              // }}
+              renderToolbar={({ nodes }: ImageRenderToolbarProps) => {
+                return [
+                  nodes.prev,
+                  nodes.next,
+                  nodes.rotateCounterclockwise,
+                  nodes.rotateClockwise,
+                  nodes.resizeToOriginalSize,
+                  nodes.zoomOut,
+                  <div
+                    class={'n-base-icon'}
+                    onClick={() => onDownload(props.item.fileList?.filePath)}>
+                    <svg
+                      viewBox="0 0 16 16"
+                      version="1.1"
+                      xmlns="http://www.w3.org/2000/svg">
+                      <g
+                        stroke="none"
+                        stroke-width="1"
+                        fill="none"
+                        fill-rule="evenodd">
+                        <g fill="currentColor" fill-rule="nonzero">
+                          <path d="M3.5,13 L12.5,13 C12.7761424,13 13,13.2238576 13,13.5 C13,13.7454599 12.8231248,13.9496084 12.5898756,13.9919443 L12.5,14 L3.5,14 C3.22385763,14 3,13.7761424 3,13.5 C3,13.2545401 3.17687516,13.0503916 3.41012437,13.0080557 L3.5,13 L12.5,13 L3.5,13 Z M7.91012437,1.00805567 L8,1 C8.24545989,1 8.44960837,1.17687516 8.49194433,1.41012437 L8.5,1.5 L8.5,10.292 L11.1819805,7.6109127 C11.3555469,7.43734635 11.6249713,7.4180612 11.8198394,7.55305725 L11.8890873,7.6109127 C12.0626536,7.78447906 12.0819388,8.05390346 11.9469427,8.2487716 L11.8890873,8.31801948 L8.35355339,11.8535534 C8.17998704,12.0271197 7.91056264,12.0464049 7.7156945,11.9114088 L7.64644661,11.8535534 L4.1109127,8.31801948 C3.91565056,8.12275734 3.91565056,7.80617485 4.1109127,7.6109127 C4.28447906,7.43734635 4.55390346,7.4180612 4.7487716,7.55305725 L4.81801948,7.6109127 L7.5,10.292 L7.5,1.5 C7.5,1.25454011 7.67687516,1.05039163 7.91012437,1.00805567 L8,1 L7.91012437,1.00805567 Z"></path>
+                        </g>
+                      </g>
+                    </svg>
+                  </div>,
+                  nodes.close
+                ];
+              }}
             />
           )}
           {props.item.fileList?.fileType === 'SOUND' && (
@@ -103,16 +139,19 @@ export default defineComponent({
           )}
           {props.item.fileList?.fileType === 'EVALUATION' &&
             (checkUrlType(props.item.fileList?.content) === 'video' ? (
-              <video
-                style={{ height: '100%' }}
-                src={props.item.fileList?.content}
+              <div
+                class={styles.videoSection}
                 onClick={() => {
                   preivewItem.value.content = props.item.fileList?.content;
                   preivewItem.value.title = props.item.musicName;
                   preivewItem.value.type = 'VIDEO';
                   previewShow.value = true;
-                }}
-              />
+                }}>
+                <video
+                  style={{ height: '100%' }}
+                  src={props.item.fileList?.content}
+                />
+              </div>
             ) : (
               <div
                 onClick={() => {
@@ -130,16 +169,19 @@ export default defineComponent({
             ))}
           {/* 'https://oss.dayaedu.com/ktqy/1715586967518b42c4fe5.mp4' */}
           {props.item.fileList?.fileType === 'VIDEO' && (
-            <video
-              style={{ height: '100%' }}
-              src={props.item.fileList?.filePath}
+            <div
+              class={styles.videoSection}
               onClick={() => {
                 preivewItem.value.content = props.item.fileList?.filePath;
                 preivewItem.value.title = props.item.musicName;
                 preivewItem.value.type = 'VIDEO';
                 previewShow.value = true;
-              }}
-            />
+              }}>
+              <video
+                style={{ height: '100%' }}
+                src={props.item.fileList?.filePath}
+              />
+            </div>
           )}
 
           {/* 判断是否过期 */}
@@ -192,15 +234,27 @@ export default defineComponent({
 
           {props.item.trainingType === 'EVALUATION' ? (
             <div class={[styles.scoreGroup, styles.scoreGroupEval]}>
-              {props.item.trainingTimes}
-              <span>分</span>
+              {props.item.trainingStatus !== 'UNSUBMITTED' ? (
+                <>
+                  {props.item.trainingTimes}
+                  <span>分</span>
+                </>
+              ) : (
+                <span class={styles.noSubmit}>未提交</span>
+              )}
             </div>
           ) : (
             <div class={[styles.scoreGroup]}>
-              {props.item.trainingTimes
-                ? parseInt(props.item.trainingTimes / 60 + '')
-                : 0}
-              <span>分钟</span>
+              {props.item.trainingStatus !== 'UNSUBMITTED' ? (
+                <>
+                  {props.item.trainingTimes
+                    ? parseInt(props.item.trainingTimes / 60 + '')
+                    : 0}
+                  <span>分钟</span>
+                </>
+              ) : (
+                <span class={styles.noSubmit}>未提交</span>
+              )}
             </div>
           )}
         </div>

+ 15 - 0
src/views/homework-record/detail/index.module.less

@@ -248,4 +248,19 @@
 
 .wordDetailModel {
   width: 1012px;
+}
+
+.isok {
+  font-weight: 600;
+  color: #333333;
+}
+
+.ison {
+  font-weight: 600;
+  color: #ea4132;
+}
+
+.nosub {
+  font-weight: 600;
+  color: #aaa;
 }

+ 271 - 261
src/views/login/index.tsx

@@ -1,261 +1,271 @@
-import {
-  defineComponent,
-  onBeforeUnmount,
-  reactive,
-  ref,
-  onMounted
-} from 'vue';
-import loginStyles from './images/login_styles.png';
-import loginLeft from './images/login-left.png';
-import loginRight from './images/loginright.png';
-import colLogo from './images/colLogo.png';
-import CodeLogin from './components/codeLogin';
-import PwdLogin from './components/pwdLogin';
-import {
-  NTabs,
-  NTabPane,
-  useDialog,
-  NModal,
-  NButton,
-  NSpace,
-  NAlert,
-  NImage
-} from 'naive-ui';
-import styles from './index.module.less';
-import ForgotPassword from './components/forgotPassword';
-import moveTop from './images/moveTopBg.png';
-import dingPng from './images/ding.png';
-import closeAble from './images/closeAble.png';
-import infoIcon from './images/infoIcon.png';
-import { state } from '/src/state';
-import TheAuth from '/src/components/TheAuth';
-import { mutualTLSQuery } from './api';
-export default defineComponent({
-  name: 'login-page',
-  setup() {
-    const isForgot = ref(false);
-    const NavsValue = ref('pwdLogin');
-    const pwdLoginRef = ref();
-    const forgotPasswordRef = ref();
-    const popEvent = ref();
-    const dialog = useDialog();
-    const userPhone = ref(); // 用户手机号
-    const showModalMask = ref(false);
-    const showAuthStatus = ref(false);
-    const showAuthMask = ref(false);
-    const checkInstall = async (event: any) => {
-      event.preventDefault();
-      console.log('checkInstall', event);
-      popEvent.value = event;
-      console.log('beforeoutcome');
-      // const { outcome } = await event.userChoice;
-      // console.log(outcome, 'outcome')
-      // setTimeout(async function () {
-      //   try {
-      //     const { outcome } = await event.userChoice;
-      //     console.log(outcome, 'outcome')
-      //   } catch (e) {
-      //     console.log(e)
-      //   }
-
-      // }, 2000)
-
-      if (window.matchMedia('(display-mode: standalone)').matches) {
-        state.application = window.matchMedia(
-          '(display-mode: standalone)'
-        ).matches;
-      } else {
-        console.log(popEvent.value, 'popEvent.value');
-        if (popEvent.value) {
-          showModalMask.value = true;
-          setTimeout(() => {
-            const btn = document.querySelector('#submitBtn');
-            // console.log(btn);
-            if (btn) {
-              btn.addEventListener('click', () => {
-                showModalMask.value = false;
-                if (!popEvent.value) {
-                  return;
-                }
-                popEvent.value.prompt();
-                popEvent.value.userChoice.then((choiceResult: any) => {
-                  if (choiceResult.outcome === 'accepted') {
-                    console.log('用户已同意添加到桌面');
-                    showModalMask.value = false;
-                    checkAuthShow();
-                  } else {
-                    console.log('用户已取消添加到桌面');
-                    showModalMask.value = false;
-                    checkAuthShow();
-                  }
-                });
-              });
-            }
-          }, 500);
-        }
-      }
-
-      // 是否显示桌面安装,是否安装了证书
-      await checkAuthError();
-      if (!showModalMask.value && showAuthStatus.value) {
-        showAuthMask.value = true;
-      }
-    };
-
-    const checkAuthShow = () => {
-      if (showAuthStatus.value) showAuthMask.value = true;
-    };
-
-    window.addEventListener('beforeinstallprompt', checkInstall, {
-      once: true
-    });
-
-    onBeforeUnmount(() => {
-      window.removeEventListener('beforeinstallprompt', checkInstall);
-    });
-
-    const checkAuthError = async () => {
-      try {
-        await mutualTLSQuery({});
-      } catch (err: any) {
-        if (err.message.indexOf('511')) {
-          // showAuthMask.value = true;
-          showAuthStatus.value = true;
-        }
-      }
-    };
-
-    onMounted(async () => {
-      // 删除打谱里面上传记录
-      sessionStorage.removeItem('task-upload-music');
-      // const relatedApps = await navigator?.getInstalledRelatedApps();
-    });
-    const downChrome = () => {
-      const agent = navigator.userAgent.toLowerCase();
-      const isMac = (function () {
-        return /macintosh|mac os x/i.test(navigator.userAgent);
-      })();
-      if (agent.indexOf('win32') >= 0 || agent.indexOf('wow32') >= 0) {
-        window.open(
-          'https://oss.dayaedu.com/appstore/ChromeStandaloneSetup32.exe'
-        );
-      }
-      if (agent.indexOf('win64') >= 0 || agent.indexOf('wow64') >= 0) {
-        window.open(
-          'https://oss.dayaedu.com/appstore/ChromeStandaloneSetup64.exe'
-        );
-      }
-      if (isMac) {
-        window.open('https://oss.dayaedu.com/appstore/googlechrome-mac.dmg');
-      }
-    };
-
-    return () => (
-      <div class={styles['view-account']}>
-        <div class={styles['view-account-container']}>
-          <img src={loginLeft} class={styles.loginLeft} alt="" />
-          <img src={loginRight} class={styles.loginRight} alt="" />
-          <div class={styles['stylesWrap']}>
-            <img src={loginStyles} alt="" />
-          </div>
-        </div>
-        <div class={styles['view-account-form']}>
-          <img class={styles.colLogo} src={colLogo}></img>
-          {isForgot.value ? (
-            <NTabs
-              key="forgotPassword"
-              default-value={NavsValue.value}
-              class={[styles.loginTabs, styles.loginForgot]}
-              ref={forgotPasswordRef}
-              justify-content="center">
-              <NTabPane name="forgotPassword" tab="重置密码">
-                <ForgotPassword
-                  v-model:phone={userPhone.value}
-                  onChangType={() => {
-                    isForgot.value = false;
-                    NavsValue.value = 'pwdLogin';
-                    // pwdLoginRef.value.syncBarPosition();
-                  }}></ForgotPassword>
-              </NTabPane>
-            </NTabs>
-          ) : (
-            <NTabs
-              key="pwdLogin"
-              ref={pwdLoginRef}
-              default-value={NavsValue.value}
-              class={[styles.loginTabs]}
-              justify-content="center">
-              <NTabPane name="pwdLogin" tab="密码登录">
-                <PwdLogin
-                  v-model:phone={userPhone.value}
-                  onChangType={() => {
-                    isForgot.value = true;
-                    NavsValue.value = 'forgotPassword';
-                    // forgotPasswordRef.value.syncBarPosition();
-                  }}></PwdLogin>
-              </NTabPane>
-              <NTabPane name="codeLogin" tab="短信验证">
-                <CodeLogin v-model:phone={userPhone.value}></CodeLogin>
-              </NTabPane>
-            </NTabs>
-          )}
-          <div class={styles.alertWrap}>
-            <div class={styles.alertInfo}>
-              <NImage
-                src={infoIcon}
-                class={styles.infoIcon}
-                previewDisabled></NImage>
-              为了您更好的上课体验,推荐使用Chrome浏览器
-            </div>
-            <div class={styles.down} onClick={downChrome}>
-              立即下载
-            </div>
-          </div>
-        </div>
-        <NModal
-          v-model:show={showModalMask.value}
-          onMaskClick={() => {
-            checkAuthShow();
-          }}
-          onClose={() => {
-            checkAuthShow();
-          }}>
-          <div class={styles.downMove}>
-            <img src={dingPng} class={styles.dingPng} alt="" />
-            <img src={moveTop} class={styles.downMoveBg} alt="" />
-            <img
-              src={closeAble}
-              class={styles.closeAble}
-              onClick={() => {
-                showModalMask.value = false;
-                checkAuthShow();
-              }}
-              alt=""
-            />
-            <h2>温馨提示</h2>
-            <p>
-              检测到您尚未安装“音乐数字课堂”应用程序,为了更好的使用体验,是否立即下载?
-            </p>
-            {/* <NButton>确定</NButton> */}
-            <NSpace style={{ padding: '25px 0 0 0' }} justify="center">
-              <NButton
-                {...{ id: 'submitBtn' }}
-                class={styles.submitAppBtn}
-                round
-                type="primary">
-                立即下载
-              </NButton>
-            </NSpace>
-          </div>
-        </NModal>
-
-        <NModal
-          v-model:show={showAuthMask.value}
-          closeOnEsc={false}
-          maskClosable={false}>
-          <TheAuth onClose={() => (showAuthMask.value = false)} />
-        </NModal>
-      </div>
-    );
-  }
-});
+import {
+  defineComponent,
+  onBeforeUnmount,
+  reactive,
+  ref,
+  onMounted
+} from 'vue';
+import loginStyles from './images/login_styles.png';
+import loginLeft from './images/login-left.png';
+import loginRight from './images/loginright.png';
+import colLogo from './images/colLogo.png';
+import CodeLogin from './components/codeLogin';
+import PwdLogin from './components/pwdLogin';
+import {
+  NTabs,
+  NTabPane,
+  useDialog,
+  NModal,
+  NButton,
+  NSpace,
+  NAlert,
+  NImage
+} from 'naive-ui';
+import styles from './index.module.less';
+import ForgotPassword from './components/forgotPassword';
+import moveTop from './images/moveTopBg.png';
+import dingPng from './images/ding.png';
+import closeAble from './images/closeAble.png';
+import infoIcon from './images/infoIcon.png';
+import { state } from '/src/state';
+import TheAuth from '/src/components/TheAuth';
+import { mutualTLSQuery } from './api';
+export default defineComponent({
+  name: 'login-page',
+  setup() {
+    const isForgot = ref(false);
+    const NavsValue = ref('pwdLogin');
+    const pwdLoginRef = ref();
+    const forgotPasswordRef = ref();
+    const popEvent = ref();
+    const dialog = useDialog();
+    const userPhone = ref(); // 用户手机号
+    const showModalMask = ref(false);
+    const showAuthStatus = ref(false);
+    const showAuthMask = ref(false);
+    const checkInstall = async (event: any) => {
+      event.preventDefault();
+      console.log('checkInstall', event);
+      popEvent.value = event;
+      console.log('beforeoutcome');
+      // const { outcome } = await event.userChoice;
+      // console.log(outcome, 'outcome')
+      // setTimeout(async function () {
+      //   try {
+      //     const { outcome } = await event.userChoice;
+      //     console.log(outcome, 'outcome')
+      //   } catch (e) {
+      //     console.log(e)
+      //   }
+
+      // }, 2000)
+
+      if (window.matchMedia('(display-mode: standalone)').matches) {
+        state.application = window.matchMedia(
+          '(display-mode: standalone)'
+        ).matches;
+      } else {
+        console.log(popEvent.value, 'popEvent.value');
+        if (popEvent.value) {
+          showModalMask.value = true;
+          setTimeout(() => {
+            const btn = document.querySelector('#submitBtn');
+            // console.log(btn);
+            if (btn) {
+              btn.addEventListener('click', () => {
+                showModalMask.value = false;
+                if (!popEvent.value) {
+                  return;
+                }
+                popEvent.value.prompt();
+                popEvent.value.userChoice.then((choiceResult: any) => {
+                  if (choiceResult.outcome === 'accepted') {
+                    console.log('用户已同意添加到桌面');
+                    showModalMask.value = false;
+                    checkAuthShow();
+                  } else {
+                    console.log('用户已取消添加到桌面');
+                    showModalMask.value = false;
+                    checkAuthShow();
+                  }
+                });
+              });
+            }
+          }, 500);
+        }
+      }
+
+      // 是否显示桌面安装,是否安装了证书
+      await checkAuthError();
+      if (!showModalMask.value && showAuthStatus.value) {
+        showAuthMask.value = true;
+      }
+    };
+
+    const checkAuthShow = () => {
+      if (showAuthStatus.value) showAuthMask.value = true;
+    };
+
+    window.addEventListener('beforeinstallprompt', checkInstall, {
+      once: true
+    });
+    onBeforeUnmount(() => {
+      window.removeEventListener('beforeinstallprompt', checkInstall);
+    });
+
+    const checkAuthError = async () => {
+      try {
+        await mutualTLSQuery({});
+      } catch (err: any) {
+        if (err.message.indexOf('511')) {
+          // showAuthMask.value = true;
+          showAuthStatus.value = true;
+        }
+      }
+    };
+
+    onMounted(async () => {
+      // 已经安装 pwa 不会触发 beforeinstallprompt, 所以加上以下判断
+      if (
+        window.matchMedia('(display-mode: standalone)').matches ||
+        (window.navigator as any).standalone === true
+      ) {
+        // 是否显示桌面安装,是否安装了证书
+        await checkAuthError();
+        if (!showModalMask.value && showAuthStatus.value) {
+          showAuthMask.value = true;
+        }
+      }
+      // 删除打谱里面上传记录
+      sessionStorage.removeItem('task-upload-music');
+      // const relatedApps = await navigator?.getInstalledRelatedApps();
+    });
+    const downChrome = () => {
+      const agent = navigator.userAgent.toLowerCase();
+      const isMac = (function () {
+        return /macintosh|mac os x/i.test(navigator.userAgent);
+      })();
+      if (agent.indexOf('win32') >= 0 || agent.indexOf('wow32') >= 0) {
+        window.open(
+          'https://oss.dayaedu.com/appstore/ChromeStandaloneSetup32.exe'
+        );
+      }
+      if (agent.indexOf('win64') >= 0 || agent.indexOf('wow64') >= 0) {
+        window.open(
+          'https://oss.dayaedu.com/appstore/ChromeStandaloneSetup64.exe'
+        );
+      }
+      if (isMac) {
+        window.open('https://oss.dayaedu.com/appstore/googlechrome-mac.dmg');
+      }
+    };
+
+    return () => (
+      <div class={styles['view-account']}>
+        <div class={styles['view-account-container']}>
+          <img src={loginLeft} class={styles.loginLeft} alt="" />
+          <img src={loginRight} class={styles.loginRight} alt="" />
+          <div class={styles['stylesWrap']}>
+            <img src={loginStyles} alt="" />
+          </div>
+        </div>
+        <div class={styles['view-account-form']}>
+          <img class={styles.colLogo} src={colLogo}></img>
+          {isForgot.value ? (
+            <NTabs
+              key="forgotPassword"
+              default-value={NavsValue.value}
+              class={[styles.loginTabs, styles.loginForgot]}
+              ref={forgotPasswordRef}
+              justify-content="center">
+              <NTabPane name="forgotPassword" tab="重置密码">
+                <ForgotPassword
+                  v-model:phone={userPhone.value}
+                  onChangType={() => {
+                    isForgot.value = false;
+                    NavsValue.value = 'pwdLogin';
+                    // pwdLoginRef.value.syncBarPosition();
+                  }}></ForgotPassword>
+              </NTabPane>
+            </NTabs>
+          ) : (
+            <NTabs
+              key="pwdLogin"
+              ref={pwdLoginRef}
+              default-value={NavsValue.value}
+              class={[styles.loginTabs]}
+              justify-content="center">
+              <NTabPane name="pwdLogin" tab="密码登录">
+                <PwdLogin
+                  v-model:phone={userPhone.value}
+                  onChangType={() => {
+                    isForgot.value = true;
+                    NavsValue.value = 'forgotPassword';
+                    // forgotPasswordRef.value.syncBarPosition();
+                  }}></PwdLogin>
+              </NTabPane>
+              <NTabPane name="codeLogin" tab="短信验证">
+                <CodeLogin v-model:phone={userPhone.value}></CodeLogin>
+              </NTabPane>
+            </NTabs>
+          )}
+          <div class={styles.alertWrap}>
+            <div class={styles.alertInfo}>
+              <NImage
+                src={infoIcon}
+                class={styles.infoIcon}
+                previewDisabled></NImage>
+              为了您更好的上课体验,推荐使用Chrome浏览器
+            </div>
+            <div class={styles.down} onClick={downChrome}>
+              立即下载
+            </div>
+          </div>
+        </div>
+        <NModal
+          v-model:show={showModalMask.value}
+          onMaskClick={() => {
+            checkAuthShow();
+          }}
+          onClose={() => {
+            checkAuthShow();
+          }}>
+          <div class={styles.downMove}>
+            <img src={dingPng} class={styles.dingPng} alt="" />
+            <img src={moveTop} class={styles.downMoveBg} alt="" />
+            <img
+              src={closeAble}
+              class={styles.closeAble}
+              onClick={() => {
+                showModalMask.value = false;
+                checkAuthShow();
+              }}
+              alt=""
+            />
+            <h2>温馨提示</h2>
+            <p>
+              检测到您尚未安装“音乐数字课堂”应用程序,为了更好的使用体验,是否立即下载?
+            </p>
+            {/* <NButton>确定</NButton> */}
+            <NSpace style={{ padding: '25px 0 0 0' }} justify="center">
+              <NButton
+                {...{ id: 'submitBtn' }}
+                class={styles.submitAppBtn}
+                round
+                type="primary">
+                立即下载
+              </NButton>
+            </NSpace>
+          </div>
+        </NModal>
+
+        <NModal
+          v-model:show={showAuthMask.value}
+          closeOnEsc={false}
+          maskClosable={false}>
+          <TheAuth onClose={() => (showAuthMask.value = false)} />
+        </NModal>
+      </div>
+    );
+  }
+});

+ 5 - 1
src/views/natural-resources/components/share-resources/index.tsx

@@ -155,7 +155,11 @@ export default defineComponent({
         />
 
         {/* 弹窗查看 */}
-        <CardPreview v-model:show={state.show} item={state.item} />
+        <CardPreview
+          v-model:show={state.show}
+          item={state.item}
+          isDownload={false}
+        />
 
         {/* 添加自定义教材 */}
         <NModal

+ 4 - 0
src/views/prepare-lessons/components/lesson-main/courseware-presets/index.module.less

@@ -33,6 +33,10 @@
       font-size: max(15px, 13Px);
     }
 
+    .n-tabs-tab {
+      font-weight: 500 !important;
+    }
+
     .n-tabs-tab-pad {
       width: 33px !important;
     }

+ 4 - 0
src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx

@@ -798,6 +798,10 @@ export default defineComponent({
                     // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                     // @ts-ignore
                     group="description"
+                    scroll={true}
+                    scrollSensitivity={100}
+                    animation={200}
+                    forceAutoScrollFallback={true}
                     componentData={{
                       itemKey: 'id',
                       tag: 'div',

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

@@ -73,9 +73,9 @@ export default defineComponent({
         const tempRows = data.rows || [];
         const temp: any = [];
         tempRows.forEach((row: any) => {
-          const index = prepareStore.getTrainList.findIndex(
-            (course: any) => course.musicId === row.id
-          );
+          // const index = prepareStore.getTrainList.findIndex(
+          //   (course: any) => course.musicId === row.id
+          // );
           temp.push({
             id: row.id,
             coverImg: row.coverImg || row.musicSvg,
@@ -86,8 +86,8 @@ export default defineComponent({
             refFlag: row.refFlag,
             content: row.id,
             xmlFileUrl: row.xmlFileUrl,
-            containAccompaniment: row.containAccompaniment,
-            exist: index !== -1 ? true : false // 是否存在
+            containAccompaniment: row.containAccompaniment
+            // exist: index !== -1 ? true : false // 是否存在
           });
         });
         state.tableList.push(...temp);
@@ -98,21 +98,21 @@ export default defineComponent({
       }
     };
 
-    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
-      }
-    );
+    // 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 throttledFnSearch = useDebounceFn(item => {
       state.pagination.page = 1;

+ 8 - 1
src/views/studentList/modals/studentTraomomhDetails.tsx

@@ -199,9 +199,16 @@ export default defineComponent({
             style="max-height:400px;min-height: 260px;"
             trigger="none">
             <div class={styles.workList}>
+              {/* studentName: studnetInfo.value.studentName */}
               {teacherInfo.value.studentLessonTrainingDetails.map(
                 (item: any) => (
-                  <WorkItem style={{ marginBottom: '20px' }} item={item} />
+                  <WorkItem
+                    style={{ marginBottom: '20px' }}
+                    item={{
+                      ...item,
+                      studentName: teacherInfo.value.studentName
+                    }}
+                  />
                 )
               )}
             </div>