Просмотр исходного кода

Merge branch 'beta' of https://git.cocorobo.cn/jack/PPT into beta

SanHQin 3 недель назад
Родитель
Сommit
c21632e87b

+ 29 - 23
src/App.vue

@@ -1,28 +1,34 @@
 <template>
   <template v-if="slides.length">
-    <Screen v-if="viewMode !== 'student' && screening" />
-    <Editor
-      v-if="viewMode === 'editor' && _isPC && !screening"
-      :courseid="urlParams.courseid"
-    />
-    <Editor2
-      v-else-if="viewMode === 'editor2' && _isPC && !screening"
-      :courseid="urlParams.courseid"
-    />
-    <Editor3
-      v-else-if="viewMode === 'editor3' && _isPC && !screening"
-      :courseid="urlParams.courseid"
-    />
-    <Student
-      v-else-if="viewMode === 'student'"
-      :courseid="urlParams.courseid"
-      :type="urlParams.type"
-      :userid="urlParams.userid"
-      :oid="urlParams.oid"
-      :org="urlParams.org"
-      :cid="urlParams.cid"
-    />
-    <Mobile v-else />
+    <Screen v-if="viewMode !== 'student' && screening" key="screen" />
+    <KeepAlive>
+      <Editor
+        v-if="viewMode === 'editor' && _isPC && !screening"
+        :courseid="urlParams.courseid"
+        key="editor"
+      />
+      <Editor2
+        v-else-if="viewMode === 'editor2' && _isPC && !screening"
+        :courseid="urlParams.courseid"
+        key="editor2"
+      />
+      <Editor3
+        v-else-if="viewMode === 'editor3' && _isPC && !screening"
+        :courseid="urlParams.courseid"
+        key="editor3"
+      />
+      <Student
+        v-else-if="viewMode === 'student'"
+        :courseid="urlParams.courseid"
+        :type="urlParams.type"
+        :userid="urlParams.userid"
+        :oid="urlParams.oid"
+        :org="urlParams.org"
+        :cid="urlParams.cid"
+        key="student"
+      />
+      <Mobile v-else key="mobile" />
+    </KeepAlive>
   </template>
   <FullscreenSpin :tip="lang.ssInitDataWait" v-else loading :mask="false" />
 </template>

+ 113 - 113
src/hooks/useImport.ts

@@ -405,7 +405,7 @@ export default () => {
   /**
    * 将 base64 字符串或 Blob 转换为 File 对象
    */
-  
+
   const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): File => {
     if (typeof data === 'string') {
       // 1. 通过 fetch 获取 Blob 数据
@@ -425,61 +425,61 @@ export default () => {
     }
     throw new Error('Unsupported data type')
   }
+
+  /*
+    // 你原有的 dataToFile 函数保持不变
+    const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): Promise<File> => {
+      if (typeof data === 'string') {
+        const response = await fetch(data);
+        if (!response.ok) {
+          throw new Error(`Failed to fetch blob: ${response.statusText}`);
+        }
+        const blob = await response.blob();
+        const mime = videoMimeType || blob.type;
+        return new File([blob], filename, { type: mime });
+      } else if (data instanceof Blob) {
+        return new File([data], filename, { type: data.type });
+      }
+      throw new Error('Unsupported data type');
+    };
   
-/*
-  // 你原有的 dataToFile 函数保持不变
-  const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): Promise<File> => {
-    if (typeof data === 'string') {
-      const response = await fetch(data);
-      if (!response.ok) {
-        throw new Error(`Failed to fetch blob: ${response.statusText}`);
+  
+    const convertVideoToMP4 = async (
+      videoSource: string | Blob,
+      outputFilename: string = `video_${Date.now()}.mp4`
+    ): Promise<File> => {
+      // 1. 检查浏览器支持
+      const supported = await canEncode();
+      if (!supported) {
+        throw new Error('当前浏览器不支持 WebCodecs,请使用最新 Chrome/Edge 并确保 HTTPS');
       }
-      const blob = await response.blob();
-      const mime = videoMimeType || blob.type;
-      return new File([blob], filename, { type: mime });
-    } else if (data instanceof Blob) {
-      return new File([data], filename, { type: data.type });
-    }
-    throw new Error('Unsupported data type');
-  };
-
-
-  const convertVideoToMP4 = async (
-    videoSource: string | Blob,
-    outputFilename: string = `video_${Date.now()}.mp4`
-  ): Promise<File> => {
-    // 1. 检查浏览器支持
-    const supported = await canEncode();
-    if (!supported) {
-      throw new Error('当前浏览器不支持 WebCodecs,请使用最新 Chrome/Edge 并确保 HTTPS');
-    }
-
-    // 2. 转为 File
-    const inputFile = await dataToFile(videoSource, 'input.mp4', 'video/mp4');
-
-    // 3. 创建 VideoFile 对象(webcodecs-encoder 的输入包装)
-    //const videoFile = new VideoFile(inputFile);
-    const videoFile = {
-      file: inputFile,
-      type: 'video/mp4'
+  
+      // 2. 转为 File
+      const inputFile = await dataToFile(videoSource, 'input.mp4', 'video/mp4');
+  
+      // 3. 创建 VideoFile 对象(webcodecs-encoder 的输入包装)
+      //const videoFile = new VideoFile(inputFile);
+      const videoFile = {
+        file: inputFile,
+        type: 'video/mp4'
+      };
+      // 4. 执行编码
+      const encodedData = await encode(videoFile, {
+        quality: 'high',
+        video: {
+          codec: 'av1',
+          bitrate: 2_000_000,
+          hardwareAcceleration: 'prefer-hardware'
+        },
+        audio: false,           // 显式禁用音频编码
+        container: 'mp4',
+        onProgress: (progress) => console.log(progress)
+      });
+  
+      // 5. 返回 File
+      return new File([encodedData], outputFilename, { type: 'video/mp4' });
     };
-    // 4. 执行编码
-    const encodedData = await encode(videoFile, {
-      quality: 'high',
-      video: {
-        codec: 'av1',
-        bitrate: 2_000_000,
-        hardwareAcceleration: 'prefer-hardware'
-      },
-      audio: false,           // 显式禁用音频编码
-      container: 'mp4',
-      onProgress: (progress) => console.log(progress)
-    });
-
-    // 5. 返回 File
-    return new File([encodedData], outputFilename, { type: 'video/mp4' });
-  };
-*/
+  */
 
 
   /*
@@ -587,7 +587,7 @@ export default () => {
         const response = await fetch(input)
         const blob = await response.blob()
         return { blob, mime: blob.type }
-      } 
+      }
       // 纯 base64 字符串 → 按原逻辑默认当作 PNG
       const binary = atob(input)
       const bytes = new Uint8Array(binary.length)
@@ -597,7 +597,7 @@ export default () => {
       // 默认 MIME 为 image/png(与原函数行为一致)
       const blob = new Blob([bytes], { type: 'image/png' })
       return { blob, mime: 'image/png' }
-      
+
     }
 
     // 获取统一的 blob 和实际 MIME 类型
@@ -612,7 +612,7 @@ export default () => {
     // 1. 创建对象 URL 用于加载图片
     const imageUrl = URL.createObjectURL(blob)
     const needRevoke = true
-  
+
     // 2. 加载图像
     const img = await new Promise<HTMLImageElement>((resolve, reject) => {
       const image = new Image()
@@ -1467,12 +1467,47 @@ export default () => {
                   range: [[0, 0], [100, 100]],
                 }
               }
+
+              // 如果 src 是 base64,触发上传
+              if (el.src && typeof el.src === 'string' && el.src.startsWith('data:')) {
+                const uploadTask = (async () => {
+                  try {
+                    const file = await makeWhiteTransparent(el.src, `image_${Date.now()}.png`)
+                    if (file) {
+                      const url = await uploadFileToS3(file)
+                      element.src = url // 替换为远程 URL
+                      const slidesStore = useSlidesStore()
+                      slidesStore.updateElement({ id: element.id, props: { src: url } })
+                    }
+                  }
+                  catch (error) {
+                    console.error('Image upload failed:', error)
+                    // 失败时保留原 base64(或可置空)
+                  }
+                })()
+                uploadTasks.push(uploadTask)
+              }
+
+              slide.elements.push(element)
+            }
+            else if (el.type === 'math') {
+              const element: PPTImageElement = {
+                type: 'image',
+                id: nanoid(10),
+                src: el.picBase64,
+                width: el.width,
+                height: el.height,
+                left: el.left,
+                top: el.top,
+                fixedRatio: true,
+                rotate: 0,
+              }
               /*
                             // 如果 src 是 base64,触发上传
-                            if (el.src && typeof el.src === 'string' && el.src.startsWith('data:')) {
+                            if (el.picBase64 && typeof el.picBase64 === 'string' && el.picBase64.startsWith('data:')) {
                               const uploadTask = (async () => {
                                 try {
-                                  const file = await makeWhiteTransparent(el.src, `image_${Date.now()}.png`)
+                                  const file = makeWhiteTransparent(el.picBase64, `image_${Date.now()}.png`)
                                   if (file) {
                                     const url = await uploadFileToS3(file)
                                     element.src = url // 替换为远程 URL
@@ -1488,41 +1523,6 @@ export default () => {
                               uploadTasks.push(uploadTask)
                             }
               */
-              slide.elements.push(element)
-            }
-            else if (el.type === 'math') {
-              const element: PPTImageElement = {
-                type: 'image',
-                id: nanoid(10),
-                src: el.picBase64,
-                width: el.width,
-                height: el.height,
-                left: el.left,
-                top: el.top,
-                fixedRatio: true,
-                rotate: 0,
-              }
-/*
-              // 如果 src 是 base64,触发上传
-              if (el.picBase64 && typeof el.picBase64 === 'string' && el.picBase64.startsWith('data:')) {
-                const uploadTask = (async () => {
-                  try {
-                    const file = makeWhiteTransparent(el.picBase64, `image_${Date.now()}.png`)
-                    if (file) {
-                      const url = await uploadFileToS3(file)
-                      element.src = url // 替换为远程 URL
-                      const slidesStore = useSlidesStore()
-                      slidesStore.updateElement({ id: element.id, props: { src: url } })
-                    }
-                  }
-                  catch (error) {
-                    console.error('Image upload failed:', error)
-                    // 失败时保留原 base64(或可置空)
-                  }
-                })()
-                uploadTasks.push(uploadTask)
-              }
-*/
 
               slide.elements.push(element)
 
@@ -1579,26 +1579,26 @@ export default () => {
                 rotate: 0,
                 autoplay: false,
               }
-/*
-              const localData = el.blob || (el.src && typeof el.src === 'string' && el.src.startsWith('data:') ? el.src : null)
-              if (localData) {
-                const uploadTask = (async () => {
-                  try {
-                    const file = await dataToFile(localData, `video_${Date.now()}.mp4`, 'video/mp4')
-                    if (file) {
-                      const url = await uploadFileToS3(file)
-                      element.src = url
-                      const slidesStore = useSlidesStore()
-                      slidesStore.updateElement({ id: element.id, props: { src: url } })
-                    }
-                  }
-                  catch (error) {
-                    console.error('Video upload failed:', error)
-                  }
-                })()
-                uploadTasks.push(uploadTask)
-              }
-*/
+              /*
+                            const localData = el.blob || (el.src && typeof el.src === 'string' && el.src.startsWith('data:') ? el.src : null)
+                            if (localData) {
+                              const uploadTask = (async () => {
+                                try {
+                                  const file = await dataToFile(localData, `video_${Date.now()}.mp4`, 'video/mp4')
+                                  if (file) {
+                                    const url = await uploadFileToS3(file)
+                                    element.src = url
+                                    const slidesStore = useSlidesStore()
+                                    slidesStore.updateElement({ id: element.id, props: { src: url } })
+                                  }
+                                }
+                                catch (error) {
+                                  console.error('Video upload failed:', error)
+                                }
+                              })()
+                              uploadTasks.push(uploadTask)
+                            }
+              */
               slide.elements.push(element)
             }
 

+ 2 - 0
src/plugins/icon.ts

@@ -98,6 +98,7 @@ import {
   Power,
   ListView,
   Magic,
+  Tips,
   HighLight,
   Download,
   IndentLeft,
@@ -232,6 +233,7 @@ export const icons: Icons = {
   IconPower: Power,
   IconListView: ListView,
   IconMagic: Magic,
+  IconTips: Tips,
   IconHighLight: HighLight,
   IconDownload: Download,
   IconIndentLeft: IndentLeft,

+ 6 - 6
src/views/Student/components/choiceQuestionDetailDialog.vue

@@ -4,7 +4,7 @@
       width: slideWidth + 'px',
       height: slideHeight + 'px',
     }">
-      <span class="closeIcon" @click="closeSlideIndex()">
+      <span v-show="false" class="closeIcon" @click="closeSlideIndex()">
         <img src="../../../assets/img/close.png" />
       </span>
 
@@ -500,7 +500,7 @@ const setEchartsArea1 = () => {
           fontSize: 17,
           lineHeight: 20,
           interval: 0,
-          formatter: function (value: any, idx: number) {
+          formatter: function(value: any, idx: number) {
             // 如果是字符串且格式为JSON(图片),则解析处理
             if (typeof value === 'string') {
               try {
@@ -635,8 +635,8 @@ const setEchartsArea1 = () => {
       const selectedOption = _work.choiceUser[idx]
       if (selectedOption && selectUserDialogRef.value) {
         // console.log(selectedOption)
-        console.log("selectedOption",selectedOption)
-        selectUserDialogRef.value.open(`${lang.ssSelectUser.replace("{a}","<span>"+selectedOption.index+"</span>")}`,selectedOption)
+        console.log('selectedOption', selectedOption)
+        selectUserDialogRef.value.open(`${lang.ssSelectUser.replace('{a}', '<span>' + selectedOption.index + '</span>')}`, selectedOption)
       }
     })
   }
@@ -769,9 +769,9 @@ watch(
 
 // 查看未提交学生
 const viewUnsubmittedStudents = () => {
-  selectUserDialogRef.value.open(lang.ssUnsubmittedStudents,{user:props.showData.unsubmittedStudents.map((item: any) => item.name)})
+  selectUserDialogRef.value.open(lang.ssUnsubmittedStudents, {user: props.showData.unsubmittedStudents.map((item: any) => item.name)})
   // if (props.unsubmittedStudents.length > 0) {
-    // unsubmittedStudentsDialogRef.value.open(props.unsubmittedStudents)
+  // unsubmittedStudentsDialogRef.value.open(props.unsubmittedStudents)
   // }
 }
 

Разница между файлами не показана из-за своего большого размера
+ 4682 - 0
src/views/Student/index2.vue


+ 2 - 0
src/views/lang/cn.json

@@ -721,6 +721,8 @@
   "ssCocoLinkTip":"请添加 Cocorobo 同域、亚马逊或可访问的 HTML 链接。",
   "ssChoiceQuestion":"选择题",
   "ssAnswerCount":"回答人数",
+  "ssQuestion":"题目",
+  "ssAnswer":"回答",
   "ssViewUnsubmittedStudents":"点击查看未提交学生",
   "ssSelectUser":"选择{a}的成员",
   "ssUnsubmittedStudents":"未提交学生",

+ 2 - 0
src/views/lang/en.json

@@ -721,6 +721,8 @@
   "ssCocoLinkTip":"Please add Cocorobo, Amazon, or accessible HTML link.",
   "ssChoiceQuestion":"Choice Question",
   "ssAnswerCount":"Answer count",
+  "ssQuestion":"Question",
+  "ssAnswer":"Answer",
   "ssViewUnsubmittedStudents":"Click to view unsubmitted students",
   "ssSelectUser":"Select members of {a}",
   "ssUnsubmittedStudents":"Unsubmitted students",

+ 2 - 0
src/views/lang/hk.json

@@ -721,6 +721,8 @@
   "ssCocoLinkTip":"請添加 Cocorobo 同域、亚马逊或可访问的 HTML 链接。",
   "ssChoiceQuestion":"選擇題",
   "ssAnswerCount":"回答人數",
+  "ssQuestion":"題目",
+  "ssAnswer":"回答",
   "ssViewUnsubmittedStudents":"點擊查看未提交學生",
   "ssSelectUser":"選擇{a}的成員",
   "ssUnsubmittedStudents":"未提交學生",

Некоторые файлы не были показаны из-за большого количества измененных файлов