mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 添加导出JSON,导出图片
This commit is contained in:
parent
c7d233abff
commit
6283f61417
16
package-lock.json
generated
16
package-lock.json
generated
@ -2182,6 +2182,12 @@
|
||||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/file-saver": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npm.taobao.org/@types/file-saver/download/@types/file-saver-2.0.1.tgz?cache=0&sync_timestamp=1613380173874&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Ffile-saver%2Fdownload%2F%40types%2Ffile-saver-2.0.1.tgz",
|
||||
"integrity": "sha1-4Y64sGnkQve5VtMT9PrdPviHNU4=",
|
||||
"dev": true
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npm.taobao.org/@types/glob/download/@types/glob-7.1.3.tgz",
|
||||
@ -8072,6 +8078,11 @@
|
||||
"schema-utils": "^2.5.0"
|
||||
}
|
||||
},
|
||||
"file-saver": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npm.taobao.org/file-saver/download/file-saver-2.0.5.tgz",
|
||||
"integrity": "sha1-1hz+LOBZ9BTYmendbUEH7iVnDDg="
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npm.taobao.org/file-uri-to-path/download/file-uri-to-path-1.0.0.tgz",
|
||||
@ -9070,6 +9081,11 @@
|
||||
"integrity": "sha1-e15vfmZen7QfMAB+2eDUHpf7IUA=",
|
||||
"dev": true
|
||||
},
|
||||
"html-to-image": {
|
||||
"version": "1.3.25",
|
||||
"resolved": "https://registry.npm.taobao.org/html-to-image/download/html-to-image-1.3.25.tgz",
|
||||
"integrity": "sha1-vxF4jQPFrT4FuPpO0AuhFkhGtFs="
|
||||
},
|
||||
"html-webpack-plugin": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npm.taobao.org/html-webpack-plugin/download/html-webpack-plugin-3.2.0.tgz?cache=0&sync_timestamp=1615296080987&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhtml-webpack-plugin%2Fdownload%2Fhtml-webpack-plugin-3.2.0.tgz",
|
||||
|
@ -17,6 +17,8 @@
|
||||
"core-js": "^3.6.5",
|
||||
"crypto-js": "^4.0.0",
|
||||
"dexie": "^3.0.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"html-to-image": "^1.3.25",
|
||||
"lodash": "^4.17.20",
|
||||
"mitt": "^2.1.0",
|
||||
"prosemirror-commands": "^1.1.7",
|
||||
@ -40,6 +42,7 @@
|
||||
"@types/chartist": "^0.11.0",
|
||||
"@types/clipboard": "^2.0.1",
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/jest": "^24.0.19",
|
||||
"@types/prosemirror-commands": "^1.0.3",
|
||||
"@types/prosemirror-dropcursor": "^1.0.0",
|
||||
|
@ -39,6 +39,7 @@ import {
|
||||
Menu,
|
||||
Checkbox,
|
||||
Drawer,
|
||||
Spin,
|
||||
} from 'ant-design-vue'
|
||||
|
||||
const app = createApp(App)
|
||||
@ -75,6 +76,7 @@ app.component('Menu', Menu)
|
||||
app.component('MenuItem', Menu.Item)
|
||||
app.component('Checkbox', Checkbox)
|
||||
app.component('Drawer', Drawer)
|
||||
app.component('Spin', Spin)
|
||||
|
||||
app.use(store, key)
|
||||
app.mount('#app')
|
||||
|
219
src/views/Editor/EditorHeader/ExportDialog.vue
Normal file
219
src/views/Editor/EditorHeader/ExportDialog.vue
Normal file
@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<div class="export-dialog">
|
||||
<div class="tabs">
|
||||
<div
|
||||
class="tab"
|
||||
:class="{ 'active': tab.value === currentTab }"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
@click="currentTab = tab.value"
|
||||
>{{tab.label}}</div>
|
||||
</div>
|
||||
|
||||
<div class="content json" v-if="currentTab === 'json'">
|
||||
<div class="json-preview">
|
||||
<pre>{{slides}}</pre>
|
||||
</div>
|
||||
<div class="json-configs">
|
||||
<Button class="btn" type="primary" @click="exportJSON()">导出 JSON 文件</Button>
|
||||
<Button class="btn" @click="emit('close')">关闭</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content image" v-if="currentTab === 'image'">
|
||||
<div class="thumbnails-view">
|
||||
<div class="thumbnails" ref="imageThumbnailsRef">
|
||||
<ThumbnailSlide
|
||||
class="thumbnail"
|
||||
v-for="slide in slides"
|
||||
:key="slide.id"
|
||||
:slide="slide"
|
||||
:size="1600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="configs">
|
||||
<Button class="btn" type="primary" @click="exportImage('png')">导出 PNG 图片</Button>
|
||||
<Button class="btn" type="primary" @click="exportImage('jpeg')">导出 JPEG 图片</Button>
|
||||
<Button class="btn" @click="emit('close')">关闭</Button>
|
||||
</div>
|
||||
<div class="spinning" v-if="spinning">
|
||||
<Spin />
|
||||
<div class="tip">正在导出,请稍等...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from 'vue'
|
||||
import { useStore } from '@/store'
|
||||
import { saveAs } from 'file-saver'
|
||||
import { toPng, toJpeg } from 'html-to-image'
|
||||
|
||||
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'export-dialog',
|
||||
components: {
|
||||
ThumbnailSlide,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const store = useStore()
|
||||
const slides = computed(() => store.state.slides)
|
||||
|
||||
const tabs = ref([
|
||||
{ label: 'JSON', value: 'json' },
|
||||
{ label: '图片', value: 'image' },
|
||||
])
|
||||
|
||||
const currentTab = ref('json')
|
||||
const spinning = ref(false)
|
||||
|
||||
const exportJSON = () => {
|
||||
const blob = new Blob([JSON.stringify(slides.value)], { type: '' })
|
||||
saveAs(blob, 'pptist_slides.json')
|
||||
}
|
||||
|
||||
const imageThumbnailsRef = ref<HTMLElement>()
|
||||
const exportImage = (type: string) => {
|
||||
spinning.value = true
|
||||
const toImage = type === 'png' ? toPng : toJpeg
|
||||
|
||||
setTimeout(() => {
|
||||
if (!imageThumbnailsRef.value) return
|
||||
|
||||
toImage(imageThumbnailsRef.value, {
|
||||
quality: 0.95,
|
||||
width: 1600,
|
||||
}).then(dataUrl => {
|
||||
spinning.value = false
|
||||
saveAs(dataUrl, `pptist_slides.${type}`)
|
||||
}).catch(() => {
|
||||
spinning.value = false
|
||||
message.error('导出图片失败')
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
|
||||
return {
|
||||
tabs,
|
||||
currentTab,
|
||||
spinning,
|
||||
slides,
|
||||
exportJSON,
|
||||
exportImage,
|
||||
imageThumbnailsRef,
|
||||
emit,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.export-dialog {
|
||||
height: 500px;
|
||||
}
|
||||
.tabs {
|
||||
height: 40px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
margin: -24px -24px 20px -24px;
|
||||
}
|
||||
.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: calc(100% - 60px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.json-preview {
|
||||
width: 460px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
margin-right: 20px;
|
||||
background-color: #2d2d30;
|
||||
color: #fff;
|
||||
|
||||
pre {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.json-configs {
|
||||
flex: 1;
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnails-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.configs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.btn {
|
||||
width: 240px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.spinning {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
|
||||
.tip {
|
||||
margin-top: 10px;
|
||||
color: $themeColor;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -11,6 +11,7 @@
|
||||
<MenuItem @click="deleteSlide()">删除页面</MenuItem>
|
||||
<MenuItem @click="toggleGridLines()">{{ showGridLines ? '关闭网格线' : '打开网格线' }}</MenuItem>
|
||||
<MenuItem @click="resetSlides()">重置幻灯片</MenuItem>
|
||||
<MenuItem @click="exportDialogVisible = true">导出为</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
@ -53,11 +54,22 @@
|
||||
>
|
||||
<HotkeyDoc />
|
||||
</Drawer>
|
||||
|
||||
<Modal
|
||||
v-model:visible="exportDialogVisible"
|
||||
:footer="null"
|
||||
centered
|
||||
:closable="false"
|
||||
:width="680"
|
||||
destroyOnClose
|
||||
>
|
||||
<ExportDialog @close="exportDialogVisible = false"/>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from 'vue'
|
||||
import { computed, defineAsyncComponent, defineComponent, ref } from 'vue'
|
||||
import { MutationTypes, useStore } from '@/store'
|
||||
import useScreening from '@/hooks/useScreening'
|
||||
import useSlideHandler from '@/hooks/useSlideHandler'
|
||||
@ -70,6 +82,7 @@ export default defineComponent({
|
||||
name: 'editor-header',
|
||||
components: {
|
||||
HotkeyDoc,
|
||||
ExportDialog: defineAsyncComponent(() => import('./ExportDialog.vue')),
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
@ -88,6 +101,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const hotkeyDrawerVisible = ref(false)
|
||||
const exportDialogVisible = ref(false)
|
||||
|
||||
return {
|
||||
enterScreening,
|
||||
@ -101,6 +115,7 @@ export default defineComponent({
|
||||
resetSlides,
|
||||
openDoc,
|
||||
hotkeyDrawerVisible,
|
||||
exportDialogVisible,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user