Browse Source

Merge branch 'iteration-20240520' into jenkins-dev

lex 1 year ago
parent
commit
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 show = toRef(props.show);
     const item = toRef(props.item);
     const item = toRef(props.item);
     const pptLoading = ref(true);
     const pptLoading = ref(true);
-
     watch(
     watch(
       () => props.show,
       () => props.show,
       () => {
       () => {
@@ -62,6 +61,8 @@ export default defineComponent({
       () => props.item,
       () => props.item,
       () => {
       () => {
         item.value = props.item;
         item.value = props.item;
+
+        console.log(item.value, 'item.value  value');
       }
       }
     );
     );
     // 拖动
     // 拖动
@@ -80,6 +81,7 @@ export default defineComponent({
         users.info.id
         users.info.id
       );
       );
     }
     }
+
     return () => (
     return () => (
       <>
       <>
         <NModal
         <NModal
@@ -107,7 +109,10 @@ export default defineComponent({
           blockScroll={false}>
           blockScroll={false}>
           {item.value.type === 'VIDEO' && (
           {item.value.type === 'VIDEO' && (
             <VideoModal
             <VideoModal
-              title={item.value.title}
+              title={
+                item.value.title +
+                (props.item.studentName ? '-' + props.item.studentName : '')
+              }
               poster={item.value.url}
               poster={item.value.url}
               src={item.value.content}
               src={item.value.content}
               isDownload={props.isDownload}
               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);
   background: rgba(0, 0, 0, 0.6);
   backdrop-filter: blur(26px);
   backdrop-filter: blur(26px);
   height: 80px;
   height: 80px;
-  padding: 0 26px 0 26px !important;
+  padding: 0 24px 0 24px !important;
   transition: all 0.3s;
   transition: all 0.3s;
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
@@ -56,7 +56,7 @@
     display: flex;
     display: flex;
     justify-content: space-between;
     justify-content: space-between;
     color: #fff;
     color: #fff;
-    padding: 4px 12px 4px;
+    padding: 4px 12px 4px 18px;
     font-size: 24px;
     font-size: 24px;
     font-weight: 600;
     font-weight: 600;
     line-height: 33px;
     line-height: 33px;
@@ -112,8 +112,8 @@
     height: 36px;
     height: 36px;
     background-color: transparent;
     background-color: transparent;
     cursor: pointer;
     cursor: pointer;
-    margin-left: 22px;
-    margin-right: 10px;
+    margin-left: 12px;
+    margin-right: 12px;
 
 
     &>img {
     &>img {
       width: 100%;
       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 {
   .iconDownload {
-    width: 36px;
-    height: 36px;
-    margin-left: 22px;
+    width: 48px;
+    height: 48px;
+    margin-left: 14px;
+    border-radius: 6px;
     background-color: transparent;
     background-color: transparent;
     cursor: pointer;
     cursor: pointer;
+    transition: background .1s ease;
+    display: flex;
+    align-items: center;
+    justify-content: center;
 
 
     &>img {
     &>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 styles from './index.module.less';
 import iconplay from '@views/attend-class/image/icon-pause.png';
 import iconplay from '@views/attend-class/image/icon-pause.png';
 import iconpause from '@views/attend-class/image/icon-play.png';
 import iconpause from '@views/attend-class/image/icon-play.png';
 import iconReplay from '@views/attend-class/image/icon-replay.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 iconFullscreen from '@views/attend-class/image/icon-fullscreen.png';
 import iconFullscreenExit from '@views/attend-class/image/icon-fullscreen-exit.png';
 import iconFullscreenExit from '@views/attend-class/image/icon-fullscreen-exit.png';
 import { NSlider, useMessage } from 'naive-ui';
 import { NSlider, useMessage } from 'naive-ui';
@@ -118,12 +125,15 @@ export default defineComponent({
         return;
         return;
       }
       }
       const fileUrl = props.item.content;
       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请求
       fetch(fileUrl)
       fetch(fileUrl)
         .then(response => response.blob())
         .then(response => response.blob())
         .then(blob => {
         .then(blob => {
-          saveAs(blob, filename || new Date().getTime() + '.mp3');
+          saveAs(blob, (filename || new Date().getTime()) + sfixx);
         })
         })
         .catch(() => {
         .catch(() => {
           message.error('下载失败');
           message.error('下载失败');
@@ -171,13 +181,11 @@ export default defineComponent({
       if (el) {
       if (el) {
         if (isElementFullscreen(el)) {
         if (isElementFullscreen(el)) {
           exitFullscreen();
           exitFullscreen();
-          audioForms.isFullScreen = false;
         } else {
         } else {
           (el.requestFullscreen && el.requestFullscreen()) ||
           (el.requestFullscreen && el.requestFullscreen()) ||
             (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
             (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
             (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
             (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
             (el.msRequestFullscreen && el.msRequestFullscreen());
             (el.msRequestFullscreen && el.msRequestFullscreen());
-          audioForms.isFullScreen = true;
         }
         }
         // audioForms.isFullScreen = isElementFullscreen(el);
         // audioForms.isFullScreen = isElementFullscreen(el);
       }
       }
@@ -200,6 +208,23 @@ export default defineComponent({
       }, 3000);
       }, 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 () => (
     return () => (
       <div class={styles.audioWrap} id={videoId} onClick={setModelOpen2}>
       <div class={styles.audioWrap} id={videoId} onClick={setModelOpen2}>
         <div class={styles.audioContainer}>
         <div class={styles.audioContainer}>

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

@@ -33,14 +33,14 @@
 
 
     :global {
     :global {
       .video-js .vjs-tech {
       .video-js .vjs-tech {
-        object-fit: cover;
+        // object-fit: cover;
       }
       }
     }
     }
   }
   }
 
 
 
 
   .controls {
   .controls {
-    border-radius: 0 0 16px 16px !important;
+    // border-radius: 0 0 16px 16px !important;
     position: absolute;
     position: absolute;
     bottom: 0;
     bottom: 0;
     left: 0;
     left: 0;
@@ -58,7 +58,7 @@
       display: flex;
       display: flex;
       justify-content: space-between;
       justify-content: space-between;
       color: #fff;
       color: #fff;
-      padding: 4px 0 4px 12px;
+      padding: 4px 12px 4px 26px;
       font-size: max(24px, 14Px);
       font-size: max(24px, 14Px);
       font-weight: 600;
       font-weight: 600;
       line-height: 33px;
       line-height: 33px;
@@ -130,7 +130,7 @@
       height: 36px;
       height: 36px;
       background-color: transparent;
       background-color: transparent;
       cursor: pointer;
       cursor: pointer;
-      margin: 0 22px;
+      margin: 0 14px 0 12px;
 
 
       &>img {
       &>img {
         width: 100%;
         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 {
     .iconDownload {
-      width: 36px;
-      height: 36px;
-      margin-left: 22px;
+      width: 48px;
+      height: 48px;
+      margin-left: 14px;
+      border-radius: 6px;
       background-color: transparent;
       background-color: transparent;
       cursor: pointer;
       cursor: pointer;
+      transition: background .1s ease;
+      display: flex;
+      align-items: center;
+      justify-content: center;
 
 
       &>img {
       &>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 {
 .sliderPopup {
   position: absolute;
   position: absolute;
   z-index: 9999;
   z-index: 9999;
-  left: -13px;
-  bottom: 35px;
+  left: -8px;
+  bottom: 55px;
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   flex-direction: column;
   flex-direction: column;
@@ -213,11 +245,11 @@
     background: #FFFFFF;
     background: #FFFFFF;
     box-shadow: 0 2px 4px 0px rgba(102, 102, 102, 0.77);
     box-shadow: 0 2px 4px 0px rgba(102, 102, 102, 0.77);
     border-radius: 14px;
     border-radius: 14px;
-    font-size: 14Px;
+    font-size: 13Px;
     font-weight: 500;
     font-weight: 500;
     height: 22Px;
     height: 22Px;
     color: #198CFE;
     color: #198CFE;
-    min-width: 36px;
+    min-width: 42px;
     text-align: center;
     text-align: center;
     vertical-align: text-bottom;
     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/dist/plyr.css';
 // import Plyr from 'plyr';
 // import Plyr from 'plyr';
 import { ref } from 'vue';
 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 iconplay from '@views/attend-class/image/icon-pause.png';
 import iconpause from '@views/attend-class/image/icon-play.png';
 import iconpause from '@views/attend-class/image/icon-play.png';
 import iconReplay from '@views/attend-class/image/icon-replay.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 iconFullscreen from '@views/attend-class/image/icon-fullscreen.png';
 import iconFullscreenExit from '@views/attend-class/image/icon-fullscreen-exit.png';
 import iconFullscreenExit from '@views/attend-class/image/icon-fullscreen-exit.png';
 import iconSpeed from '@views/attend-class/image/icon-speed.png';
 import iconSpeed from '@views/attend-class/image/icon-speed.png';
@@ -194,25 +201,36 @@ export default defineComponent({
       if (el) {
       if (el) {
         if (isElementFullscreen(el)) {
         if (isElementFullscreen(el)) {
           exitFullscreen();
           exitFullscreen();
-          videoFroms.isFullScreen = false;
         } else {
         } else {
           (el.requestFullscreen && el.requestFullscreen()) ||
           (el.requestFullscreen && el.requestFullscreen()) ||
             (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
             (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
             (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
             (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
             (el.msRequestFullscreen && el.msRequestFullscreen());
             (el.msRequestFullscreen && el.msRequestFullscreen());
-          videoFroms.isFullScreen = true;
         }
         }
         // videoFroms.isFullScreen = isElementFullscreen(el);
         // videoFroms.isFullScreen = isElementFullscreen(el);
       }
       }
     };
     };
 
 
+    const onFullScreenChange = () => {
+      if (document.fullscreenElement) {
+        videoFroms.isFullScreen = true;
+      } else {
+        videoFroms.isFullScreen = false;
+      }
+    };
+
     onMounted(() => {
     onMounted(() => {
       videoItem.value = TCPlayer(videoID, {
       videoItem.value = TCPlayer(videoID, {
         appID: '',
         appID: '',
         controls: false
         controls: false
       }); // player-container-id 为播放器容器 ID,必须与 html 中一致
       }); // player-container-id 为播放器容器 ID,必须与 html 中一致
       __init();
       __init();
+      window.addEventListener('fullscreenchange', onFullScreenChange);
+    });
+    onUnmounted(() => {
+      window.removeEventListener('fullscreenchange', onFullScreenChange);
     });
     });
+
     expose({
     expose({
       // changePlayBtn,
       // changePlayBtn,
       toggleHideControl
       toggleHideControl
@@ -259,8 +277,15 @@ export default defineComponent({
 
 
                 <div
                 <div
                   class={styles.actionBtnSpeed}
                   class={styles.actionBtnSpeed}
-                  onClick={() => {
+                  onClick={(e: any) => {
+                    e.stopPropagation();
                     videoFroms.speedControl = !videoFroms.speedControl;
                     videoFroms.speedControl = !videoFroms.speedControl;
+                    if (videoFroms.speedControl) {
+                      clearTimeout(videoFroms.timer);
+                      videoFroms.showBar = true;
+                    } else {
+                      setModelOpen(true);
+                    }
                   }}>
                   }}>
                   <img src={iconSpeed} />
                   <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>
+    );
+  }
+});

BIN
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>
               </NButton>
 
 
-              {props.item.containAccompaniment ? (
+              {!props.item.containAccompaniment ? (
                 <NTooltip showArrow={false}>
                 <NTooltip showArrow={false}>
                   {{
                   {{
                     trigger: () => (
                     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}>
             <div class={styles.workList}>
               {studnetInfo.value.studentLessonTrainingDetails.map(
               {studnetInfo.value.studentLessonTrainingDetails.map(
                 (item: any) => (
                 (item: any) => (
-                  <WorkItem item={item} />
+                  <WorkItem
+                    item={{
+                      ...item,
+                      studentName: studnetInfo.value.studentName
+                    }}
+                  />
                 )
                 )
               )}
               )}
             </div>
             </div>

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

@@ -36,6 +36,14 @@
   align-items: center;
   align-items: center;
   justify-content: center;
   justify-content: center;
 
 
+  .videoSection {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
   .expireBg {
   .expireBg {
     position: absolute;
     position: absolute;
     inset: 0;
     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 { checkUrlType, iframeDislableKeyboard } from '/src/utils';
 import { useUserStore } from '/src/store/modules/users';
 import { useUserStore } from '/src/store/modules/users';
 import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
 import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
+import { saveAs } from 'file-saver';
 
 
 export default defineComponent({
 export default defineComponent({
   name: 'work-item',
   name: 'work-item',
@@ -31,32 +32,49 @@ export default defineComponent({
     const preivewItem = ref({
     const preivewItem = ref({
       type: 'MUSIC',
       type: 'MUSIC',
       content: props.item.musicId,
       content: props.item.musicId,
-      title: props.item.musicName
+      title: props.item.musicName,
+      studentName: props.item.studentName
     });
     });
     const reportSrc = ref('');
     const reportSrc = ref('');
     const detailVisiable = ref(false);
     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 () => (
     return () => (
       <div
       <div
         class={[
         class={[
           styles.workItem,
           styles.workItem,
-          (props.item.fileList?.expireFlag || !props.item.fileList?.fileType) &&
+          (props.item.fileList?.expireFlag ||
+            props.item.trainingStatus === 'UNSUBMITTED') &&
             styles['work-content-disabled']
             styles['work-content-disabled']
         ]}>
         ]}>
         <div
         <div
           class={[styles['work-content']]}
           class={[styles['work-content']]}
           style={{
           style={{
-            cursor: !props.item.fileList?.fileType ? 'default' : 'pointer'
+            cursor:
+              props.item.trainingStatus === 'UNSUBMITTED'
+                ? 'default'
+                : 'pointer'
           }}>
           }}>
           {/* ("文件类型:评测:EVALUATION,IMG:图片,SOUND:音频,VIDEO:视频")
           {/* ("文件类型:评测:EVALUATION,IMG:图片,SOUND:音频,VIDEO:视频")
         private String fileType; */}
         private String fileType; */}
@@ -73,17 +91,35 @@ export default defineComponent({
             <NImage
             <NImage
               src={props.item.fileList?.filePath}
               src={props.item.fileList?.filePath}
               objectFit="contain"
               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' && (
           {props.item.fileList?.fileType === 'SOUND' && (
@@ -103,16 +139,19 @@ export default defineComponent({
           )}
           )}
           {props.item.fileList?.fileType === 'EVALUATION' &&
           {props.item.fileList?.fileType === 'EVALUATION' &&
             (checkUrlType(props.item.fileList?.content) === 'video' ? (
             (checkUrlType(props.item.fileList?.content) === 'video' ? (
-              <video
-                style={{ height: '100%' }}
-                src={props.item.fileList?.content}
+              <div
+                class={styles.videoSection}
                 onClick={() => {
                 onClick={() => {
                   preivewItem.value.content = props.item.fileList?.content;
                   preivewItem.value.content = props.item.fileList?.content;
                   preivewItem.value.title = props.item.musicName;
                   preivewItem.value.title = props.item.musicName;
                   preivewItem.value.type = 'VIDEO';
                   preivewItem.value.type = 'VIDEO';
                   previewShow.value = true;
                   previewShow.value = true;
-                }}
-              />
+                }}>
+                <video
+                  style={{ height: '100%' }}
+                  src={props.item.fileList?.content}
+                />
+              </div>
             ) : (
             ) : (
               <div
               <div
                 onClick={() => {
                 onClick={() => {
@@ -130,16 +169,19 @@ export default defineComponent({
             ))}
             ))}
           {/* 'https://oss.dayaedu.com/ktqy/1715586967518b42c4fe5.mp4' */}
           {/* 'https://oss.dayaedu.com/ktqy/1715586967518b42c4fe5.mp4' */}
           {props.item.fileList?.fileType === 'VIDEO' && (
           {props.item.fileList?.fileType === 'VIDEO' && (
-            <video
-              style={{ height: '100%' }}
-              src={props.item.fileList?.filePath}
+            <div
+              class={styles.videoSection}
               onClick={() => {
               onClick={() => {
                 preivewItem.value.content = props.item.fileList?.filePath;
                 preivewItem.value.content = props.item.fileList?.filePath;
                 preivewItem.value.title = props.item.musicName;
                 preivewItem.value.title = props.item.musicName;
                 preivewItem.value.type = 'VIDEO';
                 preivewItem.value.type = 'VIDEO';
                 previewShow.value = true;
                 previewShow.value = true;
-              }}
-            />
+              }}>
+              <video
+                style={{ height: '100%' }}
+                src={props.item.fileList?.filePath}
+              />
+            </div>
           )}
           )}
 
 
           {/* 判断是否过期 */}
           {/* 判断是否过期 */}
@@ -192,15 +234,27 @@ export default defineComponent({
 
 
           {props.item.trainingType === 'EVALUATION' ? (
           {props.item.trainingType === 'EVALUATION' ? (
             <div class={[styles.scoreGroup, styles.scoreGroupEval]}>
             <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>
           ) : (
           ) : (
             <div class={[styles.scoreGroup]}>
             <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>
           )}
           )}
         </div>
         </div>

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

@@ -248,4 +248,19 @@
 
 
 .wordDetailModel {
 .wordDetailModel {
   width: 1012px;
   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
         <NModal

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

@@ -33,6 +33,10 @@
       font-size: max(15px, 13Px);
       font-size: max(15px, 13Px);
     }
     }
 
 
+    .n-tabs-tab {
+      font-weight: 500 !important;
+    }
+
     .n-tabs-tab-pad {
     .n-tabs-tab-pad {
       width: 33px !important;
       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
                     // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                     // @ts-ignore
                     // @ts-ignore
                     group="description"
                     group="description"
+                    scroll={true}
+                    scrollSensitivity={100}
+                    animation={200}
+                    forceAutoScrollFallback={true}
                     componentData={{
                     componentData={{
                       itemKey: 'id',
                       itemKey: 'id',
                       tag: 'div',
                       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 tempRows = data.rows || [];
         const temp: any = [];
         const temp: any = [];
         tempRows.forEach((row: 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({
           temp.push({
             id: row.id,
             id: row.id,
             coverImg: row.coverImg || row.musicSvg,
             coverImg: row.coverImg || row.musicSvg,
@@ -86,8 +86,8 @@ export default defineComponent({
             refFlag: row.refFlag,
             refFlag: row.refFlag,
             content: row.id,
             content: row.id,
             xmlFileUrl: row.xmlFileUrl,
             xmlFileUrl: row.xmlFileUrl,
-            containAccompaniment: row.containAccompaniment,
-            exist: index !== -1 ? true : false // 是否存在
+            containAccompaniment: row.containAccompaniment
+            // exist: index !== -1 ? true : false // 是否存在
           });
           });
         });
         });
         state.tableList.push(...temp);
         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 => {
     const throttledFnSearch = useDebounceFn(item => {
       state.pagination.page = 1;
       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;"
             style="max-height:400px;min-height: 260px;"
             trigger="none">
             trigger="none">
             <div class={styles.workList}>
             <div class={styles.workList}>
+              {/* studentName: studnetInfo.value.studentName */}
               {teacherInfo.value.studentLessonTrainingDetails.map(
               {teacherInfo.value.studentLessonTrainingDetails.map(
                 (item: any) => (
                 (item: any) => (
-                  <WorkItem style={{ marginBottom: '20px' }} item={item} />
+                  <WorkItem
+                    style={{ marginBottom: '20px' }}
+                    item={{
+                      ...item,
+                      studentName: teacherInfo.value.studentName
+                    }}
+                  />
                 )
                 )
               )}
               )}
             </div>
             </div>