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

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

jack 4 дней назад
Родитель
Сommit
840045d479

+ 1 - 0
src/components/CollapsibleToolbar/index2.vue

@@ -742,6 +742,7 @@ const uploadWebpageLink = async () => {
   }
   isLoading.value = false
 
+  createSlide()
   createFrameElement(webpageUrl.value, 73) // 假设15是网页工具的类型
   // 清空输入框和验证状态
   webpageUrl.value = ''

+ 158 - 136
src/hooks/useImport.ts

@@ -11,8 +11,8 @@ import useSlideHandler from '@/hooks/useSlideHandler'
 import useHistorySnapshot from './useHistorySnapshot'
 import message from '@/utils/message'
 import { getSvgPathRange } from '@/utils/svgPathParser'
-import { EMFJS, WMFJS } from 'rtf.js';
-import * as UTIF from 'utif2';
+import { EMFJS, WMFJS } from 'rtf.js'
+import * as UTIF from 'utif2'
 
 import type {
   Slide,
@@ -32,7 +32,9 @@ import type {
 } from '@/types/slides'
 
 const convertFontSizePtToPx = (html: string, ratio: number, autoFit: any) => {
-  if (autoFit?.fontScale && autoFit?.type == "text") { ratio = ratio * autoFit.fontScale / 100; }
+  if (autoFit?.fontScale && autoFit?.type == 'text') {
+    ratio = ratio * autoFit.fontScale / 100 
+  }
   // return html;
   return html.replace(/\s*([\d.]+)pt/g, (match, p1) => {
     return `${Math.round(parseFloat(p1) * ratio)}px `
@@ -64,6 +66,7 @@ export default () => {
   const { isEmptySlide } = useSlideHandler()
 
   const exporting = ref(false)
+  const imgExporting = ref(false)
 
   // 导入JSON文件
   const importJSON = (files: FileList, cover = false) => {
@@ -224,6 +227,7 @@ export default () => {
     const win = window as any
     if (!win.exportJSON) win.exportJSON = exportJSON2
     if (!win.readJSON) win.readJSON = readJSON
+    if (!win.imgExporting) win.imgExporting = () => imgExporting.value
   }
 
   // 导入pptist文件
@@ -737,108 +741,113 @@ export default () => {
     filename: string,
     options?: { tolerance?: number }
   ): Promise<File> => {
-    const tolerance = options?.tolerance ?? 15;
+    const tolerance = options?.tolerance ?? 15
   
     // 1. 统一输入为 Blob 和 MIME
-    const { blob, mime } = await getBlobAndMime(data);
-    const format = getFormat(mime, filename);
+    const { blob, mime } = await getBlobAndMime(data)
+    const format = getFormat(mime, filename)
   
     // 2. 浏览器原生支持的格式直接返回
     if (format === 'browser') {
-      return new File([blob], filename, { type: mime });
+      return new File([blob], filename, { type: mime })
     }
   
     // 3. 需要转换成 PNG 的格式
-    let pngBlob: Blob;
+    let pngBlob: Blob
     if (format === 'tiff') {
-      pngBlob = await convertTiffToPng(blob);
-    } else if (format === 'emf') {
-      pngBlob = await convertEmfToPng(blob);
-    } else if (format === 'wmf') {
-      pngBlob = await convertWmfToPng(blob);
-    } else {
+      pngBlob = await convertTiffToPng(blob)
+    }
+    else if (format === 'emf') {
+      pngBlob = await convertEmfToPng(blob)
+    }
+    else if (format === 'wmf') {
+      pngBlob = await convertWmfToPng(blob)
+    }
+    else {
       // format === 'png' 的情况
-      pngBlob = blob;
+      pngBlob = blob
     }
   
     // --- 新增:检测 PNG 是否已经包含透明背景 ---
-    let alreadyTransparent = false;
+    let alreadyTransparent = false
     // 无论原始格式是 PNG 还是转换后得到的 PNG,都进行检测
-    const checkUrl = URL.createObjectURL(pngBlob);
+    const checkUrl = URL.createObjectURL(pngBlob)
     try {
       const img = await new Promise<HTMLImageElement>((resolve, reject) => {
-        const image = new Image();
-        image.onload = () => resolve(image);
-        image.onerror = reject;
-        image.src = checkUrl;
-      });
-      const canvas = document.createElement('canvas');
-      canvas.width = img.width;
-      canvas.height = img.height;
-      const ctx = canvas.getContext('2d')!;
-      ctx.drawImage(img, 0, 0);
-      alreadyTransparent = hasTransparency(img, ctx);
-    } finally {
-      URL.revokeObjectURL(checkUrl);
+        const image = new Image()
+        image.onload = () => resolve(image)
+        image.onerror = reject
+        image.src = checkUrl
+      })
+      const canvas = document.createElement('canvas')
+      canvas.width = img.width
+      canvas.height = img.height
+      const ctx = canvas.getContext('2d')!
+      ctx.drawImage(img, 0, 0)
+      alreadyTransparent = hasTransparency(img, ctx)
+    }
+    finally {
+      URL.revokeObjectURL(checkUrl)
     }
   
-    let transparentPngBlob: Blob;
-    transparentPngBlob = pngBlob;
+    let transparentPngBlob: Blob
+    transparentPngBlob = pngBlob
     /*
     if (alreadyTransparent) {
       // 图片已有透明背景,直接使用原 PNG Blob
-      console.log('检测到透明背景,跳过白色变透明处理');
-      transparentPngBlob = pngBlob;
-    } else {
+      console.log('检测到透明背景,跳过白色变透明处理')
+      transparentPngBlob = pngBlob
+    }
+    else {
       // 否则执行白色变透明处理
-      transparentPngBlob = await makeWhiteTransparentFromPng(pngBlob, tolerance);
+      transparentPngBlob = await makeWhiteTransparentFromPng(pngBlob, tolerance)
     }
   */
-    const finalFilename = format === 'png' ? filename : filename.replace(/\.[^.]*$/, '') + '.png';
-    return new File([transparentPngBlob], finalFilename, { type: 'image/png' });
-  };
+    const finalFilename = format === 'png' ? filename : filename.replace(/\.[^.]*$/, '') + '.png'
+    return new File([transparentPngBlob], finalFilename, { type: 'image/png' })
+  }
   
 
   // ================== 辅助函数 ==================
 
   async function getBlobAndMime(input: string | Blob): Promise<{ blob: Blob; mime: string }> {
-    if (input instanceof Blob) return { blob: input, mime: input.type };
+    if (input instanceof Blob) return { blob: input, mime: input.type }
     if (input.startsWith('data:') || input.startsWith('blob:')) {
-      const res = await fetch(input);
-      const blob = await res.blob();
-      return { blob, mime: blob.type };
+      const res = await fetch(input)
+      const blob = await res.blob()
+      return { blob, mime: blob.type }
     }
-    const binary = atob(input);
-    const bytes = new Uint8Array(binary.length);
-    for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
-    const blob = new Blob([bytes], { type: 'image/png' });
-    return { blob, mime: 'image/png' };
+    const binary = atob(input)
+    const bytes = new Uint8Array(binary.length)
+    for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
+    const blob = new Blob([bytes], { type: 'image/png' })
+    return { blob, mime: 'image/png' }
   }
 
   function getFormat(mime: string, filename: string): string {
-    const ext = filename.split('.').pop()?.toLowerCase();
-    if (mime === 'image/png') return 'png';
-    if (mime === 'image/tiff' || mime === 'image/x-tiff' || ext === 'tiff' || ext === 'tif') return 'tiff';
-    if (mime === 'image/x-emf' || mime === 'application/x-emf' || ext === 'emf') return 'emf';
-    if (mime === 'image/x-wmf' || mime === 'application/x-wmf' || ext === 'wmf') return 'wmf';
-    return 'browser';
+    const ext = filename.split('.').pop()?.toLowerCase()
+    if (mime === 'image/png') return 'png'
+    if (mime === 'image/tiff' || mime === 'image/x-tiff' || ext === 'tiff' || ext === 'tif') return 'tiff'
+    if (mime === 'image/x-emf' || mime === 'application/x-emf' || ext === 'emf') return 'emf'
+    if (mime === 'image/x-wmf' || mime === 'application/x-wmf' || ext === 'wmf') return 'wmf'
+    return 'browser'
   }
 
   // TIFF 转 PNG(使用 UTIF.js)
   async function convertTiffToPng(blob: Blob): Promise<Blob> {
-    const arrayBuffer = await blob.arrayBuffer();
-    const ifds = UTIF.decode(arrayBuffer);
-    if (!ifds || ifds.length === 0) throw new Error('No TIFF image found');
-    UTIF.decodeImage(arrayBuffer, ifds[0]);
-    const rgba = UTIF.toRGBA8(ifds[0]);
-    const canvas = document.createElement('canvas');
-    canvas.width = ifds[0].width;
-    canvas.height = ifds[0].height;
-    const ctx = canvas.getContext('2d')!;
-    ctx.putImageData(new ImageData(rgba, ifds[0].width, ifds[0].height), 0, 0);
+    const arrayBuffer = await blob.arrayBuffer()
+    const ifds = UTIF.decode(arrayBuffer)
+    if (!ifds || ifds.length === 0) throw new Error('No TIFF image found')
+    UTIF.decodeImage(arrayBuffer, ifds[0])
+    const rgba = UTIF.toRGBA8(ifds[0])
+    const canvas = document.createElement('canvas')
+    canvas.width = ifds[0].width
+    canvas.height = ifds[0].height
+    const ctx = canvas.getContext('2d')!
+    ctx.putImageData(new ImageData(rgba, ifds[0].width, ifds[0].height), 0, 0)
     return new Promise((resolve, reject) => {
-      canvas.toBlob(blob => (blob ? resolve(blob) : reject(new Error('TIFF to PNG failed'))), 'image/png');
-    });
+      canvas.toBlob(blob => (blob ? resolve(blob) : reject(new Error('TIFF to PNG failed'))), 'image/png')
+    })
   }
 
   // 通用函数:将 EMF/WMF 通过 Renderer 转换为 PNG
@@ -848,10 +857,10 @@ export default () => {
     RendererClass: any // new (data: ArrayBuffer) => { render(settings: any): SVGElement }
   ): Promise<Blob> {
     // 1. 创建 Renderer 实例
-    const renderer = new RendererClass(arrayBuffer);
+    const renderer = new RendererClass(arrayBuffer)
 
     // 2. 先尝试获取图片的真实尺寸(通过临时渲染并解析 SVG 的 viewBox)
-    let width = 800, height = 600; // 默认值
+    let width = 800, height = 600 // 默认值
     try {
       // 使用一个较大的临时尺寸进行第一次渲染,以获取 SVG 的 viewBox
       const tempSettings = {
@@ -860,26 +869,28 @@ export default () => {
         xExt: 1000,
         yExt: 1000,
         mapMode: 8, // 保持宽高比
-      };
-      const tempSvg = renderer.render(tempSettings);
-      const viewBox = tempSvg.getAttribute('viewBox');
+      }
+      const tempSvg = renderer.render(tempSettings)
+      const viewBox = tempSvg.getAttribute('viewBox')
       if (viewBox) {
-        const parts = viewBox.split(/[\s,]+/);
+        const parts = viewBox.split(/[\s,]+/)
         if (parts.length >= 4) {
-          width = parseFloat(parts[2]);
-          height = parseFloat(parts[3]);
+          width = parseFloat(parts[2])
+          height = parseFloat(parts[3])
         }
-      } else {
+      }
+      else {
         // 尝试从 width/height 属性获取
-        const svgWidth = tempSvg.getAttribute('width');
-        const svgHeight = tempSvg.getAttribute('height');
+        const svgWidth = tempSvg.getAttribute('width')
+        const svgHeight = tempSvg.getAttribute('height')
         if (svgWidth && svgHeight) {
-          width = parseFloat(svgWidth);
-          height = parseFloat(svgHeight);
+          width = parseFloat(svgWidth)
+          height = parseFloat(svgHeight)
         }
       }
-    } catch (e) {
-      console.warn('Failed to get dimensions from SVG, using default', e);
+    }
+    catch (e) {
+      console.warn('Failed to get dimensions from SVG, using default', e)
     }
 
     // 3. 使用实际尺寸重新渲染
@@ -889,97 +900,99 @@ export default () => {
       xExt: width,
       yExt: height,
       mapMode: 8, // 保持宽高比
-    };
-    const svg = renderer.render(settings);
+    }
+    const svg = renderer.render(settings)
 
     // 4. 将 SVG 转为 data URL 并用 Image 加载
-    const serializer = new XMLSerializer();
-    let svgString = serializer.serializeToString(svg);
+    const serializer = new XMLSerializer()
+    let svgString = serializer.serializeToString(svg)
     // 确保有命名空间
     if (!svgString.includes('xmlns="http://www.w3.org/2000/svg"')) {
-      svgString = svgString.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
+      svgString = svgString.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"')
     }
-    const blob = new Blob([svgString], { type: 'image/svg+xml' });
-    const url = URL.createObjectURL(blob);
+    const blob = new Blob([svgString], { type: 'image/svg+xml' })
+    const url = URL.createObjectURL(blob)
     try {
       const img = await new Promise<HTMLImageElement>((resolve, reject) => {
-        const image = new Image();
-        image.onload = () => resolve(image);
-        image.onerror = reject;
-        image.src = url;
-      });
+        const image = new Image()
+        image.onload = () => resolve(image)
+        image.onerror = reject
+        image.src = url
+      })
       // 5. 绘制到 canvas
-      const canvas = document.createElement('canvas');
-      canvas.width = img.width;
-      canvas.height = img.height;
-      const ctx = canvas.getContext('2d')!;
-      ctx.drawImage(img, 0, 0);
+      const canvas = document.createElement('canvas')
+      canvas.width = img.width
+      canvas.height = img.height
+      const ctx = canvas.getContext('2d')!
+      ctx.drawImage(img, 0, 0)
       return new Promise((resolve, reject) => {
-        canvas.toBlob(blob => (blob ? resolve(blob) : reject(new Error('Metafile to PNG failed'))), 'image/png');
-      });
-    } finally {
-      URL.revokeObjectURL(url);
+        canvas.toBlob(blob => (blob ? resolve(blob) : reject(new Error('Metafile to PNG failed'))), 'image/png')
+      })
+    }
+    finally {
+      URL.revokeObjectURL(url)
     }
   }
 
   // EMF 转 PNG(使用 EMFJS.Renderer)
   async function convertEmfToPng(blob: Blob): Promise<Blob> {
-    const arrayBuffer = await blob.arrayBuffer();
-    return convertMetafileToPng(arrayBuffer, EMFJS.Renderer);
+    const arrayBuffer = await blob.arrayBuffer()
+    return convertMetafileToPng(arrayBuffer, EMFJS.Renderer)
   }
 
   // WMF 转 PNG(使用 WMFJS.Renderer)
   async function convertWmfToPng(blob: Blob): Promise<Blob> {
-    const arrayBuffer = await blob.arrayBuffer();
-    return convertMetafileToPng(arrayBuffer, WMFJS.Renderer);
+    const arrayBuffer = await blob.arrayBuffer()
+    return convertMetafileToPng(arrayBuffer, WMFJS.Renderer)
   }
 
   function hasTransparency(img: HTMLImageElement, ctx: CanvasRenderingContext2D): boolean {
-    const imageData = ctx.getImageData(0, 0, img.width, img.height);
-    const data = imageData.data;
+    const imageData = ctx.getImageData(0, 0, img.width, img.height)
+    const data = imageData.data
     // 遍历 Alpha 通道(索引 3)
     for (let i = 3; i < data.length; i += 4) {
       if (data[i] < 255) {
-        return true; // 发现任意一个像素不是完全不透明
+        return true // 发现任意一个像素不是完全不透明
       }
     }
-    return false;
+    return false
   }
 
   // 对 PNG 执行白色变透明
   async function makeWhiteTransparentFromPng(pngBlob: Blob, tolerance: number): Promise<Blob> {
-    const url = URL.createObjectURL(pngBlob);
+    const url = URL.createObjectURL(pngBlob)
     try {
       const img = await new Promise<HTMLImageElement>((resolve, reject) => {
-        const image = new Image();
-        image.onload = () => resolve(image);
-        image.onerror = reject;
-        image.src = url;
-      });
-      const canvas = document.createElement('canvas');
-      canvas.width = img.width;
-      canvas.height = img.height;
-      const ctx = canvas.getContext('2d')!;
-      ctx.drawImage(img, 0, 0);
-      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
-      const data = imageData.data;
+        const image = new Image()
+        image.onload = () => resolve(image)
+        image.onerror = reject
+        image.src = url
+      })
+      const canvas = document.createElement('canvas')
+      canvas.width = img.width
+      canvas.height = img.height
+      const ctx = canvas.getContext('2d')!
+      ctx.drawImage(img, 0, 0)
+      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
+      const data = imageData.data
       for (let i = 0; i < data.length; i += 4) {
-        const r = data[i];
-        const g = data[i + 1];
-        const b = data[i + 2];
-        const dr = r - 255;
-        const dg = g - 255;
-        const db = b - 255;
+        const r = data[i]
+        const g = data[i + 1]
+        const b = data[i + 2]
+        const dr = r - 255
+        const dg = g - 255
+        const db = b - 255
         if (Math.sqrt(dr * dr + dg * dg + db * db) <= tolerance) {
-          data[i + 3] = 0;
+          data[i + 3] = 0
         }
       }
-      ctx.putImageData(imageData, 0, 0);
+      ctx.putImageData(imageData, 0, 0)
       return new Promise((resolve, reject) => {
-        canvas.toBlob(blob => (blob ? resolve(blob) : reject(new Error('Canvas toBlob failed'))), 'image/png');
-      });
-    } finally {
-      URL.revokeObjectURL(url);
+        canvas.toBlob(blob => (blob ? resolve(blob) : reject(new Error('Canvas toBlob failed'))), 'image/png')
+      })
+    }
+    finally {
+      URL.revokeObjectURL(url)
     }
   }
 
@@ -1585,6 +1598,7 @@ export default () => {
     if (!file) return
 
     exporting.value = true // 假设 exporting 是一个全局 ref
+    imgExporting.value = true // 假设 imgExporting 是一个全局 ref
 
     // 预加载形状库(用于后续形状匹配)
     const shapeList: ShapePoolItem[] = []
@@ -1597,6 +1611,7 @@ export default () => {
       // 检查是否已取消
       if (signal?.aborted) {
         exporting.value = false
+        imgExporting.value = false
         return
       }
 
@@ -1606,6 +1621,7 @@ export default () => {
       }
       catch (error) {
         exporting.value = false
+        imgExporting.value = false
         console.log('导入PPTX文件失败:', error)
         message.error(lang.ssFileReadFail)
         return
@@ -1613,6 +1629,7 @@ export default () => {
 
       if (signal?.aborted) {
         exporting.value = false
+        imgExporting.value = false
         return
       }
 
@@ -2265,7 +2282,11 @@ export default () => {
 
       // 等待当前幻灯片内所有上传任务完成
       // await Promise.all(uploadTasks)
-      Promise.all(uploadTasks)
+      Promise.all(uploadTasks).then(() => {
+        imgExporting.value = false
+      }).catch(() => {
+        imgExporting.value = false
+      })
 
       exporting.value = false
       onclose?.()
@@ -2414,6 +2435,7 @@ export default () => {
     readJSON,
     exportJSON2,
     exporting,
+    imgExporting,
     getFile,
     getFile2,
     dataToFile,

+ 47 - 7
src/views/Editor/index3.vue

@@ -2,6 +2,22 @@
   <div class="pptist-editor">
     <div class="ppt_header">
       <div class="header-left">
+        <div class="return_btn" @click="handleBackToList">
+          <svg width="24" height="24" viewBox="0 0 24 24"
+            fill="none" xmlns="http://www.w3.org/2000/svg">
+            <g clip-path="url(#clip0_446_5970)">
+              <g id="chevron-left">
+                <path id="Icon" d="M15 18L9 12L15 6" stroke="currentColor" stroke-opacity="0.6" stroke-width="2"
+                  stroke-linecap="round" stroke-linejoin="round" />
+              </g>
+            </g>
+            <defs>
+              <clipPath id="clip0_446_5970">
+                <rect width="24" height="24" fill="currentColor" />
+              </clipPath>
+            </defs>
+          </svg>
+        </div>
         <div class="dropdown-menu">
           <button class="dropdown-toggle" @click="toggleDropdown">
             <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -24,7 +40,7 @@
             </svg>
           </button>
           <div class="dropdown-content" v-if="isDropdownOpen">
-            <div class="dropdown-item" @click="handleBackToList"><svg width="24" height="24" viewBox="0 0 24 24"
+            <!-- <div class="dropdown-item" @click="handleBackToList"><svg width="24" height="24" viewBox="0 0 24 24"
                 fill="none" xmlns="http://www.w3.org/2000/svg">
                 <g clip-path="url(#clip0_446_5970)">
                   <g id="chevron-left">
@@ -38,7 +54,7 @@
                   </clipPath>
                 </defs>
               </svg>
-              返回列表</div>
+              {{ lang.ssBackToList }}</div> -->
             <div class="dropdown-item" @click="handleSettings">
               <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                 <g id="&#232;&#174;&#190;&#231;&#189;&#174;">
@@ -90,11 +106,11 @@
           <div class="course-title-container">
             <input v-if="editingTitle" @change="changeCourse" v-model="courseTitle" class="course-title-input" @blur="editingTitle = false"
               @keyup.enter="editingTitle = false" @input="isSaved = false" ref="titleInput" />
-            <span v-else class="course-title" @click="startEditingTitle">{{ courseTitle || '未命名课程' }}</span>
+            <span v-else class="course-title" @click="startEditingTitle">{{ courseTitle || lang.ssUnnamedCourse }}</span>
           </div>
           <div class="save-status">
-            <span v-if="lastSaveTime" class="last-save-time">上次保存时间:{{ lastSaveTime }}</span>
-            <span v-else class="status-unsaved">未保存</span>
+            <span v-if="lastSaveTime" class="last-save-time">{{ lang.ssLastSaveTime }}{{ lastSaveTime }}</span>
+            <span v-else class="status-unsaved">{{ lang.ssNotSaved }}</span>
           </div>
         </div>
       </div>
@@ -202,6 +218,7 @@ import Modal from '@/components/Modal.vue'
 import CollapsibleToolbar from '@/components/CollapsibleToolbar/index2.vue'
 import CreateCourseDialog from '@/components/CreateCourseDialog.vue'
 import api from '@/services/course'
+import lang from '../lang/cn.json'
 
 
 interface ParentWindowWithToolList extends Window {
@@ -236,7 +253,7 @@ const remarkHeight = ref(0)
 const sidebarCollapsed = ref(false)
 const showCreateCourseDialog = ref(false)
 const isDropdownOpen = ref(false)
-const courseTitle = ref('新建课程')
+const courseTitle = ref(lang.ssNewCourse)
 
 // 课程标题相关
 const editingTitle = ref(false)
@@ -297,7 +314,7 @@ const handleToolbarToggle = (collapsed: boolean) => {
 
 const handleCourseLoaded = (data: any) => {
   console.log('课程数据已加载:', data)
-  courseTitle.value = data.title || '新建课程'
+  courseTitle.value = data.title || lang.ssNewCourse
   lastSaveTime.value = data.utime || ''
 }
 
@@ -445,6 +462,29 @@ usePasteEvent()
   display: flex;
   align-items: center;
   gap: 10px;
+
+  .return_btn{
+    cursor: pointer;
+    border: 1px solid #bcbcbc;
+    border-radius: 5px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #000000;
+    width: 30px;
+    height: 30px;
+    transition: all 0.2s ease;
+
+    &:hover{
+      color: #FF9300;
+      border-color: #FF9300;
+    }
+    svg{
+      width: 20px;
+      height: 20px;
+      flex-shrink: 0;
+    }
+  }
 }
 
 .header-center {

+ 2 - 1
src/views/Student/index2.vue

@@ -1255,7 +1255,7 @@ const clearAllSyncStates = () => {
 }
 
 // 获取导入导出功能
-const { readJSON, exportJSON2, getFile, getFile2 } = useImport()
+const { readJSON, exportJSON2, getFile, getFile2, imgExporting } = useImport()
 
 // 根据iframe的URL查找对应的幻灯片索引
 const findSlideIndexByIframeUrl = (iframeUrl: string): number => {
@@ -3379,6 +3379,7 @@ onMounted(() => {
   ; (window as any).PPTistStudent = {
     importJSON,
     exportJSON,
+    imgExporting,
     slides: slidesStore.slides,
     currentSlide: computed(() => slidesStore.currentSlide),
     slideIndex: computed(() => slidesStore.slideIndex),

+ 10 - 1
src/views/lang/cn.json

@@ -738,5 +738,14 @@
   "ssAiChatFileSizeLimit": "文件大小不能超过10MB",
   "ssAiChatUploadFailed": "文件上传失败:",
   "ssClassroomAiAssistant": "课堂AI助手",
-  "ssRetryMessage": "网络有点慢,请稍后重试"
+  "ssRetryMessage": "网络有点慢,请稍后重试",
+  "ssBackToList": "返回列表",
+  "ssSettings": "设置",
+  "ssSaveAsCopy": "另存为副本",
+  "ssUnnamedCourse": "未命名课程",
+  "ssLastSaveTime": "上次保存时间:",
+  "ssNotSaved": "未保存",
+  "ssPublish": "发布",
+  "ssSave": "保存",
+  "ssNewCourse": "新建课程"
 }

+ 10 - 1
src/views/lang/en.json

@@ -738,5 +738,14 @@
   "ssAiChatFileSizeLimit": "File size cannot exceed 10MB",
   "ssAiChatUploadFailed": "File upload failed:",
   "ssClassroomAiAssistant": "Classroom AI Assistant",
-  "ssRetryMessage": "Network is slow, please try again later"
+  "ssRetryMessage": "Network is slow, please try again later",
+  "ssBackToList": "Back to List",
+  "ssSettings": "Settings",
+  "ssSaveAsCopy": "Save as Copy",
+  "ssUnnamedCourse": "Unnamed Course",
+  "ssLastSaveTime": "Last saved time: ",
+  "ssNotSaved": "Not Saved",
+  "ssPublish": "Publish",
+  "ssSave": "Save",
+  "ssNewCourse": "New Course"
 }

+ 10 - 1
src/views/lang/hk.json

@@ -738,5 +738,14 @@
   "ssAiChatFileSizeLimit": "文件大小不能超過10MB",
   "ssAiChatUploadFailed": "文件上傳失敗:",
   "ssClassroomAiAssistant": "課堂AI助手",
-  "ssRetryMessage": "網絡稍慢,請稍後重試"
+  "ssRetryMessage": "網絡稍慢,請稍後重試",
+  "ssBackToList": "返回列表",
+  "ssSettings": "設定",
+  "ssSaveAsCopy": "另存為副本",
+  "ssUnnamedCourse": "未命名課程",
+  "ssLastSaveTime": "上次保存時間:",
+  "ssNotSaved": "未保存",
+  "ssPublish": "發布",
+  "ssSave": "保存",
+  "ssNewCourse": "新建課程"
 }