mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 导入 PPTX Demo 补充
This commit is contained in:
parent
05f7edf852
commit
0b6c8c04b4
@ -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',
|
||||
|
14
package-lock.json
generated
14
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
else {
|
||||
const shape = shapeList.find(item => item.pptxShapeType === el.shapType)
|
||||
|
||||
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({
|
||||
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)
|
||||
}
|
||||
else if (el.type === 'table') {
|
||||
const row = el.data.length
|
||||
const col = el.data[0].length
|
||||
|
||||
const colWidths: number[] = new Array(col).fill(1 / col)
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
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,
|
||||
}
|
||||
}
|
@ -76,6 +76,8 @@
|
||||
>
|
||||
<HotkeyDoc />
|
||||
</Drawer>
|
||||
|
||||
<FullscreenSpin :loading="exporting" tip="正在导入..." />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user