Bladeren bron

导入导出功能修改

黄琪勇 7 maanden geleden
bovenliggende
commit
b79aa99548

+ 0 - 24
package-lock.json

@@ -18,7 +18,6 @@
         "dexie": "3.0.3",
         "echarts": "^5.5.1",
         "element-plus": "2.6.1",
-        "file-saver": "^2.0.5",
         "hfmath": "^0.0.2",
         "html-to-image": "^1.11.11",
         "html2canvas": "^1.4.1",
@@ -51,7 +50,6 @@
         "@rushstack/eslint-patch": "^1.3.3",
         "@tsconfig/node18": "^18.2.2",
         "@types/crypto-js": "^4.2.1",
-        "@types/file-saver": "^2.0.7",
         "@types/js-cookie": "^3.0.3",
         "@types/lodash": "^4.14.202",
         "@types/node": "^18.19.3",
@@ -1173,12 +1171,6 @@
       "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz",
       "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
     },
-    "node_modules/@types/file-saver": {
-      "version": "2.0.7",
-      "resolved": "https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.7.tgz",
-      "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
-      "dev": true
-    },
     "node_modules/@types/js-cookie": {
       "version": "3.0.6",
       "resolved": "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-3.0.6.tgz",
@@ -2812,11 +2804,6 @@
         "node": "^10.12.0 || >=12.0.0"
       }
     },
-    "node_modules/file-saver": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
-      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
-    },
     "node_modules/fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz",
@@ -5655,12 +5642,6 @@
       "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz",
       "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
     },
-    "@types/file-saver": {
-      "version": "2.0.7",
-      "resolved": "https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.7.tgz",
-      "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
-      "dev": true
-    },
     "@types/js-cookie": {
       "version": "3.0.6",
       "resolved": "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-3.0.6.tgz",
@@ -6932,11 +6913,6 @@
         "flat-cache": "^3.0.4"
       }
     },
-    "file-saver": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
-      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
-    },
     "fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz",

+ 0 - 2
package.json

@@ -23,7 +23,6 @@
     "dexie": "3.0.3",
     "echarts": "^5.5.1",
     "element-plus": "2.6.1",
-    "file-saver": "^2.0.5",
     "hfmath": "^0.0.2",
     "html-to-image": "^1.11.11",
     "html2canvas": "^1.4.1",
@@ -56,7 +55,6 @@
     "@rushstack/eslint-patch": "^1.3.3",
     "@tsconfig/node18": "^18.2.2",
     "@types/crypto-js": "^4.2.1",
-    "@types/file-saver": "^2.0.7",
     "@types/js-cookie": "^3.0.3",
     "@types/lodash": "^4.14.202",
     "@types/node": "^18.19.3",

+ 7 - 2
src/App.vue

@@ -1,7 +1,12 @@
 <template>
-  <router-view />
+  <el-config-provider :locale="zhCn">
+    <router-view />
+  </el-config-provider>
 </template>
 
-<script lang="ts" setup></script>
+<script lang="ts" setup>
+import { ElConfigProvider } from "element-plus"
+import zhCn from "element-plus/es/locale/lang/zh-cn"
+</script>
 
 <style lang="scss"></style>

+ 42 - 44
src/components/FullscreenSpin.vue

@@ -1,20 +1,26 @@
 <template>
   <div class="fullscreen-spin" v-if="loading">
     <div class="spin">
-      <div class="spinner"></div>
-      <div class="text">{{tip}}</div>
+      <el-progress class="progress" :percentage="progress" :show-text="false" />
+      <div class="text">{{ `${tip} ${progress}%` }}</div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-withDefaults(defineProps<{
-  loading?: boolean
-  tip?: string
-}>(), {
-  loading: false,
-  tip: '',
-})
+import { ElProgress } from "element-plus"
+withDefaults(
+  defineProps<{
+    loading?: boolean
+    tip?: string
+    progress: number
+  }>(),
+  {
+    loading: false,
+    tip: "",
+    progress: 0
+  }
+)
 </script>
 
 <style lang="scss" scoped>
@@ -24,43 +30,35 @@ withDefaults(defineProps<{
   bottom: 0;
   left: 0;
   right: 0;
-  z-index: 100;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  background-color: rgba($color: #f1f1f1, $alpha: .7);
-}
-.spin {
-  width: 200px;
-  height: 200px;
-  position: fixed;
-  top: 50%;
-  left: 50%;
-  margin-top: -100px;
-  margin-left: -100px;
+  z-index: 1000000000;
   display: flex;
-  flex-direction: column;
   justify-content: center;
   align-items: center;
-}
-.spinner {
-  width: 36px;
-  height: 36px;
-  border: 3px solid $themeColor;
-  border-top-color: transparent;
-  border-radius: 50%;
-  animation: spinner .8s linear infinite;
-}
-.text {
-  margin-top: 20px;
-  color: $themeColor;
-}
-@keyframes spinner {
-  0% {
-    transform: rotate(0deg);
-  }
-  100% {
-    transform: rotate(360deg);
+  background-color: rgba(0, 0, 0, 0.6);
+  .spin {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    .progress {
+      width: 280px;
+      height: 6px;
+      &::v-deep(.el-progress-bar) {
+        .el-progress-bar__inner {
+          background-color: #20bdff;
+        }
+        .el-progress-bar__outer {
+          background-color: rgba(255, 255, 255, 0.3);
+        }
+      }
+    }
+    .text {
+      margin-top: 24px;
+      font-weight: 400;
+      font-size: 14px;
+      color: #ffffff;
+      line-height: 20px;
+    }
   }
 }
-</style>
+</style>

+ 6 - 51
src/hooks/useExport.ts

@@ -1,63 +1,16 @@
 import { ref } from "vue"
 import { storeToRefs } from "pinia"
-import { saveAs } from "file-saver"
-import { toPng, toJpeg } from "html-to-image"
 import { useSlidesStore } from "@/store"
 import message from "@/utils/message"
 import { addCourseWareTask } from "@/worker/useCoursewaresWorker"
 import { downloadFile } from "@/libs/tools"
 
-interface ExportImageConfig {
-  quality: number
-  width: number
-  fontEmbedCSS?: string
-}
-
 export default () => {
   const slidesStore = useSlidesStore()
   const { slides, theme, viewportRatio, title, viewportSize } = storeToRefs(slidesStore)
 
   const exporting = ref(false)
-
-  // 导出图片
-  const exportImage = (domRef: HTMLElement, format: string, quality: number, ignoreWebfont = true) => {
-    exporting.value = true
-    const toImage = format === "png" ? toPng : toJpeg
-
-    const foreignObjectSpans = domRef.querySelectorAll("foreignObject [xmlns]")
-    foreignObjectSpans.forEach(spanRef => spanRef.removeAttribute("xmlns"))
-
-    setTimeout(() => {
-      const config: ExportImageConfig = {
-        quality,
-        width: 1600
-      }
-
-      if (ignoreWebfont) config.fontEmbedCSS = ""
-
-      toImage(domRef, config)
-        .then(dataUrl => {
-          exporting.value = false
-          saveAs(dataUrl, `${title.value}.${format}`)
-        })
-        .catch(() => {
-          exporting.value = false
-          message.error("导出图片失败")
-        })
-    }, 200)
-  }
-
-  // 导出JSON文件
-  const exportJSON = () => {
-    const json = {
-      width: viewportSize.value,
-      height: viewportSize.value * viewportRatio.value,
-      slides: slides.value,
-      theme: theme.value
-    }
-    const blob = new Blob([JSON.stringify(json)])
-    saveAs(blob, `${title.value}.json`)
-  }
+  const exportProgress = ref(0)
 
   // 导出PPTX文件
   const exportPPTX = () => {
@@ -69,6 +22,8 @@ export default () => {
     }
     const blob = new Blob([JSON.stringify(json)])
     const url = URL.createObjectURL(blob)
+
+    exportProgress.value = 0
     exporting.value = true
     addCourseWareTask(
       {
@@ -83,6 +38,7 @@ export default () => {
       },
       e => {
         console.log(e, "导出")
+        exportProgress.value = parseInt(e.progress)
         if (e.progress === 100) {
           if (e.status === "done") {
             exporting.value = false
@@ -101,9 +57,8 @@ export default () => {
   }
 
   return {
+    exportPPTX,
     exporting,
-    exportImage,
-    exportJSON,
-    exportPPTX
+    exportProgress
   }
 }

+ 23 - 18
src/hooks/useImport.ts

@@ -8,23 +8,23 @@ import { initWorker, addCourseWareTask, getRunJobIds } from "@/worker/useCoursew
 
 export default () => {
   const slidesStore = useSlidesStore()
-  const exporting = ref(false)
+  const importing = ref(false)
+  const importProgress = ref(0)
   onMounted(() => {
     initWorker()
   })
   // 导入PPTX文件
   const importPPTXFile = (files: FileList) => {
-    getHttpJson("./mjkJson/9.json").then(res => {
-      if (res.code === 200) {
-        jsonToPpt(res.data)
-      } else {
-        exporting.value = false
-      }
-    })
-    return
+    // getHttpJson("./mjkJson/9.json").then(res => {
+    //   if (res.code === 200) {
+    //     jsonToPpt(res.data)
+    //   }
+    // })
+    // return
     const file = files[0]
     if (!file) return
-    exporting.value = true
+    importing.value = true
+    importProgress.value = 0
     const suffix = `.${file.name.split(".")?.reverse()[0]}`
     addCourseWareTask(
       {
@@ -40,30 +40,34 @@ export default () => {
         }
       },
       (e: any) => {
-        console.log(e)
+        console.log(e, "导入")
+        importProgress.value = parseInt(e.progress)
         if (e.progress === 100) {
           if (e.status === "done") {
             getHttpJson(e.extra.url).then(res => {
               if (res.code === 200) {
                 jsonToPpt(res.data)
-                exporting.value = false
+                importing.value = false
               } else {
-                exporting.value = false
+                importing.value = false
+                message.error("导入失败!")
               }
             })
           } else if (e.status !== "doing") {
-            exporting.value = false
+            importing.value = false
+            message.error("导入失败!")
           }
         }
       }
     ).catch(err => {
-      exporting.value = false
+      importing.value = false
+      message.error("导入失败!")
       console.log(err)
     })
   }
-  //处理数据专黄
+  // 处理导入数据
   function jsonToPpt(jsonData: Record<string, any>) {
-    console.log(jsonData, 2333)
+    console.log(jsonData, "导入json数据")
     const { width, theme, slides } = jsonData
     slidesStore.updateSlideIndex(0)
     slidesStore.setViewportSize(width || 1920)
@@ -73,6 +77,7 @@ export default () => {
   }
   return {
     importPPTXFile,
-    exporting
+    importing,
+    importProgress
   }
 }

+ 0 - 7
src/store/main.ts

@@ -2,7 +2,6 @@ import { customAlphabet } from "nanoid"
 import { defineStore } from "pinia"
 import { ToolbarStates } from "@/types/toolbar"
 import type { CreatingElement, ShapeFormatPainter, TextFormatPainter } from "@/types/edit"
-import type { DialogForExportTypes } from "@/types/export"
 import { type TextAttrs, defaultRichTextAttrs } from "@/utils/prosemirror/utils"
 import { SYS_FONTS } from "@/configs/font"
 import { isSupportFont } from "@/utils/font"
@@ -31,7 +30,6 @@ export interface MainState {
   richTextAttrs: TextAttrs
   selectedTableCells: string[]
   selectedSlidesIndex: number[]
-  dialogForExport: DialogForExportTypes
   databaseId: string
   textFormatPainter: TextFormatPainter | null
   shapeFormatPainter: ShapeFormatPainter | null
@@ -66,7 +64,6 @@ export const useMainStore = defineStore("main", {
     selectedTableCells: [], // 选中的表格单元格
     isScaling: false, // 正在进行元素缩放
     selectedSlidesIndex: [], // 当前被选中的页面索引集合
-    dialogForExport: "", // 导出面板
     databaseId, // 标识当前应用的indexedDB数据库ID
     textFormatPainter: null, // 文字格式刷
     shapeFormatPainter: null, // 形状格式刷
@@ -179,10 +176,6 @@ export const useMainStore = defineStore("main", {
       this.selectedSlidesIndex = selectedSlidesIndex
     },
 
-    setDialogForExport(type: DialogForExportTypes) {
-      this.dialogForExport = type
-    },
-
     setTextFormatPainter(textFormatPainter: TextFormatPainter | null) {
       this.textFormatPainter = textFormatPainter
     },

+ 0 - 1
src/types/export.ts

@@ -1 +0,0 @@
-export type DialogForExportTypes = "image" | "pdf" | "json" | "pptx" | ""

+ 0 - 87
src/utils/print.ts

@@ -1,87 +0,0 @@
-interface PageSize {
-  width: number
-  height: number
-  margin: number
-}
-
-const createIframe = () => {
-  const iframe = document.createElement('iframe')
-  iframe.style.width = '0'
-  iframe.style.height = '0'
-  iframe.style.position = 'absolute'
-  iframe.style.right = '0'
-  iframe.style.top = '0'
-  iframe.style.border = '0'
-
-  document.body.appendChild(iframe)
-
-  return iframe
-}
-
-const writeContent = (doc: Document, printNode: HTMLElement, size: PageSize) => {
-  const docType = '<!DOCTYPE html>'
-
-  let style = ''
-  const styleSheets = document.styleSheets
-  if (styleSheets) {
-    for (const styleSheet of styleSheets) {
-      if (!styleSheet.cssRules) continue
-
-      for (const rule of styleSheet.cssRules) {
-        style += rule.cssText
-      }
-    }
-  }
-
-  const { width, height, margin } = size
-  const head = `
-    <head>
-      <style type="text/css">
-        ${style} 
-        html, body {
-          height: auto;
-          overflow: auto;
-        }
-        @media print {
-          @page {
-            size: ${width + 2 * margin}px ${height + 2 * margin}px;
-            margin: ${margin}px;
-          }
-        }
-      </style>
-    </head>
-  `
-  const body = '<body>' + printNode.innerHTML + '</body>'
-
-  doc.open()
-  doc.write(`
-    ${docType}
-    <html>
-      ${head}
-      ${body}
-    </html>
-  `)
-  doc.close()
-}
-
-export const print = (printNode: HTMLElement, size: PageSize) => {
-  const iframe = createIframe()
-  const iframeContentWindow = iframe.contentWindow
-
-  if (!iframe.contentDocument || !iframeContentWindow) return
-  writeContent(iframe.contentDocument, printNode, size)
-
-  const handleLoadIframe = () => {
-    iframeContentWindow.focus()
-    iframeContentWindow.print()
-  }
-
-  const handleAfterprint = () => {
-    iframe.removeEventListener('load', handleLoadIframe)
-    iframeContentWindow.removeEventListener('afterprint', handleAfterprint)
-    document.body.removeChild(iframe)
-  }
-
-  iframe.addEventListener('load', handleLoadIframe)
-  iframeContentWindow.addEventListener('afterprint', handleAfterprint)
-}

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

@@ -7,6 +7,7 @@
             accept="application/vnd.openxmlformats-officedocument.presentationml.presentation"
             @change="
               files => {
+                spinType = 'import'
                 importPPTXFile(files)
                 mainMenuVisible = false
               }
@@ -14,7 +15,7 @@
           >
             <PopoverMenuItem>导入pptx</PopoverMenuItem>
           </FileInput>
-          <PopoverMenuItem @click="setDialogForExport('pptx')">导出pptx</PopoverMenuItem>
+          <PopoverMenuItem @click="handleExport">导出pptx</PopoverMenuItem>
           <PopoverMenuItem
             @click="
               () => {
@@ -53,16 +54,16 @@
       <HotkeyDoc />
       <template v-slot:title>快捷操作</template>
     </Drawer>
-    <FullscreenSpin :loading="exporting" tip="正在导入..." />
+    <FullscreenSpin :loading="fullscreenSpinData.loading" :progress="fullscreenSpinData.progress" :tip="fullscreenSpinData.tip" />
   </div>
 </template>
 
 <script lang="ts" setup>
 import { storeToRefs } from "pinia"
-import { useMainStore, useSlidesStore } from "@/store"
+import { useSlidesStore } from "@/store"
 import useImport from "@/hooks/useImport"
+import useExport from "@/hooks/useExport"
 import useSlideHandler from "@/hooks/useSlideHandler"
-import type { DialogForExportTypes } from "@/types/export"
 
 import HotkeyDoc from "./HotkeyDoc.vue"
 import FileInput from "@/components/FileInput.vue"
@@ -70,22 +71,49 @@ import FullscreenSpin from "@/components/FullscreenSpin.vue"
 import Drawer from "@/components/Drawer.vue"
 import Popover from "@/components/Popover.vue"
 import PopoverMenuItem from "@/components/PopoverMenuItem.vue"
-import { ref } from "vue"
+import { ref, computed } from "vue"
+import { ElMessageBox } from "element-plus"
 
-const mainStore = useMainStore()
 const slidesStore = useSlidesStore()
 const { title } = storeToRefs(slidesStore)
-const { importPPTXFile, exporting } = useImport()
 const { resetSlides } = useSlideHandler()
 
 const mainMenuVisible = ref(false)
 const hotkeyDrawerVisible = ref(false)
 
-/* 导出老逻辑 */
-const setDialogForExport = (type: DialogForExportTypes) => {
-  mainStore.setDialogForExport(type)
+const spinType = ref<"import" | "export">("import")
+/* 导入 */
+const { importPPTXFile, importing, importProgress } = useImport()
+/* 导出 */
+const { exportPPTX, exporting, exportProgress } = useExport()
+function handleExport() {
   mainMenuVisible.value = false
+  ElMessageBox.confirm("导出pptx可能会丢失一些内容,确认导出?", "提示", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning"
+  })
+    .then(() => {
+      spinType.value = "export"
+      exportPPTX()
+    })
+    .catch(() => {})
 }
+const fullscreenSpinData = computed(() => {
+  if (spinType.value === "export") {
+    return {
+      loading: exporting.value,
+      progress: exportProgress.value,
+      tip: "正在导出中,请稍等…"
+    }
+  } else {
+    return {
+      loading: importing.value,
+      progress: importProgress.value,
+      tip: "正在导入中,请稍等…"
+    }
+  }
+})
 </script>
 
 <style lang="scss" scoped>

+ 0 - 165
src/views/Editor/ExportDialog/ExportImage.vue

@@ -1,165 +0,0 @@
-<template>
-  <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" />
-      </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>
-      </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>
-      </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="row">
-        <div class="title">图片质量:</div>
-        <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="
-              '导出时默认忽略在线字体,若您在幻灯片中使用了在线字体,且希望导出后保留相关样式,可选择关闭【忽略在线字体】选项,但要注意这将会增加导出用时。'
-            "
-          />
-        </div>
-      </div>
-    </div>
-
-    <div class="btns">
-      <Button class="btn export" type="primary" @click="expImage()">导出图片</Button>
-      <Button class="btn close" @click="emit('close')">关闭</Button>
-    </div>
-
-    <FullscreenSpin :loading="exporting" tip="正在导出..." />
-  </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 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
-}>()
-
-const { slides, currentSlide } = storeToRefs(useSlidesStore())
-
-const imageThumbnailsRef = ref<HTMLElement>()
-const rangeType = ref<"all" | "current" | "custom">("all")
-const range = ref<[number, number]>([1, slides.value.length])
-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]
-  return slides.value.filter((item, index) => {
-    const [min, max] = range.value
-    return index >= min - 1 && index <= max - 1
-  })
-})
-
-const { exportImage, exporting } = useExport()
-
-const expImage = () => {
-  if (!imageThumbnailsRef.value) return
-  exportImage(imageThumbnailsRef.value, format.value, quality.value, ignoreWebfont.value)
-}
-</script>
-
-<style lang="scss" scoped>
-.export-img-dialog {
-  height: 100%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: column;
-  position: relative;
-  overflow: hidden;
-}
-.thumbnails-view {
-  @include absolute-0();
-
-  &::after {
-    content: "";
-    background-color: #fff;
-    @include absolute-0();
-  }
-}
-.configs {
-  width: 350px;
-  height: calc(100% - 100px);
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  z-index: 1;
-
-  .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;
-  }
-}
-.btns {
-  width: 300px;
-  height: 100px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  z-index: 1;
-
-  .export {
-    flex: 1;
-  }
-  .close {
-    width: 100px;
-    margin-left: 10px;
-  }
-}
-</style>

+ 0 - 83
src/views/Editor/ExportDialog/ExportJSON.vue

@@ -1,83 +0,0 @@
-<template>
-  <div class="export-json-dialog">
-    <div class="preview">
-      <pre>{{ json }}</pre>
-    </div>
-
-    <div class="btns">
-      <Button class="btn export" type="primary" @click="exportJSON()">导出 JSON</Button>
-      <Button class="btn close" @click="emit('close')">关闭</Button>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { computed } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useSlidesStore } from '@/store'
-import useExport from '@/hooks/useExport'
-import Button from '@/components/Button.vue'
-
-const emit = defineEmits<{
-  (event: 'close'): void
-}>()
-
-const { slides, viewportRatio, title, viewportSize, theme } = storeToRefs(useSlidesStore())
-const { exportJSON } = useExport()
-
-const json = computed(() => {
-  return {
-    title: title.value,
-    width: viewportSize.value,
-    height: viewportSize.value * viewportRatio.value,
-    slides: slides.value,
-    theme: theme.value
-  }
-})
-</script>
-
-<style lang="scss" scoped>
-.export-json-dialog {
-  height: 100%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: column;
-  position: relative;
-  overflow: hidden;
-}
-.preview {
-  width: 100%;
-  height: calc(100% - 100px);
-  background-color: #f9f9f9;
-  color: #0451a5;
-  overflow: auto;
-}
-pre {
-  font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;
-}
-.btns {
-  width: 300px;
-  height: 100px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-
-  .export {
-    flex: 1;
-  }
-  .close {
-    width: 100px;
-    margin-left: 10px;
-  }
-}
-::-webkit-scrollbar {
-  width: 10px;
-  height: 10px;
-  background-color: transparent;
-}
-::-webkit-scrollbar-thumb {
-  background-color: #e1e1e1;
-  border-radius: 5px;
-}
-</style>

+ 0 - 168
src/views/Editor/ExportDialog/ExportPDF.vue

@@ -1,168 +0,0 @@
-<template>
-  <div class="export-pdf-dialog">
-    <div class="thumbnails-view">
-      <div class="thumbnails" ref="pdfThumbnailsRef">
-        <ThumbnailSlide 
-          class="thumbnail" 
-          :slide="currentSlide" 
-          :size="1600" 
-          v-if="rangeType === 'current'"
-        />
-        <template v-else>
-          <ThumbnailSlide 
-            class="thumbnail" 
-            :class="{ 'break-page': (index + 1) % count === 0 }"
-            v-for="(slide, index) in slides" 
-            :key="slide.id" 
-            :slide="slide" 
-            :size="1600" 
-          />
-        </template>
-      </div>
-    </div>
-    <div class="configs">
-      <div class="row">
-        <div class="title">导出范围:</div>
-        <RadioGroup
-          class="config-item"
-          v-model:value="rangeType"
-        >
-          <RadioButton style="width: 50%;" value="all">全部</RadioButton>
-          <RadioButton style="width: 50%;" value="current">当前页</RadioButton>
-        </RadioGroup>
-      </div>
-      <div class="row">
-        <div class="title">每页数量:</div>
-        <Select 
-          class="config-item"
-          v-model:value="count"
-          :options="[
-            { label: '1', value: 1 },
-            { label: '2', value: 2 },
-            { label: '3', value: 3 },
-          ]"
-        />
-      </div>
-      <div class="row">
-        <div class="title">边缘留白:</div>
-        <div class="config-item">
-          <Switch v-model:value="padding" />
-        </div>
-      </div>
-      <div class="tip">
-        提示:若打印预览与实际样式不一致,请在弹出的打印窗口中勾选【背景图形】选项。
-      </div>
-    </div>
-
-    <div class="btns">
-      <Button class="btn export" type="primary" @click="expPDF()">打印 / 导出 PDF</Button>
-      <Button class="btn close" @click="emit('close')">关闭</Button>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { ref } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useSlidesStore } from '@/store'
-import { print } from '@/utils/print'
-
-import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
-import Switch from '@/components/Switch.vue'
-import Button from '@/components/Button.vue'
-import RadioButton from '@/components/RadioButton.vue'
-import RadioGroup from '@/components/RadioGroup.vue'
-import Select from '@/components/Select.vue'
-
-const emit = defineEmits<{
-  (event: 'close'): void
-}>()
-
-const { slides, currentSlide, viewportRatio } = storeToRefs(useSlidesStore())
-
-const pdfThumbnailsRef = ref<HTMLElement>()
-const rangeType = ref<'all' | 'current'>('all')
-const count = ref(1)
-const padding = ref(true)
-
-const expPDF = () => {
-  if (!pdfThumbnailsRef.value) return
-  const pageSize = {
-    width: 1600,
-    height: rangeType.value === 'all' ? 1600 * viewportRatio.value * count.value : 1600 * viewportRatio.value,
-    margin: padding.value ? 50 : 0,
-  }
-  print(pdfThumbnailsRef.value, pageSize)
-}
-</script>
-
-<style lang="scss" scoped>
-.export-pdf-dialog {
-  height: 100%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: column;
-  position: relative;
-  overflow: hidden;
-}
-.thumbnails-view {
-  @include absolute-0();
-
-  &::after {
-    content: '';
-    background-color: #fff;
-    @include absolute-0();
-  }
-}
-.thumbnail {
-  &.break-page {
-    break-after: page;
-  }
-}
-.configs {
-  width: 300px;
-  height: calc(100% - 100px);
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  z-index: 1;
-
-  .row {
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    margin-bottom: 25px;
-  }
-
-  .title {
-    width: 100px;
-  }
-  .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;
-  z-index: 1;
-
-  .export {
-    flex: 1;
-  }
-  .close {
-    width: 100px;
-    margin-left: 10px;
-  }
-}
-</style>

+ 0 - 50
src/views/Editor/ExportDialog/ExportPPTX.vue

@@ -1,50 +0,0 @@
-<template>
-  <div class="export-pptx-dialog">
-    <div class="btns">
-      <Button class="btn export" type="primary" @click="exportPPTX()">导出 PPTX</Button>
-      <Button class="btn close" @click="emit('close')">关闭</Button>
-    </div>
-
-    <FullscreenSpin :loading="exporting" tip="正在导出..." />
-  </div>
-</template>
-
-<script lang="ts" setup>
-import useExport from "@/hooks/useExport"
-
-import FullscreenSpin from "@/components/FullscreenSpin.vue"
-import Button from "@/components/Button.vue"
-
-const emit = defineEmits<{
-  (event: "close"): void
-}>()
-
-const { exportPPTX, exporting } = useExport()
-</script>
-
-<style lang="scss" scoped>
-.export-pptx-dialog {
-  height: 100%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  flex-direction: column;
-  position: relative;
-  overflow: hidden;
-}
-.btns {
-  width: 300px;
-  height: 100px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-
-  .export {
-    flex: 1;
-  }
-  .close {
-    width: 100px;
-    margin-left: 10px;
-  }
-}
-</style>

+ 0 - 62
src/views/Editor/ExportDialog/index.vue

@@ -1,62 +0,0 @@
-<template>
-  <div class="export-dialog">
-    <Tabs :tabs="tabs" :value="dialogForExport" card @update:value="key => setDialogForExport(key as DialogForExportTypes)" />
-    <div class="content">
-      <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 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
-  label: string
-}
-
-const mainStore = useMainStore()
-const { dialogForExport } = storeToRefs(mainStore)
-
-const setDialogForExport = mainStore.setDialogForExport
-
-const tabs: TabItem[] = [
-  { 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
-  }
-  if (dialogForExport.value) return dialogMap[dialogForExport.value] || null
-  return null
-})
-</script>
-
-<style lang="scss" scoped>
-.export-dialog {
-  margin: -20px;
-}
-.content {
-  height: 460px;
-  padding: 12px;
-  font-size: 13px;
-
-  @include overflow-overlay();
-}
-</style>

+ 1 - 8
src/views/Editor/index.vue

@@ -15,10 +15,6 @@
   <SelectPanel v-if="showSelectPanel" />
   <SearchPanel v-if="showSearchPanel" />
   <NotesPanel v-if="showNotesPanel" />
-
-  <Modal :visible="!!dialogForExport" :width="680" @closed="closeExportDialog()">
-    <ExportDialog />
-  </Modal>
 </template>
 
 <script lang="ts" setup>
@@ -34,15 +30,12 @@ import CanvasTool from "./CanvasTool/index.vue"
 import Thumbnails from "./Thumbnails/index.vue"
 import Toolbar from "./Toolbar/index.vue"
 import Remark from "./Remark/index.vue"
-import ExportDialog from "./ExportDialog/index.vue"
 import SelectPanel from "./SelectPanel.vue"
 import SearchPanel from "./SearchPanel.vue"
 import NotesPanel from "./NotesPanel.vue"
-import Modal from "@/components/Modal.vue"
 
 const mainStore = useMainStore()
-const { dialogForExport, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
-const closeExportDialog = () => mainStore.setDialogForExport("")
+const { showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
 
 const remarkHeight = ref(50)
 

+ 1 - 1
vite.config.ts

@@ -28,7 +28,7 @@ export default defineConfig({
     proxy: {
       // 正则表达式写法
       "^/pptApi/.*": {
-        target: "https://test.kt.colexiu.com",
+        target: "https://dev.kt.colexiu.com",
         changeOrigin: true,
         rewrite: path => path.replace(/^\/pptApi/, "")
       }