feat: 添加导出JSON,导出图片

This commit is contained in:
pipipi-pikachu 2021-04-05 15:31:53 +08:00
parent c7d233abff
commit 6283f61417
5 changed files with 256 additions and 1 deletions

16
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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')

View 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>

View File

@ -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,
}
},
})