diff --git a/.eslintrc.js b/.eslintrc.js
index 9d47cc17..dac1e9e5 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -46,7 +46,7 @@ module.exports = {
}],
'default-case': 'error',
'consistent-this': ['error', '_this'],
- 'max-depth': ['error', 6],
+ 'max-depth': ['error', 8],
'max-lines': ['error', 800],
'no-multi-str': 'error',
'space-infix-ops': 'error',
diff --git a/package-lock.json b/package-lock.json
index 42843d06..5b18655e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,7 +24,7 @@
"nanoid": "^4.0.0",
"pinia": "^2.0.32",
"pptxgenjs": "^3.11.0",
- "pptxtojson": "^0.0.8",
+ "pptxtojson": "^0.0.10",
"prosemirror-commands": "^1.3.0",
"prosemirror-dropcursor": "^1.6.0",
"prosemirror-gapcursor": "^1.3.1",
@@ -11735,9 +11735,9 @@
}
},
"node_modules/pptxtojson": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/pptxtojson/-/pptxtojson-0.0.8.tgz",
- "integrity": "sha512-9RDoPhTF9Nq7xlJbzmi05lfLV4TVa7sATX5uUmzWEj5mZyQ1SymIU8e6E1/h86/4BUVB0DbM9blwjCBXjiVlpw==",
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/pptxtojson/-/pptxtojson-0.0.10.tgz",
+ "integrity": "sha512-OOVbl4y5tgnGkQIlSyZ742oBRivVSBYJV7dOwbhXEcnRZvPRVGBu8KPo7PE9QP2tDwpvuXJvqFrRaG7mbnfSfA==",
"dependencies": {
"jszip": "^3.10.1",
"tinycolor2": "1.6.0",
@@ -24676,9 +24676,9 @@
}
},
"pptxtojson": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/pptxtojson/-/pptxtojson-0.0.8.tgz",
- "integrity": "sha512-9RDoPhTF9Nq7xlJbzmi05lfLV4TVa7sATX5uUmzWEj5mZyQ1SymIU8e6E1/h86/4BUVB0DbM9blwjCBXjiVlpw==",
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/pptxtojson/-/pptxtojson-0.0.10.tgz",
+ "integrity": "sha512-OOVbl4y5tgnGkQIlSyZ742oBRivVSBYJV7dOwbhXEcnRZvPRVGBu8KPo7PE9QP2tDwpvuXJvqFrRaG7mbnfSfA==",
"requires": {
"jszip": "^3.10.1",
"tinycolor2": "1.6.0",
diff --git a/package.json b/package.json
index 20d59b35..333bf588 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"nanoid": "^4.0.0",
"pinia": "^2.0.32",
"pptxgenjs": "^3.11.0",
- "pptxtojson": "^0.0.8",
+ "pptxtojson": "^0.0.10",
"prosemirror-commands": "^1.3.0",
"prosemirror-dropcursor": "^1.6.0",
"prosemirror-gapcursor": "^1.3.1",
diff --git a/src/assets/styles/antd.scss b/src/assets/styles/antd.scss
index 10d00b42..c938d13b 100644
--- a/src/assets/styles/antd.scss
+++ b/src/assets/styles/antd.scss
@@ -52,6 +52,7 @@
// select
.ant-select {
user-select: none;
+ overflow: hidden;
}
.ant-select-item-option-active:not(.ant-select-item-option-disabled) {
background-color: rgba($color: $themeColor, $alpha: .2);
diff --git a/src/hooks/useImport.ts b/src/hooks/useImport.ts
index af589777..78baf883 100644
--- a/src/hooks/useImport.ts
+++ b/src/hooks/useImport.ts
@@ -1,10 +1,12 @@
+import { ref } from 'vue'
import { storeToRefs } from 'pinia'
-import { parse } from 'pptxtojson'
+import { parse, Shape, Element } from 'pptxtojson'
import { nanoid } from 'nanoid'
-import { Slide, TableCellStyle, TableCell, ChartType, ChartOptions, SlideBackground, PPTShapeElement } from '@/types/slides'
+import { Slide, TableCellStyle, TableCell, ChartType, ChartOptions, SlideBackground, PPTShapeElement, PPTLineElement } from '@/types/slides'
import { useSlidesStore } from '@/store'
import { decrypt } from '@/utils/crypto'
import { ShapePoolItem, SHAPE_LIST, SHAPE_PATH_FORMULAS } from '@/configs/shapes'
+import { VIEWPORT_SIZE } from '@/configs/canvas'
import useAddSlidesOrElements from '@/hooks/useAddSlidesOrElements'
import { message } from 'ant-design-vue'
@@ -15,6 +17,8 @@ export default () => {
const { addSlidesFromData } = useAddSlidesOrElements()
+ const exporting = ref(false)
+
// 导入pptist文件
const importSpecificFile = (files: FileList, cover = false) => {
const file = files[0]
@@ -33,11 +37,47 @@ export default () => {
reader.readAsText(file)
}
+ const parseLineElement = (el: Shape): PPTLineElement => {
+ let start: [number, number] = [0, 0]
+ let end: [number, number] = [0, 0]
+
+ if (!el.isFlipV && !el.isFlipH) { // 右下
+ start = [0, 0]
+ end = [el.width, el.height]
+ }
+ else if (el.isFlipV && el.isFlipH) { // 左上
+ start = [el.width, el.height]
+ end = [0, 0]
+ }
+ else if (el.isFlipV && !el.isFlipH) { // 右上
+ start = [0, el.height]
+ end = [el.width, 0]
+ }
+ else { // 左下
+ start = [el.width, 0]
+ end = [0, el.height]
+ }
+ return {
+ type: 'line',
+ id: nanoid(10),
+ width: el.borderWidth || 1,
+ left: el.left,
+ top: el.top,
+ start,
+ end,
+ style: el.borderType,
+ color: el.borderColor,
+ points: ['', el.shapType === 'straightConnector1' ? 'arrow' : '']
+ }
+ }
+
// 导入PPTX文件
const importPPTXFile = (files: FileList) => {
const file = files[0]
if (!file) return
+ exporting.value = true
+
const shapeList: ShapePoolItem[] = []
for (const item of SHAPE_LIST) {
shapeList.push(...item.children)
@@ -46,6 +86,10 @@ export default () => {
const reader = new FileReader()
reader.onload = async e => {
const json = await parse(e.target!.result as ArrayBuffer)
+
+ const width = json.size.width
+ const scale = VIEWPORT_SIZE / width
+
const slides: Slide[] = []
for (const item of json.slides) {
const { type, value } = item.fill
@@ -69,192 +113,227 @@ export default () => {
elements: [],
background,
}
- for (const el of item.elements) {
- if (el.type === 'text') {
- slide.elements.push({
- type: 'text',
- id: nanoid(10),
- width: el.width,
- height: el.height,
- left: el.left,
- top: el.top,
- rotate: el.rotate,
- defaultFontName: theme.value.fontName,
- defaultColor: theme.value.fontColor,
- content: el.content,
- lineHeight: 1,
- outline: {
- color: el.borderColor,
- width: el.borderWidth,
- style: el.borderType,
- },
- fill: el.fillColor,
- })
- }
- else if (el.type === 'image') {
- slide.elements.push({
- type: 'image',
- id: nanoid(10),
- src: el.src,
- width: el.width,
- height: el.height,
- left: el.left,
- top: el.top,
- fixedRatio: true,
- rotate: el.rotate,
- })
- }
- else if (el.type === 'shape') {
- const shape = shapeList.find(item => item.pptxShapeType === el.shapType)
-
- const element: PPTShapeElement = {
- 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,
- fixedRatio: false,
- rotate: el.rotate,
- outline: {
- color: el.borderColor,
- width: el.borderWidth,
- style: el.borderType,
- },
- text: {
- content: el.content,
+
+ const parseElements = (elements: Element[]) => {
+ for (const el of elements) {
+ el.width = el.width * scale
+ el.height = el.height * scale
+ el.left = el.left * scale
+ el.top = el.top * scale
+
+ if (el.type === 'text') {
+ slide.elements.push({
+ type: 'text',
+ id: nanoid(10),
+ width: el.width,
+ height: el.height,
+ left: el.left,
+ top: el.top,
+ rotate: el.rotate,
defaultFontName: theme.value.fontName,
defaultColor: theme.value.fontColor,
- align: 'middle',
+ content: el.content,
+ lineHeight: 1,
+ outline: {
+ color: el.borderColor,
+ width: el.borderWidth,
+ style: el.borderType,
+ },
+ fill: el.fillColor,
+ })
+ }
+ else if (el.type === 'image') {
+ slide.elements.push({
+ type: 'image',
+ id: nanoid(10),
+ src: el.src,
+ width: el.width,
+ height: el.height,
+ left: el.left,
+ top: el.top,
+ fixedRatio: true,
+ rotate: el.rotate,
+ })
+ }
+ else if (el.type === 'shape') {
+ if (el.shapType === 'line' || el.shapType === 'straightConnector1') {
+ const lineElement = parseLineElement(el)
+ slide.elements.push(lineElement)
}
- }
-
- 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) {
- element.path = pathFormula.formula(el.width, el.height, pathFormula.defaultValue)
- element.keypoint = pathFormula.defaultValue
- }
- else element.path = pathFormula.formula(el.width, el.height)
- }
- }
-
- slide.elements.push(element)
- }
- 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,
- }
- const data: TableCell[][] = []
- for (let i = 0; i < row; i++) {
- const rowCells: TableCell[] = []
- for (let j = 0; j < col; j++) {
- const cellData = el.data[i][j]
- rowCells.push({
+ else {
+ const shape = shapeList.find(item => item.pptxShapeType === el.shapType)
+
+ const element: PPTShapeElement = {
+ type: 'shape',
id: nanoid(10),
- colspan: 1,
- rowspan: cellData.rowSpan || 1,
- text: cellData.text,
- style,
- })
+ 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',
+ fixedRatio: false,
+ rotate: el.rotate,
+ outline: {
+ color: el.borderColor,
+ width: el.borderWidth,
+ style: el.borderType,
+ },
+ text: {
+ content: el.content,
+ defaultFontName: theme.value.fontName,
+ defaultColor: theme.value.fontColor,
+ align: 'middle',
+ }
+ }
+
+ 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) {
+ element.path = pathFormula.formula(el.width, el.height, pathFormula.defaultValue)
+ element.keypoint = pathFormula.defaultValue
+ }
+ else element.path = pathFormula.formula(el.width, el.height)
+ }
+ }
+
+ slide.elements.push(element)
}
- data.push(rowCells)
}
-
- const colWidths: number[] = new Array(col).fill(1 / col)
-
- slide.elements.push({
- type: 'table',
- id: nanoid(10),
- width: el.width,
- height: el.height,
- left: el.left,
- top: el.top,
- colWidths,
- rotate: 0,
- data,
- outline: {
- width: 2,
- style: 'solid',
- color: '#eeece1',
- },
- theme: {
- color: theme.value.themeColor,
- rowHeader: true,
- rowFooter: false,
- colHeader: false,
- colFooter: false,
- },
- cellMinHeight: 36,
- })
+ 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,
+ }
+ const data: TableCell[][] = []
+ for (let i = 0; i < row; i++) {
+ const rowCells: TableCell[] = []
+ for (let j = 0; j < col; j++) {
+ const cellData = el.data[i][j]
+ rowCells.push({
+ id: nanoid(10),
+ colspan: 1,
+ rowspan: cellData.rowSpan || 1,
+ text: cellData.text,
+ style,
+ })
+ }
+ data.push(rowCells)
+ }
+
+ const colWidths: number[] = new Array(col).fill(1 / col)
+
+ slide.elements.push({
+ type: 'table',
+ id: nanoid(10),
+ width: el.width,
+ height: el.height,
+ left: el.left,
+ top: el.top,
+ colWidths,
+ rotate: 0,
+ data,
+ outline: {
+ width: 2,
+ style: 'solid',
+ color: '#eeece1',
+ },
+ theme: {
+ color: theme.value.themeColor,
+ rowHeader: true,
+ rowFooter: false,
+ colHeader: false,
+ colFooter: false,
+ },
+ cellMinHeight: 36,
+ })
+ }
+ else if (el.type === 'chart') {
+ let labels: string[]
+ let legends: string[]
+ let series: number[][]
+
+ if (el.chartType === 'scatterChart') {
+ labels = el.data[0].map(item => item + '')
+ legends = ['系列1']
+ series = [el.data[1]]
+ }
+ else {
+ labels = Object.values(el.data[0].xlabels)
+ legends = el.data.map(item => item.key)
+ series = el.data.map(item => item.values.map(v => v.y))
+ }
+
+ let options: ChartOptions = {}
+
+ let chartType: ChartType = 'bar'
+ if (el.chartType === 'barChart') {
+ chartType = 'bar'
+ }
+ if (el.chartType === 'stackedBarChart') {
+ chartType = 'bar'
+ options = { stackBars: true }
+ }
+ else if (el.chartType === 'lineChart') {
+ chartType = 'line'
+ }
+ else if (el.chartType === 'areaChart') {
+ chartType = 'line'
+ options = { showArea: true }
+ }
+ else if (el.chartType === 'scatterChart') {
+ chartType = 'line'
+ options = { showLine: false }
+ }
+ else if (el.chartType === 'pieChart' || el.chartType === 'pie3DChart') {
+ chartType = 'pie'
+ }
+
+ slide.elements.push({
+ type: 'chart',
+ id: nanoid(10),
+ chartType: chartType,
+ width: el.width,
+ height: el.height,
+ left: el.left,
+ top: el.top,
+ rotate: 0,
+ themeColor: [theme.value.themeColor],
+ gridColor: theme.value.fontColor,
+ data: {
+ labels,
+ legends,
+ series,
+ },
+ options,
+ })
+ }
+ else if (el.type === 'group') {
+ const elements = el.elements.map(_el => ({
+ ..._el,
+ left: _el.left + el.left,
+ top: _el.top + el.top,
+ }))
+ parseElements(elements)
+ }
}
- else if (el.type === 'chart') {
- const labels = Object.values(el.data[0].xlabels)
- const legends = el.data.map(item => item.key)
- const series = el.data.map(item => item.values.map(v => v.y))
- let options: ChartOptions = {}
-
- let chartType: ChartType = 'bar'
- if (el.chartType === 'barChart') {
- chartType = 'bar'
- }
- if (el.chartType === 'stackedBarChart') {
- chartType = 'bar'
- options = { stackBars: true }
- }
- else if (el.chartType === 'lineChart') {
- chartType = 'line'
- }
- else if (el.chartType === 'areaChart') {
- chartType = 'line'
- options = { showArea: true }
- }
- else if (el.chartType === 'scatterChart') {
- chartType = 'line'
- options = { showLine: false }
- }
- else if (el.chartType === 'pieChart' || el.chartType === 'pie3DChart') {
- chartType = 'pie'
- }
-
- slide.elements.push({
- type: 'chart',
- id: nanoid(10),
- chartType: chartType,
- width: el.width,
- height: el.height,
- left: el.left,
- top: el.top,
- rotate: 0,
- themeColor: [theme.value.themeColor],
- gridColor: theme.value.fontColor,
- data: {
- labels,
- legends,
- series,
- },
- options,
- })
- }
- // else if (el.type === 'group') {}
}
+ parseElements(item.elements)
slides.push(slide)
}
addSlidesFromData(slides)
+ exporting.value = false
}
reader.readAsArrayBuffer(file)
}
@@ -262,5 +341,6 @@ export default () => {
return {
importSpecificFile,
importPPTXFile,
+ exporting,
}
}
\ No newline at end of file
diff --git a/src/views/Editor/EditorHeader/index.vue b/src/views/Editor/EditorHeader/index.vue
index f0b39530..3c673e6a 100644
--- a/src/views/Editor/EditorHeader/index.vue
+++ b/src/views/Editor/EditorHeader/index.vue
@@ -76,6 +76,8 @@
>
+
+
@@ -90,6 +92,7 @@ import useImport from '@/hooks/useImport'
import HotkeyDoc from './HotkeyDoc.vue'
import FileInput from '@/components/FileInput.vue'
+import FullscreenSpin from '@/components/FullscreenSpin.vue'
import {
Tooltip,
Dropdown,
@@ -104,7 +107,7 @@ const { gridLineSize, showRuler, showSelectPanel } = storeToRefs(mainStore)
const { enterScreening, enterScreeningFromStart } = useScreening()
const { createSlide, deleteSlide, resetSlides } = useSlideHandler()
const { redo, undo } = useHistorySnapshot()
-const { importSpecificFile, importPPTXFile } = useImport()
+const { importSpecificFile, importPPTXFile, exporting } = useImport()
const setDialogForExport = mainStore.setDialogForExport