Jelajahi Sumber

导入导出 相关修改

黄琪勇 3 bulan lalu
induk
melakukan
fefe249eb0

+ 103 - 106
src/configs/hotkey.ts

@@ -1,129 +1,126 @@
 export const enum KEYS {
-  C = 'C',
-  X = 'X',
-  Z = 'Z',
-  Y = 'Y',
-  A = 'A',
-  G = 'G',
-  L = 'L',
-  F = 'F',
-  D = 'D',
-  B = 'B',
-  P = 'P',
-  O = 'O',
-  R = 'R',
-  T = 'T',
-  MINUS = '-',
-  EQUAL = '=',
-  DIGIT_0 = '0',
-  DELETE = 'DELETE',
-  UP = 'ARROWUP',
-  DOWN = 'ARROWDOWN',
-  LEFT = 'ARROWLEFT',
-  RIGHT = 'ARROWRIGHT',
-  ENTER = 'ENTER',
-  SPACE = ' ',
-  TAB = 'TAB',
-  BACKSPACE = 'BACKSPACE',
-  ESC = 'ESCAPE',
-  PAGEUP = 'PAGEUP',
-  PAGEDOWN = 'PAGEDOWN',
-  F5 = 'F5',
+  C = "C",
+  X = "X",
+  Z = "Z",
+  Y = "Y",
+  A = "A",
+  G = "G",
+  L = "L",
+  F = "F",
+  D = "D",
+  B = "B",
+  P = "P",
+  O = "O",
+  R = "R",
+  T = "T",
+  MINUS = "-",
+  EQUAL = "=",
+  DIGIT_0 = "0",
+  DELETE = "DELETE",
+  UP = "ARROWUP",
+  DOWN = "ARROWDOWN",
+  LEFT = "ARROWLEFT",
+  RIGHT = "ARROWRIGHT",
+  ENTER = "ENTER",
+  SPACE = " ",
+  TAB = "TAB",
+  BACKSPACE = "BACKSPACE",
+  ESC = "ESCAPE",
+  PAGEUP = "PAGEUP",
+  PAGEDOWN = "PAGEDOWN",
+  F5 = "F5"
 }
 
 export const HOTKEY_DOC = [
   {
-    type: '通用',
+    type: "通用",
     children: [
-      { label: '剪切', value: 'Ctrl + X' },
-      { label: '复制', value: 'Ctrl + C' },
-      { label: '粘贴', value: 'Ctrl + V' },
-      { label: '粘贴为纯文本', value: 'Ctrl + Shift + V' },
-      { label: '快速复制粘贴', value: 'Ctrl + D' },
-      { label: '全选', value: 'Ctrl + A' },
-      { label: '撤销', value: 'Ctrl + Z' },
-      { label: '恢复', value: 'Ctrl + Y' },
-      { label: '删除', value: 'Delete / Backspace' },
-      { label: '多选', value: '按住 Ctrl 或 Shift' },
-      { label: '打开搜索替换', value: 'Ctrl + F' },
-      { label: '打印', value: 'Ctrl + P' },
-      { label: '关闭弹窗', value: 'ESC' },
-    ],
+      { label: "剪切", value: "Ctrl + X" },
+      { label: "复制", value: "Ctrl + C" },
+      { label: "粘贴", value: "Ctrl + V" },
+      { label: "粘贴为纯文本", value: "Ctrl + Shift + V" },
+      { label: "快速复制粘贴", value: "Ctrl + D" },
+      { label: "全选", value: "Ctrl + A" },
+      { label: "撤销", value: "Ctrl + Z" },
+      { label: "恢复", value: "Ctrl + Y" },
+      { label: "删除", value: "Delete / Backspace" },
+      { label: "多选", value: "按住 Ctrl 或 Shift" },
+      { label: "打开搜索替换", value: "Ctrl + F" },
+      { label: "关闭弹窗", value: "ESC" }
+    ]
   },
   {
-    type: '幻灯片放映',
+    type: "幻灯片放映",
     children: [
-      { label: '从头开始放映幻灯片', value: 'F5' },
-      { label: '从当前开始放映幻灯片', value: 'Shift + F5' },
-      { label: '切换上一页', value: '↑ / ← / PgUp' },
-      { label: '切换下一页', value: '↓ / → / PgDown' },
-      { label: '切换下一页', value: 'Enter / Space' },
-      { label: '退出放映', value: 'ESC' },
-    ],
+      { label: "从头开始放映幻灯片", value: "F5" },
+      { label: "从当前开始放映幻灯片", value: "Shift + F5" },
+      { label: "切换上一页", value: "↑ / ← / PgUp" },
+      { label: "切换下一页", value: "↓ / → / PgDown" },
+      { label: "切换下一页", value: "Enter / Space" },
+      { label: "退出放映", value: "ESC" }
+    ]
   },
   {
-    type: '幻灯片编辑',
+    type: "幻灯片编辑",
     children: [
-      { label: '新建幻灯片', value: 'Enter' },
-      { label: '移动画布', value: 'Space + 鼠标拖拽' },
-      { label: '缩放画布', value: 'Ctrl + 鼠标滚轮' },
-      { label: '放大画布', value: 'Ctrl + =' },
-      { label: '缩小画布', value: 'Ctrl + -' },
-      { label: '使画布适应当前屏幕', value: 'Ctrl + 0' },
-      { label: '上一页(未选中元素)', value: '↑' },
-      { label: '下一页(未选中元素)', value: '↓' },
-      { label: '上一页', value: '鼠标上滚 / PgUp' },
-      { label: '下一页', value: '鼠标下滚 / PgDown' },
-      { label: '快速创建文本', value: '双击空白处 / T' },
-      { label: '快速创建矩形', value: 'R' },
-      { label: '快速创建圆形', value: 'O' },
-      { label: '快速创建线条', value: 'L' },
-      { label: '退出绘制状态', value: '鼠标右键' },
-    ],
+      { label: "新建幻灯片", value: "Enter" },
+      { label: "移动画布", value: "Space + 鼠标拖拽" },
+      { label: "缩放画布", value: "Ctrl + 鼠标滚轮" },
+      { label: "放大画布", value: "Ctrl + =" },
+      { label: "缩小画布", value: "Ctrl + -" },
+      { label: "使画布适应当前屏幕", value: "Ctrl + 0" },
+      { label: "上一页(未选中元素)", value: "↑" },
+      { label: "下一页(未选中元素)", value: "↓" },
+      { label: "上一页", value: "鼠标上滚 / PgUp" },
+      { label: "下一页", value: "鼠标下滚 / PgDown" },
+      { label: "快速创建文本", value: "双击空白处 / T" },
+      { label: "快速创建矩形", value: "R" },
+      { label: "快速创建圆形", value: "O" },
+      { label: "快速创建线条", value: "L" },
+      { label: "退出绘制状态", value: "鼠标右键" }
+    ]
   },
   {
-    type: '元素操作',
+    type: "元素操作",
     children: [
-      { label: '移动', value: '↑ / ← / ↓ / →' },
-      { label: '锁定', value: 'Ctrl + L' },
-      { label: '组合', value: 'Ctrl + G' },
-      { label: '取消组合', value: 'Ctrl + Shift + G' },
-      { label: '置顶层', value: 'Alt + F' },
-      { label: '置底层', value: 'Alt + B' },
-      { label: '锁定宽高比例', value: '按住 Ctrl 或 Shift' },
-      { label: '创建水平 / 垂直线条', value: '按住 Ctrl 或 Shift' },
-      { label: '切换焦点元素', value: 'Tab' },
-      { label: '确认图片裁剪', value: 'Enter' },
-      { label: '完成自定义形状绘制', value: 'Enter' },
-    ],
+      { label: "移动", value: "↑ / ← / ↓ / →" },
+      { label: "锁定", value: "Ctrl + L" },
+      { label: "组合", value: "Ctrl + G" },
+      { label: "取消组合", value: "Ctrl + Shift + G" },
+      { label: "置顶层", value: "Alt + F" },
+      { label: "置底层", value: "Alt + B" },
+      { label: "锁定宽高比例", value: "按住 Ctrl 或 Shift" },
+      { label: "创建水平 / 垂直线条", value: "按住 Ctrl 或 Shift" },
+      { label: "切换焦点元素", value: "Tab" },
+      { label: "确认图片裁剪", value: "Enter" },
+      { label: "完成自定义形状绘制", value: "Enter" }
+    ]
   },
   {
-    type: '表格编辑',
+    type: "表格编辑",
     children: [
-      { label: '聚焦到下一个单元格', value: 'Tab' },
-      { label: '移动焦点单元格', value: '↑ / ← / ↓ / →' },
-      { label: '在上方插入一行', value: 'Ctrl + ↑' },
-      { label: '在下方插入一行', value: 'Ctrl + ↓' },
-      { label: '在左侧插入一列', value: 'Ctrl + ←' },
-      { label: '在右侧插入一列', value: 'Ctrl + →' },
-    ],
+      { label: "聚焦到下一个单元格", value: "Tab" },
+      { label: "移动焦点单元格", value: "↑ / ← / ↓ / →" },
+      { label: "在上方插入一行", value: "Ctrl + ↑" },
+      { label: "在下方插入一行", value: "Ctrl + ↓" },
+      { label: "在左侧插入一列", value: "Ctrl + ←" },
+      { label: "在右侧插入一列", value: "Ctrl + →" }
+    ]
   },
   {
-    type: '图表数据编辑',
-    children: [
-      { label: '聚焦到下一行', value: 'Enter' },
-    ],
+    type: "图表数据编辑",
+    children: [{ label: "聚焦到下一行", value: "Enter" }]
   },
   {
-    type: '文本编辑',
+    type: "文本编辑",
     children: [
-      { label: '加粗', value: 'Ctrl + B' },
-      { label: '斜体', value: 'Ctrl + I' },
-      { label: '下划线', value: 'Ctrl + U' },
-      { label: '行内代码', value: 'Ctrl + E' },
-      { label: '上角标', value: 'Ctrl + ;' },
-      { label: '下角标', value: `Ctrl + '` },
-      { label: '选中段落', value: `ESC` },
-    ],
-  },
-]
+      { label: "加粗", value: "Ctrl + B" },
+      { label: "斜体", value: "Ctrl + I" },
+      { label: "下划线", value: "Ctrl + U" },
+      { label: "行内代码", value: "Ctrl + E" },
+      { label: "上角标", value: "Ctrl + ;" },
+      { label: "下角标", value: `Ctrl + '` },
+      { label: "选中段落", value: `ESC` }
+    ]
+  }
+]

+ 0 - 8
src/hooks/useExport.ts

@@ -10,7 +10,6 @@ import type { PPTElementOutline, PPTElementShadow, PPTElementLink, Slide } from
 import { getElementRange, getLineElementPath, getTableSubThemeColor } from "@/utils/element"
 import { type AST, toAST } from "@/utils/htmlParser"
 import { type SvgPoints, toPoints } from "@/utils/svgPathParser"
-import { encrypt } from "@/utils/crypto"
 import { svg2Base64 } from "@/utils/svg2Base64"
 import message from "@/utils/message"
 
@@ -61,12 +60,6 @@ export default () => {
     }, 200)
   }
 
-  // 导出pptist文件(特有 .pptist 后缀文件)
-  const exportSpecificFile = (_slides: Slide[]) => {
-    const blob = new Blob([encrypt(JSON.stringify(_slides))], { type: "" })
-    saveAs(blob, `${title.value}.pptist`)
-  }
-
   // 导出JSON文件
   const exportJSON = () => {
     const json = {
@@ -825,7 +818,6 @@ export default () => {
     exporting,
     exportImage,
     exportJSON,
-    exportSpecificFile,
     exportPPTX
   }
 }

+ 58 - 73
src/hooks/useGlobalHotkey.ts

@@ -1,45 +1,30 @@
-import { onMounted, onUnmounted } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'
-import { ElementOrderCommands } from '@/types/edit'
-import { KEYS } from '@/configs/hotkey'
+import { onMounted, onUnmounted } from "vue"
+import { storeToRefs } from "pinia"
+import { useMainStore, useSlidesStore, useKeyboardStore } from "@/store"
+import { ElementOrderCommands } from "@/types/edit"
+import { KEYS } from "@/configs/hotkey"
 
-import useSlideHandler from './useSlideHandler'
-import useLockElement from './useLockElement'
-import useDeleteElement from './useDeleteElement'
-import useCombineElement from './useCombineElement'
-import useCopyAndPasteElement from './useCopyAndPasteElement'
-import useSelectElement from './useSelectElement'
-import useMoveElement from './useMoveElement'
-import useOrderElement from './useOrderElement'
-import useHistorySnapshot from './useHistorySnapshot'
-import useScreening from './useScreening'
-import useScaleCanvas from './useScaleCanvas'
+import useSlideHandler from "./useSlideHandler"
+import useLockElement from "./useLockElement"
+import useDeleteElement from "./useDeleteElement"
+import useCombineElement from "./useCombineElement"
+import useCopyAndPasteElement from "./useCopyAndPasteElement"
+import useSelectElement from "./useSelectElement"
+import useMoveElement from "./useMoveElement"
+import useOrderElement from "./useOrderElement"
+import useHistorySnapshot from "./useHistorySnapshot"
+import useScreening from "./useScreening"
+import useScaleCanvas from "./useScaleCanvas"
 
 export default () => {
   const mainStore = useMainStore()
   const keyboardStore = useKeyboardStore()
-  const {
-    activeElementIdList,
-    disableHotkeys,
-    handleElement,
-    handleElementId,
-    editorAreaFocus,
-    thumbnailsFocus,
-    showSearchPanel,
-  } = storeToRefs(mainStore)
+  const { activeElementIdList, disableHotkeys, handleElement, handleElementId, editorAreaFocus, thumbnailsFocus, showSearchPanel } =
+    storeToRefs(mainStore)
   const { currentSlide } = storeToRefs(useSlidesStore())
   const { ctrlKeyState, shiftKeyState, spaceKeyState } = storeToRefs(keyboardStore)
 
-  const {
-    updateSlideIndex,
-    copySlide,
-    createSlide,
-    deleteSlide,
-    cutSlide,
-    copyAndPasteSlide,
-    selectAllSlide,
-  } = useSlideHandler()
+  const { updateSlideIndex, copySlide, createSlide, deleteSlide, cutSlide, copyAndPasteSlide, selectAllSlide } = useSlideHandler()
 
   const { combineElements, uncombineElements } = useCombineElement()
   const { deleteElement } = useDeleteElement()
@@ -128,19 +113,13 @@ export default () => {
   const keydownListener = (e: KeyboardEvent) => {
     const { ctrlKey, shiftKey, altKey, metaKey } = e
     const ctrlOrMetaKeyActive = ctrlKey || metaKey
-    
+
     const key = e.key.toUpperCase()
 
     if (ctrlOrMetaKeyActive && !ctrlKeyState.value) keyboardStore.setCtrlKeyState(true)
     if (shiftKey && !shiftKeyState.value) keyboardStore.setShiftKeyState(true)
     if (!disableHotkeys.value && key === KEYS.SPACE) keyboardStore.setSpaceKeyState(true)
 
-    
-    if (ctrlOrMetaKeyActive && key === KEYS.P) {
-      e.preventDefault()
-      mainStore.setDialogForExport('pdf')
-      return
-    }
     if (shiftKey && key === KEYS.F5) {
       e.preventDefault()
       enterScreening()
@@ -157,8 +136,8 @@ export default () => {
       mainStore.setSearchPanelState(!showSearchPanel.value)
       return
     }
-    
-    if (!editorAreaFocus.value && !thumbnailsFocus.value) return      
+
+    if (!editorAreaFocus.value && !thumbnailsFocus.value) return
 
     if (ctrlOrMetaKeyActive && key === KEYS.C) {
       if (disableHotkeys.value) return
@@ -258,12 +237,12 @@ export default () => {
     if (key === KEYS.MINUS) {
       if (disableHotkeys.value) return
       e.preventDefault()
-      scaleCanvas('-')
+      scaleCanvas("-")
     }
     if (key === KEYS.EQUAL) {
       if (disableHotkeys.value) return
       e.preventDefault()
-      scaleCanvas('+')
+      scaleCanvas("+")
     }
     if (key === KEYS.DIGIT_0) {
       if (disableHotkeys.value) return
@@ -277,30 +256,36 @@ export default () => {
     }
     if (editorAreaFocus.value && !shiftKey && !ctrlOrMetaKeyActive && !disableHotkeys.value) {
       if (key === KEYS.T) {
-        mainStore.setCreatingElement({ type: 'text' })
-      }
-      else if (key === KEYS.R) {
-        mainStore.setCreatingElement({ type: 'shape', data: {
-          viewBox: [200, 200],
-          path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
-        }})
-      }
-      else if (key === KEYS.O) {
-        mainStore.setCreatingElement({ type: 'shape', data: {
-          viewBox: [200, 200],
-          path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z',
-        }})
-      }
-      else if (key === KEYS.L) {
-        mainStore.setCreatingElement({ type: 'line', data: {
-          path: 'M 0 0 L 20 20',
-          style: 'solid',
-          points: ['', ''],
-        }})
+        mainStore.setCreatingElement({ type: "text" })
+      } else if (key === KEYS.R) {
+        mainStore.setCreatingElement({
+          type: "shape",
+          data: {
+            viewBox: [200, 200],
+            path: "M 0 0 L 200 0 L 200 200 L 0 200 Z"
+          }
+        })
+      } else if (key === KEYS.O) {
+        mainStore.setCreatingElement({
+          type: "shape",
+          data: {
+            viewBox: [200, 200],
+            path: "M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z"
+          }
+        })
+      } else if (key === KEYS.L) {
+        mainStore.setCreatingElement({
+          type: "line",
+          data: {
+            path: "M 0 0 L 20 20",
+            style: "solid",
+            points: ["", ""]
+          }
+        })
       }
     }
   }
-  
+
   const keyupListener = () => {
     if (ctrlKeyState.value) keyboardStore.setCtrlKeyState(false)
     if (shiftKeyState.value) keyboardStore.setShiftKeyState(false)
@@ -308,13 +293,13 @@ export default () => {
   }
 
   onMounted(() => {
-    document.addEventListener('keydown', keydownListener)
-    document.addEventListener('keyup', keyupListener)
-    window.addEventListener('blur', keyupListener)
+    document.addEventListener("keydown", keydownListener)
+    document.addEventListener("keyup", keyupListener)
+    window.addEventListener("blur", keyupListener)
   })
   onUnmounted(() => {
-    document.removeEventListener('keydown', keydownListener)
-    document.removeEventListener('keyup', keyupListener)
-    window.removeEventListener('blur', keyupListener)
+    document.removeEventListener("keydown", keydownListener)
+    document.removeEventListener("keyup", keyupListener)
+    window.removeEventListener("blur", keyupListener)
   })
-}
+}

+ 129 - 172
src/hooks/useImport.ts

@@ -1,14 +1,11 @@
-import { ref } from 'vue'
-import { storeToRefs } from 'pinia'
-import { parse, type Shape, type Element, type ChartItem } from 'pptxtojson'
-import { nanoid } from 'nanoid'
-import { useSlidesStore } from '@/store'
-import { decrypt } from '@/utils/crypto'
-import { type ShapePoolItem, SHAPE_LIST, SHAPE_PATH_FORMULAS } from '@/configs/shapes'
-import useAddSlidesOrElements from '@/hooks/useAddSlidesOrElements'
-import useSlideHandler from '@/hooks/useSlideHandler'
-import message from '@/utils/message'
-import { getSvgPathRange } from '@/utils/svgPathParser'
+import { ref } from "vue"
+import { storeToRefs } from "pinia"
+import { parse, type Shape, type Element, type ChartItem } from "pptxtojson"
+import { nanoid } from "nanoid"
+import { useSlidesStore } from "@/store"
+import { type ShapePoolItem, SHAPE_LIST, SHAPE_PATH_FORMULAS } from "@/configs/shapes"
+import message from "@/utils/message"
+import { getSvgPathRange } from "@/utils/svgPathParser"
 import type {
   Slide,
   TableCellStyle,
@@ -19,8 +16,8 @@ import type {
   PPTLineElement,
   ShapeTextAlign,
   PPTTextElement,
-  ChartOptions,
-} from '@/types/slides'
+  ChartOptions
+} from "@/types/slides"
 
 const convertFontSizePtToPx = (html: string, ratio: number) => {
   return html.replace(/font-size:\s*([\d.]+)pt/g, (match, p1) => {
@@ -32,56 +29,32 @@ export default () => {
   const slidesStore = useSlidesStore()
   const { theme } = storeToRefs(useSlidesStore())
 
-  const { addSlidesFromData } = useAddSlidesOrElements()
-  const { isEmptySlide } = useSlideHandler()
-
   const exporting = ref(false)
 
-  // 导入pptist文件
-  const importSpecificFile = (files: FileList, cover = false) => {
-    const file = files[0]
-
-    const reader = new FileReader()
-    reader.addEventListener('load', () => {
-      try {
-        const slides = JSON.parse(decrypt(reader.result as string))
-        if (cover) {
-          slidesStore.updateSlideIndex(0)
-          slidesStore.setSlides(slides)
-        }
-        else if (isEmptySlide.value) slidesStore.setSlides(slides)
-        else addSlidesFromData(slides)
-      }
-      catch {
-        message.error('无法正确读取 / 解析该文件')
-      }
-    })
-    reader.readAsText(file)
-  }
-
   const parseLineElement = (el: Shape) => {
     let start: [number, number] = [0, 0]
     let end: [number, number] = [0, 0]
 
-    if (!el.isFlipV && !el.isFlipH) { // 右下
+    if (!el.isFlipV && !el.isFlipH) {
+      // 右下
       start = [0, 0]
       end = [el.width, el.height]
-    }
-    else if (el.isFlipV && el.isFlipH) { // 左上
+    } else if (el.isFlipV && el.isFlipH) {
+      // 左上
       start = [el.width, el.height]
       end = [0, 0]
-    }
-    else if (el.isFlipV && !el.isFlipH) { // 右上
+    } else if (el.isFlipV && !el.isFlipH) {
+      // 右上
       start = [0, el.height]
       end = [el.width, 0]
-    }
-    else { // 左下
+    } else {
+      // 左下
       start = [el.width, 0]
       end = [0, el.height]
     }
 
     const data: PPTLineElement = {
-      type: 'line',
+      type: "line",
       id: nanoid(10),
       width: el.borderWidth || 1,
       left: el.left,
@@ -90,13 +63,10 @@ export default () => {
       end,
       style: el.borderType,
       color: el.borderColor,
-      points: ['', /straightConnector/.test(el.shapType) ? 'arrow' : '']
+      points: ["", /straightConnector/.test(el.shapType) ? "arrow" : ""]
     }
     if (/bentConnector/.test(el.shapType)) {
-      data.broken2 = [
-        Math.abs(start[0] - end[0]) / 2,
-        Math.abs(start[1] - end[1]) / 2,
-      ]
+      data.broken2 = [Math.abs(start[0] - end[0]) / 2, Math.abs(start[1] - end[1]) / 2]
     }
 
     return data
@@ -113,7 +83,7 @@ export default () => {
     for (const item of SHAPE_LIST) {
       shapeList.push(...item.children)
     }
-    
+
     const reader = new FileReader()
     reader.onload = async e => {
       const json = await parse(e.target!.result as ArrayBuffer)
@@ -127,39 +97,37 @@ export default () => {
       for (const item of json.slides) {
         const { type, value } = item.fill
         let background: SlideBackground
-        if (type === 'image') {
+        if (type === "image") {
           background = {
-            type: 'image',
+            type: "image",
             image: {
               src: value.picBase64,
-              size: 'cover',
-            },
+              size: "cover"
+            }
           }
-        }
-        else if (type === 'gradient') {
+        } else if (type === "gradient") {
           background = {
-            type: 'gradient',
+            type: "gradient",
             gradient: {
-              type: 'linear',
+              type: "linear",
               colors: value.colors.map(item => ({
                 ...item,
-                pos: parseInt(item.pos),
+                pos: parseInt(item.pos)
               })),
-              rotate: value.rot,
-            },
+              rotate: value.rot
+            }
           }
-        }
-        else {
+        } else {
           background = {
-            type: 'solid',
-            color: value,
+            type: "solid",
+            color: value
           }
         }
 
         const slide: Slide = {
           id: nanoid(10),
           elements: [],
-          background,
+          background
         }
 
         const parseElements = (elements: Element[]) => {
@@ -173,10 +141,10 @@ export default () => {
             el.height = el.height * ratio
             el.left = el.left * ratio
             el.top = el.top * ratio
-  
-            if (el.type === 'text') {
+
+            if (el.type === "text") {
               const textEl: PPTTextElement = {
-                type: 'text',
+                type: "text",
                 id: nanoid(10),
                 width: el.width,
                 height: el.height,
@@ -190,24 +158,23 @@ export default () => {
                 outline: {
                   color: el.borderColor,
                   width: el.borderWidth,
-                  style: el.borderType,
+                  style: el.borderType
                 },
                 fill: el.fillColor,
-                vertical: el.isVertical,
+                vertical: el.isVertical
               }
               if (el.shadow) {
                 textEl.shadow = {
                   h: el.shadow.h * ratio,
                   v: el.shadow.v * ratio,
                   blur: el.shadow.blur * ratio,
-                  color: el.shadow.color,
+                  color: el.shadow.color
                 }
               }
               slide.elements.push(textEl)
-            }
-            else if (el.type === 'image') {
+            } else if (el.type === "image") {
               slide.elements.push({
-                type: 'image',
+                type: "image",
                 id: nanoid(10),
                 src: el.src,
                 width: el.width,
@@ -217,12 +184,11 @@ export default () => {
                 fixedRatio: true,
                 rotate: el.rotate,
                 flipH: el.isFlipH,
-                flipV: el.isFlipV,
+                flipV: el.isFlipV
               })
-            }
-            else if (el.type === 'audio') {
+            } else if (el.type === "audio") {
               slide.elements.push({
-                type: 'audio',
+                type: "audio",
                 id: nanoid(10),
                 src: el.blob,
                 width: el.width,
@@ -233,12 +199,11 @@ export default () => {
                 fixedRatio: false,
                 color: theme.value.themeColor,
                 loop: false,
-                autoplay: false,
+                autoplay: false
               })
-            }
-            else if (el.type === 'video') {
+            } else if (el.type === "video") {
               slide.elements.push({
-                type: 'video',
+                type: "video",
                 id: nanoid(10),
                 src: (el.blob || el.src)!,
                 width: el.width,
@@ -246,95 +211,91 @@ export default () => {
                 left: el.left,
                 top: el.top,
                 rotate: 0,
-                autoplay: false,
+                autoplay: false
               })
-            }
-            else if (el.type === 'shape') {
-              if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
+            } else if (el.type === "shape") {
+              if (el.shapType === "line" || /Connector/.test(el.shapType)) {
                 const lineElement = parseLineElement(el)
                 slide.elements.push(lineElement)
-              }
-              else {
+              } else {
                 const shape = shapeList.find(item => item.pptxShapeType === el.shapType)
 
                 const vAlignMap: { [key: string]: ShapeTextAlign } = {
-                  'mid': 'middle',
-                  'down': 'bottom',
-                  'up': 'top',
+                  mid: "middle",
+                  down: "bottom",
+                  up: "top"
                 }
-                
+
                 const element: PPTShapeElement = {
-                  type: 'shape',
+                  type: "shape",
                   id: nanoid(10),
                   width: el.width,
                   height: el.height,
                   left: el.left,
                   top: el.top,
                   viewBox: [200, 200],
-                  path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
-                  fill: el.fillColor || 'none',
+                  path: "M 0 0 L 200 0 L 200 200 L 0 200 Z",
+                  fill: el.fillColor || "none",
                   fixedRatio: false,
                   rotate: el.rotate,
                   outline: {
                     color: el.borderColor,
                     width: el.borderWidth,
-                    style: el.borderType,
+                    style: el.borderType
                   },
                   text: {
                     content: convertFontSizePtToPx(el.content, ratio),
                     defaultFontName: theme.value.fontName,
                     defaultColor: theme.value.fontColor,
-                    align: vAlignMap[el.vAlign] || 'middle',
+                    align: vAlignMap[el.vAlign] || "middle"
                   },
                   flipH: el.isFlipH,
-                  flipV: el.isFlipV,
+                  flipV: el.isFlipV
                 }
                 if (el.shadow) {
                   element.shadow = {
                     h: el.shadow.h * ratio,
                     v: el.shadow.v * ratio,
                     blur: el.shadow.blur * ratio,
-                    color: el.shadow.color,
+                    color: el.shadow.color
                   }
                 }
-    
+
                 if (shape) {
                   element.path = shape.path
                   element.viewBox = shape.viewBox
-    
+
                   if (shape.pathFormula) {
                     element.pathFormula = shape.pathFormula
                     element.viewBox = [el.width, el.height]
-    
+
                     const pathFormula = SHAPE_PATH_FORMULAS[shape.pathFormula]
-                    if ('editable' in pathFormula && pathFormula.editable) {
+                    if ("editable" in pathFormula && pathFormula.editable) {
                       element.path = pathFormula.formula(el.width, el.height, pathFormula.defaultValue)
                       element.keypoints = pathFormula.defaultValue
-                    }
-                    else element.path = pathFormula.formula(el.width, el.height)
+                    } else element.path = pathFormula.formula(el.width, el.height)
                   }
                 }
-                if (el.shapType === 'custom') {
-                  if (el.path!.indexOf('NaN') !== -1) element.path = ''
+                if (el.shapType === "custom") {
+                  if (el.path!.indexOf("NaN") !== -1) element.path = ""
                   else {
                     element.special = true
                     element.path = el.path!
-  
+
                     const { maxX, maxY } = getSvgPathRange(element.path)
                     element.viewBox = [maxX || originWidth, maxY || originHeight]
                   }
                 }
-    
+
                 if (element.path) slide.elements.push(element)
               }
-            }
-            else if (el.type === 'table') {
+            } else if (el.type === "table") {
               const row = el.data.length
               const col = el.data[0].length
-  
+
               const style: TableCellStyle = {
                 fontname: theme.value.fontName,
-                color: theme.value.fontColor,
+                color: theme.value.fontColor
               }
               const data: TableCell[][] = []
               for (let i = 0; i < row; i++) {
@@ -342,14 +303,14 @@ export default () => {
                 for (let j = 0; j < col; j++) {
                   const cellData = el.data[i][j]
 
-                  let textDiv: HTMLDivElement | null = document.createElement('div')
+                  let textDiv: HTMLDivElement | null = document.createElement("div")
                   textDiv.innerHTML = cellData.text
-                  const p = textDiv.querySelector('p')
-                  const align = p?.style.textAlign || 'left'
+                  const p = textDiv.querySelector("p")
+                  const align = p?.style.textAlign || "left"
 
-                  const span = textDiv.querySelector('span')
-                  const fontsize = span?.style.fontSize ? (parseInt(span?.style.fontSize) * ratio).toFixed(1) + 'px' : ''
-                  const fontname = span?.style.fontFamily || ''
+                  const span = textDiv.querySelector("span")
+                  const fontsize = span?.style.fontSize ? (parseInt(span?.style.fontSize) * ratio).toFixed(1) + "px" : ""
+                  const fontname = span?.style.fontFamily || ""
                   const color = span?.style.color || cellData.fontColor
 
                   rowCells.push({
@@ -359,23 +320,23 @@ export default () => {
                     text: textDiv.innerText,
                     style: {
                       ...style,
-                      align: ['left', 'right', 'center'].includes(align) ? (align as 'left' | 'right' | 'center') : 'left',
+                      align: ["left", "right", "center"].includes(align) ? (align as "left" | "right" | "center") : "left",
                       fontsize,
                       fontname,
                       color,
                       bold: cellData.fontBold,
-                      backcolor: cellData.fillColor,
-                    },
+                      backcolor: cellData.fillColor
+                    }
                   })
                   textDiv = null
                 }
                 data.push(rowCells)
               }
-  
+
               const colWidths: number[] = new Array(col).fill(1 / col)
-  
+
               slide.elements.push({
-                type: 'table',
+                type: "table",
                 id: nanoid(10),
                 width: el.width,
                 height: el.height,
@@ -387,22 +348,20 @@ export default () => {
                 outline: {
                   width: el.borderWidth || 2,
                   style: el.borderType,
-                  color: el.borderColor || '#eeece1',
+                  color: el.borderColor || "#eeece1"
                 },
-                cellMinHeight: 36,
+                cellMinHeight: 36
               })
-            }
-            else if (el.type === 'chart') {
+            } else if (el.type === "chart") {
               let labels: string[]
               let legends: string[]
               let series: number[][]
-  
-              if (el.chartType === 'scatterChart' || el.chartType === 'bubbleChart') {
+
+              if (el.chartType === "scatterChart" || el.chartType === "bubbleChart") {
                 labels = el.data[0].map((item, index) => `坐标${index + 1}`)
-                legends = ['X', 'Y']
+                legends = ["X", "Y"]
                 series = el.data
-              }
-              else {
+              } else {
                 const data = el.data as ChartItem[]
                 labels = Object.values(data[0].xlabels)
                 legends = data.map(item => item.key)
@@ -410,45 +369,45 @@ export default () => {
               }
 
               const options: ChartOptions = {}
-  
-              let chartType: ChartType = 'bar'
+
+              let chartType: ChartType = "bar"
 
               switch (el.chartType) {
-                case 'barChart':
-                case 'bar3DChart':
-                  chartType = 'bar'
-                  if (el.barDir === 'bar') chartType = 'column'
-                  if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
+                case "barChart":
+                case "bar3DChart":
+                  chartType = "bar"
+                  if (el.barDir === "bar") chartType = "column"
+                  if (el.grouping === "stacked" || el.grouping === "percentStacked") options.stack = true
                   break
-                case 'lineChart':
-                case 'line3DChart':
-                  if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
-                  chartType = 'line'
+                case "lineChart":
+                case "line3DChart":
+                  if (el.grouping === "stacked" || el.grouping === "percentStacked") options.stack = true
+                  chartType = "line"
                   break
-                case 'areaChart':
-                case 'area3DChart':
-                  if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
-                  chartType = 'area'
+                case "areaChart":
+                case "area3DChart":
+                  if (el.grouping === "stacked" || el.grouping === "percentStacked") options.stack = true
+                  chartType = "area"
                   break
-                case 'scatterChart':
-                case 'bubbleChart':
-                  chartType = 'scatter'
+                case "scatterChart":
+                case "bubbleChart":
+                  chartType = "scatter"
                   break
-                case 'pieChart':
-                case 'pie3DChart':
-                  chartType = 'pie'
+                case "pieChart":
+                case "pie3DChart":
+                  chartType = "pie"
                   break
-                case 'radarChart':
-                  chartType = 'radar'
+                case "radarChart":
+                  chartType = "radar"
                   break
-                case 'doughnutChart':
-                  chartType = 'ring'
+                case "doughnutChart":
+                  chartType = "ring"
                   break
                 default:
               }
-  
+
               slide.elements.push({
-                type: 'chart',
+                type: "chart",
                 id: nanoid(10),
                 chartType: chartType,
                 width: el.width,
@@ -461,16 +420,15 @@ export default () => {
                 data: {
                   labels,
                   legends,
-                  series,
+                  series
                 },
-                options,
+                options
               })
-            }
-            else if (el.type === 'group' || el.type === 'diagram') {
+            } else if (el.type === "group" || el.type === "diagram") {
               const elements = el.elements.map(_el => ({
                 ..._el,
                 left: _el.left + originLeft,
-                top: _el.top + originTop,
+                top: _el.top + originTop
               }))
               parseElements(elements)
             }
@@ -487,8 +445,7 @@ export default () => {
   }
 
   return {
-    importSpecificFile,
     importPPTXFile,
-    exporting,
+    exporting
   }
-}
+}

+ 35 - 39
src/hooks/useSlideHandler.ts

@@ -1,16 +1,16 @@
-import { computed } from 'vue'
-import { storeToRefs } from 'pinia'
-import { nanoid } from 'nanoid'
-import { useMainStore, useSlidesStore } from '@/store'
-import type { Slide } from '@/types/slides'
-import { copyText, readClipboard } from '@/utils/clipboard'
-import { encrypt } from '@/utils/crypto'
-import { createElementIdMap } from '@/utils/element'
-import { KEYS } from '@/configs/hotkey'
-import message from '@/utils/message'
-import usePasteTextClipboardData from '@/hooks/usePasteTextClipboardData'
-import useHistorySnapshot from '@/hooks/useHistorySnapshot'
-import useAddSlidesOrElements from '@/hooks/useAddSlidesOrElements'
+import { computed } from "vue"
+import { storeToRefs } from "pinia"
+import { nanoid } from "nanoid"
+import { useMainStore, useSlidesStore } from "@/store"
+import type { Slide } from "@/types/slides"
+import { copyText, readClipboard } from "@/utils/clipboard"
+import { encrypt } from "@/utils/crypto"
+import { createElementIdMap } from "@/utils/element"
+import { KEYS } from "@/configs/hotkey"
+import message from "@/utils/message"
+import usePasteTextClipboardData from "@/hooks/usePasteTextClipboardData"
+import useHistorySnapshot from "@/hooks/useHistorySnapshot"
+import useAddSlidesOrElements from "@/hooks/useAddSlidesOrElements"
 
 export default () => {
   const mainStore = useMainStore()
@@ -32,9 +32,9 @@ export default () => {
       id: nanoid(10),
       elements: [],
       background: {
-        type: 'solid',
-        color: theme.value.backgroundColor,
-      },
+        type: "solid",
+        color: theme.value.backgroundColor
+      }
     }
     slidesStore.updateSlideIndex(0)
     mainStore.setActiveElementIdList([])
@@ -49,8 +49,7 @@ export default () => {
     if (command === KEYS.UP && slideIndex.value > 0) {
       if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])
       slidesStore.updateSlideIndex(slideIndex.value - 1)
-    }
-    else if (command === KEYS.DOWN && slideIndex.value < slides.value.length - 1) {
+    } else if (command === KEYS.DOWN && slideIndex.value < slides.value.length - 1) {
       if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])
       slidesStore.updateSlideIndex(slideIndex.value + 1)
     }
@@ -58,10 +57,12 @@ export default () => {
 
   // 将当前页面数据加密后复制到剪贴板
   const copySlide = () => {
-    const text = encrypt(JSON.stringify({
-      type: 'slides',
-      data: selectedSlides.value,
-    }))
+    const text = encrypt(
+      JSON.stringify({
+        type: "slides",
+        data: selectedSlides.value
+      })
+    )
 
     copyText(text).then(() => {
       mainStore.setThumbnailsFocus(true)
@@ -70,9 +71,11 @@ export default () => {
 
   // 尝试将剪贴板页面数据解密后添加到下一页(粘贴)
   const pasteSlide = () => {
-    readClipboard().then(text => {
-      pasteTextClipboardData(text, { onlySlide: true })
-    }).catch(err => message.warning(err))
+    readClipboard()
+      .then(text => {
+        pasteTextClipboardData(text, { onlySlide: true })
+      })
+      .catch(err => message.warning(err))
   }
 
   // 创建一页空白页并添加到下一页
@@ -81,9 +84,9 @@ export default () => {
       id: nanoid(10),
       elements: [],
       background: {
-        type: 'solid',
-        color: theme.value.backgroundColor,
-      },
+        type: "solid",
+        color: theme.value.backgroundColor
+      }
     }
     mainStore.setActiveElementIdList([])
     slidesStore.addSlide(emptySlide)
@@ -100,7 +103,7 @@ export default () => {
     }
     const newSlide = {
       ...slide,
-      id: nanoid(10),
+      id: nanoid(10)
     }
     mainStore.setActiveElementIdList([])
     slidesStore.addSlide(newSlide)
@@ -141,7 +144,7 @@ export default () => {
   // 拖拽调整幻灯片顺序同步数据
   const sortSlides = (newIndex: number, oldIndex: number) => {
     if (oldIndex === newIndex) return
-  
+
     const _slides: Slide[] = JSON.parse(JSON.stringify(slides.value))
 
     const movingSlide = _slides[oldIndex]
@@ -168,12 +171,6 @@ export default () => {
     slidesStore.updateSlideIndex(newIndex)
   }
 
-  const isEmptySlide = computed(() => {
-    if (slides.value.length > 1) return false
-    if (slides.value[0].elements.length > 0) return false
-    return true
-  })
-
   return {
     resetSlides,
     updateSlideIndex,
@@ -185,7 +182,6 @@ export default () => {
     deleteSlide,
     cutSlide,
     selectAllSlide,
-    sortSlides,
-    isEmptySlide,
+    sortSlides
   }
-}
+}

+ 1 - 1
src/types/export.ts

@@ -1 +1 @@
-export type DialogForExportTypes = 'image' | 'pdf' | 'json' | 'pptx' | 'pptist' | ''
+export type DialogForExportTypes = "image" | "pdf" | "json" | "pptx" | ""

+ 23 - 38
src/views/Editor/EditorHeader/index.vue

@@ -4,17 +4,6 @@
       <Popover trigger="click" placement="bottom-start" v-model:value="mainMenuVisible">
         <template #content>
           <FileInput
-            accept=".pptist"
-            @change="
-              files => {
-                importSpecificFile(files)
-                mainMenuVisible = false
-              }
-            "
-          >
-            <PopoverMenuItem>导入 pptist 文件</PopoverMenuItem>
-          </FileInput>
-          <FileInput
             accept="application/vnd.openxmlformats-officedocument.presentationml.presentation"
             @change="
               files => {
@@ -23,9 +12,9 @@
               }
             "
           >
-            <PopoverMenuItem>导入 pptx 文件(测试版)</PopoverMenuItem>
+            <PopoverMenuItem>导入pptx</PopoverMenuItem>
           </FileInput>
-          <PopoverMenuItem @click="setDialogForExport('pptx')">导出文件</PopoverMenuItem>
+          <PopoverMenuItem @click="setDialogForExport('pptx')">导出pptx</PopoverMenuItem>
           <PopoverMenuItem
             @click="
               () => {
@@ -49,7 +38,7 @@
       </Popover>
 
       <div class="title">
-        <Input class="title-input" ref="titleInputRef" v-model:value="titleValue" @blur="handleUpdateTitle()" v-if="editingTitle"></Input>
+        <Input class="title-input" ref="titleInputRef" v-model:value="titleValue" @blur="handleUpdateTitle()" v-if="editingTitle" />
         <div class="title-text" @click="startEditTitle()" :title="title" v-else>{{ title }}</div>
       </div>
     </div>
@@ -67,9 +56,9 @@
           <div class="arrow-btn"><IconDown class="arrow" /></div>
         </Popover>
       </div>
-      <div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')">
+      <!-- <div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')">
         <IconDownload class="icon" />
-      </div>
+      </div> -->
     </div>
 
     <Drawer :width="320" v-model:visible="hotkeyDrawerVisible" placement="right">
@@ -82,34 +71,34 @@
 </template>
 
 <script lang="ts" setup>
-import { nextTick, ref } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useMainStore, useSlidesStore } from '@/store'
-import useScreening from '@/hooks/useScreening'
-import useImport from '@/hooks/useImport'
-import useSlideHandler from '@/hooks/useSlideHandler'
-import type { DialogForExportTypes } from '@/types/export'
-
-import HotkeyDoc from './HotkeyDoc.vue'
-import FileInput from '@/components/FileInput.vue'
-import FullscreenSpin from '@/components/FullscreenSpin.vue'
-import Drawer from '@/components/Drawer.vue'
-import Input from '@/components/Input.vue'
-import Popover from '@/components/Popover.vue'
-import PopoverMenuItem from '@/components/PopoverMenuItem.vue'
+import { nextTick, ref } from "vue"
+import { storeToRefs } from "pinia"
+import { useMainStore, useSlidesStore } from "@/store"
+import useScreening from "@/hooks/useScreening"
+import useImport from "@/hooks/useImport"
+import useSlideHandler from "@/hooks/useSlideHandler"
+import type { DialogForExportTypes } from "@/types/export"
+
+import HotkeyDoc from "./HotkeyDoc.vue"
+import FileInput from "@/components/FileInput.vue"
+import FullscreenSpin from "@/components/FullscreenSpin.vue"
+import Drawer from "@/components/Drawer.vue"
+import Input from "@/components/Input.vue"
+import Popover from "@/components/Popover.vue"
+import PopoverMenuItem from "@/components/PopoverMenuItem.vue"
 
 const mainStore = useMainStore()
 const slidesStore = useSlidesStore()
 const { title } = storeToRefs(slidesStore)
 const { enterScreening, enterScreeningFromStart } = useScreening()
-const { importSpecificFile, importPPTXFile, exporting } = useImport()
+const { importPPTXFile, exporting } = useImport()
 const { resetSlides } = useSlideHandler()
 
 const mainMenuVisible = ref(false)
 const hotkeyDrawerVisible = ref(false)
 const editingTitle = ref(false)
 const titleInputRef = ref<InstanceType<typeof Input>>()
-const titleValue = ref('')
+const titleValue = ref("")
 
 const startEditTitle = () => {
   titleValue.value = title.value
@@ -122,11 +111,7 @@ const handleUpdateTitle = () => {
   editingTitle.value = false
 }
 
-const goLink = (url: string) => {
-  window.open(url)
-  mainMenuVisible.value = false
-}
-
+/* 导出老逻辑 */
 const setDialogForExport = (type: DialogForExportTypes) => {
   mainStore.setDialogForExport(type)
   mainMenuVisible.value = false

+ 35 - 55
src/views/Editor/ExportDialog/ExportImage.vue

@@ -2,64 +2,44 @@
   <div class="export-img-dialog">
     <div class="thumbnails-view">
       <div class="thumbnails" ref="imageThumbnailsRef">
-        <ThumbnailSlide 
-          class="thumbnail" 
-          v-for="slide in renderSlides" 
-          :key="slide.id" 
-          :slide="slide" 
-          :size="1600" 
-        />
+        <ThumbnailSlide class="thumbnail" v-for="slide in renderSlides" :key="slide.id" :slide="slide" :size="1600" />
       </div>
     </div>
     <div class="configs">
       <div class="row">
         <div class="title">导出格式:</div>
-        <RadioGroup
-          class="config-item"
-          v-model:value="format"
-        >
-          <RadioButton style="width: 50%;" value="jpeg">JPEG</RadioButton>
-          <RadioButton style="width: 50%;" value="png">PNG</RadioButton>
+        <RadioGroup class="config-item" v-model:value="format">
+          <RadioButton style="width: 50%" value="jpeg">JPEG</RadioButton>
+          <RadioButton style="width: 50%" value="png">PNG</RadioButton>
         </RadioGroup>
       </div>
       <div class="row">
         <div class="title">导出范围:</div>
-        <RadioGroup
-          class="config-item"
-          v-model:value="rangeType"
-        >
-          <RadioButton style="width: 33.33%;" value="all">全部</RadioButton>
-          <RadioButton style="width: 33.33%;" value="current">当前页</RadioButton>
-          <RadioButton style="width: 33.33%;" value="custom">自定义</RadioButton>
+        <RadioGroup class="config-item" v-model:value="rangeType">
+          <RadioButton style="width: 33.33%" value="all">全部</RadioButton>
+          <RadioButton style="width: 33.33%" value="current">当前页</RadioButton>
+          <RadioButton style="width: 33.33%" value="custom">自定义</RadioButton>
         </RadioGroup>
       </div>
       <div class="row" v-if="rangeType === 'custom'">
         <div class="title" :data-range="`(${range[0]} ~ ${range[1]})`">自定义范围:</div>
-        <Slider
-          class="config-item"
-          range
-          :min="1"
-          :max="slides.length"
-          :step="1"
-          v-model:value="range"
-        />
+        <Slider class="config-item" range :min="1" :max="slides.length" :step="1" v-model:value="range" />
       </div>
 
       <div class="row">
         <div class="title">图片质量:</div>
-        <Slider
-          class="config-item"
-          :min="0"
-          :max="1"
-          :step="0.1"
-          v-model:value="quality"
-        />
+        <Slider class="config-item" :min="0" :max="1" :step="0.1" v-model:value="quality" />
       </div>
 
       <div class="row">
         <div class="title">忽略在线字体:</div>
         <div class="config-item">
-          <Switch v-model:value="ignoreWebfont" v-tooltip="'导出时默认忽略在线字体,若您在幻灯片中使用了在线字体,且希望导出后保留相关样式,可选择关闭【忽略在线字体】选项,但要注意这将会增加导出用时。'" />
+          <Switch
+            v-model:value="ignoreWebfont"
+            v-tooltip="
+              '导出时默认忽略在线字体,若您在幻灯片中使用了在线字体,且希望导出后保留相关样式,可选择关闭【忽略在线字体】选项,但要注意这将会增加导出用时。'
+            "
+          />
         </div>
       </div>
     </div>
@@ -74,35 +54,35 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useSlidesStore } from '@/store'
-import useExport from '@/hooks/useExport'
-
-import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
-import FullscreenSpin from '@/components/FullscreenSpin.vue'
-import Switch from '@/components/Switch.vue'
-import Slider from '@/components/Slider.vue'
-import Button from '@/components/Button.vue'
-import RadioButton from '@/components/RadioButton.vue'
-import RadioGroup from '@/components/RadioGroup.vue'
+import { computed, ref } from "vue"
+import { storeToRefs } from "pinia"
+import { useSlidesStore } from "@/store"
+import useExport from "@/hooks/useExport"
+
+import ThumbnailSlide from "@/views/components/ThumbnailSlide/index.vue"
+import FullscreenSpin from "@/components/FullscreenSpin.vue"
+import Switch from "@/components/Switch.vue"
+import Slider from "@/components/Slider.vue"
+import Button from "@/components/Button.vue"
+import RadioButton from "@/components/RadioButton.vue"
+import RadioGroup from "@/components/RadioGroup.vue"
 
 const emit = defineEmits<{
-  (event: 'close'): void
+  (event: "close"): void
 }>()
 
 const { slides, currentSlide } = storeToRefs(useSlidesStore())
 
 const imageThumbnailsRef = ref<HTMLElement>()
-const rangeType = ref<'all' | 'current' | 'custom'>('all')
+const rangeType = ref<"all" | "current" | "custom">("all")
 const range = ref<[number, number]>([1, slides.value.length])
-const format = ref<'jpeg' | 'png'>('jpeg')
+const format = ref<"jpeg" | "png">("jpeg")
 const quality = ref(1)
 const ignoreWebfont = ref(true)
 
 const renderSlides = computed(() => {
-  if (rangeType.value === 'all') return slides.value
-  if (rangeType.value === 'current') return [currentSlide.value]
+  if (rangeType.value === "all") return slides.value
+  if (rangeType.value === "current") return [currentSlide.value]
   return slides.value.filter((item, index) => {
     const [min, max] = range.value
     return index >= min - 1 && index <= max - 1
@@ -131,7 +111,7 @@ const expImage = () => {
   @include absolute-0();
 
   &::after {
-    content: '';
+    content: "";
     background-color: #fff;
     @include absolute-0();
   }
@@ -182,4 +162,4 @@ const expImage = () => {
     margin-left: 10px;
   }
 }
-</style>
+</style>

+ 0 - 130
src/views/Editor/ExportDialog/ExportSpecificFile.vue

@@ -1,130 +0,0 @@
-<template>
-  <div class="export-pptist-dialog">
-    <div class="configs">
-      <div class="row">
-        <div class="title">导出范围:</div>
-        <RadioGroup
-          class="config-item"
-          v-model:value="rangeType"
-        >
-          <RadioButton style="width: 33.33%;" value="all">全部</RadioButton>
-          <RadioButton style="width: 33.33%;" value="current">当前页</RadioButton>
-          <RadioButton style="width: 33.33%;" value="custom">自定义</RadioButton>
-        </RadioGroup>
-      </div>
-      <div class="row" v-if="rangeType === 'custom'">
-        <div class="title" :data-range="`(${range[0]} ~ ${range[1]})`">自定义范围:</div>
-        <Slider
-          class="config-item"
-          range
-          :min="1"
-          :max="slides.length"
-          :step="1"
-          v-model:value="range"
-        />
-      </div>
-      <div class="tip">
-        提示:.pptist 是本应用的特有文件后缀,支持将该类型的文件导入回应用中。
-      </div>
-    </div>
-    <div class="btns">
-      <Button class="btn export" type="primary" @click="exportSpecificFile(selectedSlides)">导出 .pptist 文件</Button>
-      <Button class="btn close" @click="emit('close')">关闭</Button>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { computed, ref } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useSlidesStore } from '@/store'
-import useExport from '@/hooks/useExport'
-
-import Slider from '@/components/Slider.vue'
-import Button from '@/components/Button.vue'
-import RadioButton from '@/components/RadioButton.vue'
-import RadioGroup from '@/components/RadioGroup.vue'
-
-const emit = defineEmits<{
-  (event: 'close'): void
-}>()
-
-const { slides, currentSlide } = storeToRefs(useSlidesStore())
-
-const { exportSpecificFile } = useExport()
-
-const rangeType = ref<'all' | 'current' | 'custom'>('all')
-const range = ref<[number, number]>([1, slides.value.length])
-
-const selectedSlides = computed(() => {
-  if (rangeType.value === 'all') return slides.value
-  if (rangeType.value === 'current') return [currentSlide.value]
-  return slides.value.filter((item, index) => {
-    const [min, max] = range.value
-    return index >= min - 1 && index <= max - 1
-  })
-})
-</script>
-
-<style lang="scss" scoped>
-.export-pptist-dialog {
-  height: 100%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: column;
-  position: relative;
-  overflow: hidden;
-}
-.configs {
-  width: 350px;
-  height: calc(100% - 100px);
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-
-  .row {
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    margin-bottom: 25px;
-  }
-
-  .title {
-    width: 100px;
-    position: relative;
-
-    &::after {
-      content: attr(data-range);
-      position: absolute;
-      top: 20px;
-      left: 0;
-    }
-  }
-  .config-item {
-    flex: 1;
-  }
-
-  .tip {
-    font-size: 12px;
-    color: #aaa;
-    line-height: 1.8;
-    margin-top: 25px;
-  }
-}
-.btns {
-  width: 300px;
-  height: 100px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-
-  .export {
-    flex: 1;
-  }
-  .close {
-    width: 100px;
-    margin-left: 10px;
-  }
-}
-</style>

+ 20 - 28
src/views/Editor/ExportDialog/index.vue

@@ -1,29 +1,23 @@
 <template>
   <div class="export-dialog">
-    <Tabs 
-      :tabs="tabs" 
-      :value="dialogForExport" 
-      card
-      @update:value="key => setDialogForExport(key as DialogForExportTypes)" 
-    />
+    <Tabs :tabs="tabs" :value="dialogForExport" card @update:value="key => setDialogForExport(key as DialogForExportTypes)" />
     <div class="content">
-      <component :is="currentDialogComponent" @close="setDialogForExport('')"></component>
+      <component :is="currentDialogComponent" @close="setDialogForExport('')" />
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useMainStore } from '@/store'
-import type { DialogForExportTypes } from '@/types/export'
+import { computed } from "vue"
+import { storeToRefs } from "pinia"
+import { useMainStore } from "@/store"
+import type { DialogForExportTypes } from "@/types/export"
 
-import ExportImage from './ExportImage.vue'
-import ExportJSON from './ExportJSON.vue'
-import ExportPDF from './ExportPDF.vue'
-import ExportPPTX from './ExportPPTX.vue'
-import ExportSpecificFile from './ExportSpecificFile.vue'
-import Tabs from '@/components/Tabs.vue'
+import ExportImage from "./ExportImage.vue"
+import ExportJSON from "./ExportJSON.vue"
+import ExportPDF from "./ExportPDF.vue"
+import ExportPPTX from "./ExportPPTX.vue"
+import Tabs from "@/components/Tabs.vue"
 
 interface TabItem {
   key: DialogForExportTypes
@@ -36,20 +30,18 @@ const { dialogForExport } = storeToRefs(mainStore)
 const setDialogForExport = mainStore.setDialogForExport
 
 const tabs: TabItem[] = [
-  { key: 'pptist', label: '导出 pptist 文件' },
-  { key: 'pptx', label: '导出 PPTX' },
-  { key: 'image', label: '导出图片' },
-  { key: 'json', label: '导出 JSON' },
-  { key: 'pdf', label: '打印 / 导出 PDF' },
+  { key: "pptx", label: "导出 PPTX" },
+  { key: "image", label: "导出图片" },
+  { key: "json", label: "导出 JSON" },
+  { key: "pdf", label: "打印 / 导出 PDF" }
 ]
 
 const currentDialogComponent = computed<unknown>(() => {
   const dialogMap = {
-    'image': ExportImage,
-    'json': ExportJSON,
-    'pdf': ExportPDF,
-    'pptx': ExportPPTX,
-    'pptist': ExportSpecificFile,
+    image: ExportImage,
+    json: ExportJSON,
+    pdf: ExportPDF,
+    pptx: ExportPPTX
   }
   if (dialogForExport.value) return dialogMap[dialogForExport.value] || null
   return null
@@ -67,4 +59,4 @@ const currentDialogComponent = computed<unknown>(() => {
 
   @include overflow-overlay();
 }
-</style>
+</style>