mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 添加导出配置弹窗、添加导出配置(#104 #105)
This commit is contained in:
parent
4de6d5234e
commit
4bbc938fcf
@ -6,7 +6,7 @@ import pptxgen from 'pptxgenjs'
|
|||||||
import tinycolor from 'tinycolor2'
|
import tinycolor from 'tinycolor2'
|
||||||
import { toPng, toJpeg } from 'html-to-image'
|
import { toPng, toJpeg } from 'html-to-image'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import { PPTElementOutline, PPTElementShadow, PPTElementLink } from '@/types/slides'
|
import { PPTElementOutline, PPTElementShadow, PPTElementLink, Slide } from '@/types/slides'
|
||||||
import { getElementRange, getLineElementPath, getTableSubThemeColor } from '@/utils/element'
|
import { getElementRange, getLineElementPath, getTableSubThemeColor } from '@/utils/element'
|
||||||
import { AST, toAST } from '@/utils/htmlParser'
|
import { AST, toAST } from '@/utils/htmlParser'
|
||||||
import { SvgPoints, toPoints } from '@/utils/svgPathParser'
|
import { SvgPoints, toPoints } from '@/utils/svgPathParser'
|
||||||
@ -331,7 +331,7 @@ export default () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 导出PPTX文件
|
// 导出PPTX文件
|
||||||
const exportPPTX = () => {
|
const exportPPTX = (_slides: Slide[] = slides.value) => {
|
||||||
exporting.value = true
|
exporting.value = true
|
||||||
const pptx = new pptxgen()
|
const pptx = new pptxgen()
|
||||||
|
|
||||||
@ -341,7 +341,7 @@ export default () => {
|
|||||||
background: { color: bgColor, transparency: (1 - bgAlpha) * 100 },
|
background: { color: bgColor, transparency: (1 - bgAlpha) * 100 },
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const slide of slides.value) {
|
for (const slide of _slides) {
|
||||||
const pptxSlide = pptx.addSlide()
|
const pptxSlide = pptx.addSlide()
|
||||||
|
|
||||||
if (slide.background) {
|
if (slide.background) {
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { CreatingElement } from '@/types/edit'
|
import { CreatingElement } from '@/types/edit'
|
||||||
import { ToolbarStates } from '@/types/toolbar'
|
import { ToolbarStates } from '@/types/toolbar'
|
||||||
|
import { DialogForExportTypes } from '@/types/export'
|
||||||
import { SYS_FONTS } from '@/configs/font'
|
import { SYS_FONTS } from '@/configs/font'
|
||||||
import { TextAttrs, defaultRichTextAttrs } from '@/utils/prosemirror/utils'
|
import { TextAttrs, defaultRichTextAttrs } from '@/utils/prosemirror/utils'
|
||||||
import { isSupportFont } from '@/utils/font'
|
import { isSupportFont } from '@/utils/font'
|
||||||
|
|
||||||
import { useSlidesStore } from './slides'
|
import { useSlidesStore } from './slides'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface MainState {
|
export interface MainState {
|
||||||
activeElementIdList: string[];
|
activeElementIdList: string[];
|
||||||
handleElementId: string;
|
handleElementId: string;
|
||||||
@ -27,6 +30,7 @@ export interface MainState {
|
|||||||
richTextAttrs: TextAttrs;
|
richTextAttrs: TextAttrs;
|
||||||
selectedTableCells: string[];
|
selectedTableCells: string[];
|
||||||
selectedSlidesIndex: number[];
|
selectedSlidesIndex: number[];
|
||||||
|
dialogForExport: DialogForExportTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMainStore = defineStore('main', {
|
export const useMainStore = defineStore('main', {
|
||||||
@ -50,6 +54,7 @@ export const useMainStore = defineStore('main', {
|
|||||||
selectedTableCells: [], // 选中的表格单元格
|
selectedTableCells: [], // 选中的表格单元格
|
||||||
isScaling: false, // 正在进行元素缩放
|
isScaling: false, // 正在进行元素缩放
|
||||||
selectedSlidesIndex: [], // 当前被选中的页面索引集合
|
selectedSlidesIndex: [], // 当前被选中的页面索引集合
|
||||||
|
dialogForExport: '', // 导出面板
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
@ -147,5 +152,9 @@ export const useMainStore = defineStore('main', {
|
|||||||
updateSelectedSlidesIndex(selectedSlidesIndex: number[]) {
|
updateSelectedSlidesIndex(selectedSlidesIndex: number[]) {
|
||||||
this.selectedSlidesIndex = selectedSlidesIndex
|
this.selectedSlidesIndex = selectedSlidesIndex
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setDialogForExport(type: DialogForExportTypes) {
|
||||||
|
this.dialogForExport = type
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
1
src/types/export.ts
Normal file
1
src/types/export.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type DialogForExportTypes = 'image' | 'pdf' | 'json' | 'pptx' | ''
|
@ -5,10 +5,10 @@
|
|||||||
<div class="menu-item"><IconFolderClose /> <span class="text">文件</span></div>
|
<div class="menu-item"><IconFolderClose /> <span class="text">文件</span></div>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuItem @click="exportJSON()">导出 JSON</MenuItem>
|
<MenuItem @click="setDialogForExport('json')">导出 JSON</MenuItem>
|
||||||
<MenuItem @click="exportPPTX()">导出 PPTX</MenuItem>
|
<MenuItem @click="setDialogForExport('pptx')">导出 PPTX</MenuItem>
|
||||||
<MenuItem @click="exportImgDialogVisible = true">导出图片</MenuItem>
|
<MenuItem @click="setDialogForExport('image')">导出图片</MenuItem>
|
||||||
<MenuItem @click="exportPDFDialogVisible = true">打印 / 导出 PDF</MenuItem>
|
<MenuItem @click="setDialogForExport('pdf')">打印 / 导出 PDF</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</template>
|
</template>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@ -65,30 +65,6 @@
|
|||||||
>
|
>
|
||||||
<HotkeyDoc />
|
<HotkeyDoc />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<Modal
|
|
||||||
v-model:visible="exportImgDialogVisible"
|
|
||||||
:footer="null"
|
|
||||||
centered
|
|
||||||
:closable="false"
|
|
||||||
:width="680"
|
|
||||||
destroyOnClose
|
|
||||||
>
|
|
||||||
<ExportImgDialog @close="exportImgDialogVisible = false"/>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
v-model:visible="exportPDFDialogVisible"
|
|
||||||
:footer="null"
|
|
||||||
centered
|
|
||||||
:closable="false"
|
|
||||||
:width="680"
|
|
||||||
destroyOnClose
|
|
||||||
>
|
|
||||||
<ExportPDFDialog @close="exportPDFDialogVisible = false"/>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<FullscreenSpin :loading="exporting" tip="正在导出..." />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -102,15 +78,11 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|||||||
import useExport from '@/hooks/useExport'
|
import useExport from '@/hooks/useExport'
|
||||||
|
|
||||||
import HotkeyDoc from './HotkeyDoc.vue'
|
import HotkeyDoc from './HotkeyDoc.vue'
|
||||||
import ExportImgDialog from './ExportImgDialog.vue'
|
|
||||||
import ExportPDFDialog from './ExportPDFDialog.vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'editor-header',
|
name: 'editor-header',
|
||||||
components: {
|
components: {
|
||||||
HotkeyDoc,
|
HotkeyDoc,
|
||||||
ExportImgDialog,
|
|
||||||
ExportPDFDialog,
|
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const mainStore = useMainStore()
|
const mainStore = useMainStore()
|
||||||
@ -121,6 +93,8 @@ export default defineComponent({
|
|||||||
const { redo, undo } = useHistorySnapshot()
|
const { redo, undo } = useHistorySnapshot()
|
||||||
const { exporting, exportJSON, exportPPTX } = useExport()
|
const { exporting, exportJSON, exportPPTX } = useExport()
|
||||||
|
|
||||||
|
const setDialogForExport = mainStore.setDialogForExport
|
||||||
|
|
||||||
const toggleGridLines = () => {
|
const toggleGridLines = () => {
|
||||||
mainStore.setGridLinesState(!showGridLines.value)
|
mainStore.setGridLinesState(!showGridLines.value)
|
||||||
}
|
}
|
||||||
@ -130,8 +104,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hotkeyDrawerVisible = ref(false)
|
const hotkeyDrawerVisible = ref(false)
|
||||||
const exportImgDialogVisible = ref(false)
|
|
||||||
const exportPDFDialogVisible = ref(false)
|
|
||||||
|
|
||||||
const goIssues = () => {
|
const goIssues = () => {
|
||||||
window.open('https://github.com/pipipi-pikachu/PPTist/issues')
|
window.open('https://github.com/pipipi-pikachu/PPTist/issues')
|
||||||
@ -143,9 +115,8 @@ export default defineComponent({
|
|||||||
showGridLines,
|
showGridLines,
|
||||||
showRuler,
|
showRuler,
|
||||||
hotkeyDrawerVisible,
|
hotkeyDrawerVisible,
|
||||||
exportImgDialogVisible,
|
|
||||||
exportPDFDialogVisible,
|
|
||||||
exporting,
|
exporting,
|
||||||
|
setDialogForExport,
|
||||||
enterScreening,
|
enterScreening,
|
||||||
enterScreeningFromStart,
|
enterScreeningFromStart,
|
||||||
createSlide,
|
createSlide,
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="thumbnails" ref="imageThumbnailsRef">
|
<div class="thumbnails" ref="imageThumbnailsRef">
|
||||||
<ThumbnailSlide
|
<ThumbnailSlide
|
||||||
class="thumbnail"
|
class="thumbnail"
|
||||||
v-for="slide in slides"
|
v-for="slide in renderSlides"
|
||||||
:key="slide.id"
|
:key="slide.id"
|
||||||
:slide="slide"
|
:slide="slide"
|
||||||
:size="1600"
|
:size="1600"
|
||||||
@ -18,10 +18,32 @@
|
|||||||
class="config-item"
|
class="config-item"
|
||||||
v-model:value="format"
|
v-model:value="format"
|
||||||
>
|
>
|
||||||
<RadioButton value="jpeg">JPEG</RadioButton>
|
<RadioButton style="width: 50%;" value="jpeg">JPEG</RadioButton>
|
||||||
<RadioButton value="png">PNG</RadioButton>
|
<RadioButton style="width: 50%;" value="png">PNG</RadioButton>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="title">导出范围:</div>
|
||||||
|
<RadioGroup
|
||||||
|
class="config-item"
|
||||||
|
v-model:value="rangeType"
|
||||||
|
>
|
||||||
|
<RadioButton style="width: 33.33%;" value="all">全部</RadioButton>
|
||||||
|
<RadioButton style="width: 33.33%;" value="current">当前页</RadioButton>
|
||||||
|
<RadioButton style="width: 33.33%;" value="custom">自定义</RadioButton>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
<div class="row" v-if="rangeType === 'custom'">
|
||||||
|
<div class="title" :data-range="`(${range[0]} ~ ${range[1]})`">自定义范围:</div>
|
||||||
|
<Slider
|
||||||
|
class="config-item"
|
||||||
|
range
|
||||||
|
:min="1"
|
||||||
|
:max="slides.length"
|
||||||
|
:step="1"
|
||||||
|
v-model:value="range"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="title">图片质量:</div>
|
<div class="title">图片质量:</div>
|
||||||
@ -37,26 +59,24 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="title">忽略在线字体:</div>
|
<div class="title">忽略在线字体:</div>
|
||||||
<div class="config-item">
|
<div class="config-item">
|
||||||
|
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="导出时默认忽略在线字体,若您在幻灯片中使用了在线字体,且希望导出后保留相关样式,可选择关闭【忽略在线字体】选项,但要注意这将会增加导出用时。">
|
||||||
<Switch v-model:checked="ignoreWebfont" />
|
<Switch v-model:checked="ignoreWebfont" />
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tip">
|
|
||||||
提示:导出时默认会忽略在线字体,若您在幻灯片中使用了在线字体,且不希望导出图片中丢失相关样式,可以选择关闭【忽略在线字体】选项,但要注意,这将会导致导出用时大幅度增加。
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<Button class="btn export" type="primary" @click="expImage()">导出图片</Button>
|
<Button class="btn export" type="primary" @click="expImage()">导出图片</Button>
|
||||||
<Button class="btn close" @click="close()">关闭</Button>
|
<Button class="btn close" @click="close()">关闭</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<FullscreenSpin :loading="exporting" tip="正在导出..." />
|
<FullscreenSpin :loading="exporting" tip="正在导出..." />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref } from 'vue'
|
import { computed, defineComponent, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import useExport from '@/hooks/useExport'
|
import useExport from '@/hooks/useExport'
|
||||||
@ -69,13 +89,24 @@ export default defineComponent({
|
|||||||
ThumbnailSlide,
|
ThumbnailSlide,
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const { slides } = storeToRefs(useSlidesStore())
|
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
||||||
|
|
||||||
const imageThumbnailsRef = ref<HTMLElement>()
|
const imageThumbnailsRef = ref<HTMLElement>()
|
||||||
|
const rangeType = ref<'all' | 'current' | 'custom'>('all')
|
||||||
|
const range = ref<[number, number]>([1, slides.value.length])
|
||||||
const format = ref<'jpeg' | 'png'>('jpeg')
|
const format = ref<'jpeg' | 'png'>('jpeg')
|
||||||
const quality = ref(1)
|
const quality = ref(1)
|
||||||
const ignoreWebfont = ref(true)
|
const ignoreWebfont = ref(true)
|
||||||
|
|
||||||
|
const renderSlides = computed(() => {
|
||||||
|
if (rangeType.value === 'all') return slides.value
|
||||||
|
if (rangeType.value === 'current') return [currentSlide.value]
|
||||||
|
return slides.value.filter((item, index) => {
|
||||||
|
const [min, max] = range.value
|
||||||
|
return index >= min - 1 && index <= max - 1
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const close = () => emit('close')
|
const close = () => emit('close')
|
||||||
|
|
||||||
const { exportImage, exporting } = useExport()
|
const { exportImage, exporting } = useExport()
|
||||||
@ -88,9 +119,12 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
imageThumbnailsRef,
|
imageThumbnailsRef,
|
||||||
slides,
|
slides,
|
||||||
|
rangeType,
|
||||||
|
range,
|
||||||
format,
|
format,
|
||||||
quality,
|
quality,
|
||||||
ignoreWebfont,
|
ignoreWebfont,
|
||||||
|
renderSlides,
|
||||||
exporting,
|
exporting,
|
||||||
expImage,
|
expImage,
|
||||||
close,
|
close,
|
||||||
@ -101,9 +135,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.export-img-dialog {
|
.export-img-dialog {
|
||||||
height: 500px;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@ -117,7 +153,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.configs {
|
.configs {
|
||||||
width: 300px;
|
width: 350px;
|
||||||
|
height: calc(100% - 100px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -132,22 +169,25 @@ export default defineComponent({
|
|||||||
|
|
||||||
.title {
|
.title {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: attr(data-range);
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.config-item {
|
.config-item {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.tip {
|
.btns {
|
||||||
font-size: 12px;
|
width: 300px;
|
||||||
color: #aaa;
|
height: 100px;
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btns {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 40px;
|
|
||||||
|
|
||||||
.export {
|
.export {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -156,6 +196,5 @@ export default defineComponent({
|
|||||||
width: 100px;
|
width: 100px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
80
src/views/Editor/ExportDialog/ExportJSON.vue
Normal file
80
src/views/Editor/ExportDialog/ExportJSON.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="export-json-dialog">
|
||||||
|
<div class="preview">
|
||||||
|
<pre>{{slides}}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btns">
|
||||||
|
<Button class="btn export" type="primary" @click="exportJSON()">导出 JSON</Button>
|
||||||
|
<Button class="btn close" @click="close()">关闭</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useSlidesStore } from '@/store'
|
||||||
|
import useExport from '@/hooks/useExport'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'export-json-dialog',
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const close = () => emit('close')
|
||||||
|
|
||||||
|
const { slides } = storeToRefs(useSlidesStore())
|
||||||
|
|
||||||
|
const { exportJSON } = useExport()
|
||||||
|
|
||||||
|
return {
|
||||||
|
slides,
|
||||||
|
exportJSON,
|
||||||
|
close,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.export-json-dialog {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.preview {
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
height: calc(100% - 100px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #2d2d30;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.btns {
|
||||||
|
width: 300px;
|
||||||
|
height: 100px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.export {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.close {
|
||||||
|
width: 100px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: #2d2d30;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #777;
|
||||||
|
}
|
||||||
|
</style>
|
@ -6,7 +6,7 @@
|
|||||||
class="thumbnail"
|
class="thumbnail"
|
||||||
:slide="currentSlide"
|
:slide="currentSlide"
|
||||||
:size="1600"
|
:size="1600"
|
||||||
v-if="range === 'current'"
|
v-if="rangeType === 'current'"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ThumbnailSlide
|
<ThumbnailSlide
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<div class="title">导出范围:</div>
|
<div class="title">导出范围:</div>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
class="config-item"
|
class="config-item"
|
||||||
v-model:value="range"
|
v-model:value="rangeType"
|
||||||
>
|
>
|
||||||
<RadioButton style="width: 50%;" value="all">全部幻灯片</RadioButton>
|
<RadioButton style="width: 50%;" value="all">全部幻灯片</RadioButton>
|
||||||
<RadioButton style="width: 50%;" value="current">当前幻灯片</RadioButton>
|
<RadioButton style="width: 50%;" value="current">当前幻灯片</RadioButton>
|
||||||
@ -48,13 +48,13 @@
|
|||||||
<Switch v-model:checked="padding" />
|
<Switch v-model:checked="padding" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<Button class="btn export" type="primary" @click="expPDF()">打印 / 导出 PDF</Button>
|
<Button class="btn export" type="primary" @click="expPDF()">打印 / 导出 PDF</Button>
|
||||||
<Button class="btn close" @click="close()">关闭</Button>
|
<Button class="btn close" @click="close()">关闭</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@ -74,7 +74,7 @@ export default defineComponent({
|
|||||||
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
||||||
|
|
||||||
const pdfThumbnailsRef = ref<HTMLElement>()
|
const pdfThumbnailsRef = ref<HTMLElement>()
|
||||||
const range = ref<'all' | 'current'>('all')
|
const rangeType = ref<'all' | 'current'>('all')
|
||||||
const count = ref(1)
|
const count = ref(1)
|
||||||
const padding = ref(true)
|
const padding = ref(true)
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ export default defineComponent({
|
|||||||
if (!pdfThumbnailsRef.value) return
|
if (!pdfThumbnailsRef.value) return
|
||||||
const pageSize = {
|
const pageSize = {
|
||||||
width: 1600,
|
width: 1600,
|
||||||
height: range.value === 'all' ? 900 * count.value : 900,
|
height: rangeType.value === 'all' ? 900 * count.value : 900,
|
||||||
margin: padding.value ? 50 : 0,
|
margin: padding.value ? 50 : 0,
|
||||||
}
|
}
|
||||||
print(pdfThumbnailsRef.value, pageSize)
|
print(pdfThumbnailsRef.value, pageSize)
|
||||||
@ -94,7 +94,7 @@ export default defineComponent({
|
|||||||
pdfThumbnailsRef,
|
pdfThumbnailsRef,
|
||||||
slides,
|
slides,
|
||||||
currentSlide,
|
currentSlide,
|
||||||
range,
|
rangeType,
|
||||||
count,
|
count,
|
||||||
padding,
|
padding,
|
||||||
expPDF,
|
expPDF,
|
||||||
@ -106,9 +106,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.export-pdf-dialog {
|
.export-pdf-dialog {
|
||||||
height: 400px;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@ -128,6 +130,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
.configs {
|
.configs {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
height: calc(100% - 100px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -146,12 +149,13 @@ export default defineComponent({
|
|||||||
.config-item {
|
.config-item {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.btns {
|
.btns {
|
||||||
|
width: 300px;
|
||||||
|
height: 100px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 40px;
|
|
||||||
|
|
||||||
.export {
|
.export {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -160,6 +164,5 @@ export default defineComponent({
|
|||||||
width: 100px;
|
width: 100px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
136
src/views/Editor/ExportDialog/ExportPPTX.vue
Normal file
136
src/views/Editor/ExportDialog/ExportPPTX.vue
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<div class="export-pptx-dialog">
|
||||||
|
<div class="configs">
|
||||||
|
<div class="row">
|
||||||
|
<div class="title">导出范围:</div>
|
||||||
|
<RadioGroup
|
||||||
|
class="config-item"
|
||||||
|
v-model:value="rangeType"
|
||||||
|
>
|
||||||
|
<RadioButton style="width: 33.33%;" value="all">全部</RadioButton>
|
||||||
|
<RadioButton style="width: 33.33%;" value="current">当前页</RadioButton>
|
||||||
|
<RadioButton style="width: 33.33%;" value="custom">自定义</RadioButton>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
<div class="row" v-if="rangeType === 'custom'">
|
||||||
|
<div class="title" :data-range="`(${range[0]} ~ ${range[1]})`">自定义范围:</div>
|
||||||
|
<Slider
|
||||||
|
class="config-item"
|
||||||
|
range
|
||||||
|
:min="1"
|
||||||
|
:max="slides.length"
|
||||||
|
:step="1"
|
||||||
|
v-model:value="range"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btns">
|
||||||
|
<Button class="btn export" type="primary" @click="exportPPTX(selectedSlides)">导出 PPTX</Button>
|
||||||
|
<Button class="btn close" @click="close()">关闭</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FullscreenSpin :loading="exporting" tip="正在导出..." />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, ref } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useSlidesStore } from '@/store'
|
||||||
|
import useExport from '@/hooks/useExport'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'export-pptx-dialog',
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
||||||
|
|
||||||
|
const rangeType = ref<'all' | 'current' | 'custom'>('all')
|
||||||
|
const range = ref<[number, number]>([1, slides.value.length])
|
||||||
|
|
||||||
|
const selectedSlides = computed(() => {
|
||||||
|
if (rangeType.value === 'all') return slides.value
|
||||||
|
if (rangeType.value === 'current') return [currentSlide.value]
|
||||||
|
return slides.value.filter((item, index) => {
|
||||||
|
const [min, max] = range.value
|
||||||
|
return index >= min - 1 && index <= max - 1
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const close = () => emit('close')
|
||||||
|
|
||||||
|
const { exportPPTX, exporting } = useExport()
|
||||||
|
|
||||||
|
return {
|
||||||
|
slides,
|
||||||
|
rangeType,
|
||||||
|
range,
|
||||||
|
exporting,
|
||||||
|
selectedSlides,
|
||||||
|
exportPPTX,
|
||||||
|
close,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.export-pptx-dialog {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.configs {
|
||||||
|
width: 350px;
|
||||||
|
height: calc(100% - 100px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
width: 100px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: attr(data-range);
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.config-item {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #aaa;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btns {
|
||||||
|
width: 300px;
|
||||||
|
height: 100px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.export {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.close {
|
||||||
|
width: 100px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
108
src/views/Editor/ExportDialog/index.vue
Normal file
108
src/views/Editor/ExportDialog/index.vue
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<div class="export-dialog">
|
||||||
|
<div class="tabs">
|
||||||
|
<div
|
||||||
|
class="tab"
|
||||||
|
:class="{ 'active': tab.key === dialogForExport }"
|
||||||
|
v-for="tab in tabs"
|
||||||
|
:key="tab.key"
|
||||||
|
@click="setDialogForExport(tab.key)"
|
||||||
|
>{{tab.label}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<component :is="currentDialogComponent" @close="setDialogForExport('')"></component>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useMainStore } from '@/store'
|
||||||
|
import { DialogForExportTypes } from '@/types/export'
|
||||||
|
|
||||||
|
import ExportImage from './ExportImage.vue'
|
||||||
|
import ExportJSON from './ExportJSON.vue'
|
||||||
|
import ExportPDF from './ExportPDF.vue'
|
||||||
|
import ExportPPTX from './ExportPPTX.vue'
|
||||||
|
|
||||||
|
interface TabItem {
|
||||||
|
key: DialogForExportTypes;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'export-dialog',
|
||||||
|
setup() {
|
||||||
|
const mainStore = useMainStore()
|
||||||
|
const { dialogForExport } = storeToRefs(mainStore)
|
||||||
|
|
||||||
|
const setDialogForExport = mainStore.setDialogForExport
|
||||||
|
|
||||||
|
const tabs: TabItem[] = [
|
||||||
|
{ key: 'pptx', label: '导出 PPTX' },
|
||||||
|
{ key: 'image', label: '导出图片' },
|
||||||
|
{ key: 'json', label: '导出 JSON' },
|
||||||
|
{ key: 'pdf', label: '打印 / 导出 PDF' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const currentDialogComponent = computed(() => {
|
||||||
|
const dialogMap = {
|
||||||
|
'image': ExportImage,
|
||||||
|
'json': ExportJSON,
|
||||||
|
'pdf': ExportPDF,
|
||||||
|
'pptx': ExportPPTX,
|
||||||
|
}
|
||||||
|
return dialogMap[dialogForExport.value] || null
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentDialogComponent,
|
||||||
|
tabs,
|
||||||
|
dialogForExport,
|
||||||
|
setDialogForExport,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.export-dialog {
|
||||||
|
margin: -24px;
|
||||||
|
}
|
||||||
|
.tabs {
|
||||||
|
height: 50px;
|
||||||
|
font-size: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
user-select: none;
|
||||||
|
border-top-left-radius: $borderRadius;
|
||||||
|
border-top-right-radius: $borderRadius;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.tab {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: $lightGray;
|
||||||
|
border-bottom: 1px solid $borderColor;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + .tab {
|
||||||
|
border-left: 1px solid $borderColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
height: 500px;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
@include overflow-overlay();
|
||||||
|
}
|
||||||
|
</style>
|
@ -15,11 +15,24 @@
|
|||||||
<Toolbar class="layout-content-right" />
|
<Toolbar class="layout-content-right" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
:visible="!!dialogForExport"
|
||||||
|
:footer="null"
|
||||||
|
centered
|
||||||
|
:closable="false"
|
||||||
|
:width="680"
|
||||||
|
destroyOnClose
|
||||||
|
@cancel="closeExportDialog()"
|
||||||
|
>
|
||||||
|
<ExportDialog />
|
||||||
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref } from 'vue'
|
import { defineComponent, ref } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useMainStore } from '@/store'
|
||||||
import useGlobalHotkey from '@/hooks/useGlobalHotkey'
|
import useGlobalHotkey from '@/hooks/useGlobalHotkey'
|
||||||
import usePasteEvent from '@/hooks/usePasteEvent'
|
import usePasteEvent from '@/hooks/usePasteEvent'
|
||||||
|
|
||||||
@ -29,6 +42,7 @@ import CanvasTool from './CanvasTool/index.vue'
|
|||||||
import Thumbnails from './Thumbnails/index.vue'
|
import Thumbnails from './Thumbnails/index.vue'
|
||||||
import Toolbar from './Toolbar/index.vue'
|
import Toolbar from './Toolbar/index.vue'
|
||||||
import Remark from './Remark/index.vue'
|
import Remark from './Remark/index.vue'
|
||||||
|
import ExportDialog from './ExportDialog/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'editor',
|
name: 'editor',
|
||||||
@ -39,8 +53,13 @@ export default defineComponent({
|
|||||||
Thumbnails,
|
Thumbnails,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
Remark,
|
Remark,
|
||||||
|
ExportDialog,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const mainStore = useMainStore()
|
||||||
|
const { dialogForExport } = storeToRefs(mainStore)
|
||||||
|
const closeExportDialog = () => mainStore.setDialogForExport('')
|
||||||
|
|
||||||
const remarkHeight = ref(40)
|
const remarkHeight = ref(40)
|
||||||
|
|
||||||
useGlobalHotkey()
|
useGlobalHotkey()
|
||||||
@ -48,6 +67,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
remarkHeight,
|
remarkHeight,
|
||||||
|
dialogForExport,
|
||||||
|
closeExportDialog,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user