From 0025a12f4371460a47c18cdf3a9a95a5ff25fd35 Mon Sep 17 00:00:00 2001 From: pipipi-pikachu Date: Tue, 14 Mar 2023 21:04:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=BC=E5=85=A5PPTX=E6=96=87?= =?UTF-8?q?=E4=BB=B6DEMO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 99 +++++---- package.json | 1 + src/configs/shapes.ts | 82 ++++++-- src/hooks/useExport.ts | 24 +-- src/hooks/useImport.ts | 266 ++++++++++++++++++++++++ src/views/Editor/EditorHeader/index.vue | 7 +- 6 files changed, 394 insertions(+), 85 deletions(-) create mode 100644 src/hooks/useImport.ts diff --git a/package-lock.json b/package-lock.json index afda2f55..42843d06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "nanoid": "^4.0.0", "pinia": "^2.0.32", "pptxgenjs": "^3.11.0", + "pptxtojson": "^0.0.8", "prosemirror-commands": "^1.3.0", "prosemirror-dropcursor": "^1.6.0", "prosemirror-gapcursor": "^1.3.1", @@ -40,8 +41,7 @@ "svg-arc-to-cubic-bezier": "^3.2.0", "svg-pathdata": "^6.0.0", "tinycolor2": "^1.6.0", - "vue": "^3.2.47", - "vuedraggable": "^4.1.0" + "vue": "^3.2.47" }, "devDependencies": { "@commitlint/cli": "^17.4.4", @@ -11734,6 +11734,16 @@ "node": ">=14.0.0" } }, + "node_modules/pptxtojson": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/pptxtojson/-/pptxtojson-0.0.8.tgz", + "integrity": "sha512-9RDoPhTF9Nq7xlJbzmi05lfLV4TVa7sATX5uUmzWEj5mZyQ1SymIU8e6E1/h86/4BUVB0DbM9blwjCBXjiVlpw==", + "dependencies": { + "jszip": "^3.10.1", + "tinycolor2": "1.6.0", + "txml": "^5.1.1" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -12278,7 +12288,6 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -12689,7 +12698,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -13338,7 +13346,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -14395,6 +14402,23 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/txml": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/txml/-/txml-5.1.1.tgz", + "integrity": "sha512-TwMDLnXQ09enNaxybLVvKZU7rqog8LgnuAs4ZYXM0nV0eu10iLsSFwlX3AEknAXXtH1wT3CYfoiXAjyBexcmuw==", + "dependencies": { + "through2": "^3.0.1" + } + }, + "node_modules/txml/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -14887,22 +14911,6 @@ "vue": "^3.0.0" } }, - "node_modules/vuedraggable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", - "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", - "dependencies": { - "sortablejs": "1.14.0" - }, - "peerDependencies": { - "vue": "^3.0.1" - } - }, - "node_modules/vuedraggable/node_modules/sortablejs": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", - "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" - }, "node_modules/w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", @@ -24667,6 +24675,16 @@ } } }, + "pptxtojson": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/pptxtojson/-/pptxtojson-0.0.8.tgz", + "integrity": "sha512-9RDoPhTF9Nq7xlJbzmi05lfLV4TVa7sATX5uUmzWEj5mZyQ1SymIU8e6E1/h86/4BUVB0DbM9blwjCBXjiVlpw==", + "requires": { + "jszip": "^3.10.1", + "tinycolor2": "1.6.0", + "txml": "^5.1.1" + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -25104,7 +25122,6 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -25406,8 +25423,7 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safe-regex-test": { "version": "1.0.0", @@ -25928,7 +25944,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "requires": { "safe-buffer": "~5.2.0" } @@ -26708,6 +26723,25 @@ } } }, + "txml": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/txml/-/txml-5.1.1.tgz", + "integrity": "sha512-TwMDLnXQ09enNaxybLVvKZU7rqog8LgnuAs4ZYXM0nV0eu10iLsSFwlX3AEknAXXtH1wT3CYfoiXAjyBexcmuw==", + "requires": { + "through2": "^3.0.1" + }, + "dependencies": { + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -27071,21 +27105,6 @@ "is-plain-object": "3.0.1" } }, - "vuedraggable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", - "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", - "requires": { - "sortablejs": "1.14.0" - }, - "dependencies": { - "sortablejs": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", - "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" - } - } - }, "w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", diff --git a/package.json b/package.json index 416e4d56..20d59b35 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "nanoid": "^4.0.0", "pinia": "^2.0.32", "pptxgenjs": "^3.11.0", + "pptxtojson": "^0.0.8", "prosemirror-commands": "^1.3.0", "prosemirror-dropcursor": "^1.6.0", "prosemirror-gapcursor": "^1.3.1", diff --git a/src/configs/shapes.ts b/src/configs/shapes.ts index 8439c40d..622e64f0 100644 --- a/src/configs/shapes.ts +++ b/src/configs/shapes.ts @@ -10,6 +10,7 @@ export interface ShapePoolItem { special?: boolean pathFormula?: ShapePathFormulasKeys outlined?: boolean + pptxShapeType?: string } interface ShapeListItem { @@ -228,47 +229,56 @@ export const SHAPE_LIST: ShapeListItem[] = [ children: [ { viewBox: [200, 200], - path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z' + path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z', + pptxShapeType: 'rect', }, { viewBox: [200, 200], path: 'M 50 0 L 150 0 Q 200 0 200 50 L 200 150 Q 200 200 150 200 L 50 200 Q 0 200 0 150 L 0 50 Q 0 0 50 0 Z', pathFormula: ShapePathFormulasKeys.ROUND_RECT, + pptxShapeType: 'roundRect', }, { viewBox: [200, 200], path: 'M 0 200 L 0 0 L 150 0 L 200 50 L 200 200 Z', pathFormula: ShapePathFormulasKeys.CUT_RECT_SINGLE, + pptxShapeType: 'snip1Rect', }, { viewBox: [200, 200], path: 'M 0 50 L 50 0 L 150 0 L 200 50 L 200 200 L 0 200 Z', pathFormula: ShapePathFormulasKeys.CUT_RECT_SAMESIDE, + pptxShapeType: 'snip2SameRect', }, { viewBox: [200, 200], path: 'M 0 150 L 0 0 L 150 0 L 200 50 L 200 200 L 50 200 Z', pathFormula: ShapePathFormulasKeys.CUT_RECT_DIAGONAL, + pptxShapeType: 'snip2DiagRect', }, { viewBox: [200, 200], path: 'M 50 0 L 150 0 L 200 50 L 200 200 L 0 200 L 0 50 Q 0 0 50 0 Z', pathFormula: ShapePathFormulasKeys.CUT_ROUND_RECT, + pptxShapeType: 'snipRoundRect', }, { viewBox: [200, 200], path: 'M 0 0 L 150 0 Q 200 0 200 50 L 200 200 L 0 200 L 0 0 Z', pathFormula: ShapePathFormulasKeys.ROUND_RECT_SINGLE, + pptxShapeType: 'round1Rect', }, { viewBox: [200, 200], path: 'M 0 50 Q 0 0 50 0 L 150 0 Q 200 0 200 50 L 200 200 L 0 200 Z', pathFormula: ShapePathFormulasKeys.ROUND_RECT_SAMESIDE, + pptxShapeType: 'round2SameRect', }, { viewBox: [200, 200], path: 'M 50 0 L 200 0 L 200 150 Q 200 200 150 200 L 0 200 L 0 50 Q 0 0 50 0 Z', pathFormula: ShapePathFormulasKeys.ROUND_RECT_DIAGONAL, + pptxShapeType: 'round2DiagRect', }, ] }, @@ -278,12 +288,14 @@ export const SHAPE_LIST: ShapeListItem[] = [ children: [ { viewBox: [200, 200], - path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z' + path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z', + pptxShapeType: 'ellipse', }, { viewBox: [200, 200], path: 'M 100 0 L 0 200 L 200 200 L 100 0 Z', pathFormula: ShapePathFormulasKeys.TRIANGLE, + pptxShapeType: 'triangle', }, { viewBox: [200, 200], @@ -297,6 +309,7 @@ export const SHAPE_LIST: ShapeListItem[] = [ viewBox: [200, 200], path: 'M 50 0 L 200 0 L 150 200 L 0 200 L 50 0 Z', pathFormula: ShapePathFormulasKeys.PARALLELOGRAM_LEFT, + pptxShapeType: 'parallelogram', }, { viewBox: [200, 200], @@ -307,10 +320,12 @@ export const SHAPE_LIST: ShapeListItem[] = [ viewBox: [200, 200], path: 'M 50 0 L 150 0 L 200 200 L 0 200 L 50 0 Z', pathFormula: ShapePathFormulasKeys.TRAPEZOID, + pptxShapeType: 'trapezoid', }, { viewBox: [200, 200], - path: 'M 100 0 L 0 100 L 100 200 L 200 100 L 100 0 Z' + path: 'M 100 0 L 0 100 L 100 200 L 200 100 L 100 0 Z', + pptxShapeType: 'diamond', }, { viewBox: [200, 200], @@ -340,7 +355,8 @@ export const SHAPE_LIST: ShapeListItem[] = [ }, { viewBox: [200, 200], - path: 'M 100 0 A 100 100 102 1 0 200 100 L 100 100 L 100 0 Z' + path: 'M 100 0 A 100 100 102 1 0 200 100 L 100 100 L 100 0 Z', + pptxShapeType: 'pie', }, { viewBox: [200, 200], @@ -348,11 +364,13 @@ export const SHAPE_LIST: ShapeListItem[] = [ }, { viewBox: [200, 200], - path: 'M 100 0 A 100 100 102 1 0 200 100 L 100 0 Z' + path: 'M 100 0 A 100 100 102 1 0 200 100 L 100 0 Z', + pptxShapeType: 'chord', }, { viewBox: [200, 200], - path: 'M 100 0 A 100 100 102 1 0 200 100 L 200 0 L 100 0 Z' + path: 'M 100 0 A 100 100 102 1 0 200 100 L 200 0 L 100 0 Z', + pptxShapeType: 'teardrop', }, { viewBox: [200, 200], @@ -360,7 +378,13 @@ export const SHAPE_LIST: ShapeListItem[] = [ }, { viewBox: [200, 200], - path: 'M 100 0 L 0 90 L 50 200 L 150 200 L 200 90 L 100 0 Z' + path: 'M 100 0 L 0 90 L 50 200 L 150 200 L 200 90 L 100 0 Z', + pptxShapeType: 'pentagon', + }, + { + viewBox: [200, 200], + path: 'M 40 0 L 160 0 L 200 100 L 160 200 L 40 200 L 0 100 Z', + pptxShapeType: 'hexagon', }, { viewBox: [200, 200], @@ -368,7 +392,8 @@ export const SHAPE_LIST: ShapeListItem[] = [ }, { viewBox: [200, 200], - path: 'M 60 0 L 140 0 L 200 60 L 200 140 L 140 200 L 60 200 L 0 140 L 0 60 L 60 0 Z' + path: 'M 60 0 L 140 0 L 200 60 L 200 140 L 140 200 L 60 200 L 0 140 L 0 60 L 60 0 Z', + pptxShapeType: 'octagon', }, { viewBox: [200, 200], @@ -462,6 +487,13 @@ export const SHAPE_LIST: ShapeListItem[] = [ { viewBox: [200, 200], path: 'M 100 0 L 120 80 L 200 100 L 120 120 L 100 200 L 80 120 L 0 100 L 80 80 L 100 0 Z', + pptxShapeType: 'star4', + }, + { + viewBox: [1024, 1024], + path: 'M1018.67652554 400.05983681l-382.95318779-5.89158658L512 34.78141155 388.27666225 394.16825023l-382.95318779 5.89158658L311.68602415 629.83174977l-117.83174978 365.27842665 312.25413766-223.88032637 312.25413904 223.88032637-117.83175116-365.27842665 318.14572563-229.77191296z', + pptxShapeType: 'star5', + special: true, }, { viewBox: [200, 200], @@ -470,6 +502,7 @@ export const SHAPE_LIST: ShapeListItem[] = [ { viewBox: [200, 200], path: 'M 100 0 L 140 60 L 200 60 L 160 100 L 200 140 L 140 140 L 100 200 L 60 140 L 0 140 L 40 100 L 0 60 L 60 60 L 100 0 Z', + pptxShapeType: 'star6', }, { viewBox: [200, 200], @@ -487,27 +520,33 @@ export const SHAPE_LIST: ShapeListItem[] = [ children: [ { viewBox: [200, 200], - path: 'M 100 0 L 0 100 L 50 100 L 50 200 L 150 200 L 150 100 L 200 100 L 100 0 Z' + path: 'M 100 0 L 0 100 L 50 100 L 50 200 L 150 200 L 150 100 L 200 100 L 100 0 Z', + pptxShapeType: 'upArrow', }, { viewBox: [200, 200], - path: 'M 100 200 L 200 100 L 150 100 L 150 0 L 50 0 L 50 100 L 0 100 L 100 200 Z' + path: 'M 100 200 L 200 100 L 150 100 L 150 0 L 50 0 L 50 100 L 0 100 L 100 200 Z', + pptxShapeType: 'downArrow', }, { viewBox: [200, 200], - path: 'M 0 100 L 100 0 L 100 50 L 200 50 L 200 150 L 100 150 L 100 200 L 0 100 Z' + path: 'M 0 100 L 100 0 L 100 50 L 200 50 L 200 150 L 100 150 L 100 200 L 0 100 Z', + pptxShapeType: 'leftArrow', }, { viewBox: [200, 200], - path: 'M 200 100 L 100 0 L 100 50 L 0 50 L 0 150 L 100 150 L 100 200 L 200 100 Z' + path: 'M 200 100 L 100 0 L 100 50 L 0 50 L 0 150 L 100 150 L 100 200 L 200 100 Z', + pptxShapeType: 'rightArrow', }, { viewBox: [200, 200], - path: 'M 100 0 L 0 60 L 60 60 L 60 140 L 0 140 L 100 200 L 200 140 L 140 140 L 140 60 L 200 60 L 100 0 Z' + path: 'M 100 0 L 0 60 L 60 60 L 60 140 L 0 140 L 100 200 L 200 140 L 140 140 L 140 60 L 200 60 L 100 0 Z', + pptxShapeType: 'upDownArrow', }, { viewBox: [200, 200], - path: 'M 0 100 L 60 0 L 60 60 L 140 60 L 140 0 L 200 100 L 140 200 L 140 140 L 60 140 L 60 200 L 0 100 Z' + path: 'M 0 100 L 60 0 L 60 60 L 140 60 L 140 0 L 200 100 L 140 200 L 140 140 L 60 140 L 60 200 L 0 100 Z', + pptxShapeType: 'leftRightArrow', }, { viewBox: [200, 200], @@ -515,11 +554,12 @@ export const SHAPE_LIST: ShapeListItem[] = [ }, { viewBox: [200, 200], - path: 'M 0 100 L 100 0 L 100 50 L 200 50 L 150 100 L 200 150 L 100 150 L 100 200 L 0 100 Z' + path: 'M 0 100 L 100 0 L 100 50 L 200 50 L 150 100 L 200 150 L 100 150 L 100 200 L 0 100 Z', }, { viewBox: [200, 200], - path: 'M 200 100 L 100 0 L 100 50 L 0 50 L 50 100 L 0 150 L 100 150 L 100 200 L 200 100 Z' + path: 'M 200 100 L 100 0 L 100 50 L 0 50 L 50 100 L 0 150 L 100 150 L 100 200 L 200 100 Z', + pptxShapeType: 'notchedRightArrow', }, { viewBox: [200, 200], @@ -527,19 +567,21 @@ export const SHAPE_LIST: ShapeListItem[] = [ }, { viewBox: [200, 200], - path: 'M 200 100 L 120 20 L 120 80 L 80 80 L 80 0 L 0 0 L 0 200 L 80 200 L 80 120 L 120 120 L 120 180 L 200 100 Z' + path: 'M 200 100 L 120 20 L 120 80 L 80 80 L 80 0 L 0 0 L 0 200 L 80 200 L 80 120 L 120 120 L 120 180 L 200 100 Z', }, { viewBox: [200, 200], - path: 'M 0 0 L 120 0 L 200 100 L 120 200 L 0 200 L 80 100 L 0 0 Z' + path: 'M 0 0 L 120 0 L 200 100 L 120 200 L 0 200 L 80 100 L 0 0 Z', + pptxShapeType: 'chevron', }, { viewBox: [200, 200], - path: 'M 80 0 L 200 0 L 120 100 L 200 200 L 80 200 L 0 100 L 80 0 Z' + path: 'M 80 0 L 200 0 L 120 100 L 200 200 L 80 200 L 0 100 L 80 0 Z', }, { viewBox: [200, 200], - path: 'M 0 0 L 140 0 L 200 100 L 140 200 L 0 200 L 0 100 L 0 0 Z' + path: 'M 0 0 L 140 0 L 200 100 L 140 200 L 0 200 L 0 100 L 0 0 Z', + pptxShapeType: 'homePlate', }, { viewBox: [200, 200], diff --git a/src/hooks/useExport.ts b/src/hooks/useExport.ts index 212da26d..8c25baf5 100644 --- a/src/hooks/useExport.ts +++ b/src/hooks/useExport.ts @@ -10,10 +10,9 @@ import { PPTElementOutline, PPTElementShadow, PPTElementLink, Slide } from '@/ty import { getElementRange, getLineElementPath, getTableSubThemeColor } from '@/utils/element' import { AST, toAST } from '@/utils/htmlParser' import { SvgPoints, toPoints } from '@/utils/svgPathParser' -import { decrypt, encrypt } from '@/utils/crypto' +import { encrypt } from '@/utils/crypto' import { svg2Base64 } from '@/utils/svg2Base64' import { message } from 'ant-design-vue' -import useAddSlidesOrElements from '@/hooks/useAddSlidesOrElements' const INCH_PX_RATIO = 100 const PT_PX_RATIO = 0.75 @@ -28,8 +27,6 @@ export default () => { const slidesStore = useSlidesStore() const { slides, theme, viewportRatio } = storeToRefs(slidesStore) - const { addSlidesFromData } = useAddSlidesOrElements() - const exporting = ref(false) // 导出图片 @@ -64,24 +61,6 @@ export default () => { saveAs(blob, 'pptist_slides.pptist') } - // 导入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.setSlides(slides) - else addSlidesFromData(slides) - } - catch { - message.error('无法正确读取 / 解析该文件') - } - }) - reader.readAsText(file) - } - // 导出JSON文件 const exportJSON = () => { const blob = new Blob([JSON.stringify(slides.value)], { type: '' }) @@ -778,7 +757,6 @@ export default () => { exporting, exportImage, exportJSON, - importSpecificFile, exportSpecificFile, exportPPTX, } diff --git a/src/hooks/useImport.ts b/src/hooks/useImport.ts new file mode 100644 index 00000000..af589777 --- /dev/null +++ b/src/hooks/useImport.ts @@ -0,0 +1,266 @@ +import { storeToRefs } from 'pinia' +import { parse } from 'pptxtojson' +import { nanoid } from 'nanoid' +import { Slide, TableCellStyle, TableCell, ChartType, ChartOptions, SlideBackground, PPTShapeElement } from '@/types/slides' +import { useSlidesStore } from '@/store' +import { decrypt } from '@/utils/crypto' +import { ShapePoolItem, SHAPE_LIST, SHAPE_PATH_FORMULAS } from '@/configs/shapes' +import useAddSlidesOrElements from '@/hooks/useAddSlidesOrElements' + +import { message } from 'ant-design-vue' + +export default () => { + const slidesStore = useSlidesStore() + const { theme } = storeToRefs(useSlidesStore()) + + const { addSlidesFromData } = useAddSlidesOrElements() + + // 导入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.setSlides(slides) + else addSlidesFromData(slides) + } + catch { + message.error('无法正确读取 / 解析该文件') + } + }) + reader.readAsText(file) + } + + // 导入PPTX文件 + const importPPTXFile = (files: FileList) => { + const file = files[0] + if (!file) return + + const shapeList: ShapePoolItem[] = [] + 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) + const slides: Slide[] = [] + for (const item of json.slides) { + const { type, value } = item.fill + let background: SlideBackground + if (type === 'image') { + background = { + type: 'image', + image: value.picBase64, + imageSize: 'cover', + } + } + else { + background = { + type: 'solid', + color: value, + } + } + + const slide: Slide = { + id: nanoid(10), + 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, + 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) + } + 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') { + 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') {} + } + slides.push(slide) + } + addSlidesFromData(slides) + } + reader.readAsArrayBuffer(file) + } + + return { + importSpecificFile, + importPPTXFile, + } +} \ No newline at end of file diff --git a/src/views/Editor/EditorHeader/index.vue b/src/views/Editor/EditorHeader/index.vue index 8afc5fbe..f0b39530 100644 --- a/src/views/Editor/EditorHeader/index.vue +++ b/src/views/Editor/EditorHeader/index.vue @@ -8,6 +8,9 @@ 导入 pptist 文件 + + 导入 pptx 文件(demo) + 导出文件 @@ -83,7 +86,7 @@ import { useMainStore } from '@/store' import useScreening from '@/hooks/useScreening' import useSlideHandler from '@/hooks/useSlideHandler' import useHistorySnapshot from '@/hooks/useHistorySnapshot' -import useExport from '@/hooks/useExport' +import useImport from '@/hooks/useImport' import HotkeyDoc from './HotkeyDoc.vue' import FileInput from '@/components/FileInput.vue' @@ -101,7 +104,7 @@ const { gridLineSize, showRuler, showSelectPanel } = storeToRefs(mainStore) const { enterScreening, enterScreeningFromStart } = useScreening() const { createSlide, deleteSlide, resetSlides } = useSlideHandler() const { redo, undo } = useHistorySnapshot() -const { importSpecificFile } = useExport() +const { importSpecificFile, importPPTXFile } = useImport() const setDialogForExport = mainStore.setDialogForExport