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 { toPng, toJpeg } from 'html-to-image'
|
||||
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 { AST, toAST } from '@/utils/htmlParser'
|
||||
import { SvgPoints, toPoints } from '@/utils/svgPathParser'
|
||||
@ -331,7 +331,7 @@ export default () => {
|
||||
}
|
||||
|
||||
// 导出PPTX文件
|
||||
const exportPPTX = () => {
|
||||
const exportPPTX = (_slides: Slide[] = slides.value) => {
|
||||
exporting.value = true
|
||||
const pptx = new pptxgen()
|
||||
|
||||
@ -341,7 +341,7 @@ export default () => {
|
||||
background: { color: bgColor, transparency: (1 - bgAlpha) * 100 },
|
||||
})
|
||||
|
||||
for (const slide of slides.value) {
|
||||
for (const slide of _slides) {
|
||||
const pptxSlide = pptx.addSlide()
|
||||
|
||||
if (slide.background) {
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { CreatingElement } from '@/types/edit'
|
||||
import { ToolbarStates } from '@/types/toolbar'
|
||||
import { DialogForExportTypes } from '@/types/export'
|
||||
import { SYS_FONTS } from '@/configs/font'
|
||||
import { TextAttrs, defaultRichTextAttrs } from '@/utils/prosemirror/utils'
|
||||
import { isSupportFont } from '@/utils/font'
|
||||
|
||||
import { useSlidesStore } from './slides'
|
||||
|
||||
|
||||
|
||||
export interface MainState {
|
||||
activeElementIdList: string[];
|
||||
handleElementId: string;
|
||||
@ -27,6 +30,7 @@ export interface MainState {
|
||||
richTextAttrs: TextAttrs;
|
||||
selectedTableCells: string[];
|
||||
selectedSlidesIndex: number[];
|
||||
dialogForExport: DialogForExportTypes;
|
||||
}
|
||||
|
||||
export const useMainStore = defineStore('main', {
|
||||
@ -50,6 +54,7 @@ export const useMainStore = defineStore('main', {
|
||||
selectedTableCells: [], // 选中的表格单元格
|
||||
isScaling: false, // 正在进行元素缩放
|
||||
selectedSlidesIndex: [], // 当前被选中的页面索引集合
|
||||
dialogForExport: '', // 导出面板
|
||||
}),
|
||||
|
||||
getters: {
|
||||
@ -147,5 +152,9 @@ export const useMainStore = defineStore('main', {
|
||||
updateSelectedSlidesIndex(selectedSlidesIndex: number[]) {
|
||||
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>
|
||||
<template #overlay>
|
||||
<Menu>
|
||||
<MenuItem @click="exportJSON()">导出 JSON</MenuItem>
|
||||
<MenuItem @click="exportPPTX()">导出 PPTX</MenuItem>
|
||||
<MenuItem @click="exportImgDialogVisible = true">导出图片</MenuItem>
|
||||
<MenuItem @click="exportPDFDialogVisible = true">打印 / 导出 PDF</MenuItem>
|
||||
<MenuItem @click="setDialogForExport('json')">导出 JSON</MenuItem>
|
||||
<MenuItem @click="setDialogForExport('pptx')">导出 PPTX</MenuItem>
|
||||
<MenuItem @click="setDialogForExport('image')">导出图片</MenuItem>
|
||||
<MenuItem @click="setDialogForExport('pdf')">打印 / 导出 PDF</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
@ -65,30 +65,6 @@
|
||||
>
|
||||
<HotkeyDoc />
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@ -102,15 +78,11 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
import useExport from '@/hooks/useExport'
|
||||
|
||||
import HotkeyDoc from './HotkeyDoc.vue'
|
||||
import ExportImgDialog from './ExportImgDialog.vue'
|
||||
import ExportPDFDialog from './ExportPDFDialog.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editor-header',
|
||||
components: {
|
||||
HotkeyDoc,
|
||||
ExportImgDialog,
|
||||
ExportPDFDialog,
|
||||
},
|
||||
setup() {
|
||||
const mainStore = useMainStore()
|
||||
@ -121,6 +93,8 @@ export default defineComponent({
|
||||
const { redo, undo } = useHistorySnapshot()
|
||||
const { exporting, exportJSON, exportPPTX } = useExport()
|
||||
|
||||
const setDialogForExport = mainStore.setDialogForExport
|
||||
|
||||
const toggleGridLines = () => {
|
||||
mainStore.setGridLinesState(!showGridLines.value)
|
||||
}
|
||||
@ -130,8 +104,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const hotkeyDrawerVisible = ref(false)
|
||||
const exportImgDialogVisible = ref(false)
|
||||
const exportPDFDialogVisible = ref(false)
|
||||
|
||||
const goIssues = () => {
|
||||
window.open('https://github.com/pipipi-pikachu/PPTist/issues')
|
||||
@ -143,9 +115,8 @@ export default defineComponent({
|
||||
showGridLines,
|
||||
showRuler,
|
||||
hotkeyDrawerVisible,
|
||||
exportImgDialogVisible,
|
||||
exportPDFDialogVisible,
|
||||
exporting,
|
||||
setDialogForExport,
|
||||
enterScreening,
|
||||
enterScreeningFromStart,
|
||||
createSlide,
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="thumbnails" ref="imageThumbnailsRef">
|
||||
<ThumbnailSlide
|
||||
class="thumbnail"
|
||||
v-for="slide in slides"
|
||||
v-for="slide in renderSlides"
|
||||
:key="slide.id"
|
||||
:slide="slide"
|
||||
:size="1600"
|
||||
@ -18,10 +18,32 @@
|
||||
class="config-item"
|
||||
v-model:value="format"
|
||||
>
|
||||
<RadioButton value="jpeg">JPEG</RadioButton>
|
||||
<RadioButton value="png">PNG</RadioButton>
|
||||
<RadioButton style="width: 50%;" value="jpeg">JPEG</RadioButton>
|
||||
<RadioButton style="width: 50%;" value="png">PNG</RadioButton>
|
||||
</RadioGroup>
|
||||
</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="title">图片质量:</div>
|
||||
@ -37,26 +59,24 @@
|
||||
<div class="row">
|
||||
<div class="title">忽略在线字体:</div>
|
||||
<div class="config-item">
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="导出时默认忽略在线字体,若您在幻灯片中使用了在线字体,且希望导出后保留相关样式,可选择关闭【忽略在线字体】选项,但要注意这将会增加导出用时。">
|
||||
<Switch v-model:checked="ignoreWebfont" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tip">
|
||||
提示:导出时默认会忽略在线字体,若您在幻灯片中使用了在线字体,且不希望导出图片中丢失相关样式,可以选择关闭【忽略在线字体】选项,但要注意,这将会导致导出用时大幅度增加。
|
||||
</div>
|
||||
|
||||
<div class="btns">
|
||||
<Button class="btn export" type="primary" @click="expImage()">导出图片</Button>
|
||||
<Button class="btn close" @click="close()">关闭</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FullscreenSpin :loading="exporting" tip="正在导出..." />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { computed, defineComponent, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useSlidesStore } from '@/store'
|
||||
import useExport from '@/hooks/useExport'
|
||||
@ -69,13 +89,24 @@ export default defineComponent({
|
||||
ThumbnailSlide,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { slides } = storeToRefs(useSlidesStore())
|
||||
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
||||
|
||||
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 quality = ref(1)
|
||||
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 { exportImage, exporting } = useExport()
|
||||
@ -88,9 +119,12 @@ export default defineComponent({
|
||||
return {
|
||||
imageThumbnailsRef,
|
||||
slides,
|
||||
rangeType,
|
||||
range,
|
||||
format,
|
||||
quality,
|
||||
ignoreWebfont,
|
||||
renderSlides,
|
||||
exporting,
|
||||
expImage,
|
||||
close,
|
||||
@ -101,9 +135,11 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.export-img-dialog {
|
||||
height: 500px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -117,7 +153,8 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
.configs {
|
||||
width: 300px;
|
||||
width: 350px;
|
||||
height: calc(100% - 100px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@ -132,22 +169,25 @@ export default defineComponent({
|
||||
|
||||
.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 {
|
||||
}
|
||||
.btns {
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 40px;
|
||||
|
||||
.export {
|
||||
flex: 1;
|
||||
@ -156,6 +196,5 @@ export default defineComponent({
|
||||
width: 100px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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"
|
||||
:slide="currentSlide"
|
||||
:size="1600"
|
||||
v-if="range === 'current'"
|
||||
v-if="rangeType === 'current'"
|
||||
/>
|
||||
<template v-else>
|
||||
<ThumbnailSlide
|
||||
@ -25,7 +25,7 @@
|
||||
<div class="title">导出范围:</div>
|
||||
<RadioGroup
|
||||
class="config-item"
|
||||
v-model:value="range"
|
||||
v-model:value="rangeType"
|
||||
>
|
||||
<RadioButton style="width: 50%;" value="all">全部幻灯片</RadioButton>
|
||||
<RadioButton style="width: 50%;" value="current">当前幻灯片</RadioButton>
|
||||
@ -48,13 +48,13 @@
|
||||
<Switch v-model:checked="padding" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btns">
|
||||
<Button class="btn export" type="primary" @click="expPDF()">打印 / 导出 PDF</Button>
|
||||
<Button class="btn close" @click="close()">关闭</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@ -74,7 +74,7 @@ export default defineComponent({
|
||||
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
||||
|
||||
const pdfThumbnailsRef = ref<HTMLElement>()
|
||||
const range = ref<'all' | 'current'>('all')
|
||||
const rangeType = ref<'all' | 'current'>('all')
|
||||
const count = ref(1)
|
||||
const padding = ref(true)
|
||||
|
||||
@ -84,7 +84,7 @@ export default defineComponent({
|
||||
if (!pdfThumbnailsRef.value) return
|
||||
const pageSize = {
|
||||
width: 1600,
|
||||
height: range.value === 'all' ? 900 * count.value : 900,
|
||||
height: rangeType.value === 'all' ? 900 * count.value : 900,
|
||||
margin: padding.value ? 50 : 0,
|
||||
}
|
||||
print(pdfThumbnailsRef.value, pageSize)
|
||||
@ -94,7 +94,7 @@ export default defineComponent({
|
||||
pdfThumbnailsRef,
|
||||
slides,
|
||||
currentSlide,
|
||||
range,
|
||||
rangeType,
|
||||
count,
|
||||
padding,
|
||||
expPDF,
|
||||
@ -106,9 +106,11 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.export-pdf-dialog {
|
||||
height: 400px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -128,6 +130,7 @@ export default defineComponent({
|
||||
}
|
||||
.configs {
|
||||
width: 300px;
|
||||
height: calc(100% - 100px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@ -146,12 +149,13 @@ export default defineComponent({
|
||||
.config-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btns {
|
||||
}
|
||||
.btns {
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 40px;
|
||||
|
||||
.export {
|
||||
flex: 1;
|
||||
@ -160,6 +164,5 @@ export default defineComponent({
|
||||
width: 100px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
:visible="!!dialogForExport"
|
||||
:footer="null"
|
||||
centered
|
||||
:closable="false"
|
||||
:width="680"
|
||||
destroyOnClose
|
||||
@cancel="closeExportDialog()"
|
||||
>
|
||||
<ExportDialog />
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore } from '@/store'
|
||||
import useGlobalHotkey from '@/hooks/useGlobalHotkey'
|
||||
import usePasteEvent from '@/hooks/usePasteEvent'
|
||||
|
||||
@ -29,6 +42,7 @@ import CanvasTool from './CanvasTool/index.vue'
|
||||
import Thumbnails from './Thumbnails/index.vue'
|
||||
import Toolbar from './Toolbar/index.vue'
|
||||
import Remark from './Remark/index.vue'
|
||||
import ExportDialog from './ExportDialog/index.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editor',
|
||||
@ -39,8 +53,13 @@ export default defineComponent({
|
||||
Thumbnails,
|
||||
Toolbar,
|
||||
Remark,
|
||||
ExportDialog,
|
||||
},
|
||||
setup() {
|
||||
const mainStore = useMainStore()
|
||||
const { dialogForExport } = storeToRefs(mainStore)
|
||||
const closeExportDialog = () => mainStore.setDialogForExport('')
|
||||
|
||||
const remarkHeight = ref(40)
|
||||
|
||||
useGlobalHotkey()
|
||||
@ -48,6 +67,8 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
remarkHeight,
|
||||
dialogForExport,
|
||||
closeExportDialog,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user