|
@@ -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
|
|
|
}
|
|
|
-}
|
|
|
+}
|