瀏覽代碼

导出数据

黄琪勇 8 月之前
父節點
當前提交
b3d9099908
共有 3 個文件被更改,包括 257 次插入280 次删除
  1. 251 275
      src/hooks/useExport.ts
  2. 3 2
      src/views/Editor/ExportDialog/ExportJSON.vue
  3. 3 3
      src/views/Screen/SlideThumbnails.vue

+ 251 - 275
src/hooks/useExport.ts

@@ -1,18 +1,18 @@
-import { computed, ref } from 'vue'
-import { storeToRefs } from 'pinia'
-import { trim } from 'lodash'
-import { saveAs } from 'file-saver'
-import pptxgen from 'pptxgenjs'
-import tinycolor from 'tinycolor2'
-import { toPng, toJpeg } from 'html-to-image'
-import { useSlidesStore } from '@/store'
-import type { PPTElementOutline, PPTElementShadow, PPTElementLink, Slide } from '@/types/slides'
-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'
+import { computed, ref } from "vue"
+import { storeToRefs } from "pinia"
+import { trim } from "lodash"
+import { saveAs } from "file-saver"
+import pptxgen from "pptxgenjs"
+import tinycolor from "tinycolor2"
+import { toPng, toJpeg } from "html-to-image"
+import { useSlidesStore } from "@/store"
+import type { PPTElementOutline, PPTElementShadow, PPTElementLink, Slide } from "@/types/slides"
+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"
 
 interface ExportImageConfig {
   quality: number
@@ -28,7 +28,7 @@ export default () => {
     return 96 * (viewportSize.value / 960)
   })
   const ratioPx2Pt = computed(() => {
-    return 96 / 72 * (viewportSize.value / 960)
+    return (96 / 72) * (viewportSize.value / 960)
   })
 
   const exporting = ref(false)
@@ -36,35 +36,37 @@ export default () => {
   // 导出图片
   const exportImage = (domRef: HTMLElement, format: string, quality: number, ignoreWebfont = true) => {
     exporting.value = true
-    const toImage = format === 'png' ? toPng : toJpeg
+    const toImage = format === "png" ? toPng : toJpeg
 
-    const foreignObjectSpans = domRef.querySelectorAll('foreignObject [xmlns]')
-    foreignObjectSpans.forEach(spanRef => spanRef.removeAttribute('xmlns'))
+    const foreignObjectSpans = domRef.querySelectorAll("foreignObject [xmlns]")
+    foreignObjectSpans.forEach(spanRef => spanRef.removeAttribute("xmlns"))
 
     setTimeout(() => {
       const config: ExportImageConfig = {
         quality,
-        width: 1600,
+        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('导出图片失败')
-      })
+      if (ignoreWebfont) config.fontEmbedCSS = ""
+
+      toImage(domRef, config)
+        .then(dataUrl => {
+          exporting.value = false
+          saveAs(dataUrl, `${title.value}.${format}`)
+        })
+        .catch(() => {
+          exporting.value = false
+          message.error("导出图片失败")
+        })
     }, 200)
   }
-  
+
   // 导出pptist文件(特有 .pptist 后缀文件)
   const exportSpecificFile = (_slides: Slide[]) => {
-    const blob = new Blob([encrypt(JSON.stringify(_slides))], { type: '' })
+    const blob = new Blob([encrypt(JSON.stringify(_slides))], { type: "" })
     saveAs(blob, `${title.value}.pptist`)
   }
-  
+
   // 导出JSON文件
   const exportJSON = () => {
     const json = {
@@ -72,8 +74,9 @@ export default () => {
       width: viewportSize.value,
       height: viewportSize.value * viewportRatio.value,
       slides: slides.value,
+      theme: theme.value
     }
-    const blob = new Blob([JSON.stringify(json)], { type: '' })
+    const blob = new Blob([JSON.stringify(json)], { type: "" })
     saveAs(blob, `${title.value}.json`)
   }
 
@@ -81,10 +84,10 @@ export default () => {
   const formatColor = (_color: string) => {
     const c = tinycolor(_color)
     const alpha = c.getAlpha()
-    const color = alpha === 0 ? '#ffffff' : c.setAlpha(1).toHexString()
+    const color = alpha === 0 ? "#ffffff" : c.setAlpha(1).toHexString()
     return {
       alpha,
-      color,
+      color
     }
   }
 
@@ -99,9 +102,8 @@ export default () => {
 
     const slices: pptxgen.TextProps[] = []
     const parse = (obj: AST[], baseStyleObj: { [key: string]: string } = {}) => {
-
       for (const item of obj) {
-        const isBlockTag = 'tagName' in item && ['div', 'li', 'p'].includes(item.tagName)
+        const isBlockTag = "tagName" in item && ["div", "li", "p"].includes(item.tagName)
 
         if (isBlockTag && slices.length) {
           const lastSlice = slices[slices.length - 1]
@@ -110,104 +112,108 @@ export default () => {
         }
 
         const styleObj = { ...baseStyleObj }
-        const styleAttr = 'attributes' in item ? item.attributes.find(attr => attr.key === 'style') : null
+        const styleAttr = "attributes" in item ? item.attributes.find(attr => attr.key === "style") : null
         if (styleAttr && styleAttr.value) {
-          const styleArr = styleAttr.value.split(';')
+          const styleArr = styleAttr.value.split(";")
           for (const styleItem of styleArr) {
-            const [_key, _value] = styleItem.split(': ')
+            const [_key, _value] = styleItem.split(": ")
             const [key, value] = [trim(_key), trim(_value)]
             if (key && value) styleObj[key] = value
           }
         }
 
-        if ('tagName' in item) {
-          if (item.tagName === 'em') {
-            styleObj['font-style'] = 'italic'
+        if ("tagName" in item) {
+          if (item.tagName === "em") {
+            styleObj["font-style"] = "italic"
           }
-          if (item.tagName === 'strong') {
-            styleObj['font-weight'] = 'bold'
+          if (item.tagName === "strong") {
+            styleObj["font-weight"] = "bold"
           }
-          if (item.tagName === 'sup') {
-            styleObj['vertical-align'] = 'super'
+          if (item.tagName === "sup") {
+            styleObj["vertical-align"] = "super"
           }
-          if (item.tagName === 'sub') {
-            styleObj['vertical-align'] = 'sub'
+          if (item.tagName === "sub") {
+            styleObj["vertical-align"] = "sub"
           }
-          if (item.tagName === 'a') {
-            const attr = item.attributes.find(attr => attr.key === 'href')
-            styleObj['href'] = attr?.value || ''
+          if (item.tagName === "a") {
+            const attr = item.attributes.find(attr => attr.key === "href")
+            styleObj["href"] = attr?.value || ""
           }
-          if (item.tagName === 'ul') {
-            styleObj['list-type'] = 'ul'
+          if (item.tagName === "ul") {
+            styleObj["list-type"] = "ul"
           }
-          if (item.tagName === 'ol') {
-            styleObj['list-type'] = 'ol'
+          if (item.tagName === "ol") {
+            styleObj["list-type"] = "ol"
           }
-          if (item.tagName === 'li') {
+          if (item.tagName === "li") {
             bulletFlag = true
           }
-          if (item.tagName === 'p') {
-            if ('attributes' in item) {
-              const dataIndentAttr = item.attributes.find(attr => attr.key === 'data-indent')
+          if (item.tagName === "p") {
+            if ("attributes" in item) {
+              const dataIndentAttr = item.attributes.find(attr => attr.key === "data-indent")
               if (dataIndentAttr && dataIndentAttr.value) indent = +dataIndentAttr.value
             }
           }
         }
 
-        if ('tagName' in item && item.tagName === 'br') {
-          slices.push({ text: '', options: { breakLine: true } })
-        }
-        else if ('content' in item) {
-          const text = item.content.replace(/&nbsp;/g, ' ').replace(/&gt;/g, '>').replace(/&lt;/g, '<').replace(/&amp;/g, '&').replace(/\n/g, '')
+        if ("tagName" in item && item.tagName === "br") {
+          slices.push({ text: "", options: { breakLine: true } })
+        } else if ("content" in item) {
+          const text = item.content
+            .replace(/&nbsp;/g, " ")
+            .replace(/&gt;/g, ">")
+            .replace(/&lt;/g, "<")
+            .replace(/&amp;/g, "&")
+            .replace(/\n/g, "")
           const options: pptxgen.TextPropsOptions = {}
 
-          if (styleObj['font-size']) {
-            options.fontSize = parseInt(styleObj['font-size']) / ratioPx2Pt.value
+          if (styleObj["font-size"]) {
+            options.fontSize = parseInt(styleObj["font-size"]) / ratioPx2Pt.value
           }
-          if (styleObj['color']) {
-            options.color = formatColor(styleObj['color']).color
+          if (styleObj["color"]) {
+            options.color = formatColor(styleObj["color"]).color
           }
-          if (styleObj['background-color']) {
-            options.highlight = formatColor(styleObj['background-color']).color
+          if (styleObj["background-color"]) {
+            options.highlight = formatColor(styleObj["background-color"]).color
           }
-          if (styleObj['text-decoration-line']) {
-            if (styleObj['text-decoration-line'].indexOf('underline') !== -1) {
+          if (styleObj["text-decoration-line"]) {
+            if (styleObj["text-decoration-line"].indexOf("underline") !== -1) {
               options.underline = {
-                color: options.color || '#000000',
-                style: 'sng',
+                color: options.color || "#000000",
+                style: "sng"
               }
             }
-            if (styleObj['text-decoration-line'].indexOf('line-through') !== -1) {
-              options.strike = 'sngStrike'
+            if (styleObj["text-decoration-line"].indexOf("line-through") !== -1) {
+              options.strike = "sngStrike"
             }
           }
-          if (styleObj['text-decoration']) {
-            if (styleObj['text-decoration'].indexOf('underline') !== -1) {
+          if (styleObj["text-decoration"]) {
+            if (styleObj["text-decoration"].indexOf("underline") !== -1) {
               options.underline = {
-                color: options.color || '#000000',
-                style: 'sng',
+                color: options.color || "#000000",
+                style: "sng"
               }
             }
-            if (styleObj['text-decoration'].indexOf('line-through') !== -1) {
-              options.strike = 'sngStrike'
+            if (styleObj["text-decoration"].indexOf("line-through") !== -1) {
+              options.strike = "sngStrike"
             }
           }
-          if (styleObj['vertical-align']) {
-            if (styleObj['vertical-align'] === 'super') options.superscript = true
-            if (styleObj['vertical-align'] === 'sub') options.subscript = true
+          if (styleObj["vertical-align"]) {
+            if (styleObj["vertical-align"] === "super") options.superscript = true
+            if (styleObj["vertical-align"] === "sub") options.subscript = true
           }
-          if (styleObj['text-align']) options.align = styleObj['text-align'] as pptxgen.HAlign
-          if (styleObj['font-weight']) options.bold = styleObj['font-weight'] === 'bold'
-          if (styleObj['font-style']) options.italic = styleObj['font-style'] === 'italic'
-          if (styleObj['font-family']) options.fontFace = styleObj['font-family']
-          if (styleObj['href']) options.hyperlink = { url: styleObj['href'] }
+          if (styleObj["text-align"]) options.align = styleObj["text-align"] as pptxgen.HAlign
+          if (styleObj["font-weight"]) options.bold = styleObj["font-weight"] === "bold"
+          if (styleObj["font-style"]) options.italic = styleObj["font-style"] === "italic"
+          if (styleObj["font-family"]) options.fontFace = styleObj["font-family"]
+          if (styleObj["href"]) options.hyperlink = { url: styleObj["href"] }
 
-          if (bulletFlag && styleObj['list-type'] === 'ol') {
-            options.bullet = { type: 'number', indent: (options.fontSize || 20) * 1.25 }
+          if (bulletFlag && styleObj["list-type"] === "ol") {
+            options.bullet = { type: "number", indent: (options.fontSize || 20) * 1.25 }
             options.paraSpaceBefore = 0.1
             bulletFlag = false
           }
-          if (bulletFlag && styleObj['list-type'] === 'ul') {
+          if (bulletFlag && styleObj["list-type"] === "ul") {
             options.bullet = { indent: (options.fontSize || 20) * 1.25 }
             options.paraSpaceBefore = 0.1
             bulletFlag = false
@@ -218,8 +224,7 @@ export default () => {
           }
 
           slices.push({ text, options })
-        }
-        else if ('children' in item) parse(item.children, styleObj)
+        } else if ("children" in item) parse(item.children, styleObj)
       }
     }
     parse(ast)
@@ -228,9 +233,9 @@ export default () => {
 
   type Points = Array<
     | { x: number; y: number; moveTo?: boolean }
-    | { x: number; y: number; curve: { type: 'arc'; hR: number; wR: number; stAng: number; swAng: number } }
-    | { x: number; y: number; curve: { type: 'quadratic'; x1: number; y1: number } }
-    | { x: number; y: number; curve: { type: 'cubic'; x1: number; y1: number; x2: number; y2: number } }
+    | { x: number; y: number; curve: { type: "arc"; hR: number; wR: number; stAng: number; swAng: number } }
+    | { x: number; y: number; curve: { type: "quadratic"; x1: number; y1: number } }
+    | { x: number; y: number; curve: { type: "cubic"; x1: number; y1: number; x2: number; y2: number } }
     | { close: true }
   >
 
@@ -239,43 +244,40 @@ export default () => {
     return points.map(point => {
       if (point.close !== undefined) {
         return { close: true }
-      }
-      else if (point.type === 'M') {
+      } else if (point.type === "M") {
         return {
-          x: point.x / ratioPx2Inch.value * scale.x,
-          y: point.y / ratioPx2Inch.value * scale.y,
-          moveTo: true,
+          x: (point.x / ratioPx2Inch.value) * scale.x,
+          y: (point.y / ratioPx2Inch.value) * scale.y,
+          moveTo: true
         }
-      }
-      else if (point.curve) {
-        if (point.curve.type === 'cubic') {
+      } else if (point.curve) {
+        if (point.curve.type === "cubic") {
           return {
-            x: point.x / ratioPx2Inch.value * scale.x,
-            y: point.y / ratioPx2Inch.value * scale.y,
+            x: (point.x / ratioPx2Inch.value) * scale.x,
+            y: (point.y / ratioPx2Inch.value) * scale.y,
             curve: {
-              type: 'cubic',
-              x1: (point.curve.x1 as number) / ratioPx2Inch.value * scale.x,
-              y1: (point.curve.y1 as number) / ratioPx2Inch.value * scale.y,
-              x2: (point.curve.x2 as number) / ratioPx2Inch.value * scale.x,
-              y2: (point.curve.y2 as number) / ratioPx2Inch.value * scale.y,
-            },
+              type: "cubic",
+              x1: ((point.curve.x1 as number) / ratioPx2Inch.value) * scale.x,
+              y1: ((point.curve.y1 as number) / ratioPx2Inch.value) * scale.y,
+              x2: ((point.curve.x2 as number) / ratioPx2Inch.value) * scale.x,
+              y2: ((point.curve.y2 as number) / ratioPx2Inch.value) * scale.y
+            }
           }
-        }
-        else if (point.curve.type === 'quadratic') {
+        } else if (point.curve.type === "quadratic") {
           return {
-            x: point.x / ratioPx2Inch.value * scale.x,
-            y: point.y / ratioPx2Inch.value * scale.y,
+            x: (point.x / ratioPx2Inch.value) * scale.x,
+            y: (point.y / ratioPx2Inch.value) * scale.y,
             curve: {
-              type: 'quadratic',
-              x1: (point.curve.x1 as number) / ratioPx2Inch.value * scale.x,
-              y1: (point.curve.y1 as number) / ratioPx2Inch.value * scale.y,
-            },
+              type: "quadratic",
+              x1: ((point.curve.x1 as number) / ratioPx2Inch.value) * scale.x,
+              y1: ((point.curve.y1 as number) / ratioPx2Inch.value) * scale.y
+            }
           }
         }
       }
       return {
-        x: point.x / ratioPx2Inch.value * scale.x,
-        y: point.y / ratioPx2Inch.value * scale.y,
+        x: (point.x / ratioPx2Inch.value) * scale.x,
+        y: (point.y / ratioPx2Inch.value) * scale.y
       }
     })
   }
@@ -291,77 +293,69 @@ export default () => {
     if (h === 0 && v === 0) {
       offset = 4
       angle = 45
-    }
-    else if (h === 0) {
+    } else if (h === 0) {
       if (v > 0) {
         offset = v
         angle = 90
-      }
-      else {
+      } else {
         offset = -v
         angle = 270
       }
-    }
-    else if (v === 0) {
+    } else if (v === 0) {
       if (h > 0) {
         offset = h
         angle = 1
-      }
-      else {
+      } else {
         offset = -h
         angle = 180
       }
-    }
-    else if (h > 0 && v > 0) {
+    } else if (h > 0 && v > 0) {
       offset = Math.max(h, v)
       angle = 45
-    }
-    else if (h > 0 && v < 0) {
+    } else if (h > 0 && v < 0) {
       offset = Math.max(h, -v)
       angle = 315
-    }
-    else if (h < 0 && v > 0) {
+    } else if (h < 0 && v > 0) {
       offset = Math.max(-h, v)
       angle = 135
-    }
-    else if (h < 0 && v < 0) {
+    } else if (h < 0 && v < 0) {
       offset = Math.max(-h, -v)
       angle = 225
     }
 
     return {
-      type: 'outer',
-      color: c.color.replace('#', ''),
+      type: "outer",
+      color: c.color.replace("#", ""),
       opacity: c.alpha,
       blur: shadow.blur / ratioPx2Pt.value,
       offset,
-      angle,
+      angle
     }
   }
 
   const dashTypeMap = {
-    'solid': 'solid',
-    'dashed': 'dash',
-    'dotted': 'sysDot',
+    solid: "solid",
+    dashed: "dash",
+    dotted: "sysDot"
   }
 
   // 获取边框配置
   const getOutlineOption = (outline: PPTElementOutline): pptxgen.ShapeLineProps => {
-    const c = formatColor(outline?.color || '#000000')
-    
+    const c = formatColor(outline?.color || "#000000")
+
     return {
-      color: c.color, 
+      color: c.color,
       transparency: (1 - c.alpha) * 100,
-      width: (outline.width || 1) / ratioPx2Pt.value, 
-      dashType: outline.style ? dashTypeMap[outline.style] as 'solid' | 'dash' | 'sysDot' : 'solid',
+      width: (outline.width || 1) / ratioPx2Pt.value,
+      dashType: outline.style ? (dashTypeMap[outline.style] as "solid" | "dash" | "sysDot") : "solid"
     }
   }
 
   // 获取超链接配置
   const getLinkOption = (link: PPTElementLink): pptxgen.HyperlinkProps | null => {
     const { type, target } = link
-    if (type === 'web') return { url: target }
-    if (type === 'slide') {
+    if (type === "web") return { url: target }
+    if (type === "slide") {
       const index = slides.value.findIndex(slide => slide.id === target)
       if (index !== -1) return { slide: index + 1 }
     }
@@ -380,23 +374,21 @@ export default () => {
     exporting.value = true
     const pptx = new pptxgen()
 
-    if (viewportRatio.value === 0.625) pptx.layout = 'LAYOUT_16x10'
-    else if (viewportRatio.value === 0.75) pptx.layout = 'LAYOUT_4x3'
+    if (viewportRatio.value === 0.625) pptx.layout = "LAYOUT_16x10"
+    else if (viewportRatio.value === 0.75) pptx.layout = "LAYOUT_4x3"
     else if (viewportRatio.value === 0.70710678) {
-      pptx.defineLayout({ name: 'A3', width: 10, height: 7.0710678 })
-      pptx.layout = 'A3'
-    }
-    else if (viewportRatio.value === 1.41421356) {
-      pptx.defineLayout({ name: 'A3_V', width: 10, height: 14.1421356 })
-      pptx.layout = 'A3_V'
-    }
-    else pptx.layout = 'LAYOUT_16x9'
+      pptx.defineLayout({ name: "A3", width: 10, height: 7.0710678 })
+      pptx.layout = "A3"
+    } else if (viewportRatio.value === 1.41421356) {
+      pptx.defineLayout({ name: "A3_V", width: 10, height: 14.1421356 })
+      pptx.layout = "A3_V"
+    } else pptx.layout = "LAYOUT_16x9"
 
     if (masterOverwrite) {
       const { color: bgColor, alpha: bgAlpha } = formatColor(theme.value.backgroundColor)
       pptx.defineSlideMaster({
-        title: 'PPTIST_MASTER',
-        background: { color: bgColor, transparency: (1 - bgAlpha) * 100 },
+        title: "PPTIST_MASTER",
+        background: { color: bgColor, transparency: (1 - bgAlpha) * 100 }
       })
     }
 
@@ -405,15 +397,13 @@ export default () => {
 
       if (slide.background) {
         const background = slide.background
-        if (background.type === 'image' && background.image) {
+        if (background.type === "image" && background.image) {
           if (isBase64Image(background.image.src)) pptxSlide.background = { data: background.image.src }
           else pptxSlide.background = { path: background.image.src }
-        }
-        else if (background.type === 'solid' && background.color) {
+        } else if (background.type === "solid" && background.color) {
           const c = formatColor(background.color)
           pptxSlide.background = { color: c.color, transparency: (1 - c.alpha) * 100 }
-        }
-        else if (background.type === 'gradient' && background.gradient) {
+        } else if (background.type === "gradient" && background.gradient) {
           const colors = background.gradient.colors
           const color1 = colors[0].color
           const color2 = colors[colors.length - 1].color
@@ -427,7 +417,7 @@ export default () => {
       if (!slide.elements) continue
 
       for (const el of slide.elements) {
-        if (el.type === 'text') {
+        if (el.type === "text") {
           const textProps = formatHTML(el.content)
 
           const options: pptxgen.TextPropsOptions = {
@@ -436,13 +426,13 @@ export default () => {
             w: el.width / ratioPx2Inch.value,
             h: el.height / ratioPx2Inch.value,
             fontSize: 20 / ratioPx2Pt.value,
-            fontFace: '微软雅黑',
-            color: '#000000',
-            valign: 'top',
+            fontFace: "微软雅黑",
+            color: "#000000",
+            valign: "top",
             margin: 10 / ratioPx2Pt.value,
             paraSpaceBefore: 5 / ratioPx2Pt.value,
             lineSpacingMultiple: 1.5 / 1.25,
-            autoFit: true,
+            autoFit: true
           }
           if (el.rotate) options.rotate = el.rotate
           if (el.wordSpace) options.charSpacing = el.wordSpace / ratioPx2Pt.value
@@ -458,17 +448,15 @@ export default () => {
           if (el.outline?.width) options.line = getOutlineOption(el.outline)
           if (el.opacity !== undefined) options.transparency = (1 - el.opacity) * 100
           if (el.paragraphSpace !== undefined) options.paraSpaceBefore = el.paragraphSpace / ratioPx2Pt.value
-          if (el.vertical) options.vert = 'eaVert'
+          if (el.vertical) options.vert = "eaVert"
 
           pptxSlide.addText(textProps, options)
-        }
-
-        else if (el.type === 'image') {
+        } else if (el.type === "image") {
           const options: pptxgen.ImageProps = {
             x: el.left / ratioPx2Inch.value,
             y: el.top / ratioPx2Inch.value,
             w: el.width / ratioPx2Inch.value,
-            h: el.height / ratioPx2Inch.value,
+            h: el.height / ratioPx2Inch.value
           }
           if (isBase64Image(el.src)) options.data = el.src
           else options.path = el.src
@@ -482,7 +470,7 @@ export default () => {
           }
           if (el.filters?.opacity) options.transparency = 100 - parseInt(el.filters?.opacity)
           if (el.clip) {
-            if (el.clip.shape === 'ellipse') options.rounding = true
+            if (el.clip.shape === "ellipse") options.rounding = true
 
             const [start, end] = el.clip.range
             const [startX, startY] = start
@@ -495,18 +483,16 @@ export default () => {
             options.h = originH / ratioPx2Inch.value
 
             options.sizing = {
-              type: 'crop',
-              x: startX / ratioPx2Inch.value * originW / ratioPx2Inch.value,
-              y: startY / ratioPx2Inch.value * originH / ratioPx2Inch.value,
-              w: (endX - startX) / ratioPx2Inch.value * originW / ratioPx2Inch.value,
-              h: (endY - startY) / ratioPx2Inch.value * originH / ratioPx2Inch.value,
+              type: "crop",
+              x: ((startX / ratioPx2Inch.value) * originW) / ratioPx2Inch.value,
+              y: ((startY / ratioPx2Inch.value) * originH) / ratioPx2Inch.value,
+              w: (((endX - startX) / ratioPx2Inch.value) * originW) / ratioPx2Inch.value,
+              h: (((endY - startY) / ratioPx2Inch.value) * originH) / ratioPx2Inch.value
             }
           }
 
           pptxSlide.addImage(options)
-        }
-
-        else if (el.type === 'shape') {
+        } else if (el.type === "shape") {
           if (el.special) {
             const svgRef = document.querySelector(`.thumbnail-list .base-element-${el.id} svg`) as HTMLElement
             if (svgRef.clientWidth < 1 || svgRef.clientHeight < 1) continue // 临时处理(导入PPTX文件带来的异常数据)
@@ -517,7 +503,7 @@ export default () => {
               x: el.left / ratioPx2Inch.value,
               y: el.top / ratioPx2Inch.value,
               w: el.width / ratioPx2Inch.value,
-              h: el.height / ratioPx2Inch.value,
+              h: el.height / ratioPx2Inch.value
             }
             if (el.rotate) options.rotate = el.rotate
             if (el.link) {
@@ -526,14 +512,13 @@ export default () => {
             }
 
             pptxSlide.addImage(options)
-          }
-          else {
+          } else {
             const scale = {
               x: el.width / el.viewBox[0],
-              y: el.height / el.viewBox[1],
+              y: el.height / el.viewBox[1]
             }
             const points = formatPoints(toPoints(el.path), scale)
-  
+
             let fillColor = formatColor(el.fill)
             if (el.gradient) {
               const colors = el.gradient.colors
@@ -543,14 +528,14 @@ export default () => {
               fillColor = formatColor(color)
             }
             const opacity = el.opacity === undefined ? 1 : el.opacity
-  
+
             const options: pptxgen.ShapeProps = {
               x: el.left / ratioPx2Inch.value,
               y: el.top / ratioPx2Inch.value,
               w: el.width / ratioPx2Inch.value,
               h: el.height / ratioPx2Inch.value,
               fill: { color: fillColor.color, transparency: (1 - fillColor.alpha * opacity) * 100 },
-              points,
+              points
             }
             if (el.flipH) options.flipH = el.flipH
             if (el.flipV) options.flipV = el.flipV
@@ -562,7 +547,7 @@ export default () => {
               if (linkOption) options.hyperlink = linkOption
             }
 
-            pptxSlide.addShape('custGeom' as pptxgen.ShapeType, options)
+            pptxSlide.addShape("custGeom" as pptxgen.ShapeType, options)
           }
           if (el.text) {
             const textProps = formatHTML(el.text.content)
@@ -573,10 +558,10 @@ export default () => {
               w: el.width / ratioPx2Inch.value,
               h: el.height / ratioPx2Inch.value,
               fontSize: 20 / ratioPx2Pt.value,
-              fontFace: '微软雅黑',
-              color: '#000000',
+              fontFace: "微软雅黑",
+              color: "#000000",
               paraSpaceBefore: 5 / ratioPx2Pt.value,
-              valign: el.text.align,
+              valign: el.text.align
             }
             if (el.rotate) options.rotate = el.rotate
             if (el.text.defaultColor) options.color = formatColor(el.text.defaultColor).color
@@ -584,9 +569,7 @@ export default () => {
 
             pptxSlide.addText(textProps, options)
           }
-        }
-
-        else if (el.type === 'line') {
+        } else if (el.type === "line") {
           const path = getLineElementPath(el)
           const points = formatPoints(toPoints(path))
           const { minX, maxX, minY, maxY } = getElementRange(el)
@@ -598,56 +581,59 @@ export default () => {
             w: (maxX - minX) / ratioPx2Inch.value,
             h: (maxY - minY) / ratioPx2Inch.value,
             line: {
-              color: c.color, 
+              color: c.color,
               transparency: (1 - c.alpha) * 100,
-              width: el.width / ratioPx2Pt.value, 
-              dashType: dashTypeMap[el.style] as 'solid' | 'dash' | 'sysDot',
-              beginArrowType: el.points[0] ? 'arrow' : 'none',
-              endArrowType: el.points[1] ? 'arrow' : 'none',
+              width: el.width / ratioPx2Pt.value,
+              dashType: dashTypeMap[el.style] as "solid" | "dash" | "sysDot",
+              beginArrowType: el.points[0] ? "arrow" : "none",
+              endArrowType: el.points[1] ? "arrow" : "none"
             },
-            points,
+            points
           }
           if (el.shadow) options.shadow = getShadowOption(el.shadow)
 
-          pptxSlide.addShape('custGeom' as pptxgen.ShapeType, options)
-        }
-
-        else if (el.type === 'chart') {
+          pptxSlide.addShape("custGeom" as pptxgen.ShapeType, options)
+        } else if (el.type === "chart") {
           const chartData = []
           for (let i = 0; i < el.data.series.length; i++) {
             const item = el.data.series[i]
             chartData.push({
               name: `系列${i + 1}`,
               labels: el.data.labels,
-              values: item,
+              values: item
             })
           }
 
           let chartColors: string[] = []
           if (el.themeColors.length === 10) chartColors = el.themeColors.map(color => formatColor(color).color)
-          else if (el.themeColors.length === 1) chartColors = tinycolor(el.themeColors[0]).analogous(10).map(color => formatColor(color.toHexString()).color)
+          else if (el.themeColors.length === 1)
+            chartColors = tinycolor(el.themeColors[0])
+              .analogous(10)
+              .map(color => formatColor(color.toHexString()).color)
           else {
             const len = el.themeColors.length
-            const supplement = tinycolor(el.themeColors[len - 1]).analogous(10 + 1 - len).map(color => color.toHexString())
+            const supplement = tinycolor(el.themeColors[len - 1])
+              .analogous(10 + 1 - len)
+              .map(color => color.toHexString())
             chartColors = [...el.themeColors.slice(0, len - 1), ...supplement].map(color => formatColor(color).color)
           }
-          
+
           const options: pptxgen.IChartOpts = {
             x: el.left / ratioPx2Inch.value,
             y: el.top / ratioPx2Inch.value,
             w: el.width / ratioPx2Inch.value,
             h: el.height / ratioPx2Inch.value,
-            chartColors: (el.chartType === 'pie' || el.chartType === 'ring') ? chartColors : chartColors.slice(0, el.data.series.length),
+            chartColors: el.chartType === "pie" || el.chartType === "ring" ? chartColors : chartColors.slice(0, el.data.series.length)
           }
 
-          const textColor = formatColor(el.textColor || '#000000').color
+          const textColor = formatColor(el.textColor || "#000000").color
           options.catAxisLabelColor = textColor
           options.valAxisLabelColor = textColor
 
           const fontSize = 14 / ratioPx2Pt.value
           options.catAxisLabelFontSize = fontSize
           options.valAxisLabelFontSize = fontSize
-          
+
           if (el.fill || el.outline) {
             const plotArea: pptxgen.IChartPropsFillLine = {}
             if (el.fill) {
@@ -656,56 +642,47 @@ export default () => {
             if (el.outline) {
               plotArea.border = {
                 pt: el.outline.width! / ratioPx2Pt.value,
-                color: formatColor(el.outline.color!).color,
+                color: formatColor(el.outline.color!).color
               }
             }
             options.plotArea = plotArea
           }
 
-          if ((el.data.series.length > 1 && el.chartType !== 'scatter') || el.chartType === 'pie' || el.chartType === 'ring') {
+          if ((el.data.series.length > 1 && el.chartType !== "scatter") || el.chartType === "pie" || el.chartType === "ring") {
             options.showLegend = true
-            options.legendPos = 'b'
+            options.legendPos = "b"
             options.legendColor = textColor
             options.legendFontSize = fontSize
           }
 
           let type = pptx.ChartType.bar
-          if (el.chartType === 'bar') {
+          if (el.chartType === "bar") {
             type = pptx.ChartType.bar
-            options.barDir = 'col'
-            if (el.options?.stack) options.barGrouping = 'stacked'
-          }
-          else if (el.chartType === 'column') {
+            options.barDir = "col"
+            if (el.options?.stack) options.barGrouping = "stacked"
+          } else if (el.chartType === "column") {
             type = pptx.ChartType.bar
-            options.barDir = 'bar'
-            if (el.options?.stack) options.barGrouping = 'stacked'
-          }
-          else if (el.chartType === 'line') {
+            options.barDir = "bar"
+            if (el.options?.stack) options.barGrouping = "stacked"
+          } else if (el.chartType === "line") {
             type = pptx.ChartType.line
             if (el.options?.lineSmooth) options.lineSmooth = true
-          }
-          else if (el.chartType === 'area') {
+          } else if (el.chartType === "area") {
             type = pptx.ChartType.area
-          }
-          else if (el.chartType === 'radar') {
+          } else if (el.chartType === "radar") {
             type = pptx.ChartType.radar
-          }
-          else if (el.chartType === 'scatter') {
+          } else if (el.chartType === "scatter") {
             type = pptx.ChartType.scatter
             options.lineSize = 0
-          }
-          else if (el.chartType === 'pie') {
+          } else if (el.chartType === "pie") {
             type = pptx.ChartType.pie
-          }
-          else if (el.chartType === 'ring') {
+          } else if (el.chartType === "ring") {
             type = pptx.ChartType.doughnut
             options.holeSize = 60
           }
-          
-          pptxSlide.addChart(type, chartData, options)
-        }
 
-        else if (el.type === 'table') {
+          pptxSlide.addChart(type, chartData, options)
+        } else if (el.type === "table") {
           const hiddenCells = []
           for (let i = 0; i < el.data.length; i++) {
             const rowData = el.data[i]
@@ -741,11 +718,11 @@ export default () => {
                 rowspan: cell.rowspan,
                 bold: cell.style?.bold || false,
                 italic: cell.style?.em || false,
-                underline: { style: cell.style?.underline ? 'sng' : 'none' },
-                align: cell.style?.align || 'left',
-                valign: 'middle',
-                fontFace: cell.style?.fontname || '微软雅黑',
-                fontSize: (cell.style?.fontsize ? parseInt(cell.style?.fontsize) : 14) / ratioPx2Pt.value,
+                underline: { style: cell.style?.underline ? "sng" : "none" },
+                align: cell.style?.align || "left",
+                valign: "middle",
+                fontFace: cell.style?.fontname || "微软雅黑",
+                fontSize: (cell.style?.fontsize ? parseInt(cell.style?.fontsize) : 14) / ratioPx2Pt.value
               }
               if (theme && themeColor) {
                 let c: FormatColor
@@ -768,7 +745,7 @@ export default () => {
               if (!hiddenCells.includes(`${i}_${j}`)) {
                 _row.push({
                   text: cell.text,
-                  options: cellOptions,
+                  options: cellOptions
                 })
               }
             }
@@ -780,21 +757,19 @@ export default () => {
             y: el.top / ratioPx2Inch.value,
             w: el.width / ratioPx2Inch.value,
             h: el.height / ratioPx2Inch.value,
-            colW: el.colWidths.map(item => el.width * item / ratioPx2Inch.value),
+            colW: el.colWidths.map(item => (el.width * item) / ratioPx2Inch.value)
           }
-          if (el.theme) options.fill = { color: '#ffffff' }
+          if (el.theme) options.fill = { color: "#ffffff" }
           if (el.outline.width && el.outline.color) {
             options.border = {
-              type: el.outline.style === 'solid' ? 'solid' : 'dash',
+              type: el.outline.style === "solid" ? "solid" : "dash",
               pt: el.outline.width / ratioPx2Pt.value,
-              color: formatColor(el.outline.color).color,
+              color: formatColor(el.outline.color).color
             }
           }
 
           pptxSlide.addTable(tableData, options)
-        }
-        
-        else if (el.type === 'latex') {
+        } else if (el.type === "latex") {
           const svgRef = document.querySelector(`.thumbnail-list .base-element-${el.id} svg`) as HTMLElement
           const base64SVG = svg2Base64(svgRef)
 
@@ -803,7 +778,7 @@ export default () => {
             x: el.left / ratioPx2Inch.value,
             y: el.top / ratioPx2Inch.value,
             w: el.width / ratioPx2Inch.value,
-            h: el.height / ratioPx2Inch.value,
+            h: el.height / ratioPx2Inch.value
           }
           if (el.link) {
             const linkOption = getLinkOption(el.link)
@@ -811,25 +786,23 @@ export default () => {
           }
 
           pptxSlide.addImage(options)
-        }
-        
-        else if (!ignoreMedia && (el.type === 'video' || el.type === 'audio')) {
+        } else if (!ignoreMedia && (el.type === "video" || el.type === "audio")) {
           const options: pptxgen.MediaProps = {
             x: el.left / ratioPx2Inch.value,
             y: el.top / ratioPx2Inch.value,
             w: el.width / ratioPx2Inch.value,
             h: el.height / ratioPx2Inch.value,
             path: el.src,
-            type: el.type,
+            type: el.type
           }
-          if (el.type === 'video' && el.poster) options.cover = el.poster
+          if (el.type === "video" && el.poster) options.cover = el.poster
 
           const extMatch = el.src.match(/\.([a-zA-Z0-9]+)(?:[\?#]|$)/)
           if (extMatch && extMatch[1]) options.extn = extMatch[1]
           else if (el.ext) options.extn = el.ext
-          
-          const videoExts = ['avi', 'mp4', 'm4v', 'mov', 'wmv']
-          const audioExts = ['mp3', 'm4a', 'mp4', 'wav', 'wma']
+
+          const videoExts = ["avi", "mp4", "m4v", "mov", "wmv"]
+          const audioExts = ["mp3", "m4a", "mp4", "wav", "wma"]
           if (options.extn && [...videoExts, ...audioExts].includes(options.extn)) {
             pptxSlide.addMedia(options)
           }
@@ -838,10 +811,13 @@ export default () => {
     }
 
     setTimeout(() => {
-      pptx.writeFile({ fileName: `${title.value}.pptx` }).then(() => exporting.value = false).catch(() => {
-        exporting.value = false
-        message.error('导出失败')
-      })
+      pptx
+        .writeFile({ fileName: `${title.value}.pptx` })
+        .then(() => (exporting.value = false))
+        .catch(() => {
+          exporting.value = false
+          message.error("导出失败")
+        })
     }, 200)
   }
 
@@ -850,6 +826,6 @@ export default () => {
     exportImage,
     exportJSON,
     exportSpecificFile,
-    exportPPTX,
+    exportPPTX
   }
-}
+}

+ 3 - 2
src/views/Editor/ExportDialog/ExportJSON.vue

@@ -22,7 +22,7 @@ const emit = defineEmits<{
   (event: 'close'): void
 }>()
 
-const { slides, viewportRatio, title, viewportSize } = storeToRefs(useSlidesStore())
+const { slides, viewportRatio, title, viewportSize, theme } = storeToRefs(useSlidesStore())
 const { exportJSON } = useExport()
 
 const json = computed(() => {
@@ -31,6 +31,7 @@ const json = computed(() => {
     width: viewportSize.value,
     height: viewportSize.value * viewportRatio.value,
     slides: slides.value,
+    theme: theme.value
   }
 })
 </script>
@@ -79,4 +80,4 @@ pre {
   background-color: #e1e1e1;
   border-radius: 5px;
 }
-</style>
+</style>

+ 3 - 3
src/views/Screen/SlideThumbnails.vue

@@ -4,10 +4,10 @@
       <IconArrowCircleLeft class="icon" @click="emit('close')" />
     </div>
     <div class="slide-thumbnails-content">
-      <div 
+      <div
         class="thumbnail"
         :class="{ 'active': index === slideIndex }"
-        v-for="(slide, index) in slides" 
+        v-for="(slide, index) in slides"
         :key="slide.id"
         @click="turnSlide(index)"
       >
@@ -91,4 +91,4 @@ const turnSlide = (index: number) => {
     }
   }
 }
-</style>
+</style>