feat:新增图片导出功能

This commit is contained in:
奔跑的面条 2022-04-05 19:01:52 +08:00
parent f95d940ff0
commit 496c097e5f
14 changed files with 140 additions and 53 deletions

View File

@ -15,6 +15,7 @@
"crypto-ts": "^1.0.2", "crypto-ts": "^1.0.2",
"echarts-liquidfill": "3", "echarts-liquidfill": "3",
"highlight.js": "^11.5.0", "highlight.js": "^11.5.0",
"html2canvas": "^1.4.1",
"naive-ui": "^2.25.2", "naive-ui": "^2.25.2",
"pinia": "^2.0.6", "pinia": "^2.0.6",
"screenfull": "^6.0.0", "screenfull": "^6.0.0",

View File

@ -24,6 +24,7 @@ specifiers:
eslint-plugin-prettier: ^4.0.0 eslint-plugin-prettier: ^4.0.0
eslint-plugin-vue: ^8.2.0 eslint-plugin-vue: ^8.2.0
highlight.js: ^11.5.0 highlight.js: ^11.5.0
html2canvas: ^1.4.1
lodash: ~4.17.21 lodash: ~4.17.21
mockjs: ^1.1.0 mockjs: ^1.1.0
naive-ui: ^2.25.2 naive-ui: ^2.25.2
@ -56,6 +57,7 @@ dependencies:
crypto-ts: r2.cnpmjs.org/crypto-ts/1.0.2 crypto-ts: r2.cnpmjs.org/crypto-ts/1.0.2
echarts-liquidfill: r2.cnpmjs.org/echarts-liquidfill/3.1.0_echarts@5.3.0 echarts-liquidfill: r2.cnpmjs.org/echarts-liquidfill/3.1.0_echarts@5.3.0
highlight.js: registry.npmjs.org/highlight.js/11.5.0 highlight.js: registry.npmjs.org/highlight.js/11.5.0
html2canvas: 1.4.1
naive-ui: registry.npmjs.org/naive-ui/2.25.2_vue@3.2.24 naive-ui: registry.npmjs.org/naive-ui/2.25.2_vue@3.2.24
pinia: rg.cnpmjs.org/pinia/2.0.6_typescript@4.5.2+vue@3.2.24 pinia: rg.cnpmjs.org/pinia/2.0.6_typescript@4.5.2+vue@3.2.24
screenfull: rg.cnpmjs.org/screenfull/6.0.0 screenfull: rg.cnpmjs.org/screenfull/6.0.0
@ -100,6 +102,17 @@ devDependencies:
packages: packages:
/base64-arraybuffer/1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
dev: false
/css-line-break/2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
dependencies:
utrie: 1.0.2
dev: false
/esbuild-android-arm64/0.13.15: /esbuild-android-arm64/0.13.15:
resolution: {integrity: sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==} resolution: {integrity: sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==}
cpu: [arm64] cpu: [arm64]
@ -261,6 +274,14 @@ packages:
requiresBuild: true requiresBuild: true
dev: true dev: true
/html2canvas/1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
dev: false
/jest-diff/27.4.6: /jest-diff/27.4.6:
resolution: {integrity: sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==} resolution: {integrity: sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@ -290,6 +311,12 @@ packages:
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
dev: false dev: false
/text-segmentation/1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
dependencies:
utrie: 1.0.2
dev: false
/uglify-js/3.15.1: /uglify-js/3.15.1:
resolution: {integrity: sha512-FAGKF12fWdkpvNJZENacOH0e/83eG6JyVQyanIJaBXCN1J11TUQv1T1/z8S+Z0CG0ZPk1nPcreF/c7lrTd0TEQ==} resolution: {integrity: sha512-FAGKF12fWdkpvNJZENacOH0e/83eG6JyVQyanIJaBXCN1J11TUQv1T1/z8S+Z0CG0ZPk1nPcreF/c7lrTd0TEQ==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
@ -298,6 +325,12 @@ packages:
dev: true dev: true
optional: true optional: true
/utrie/1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
dependencies:
base64-arraybuffer: 1.0.2
dev: false
/vfonts/0.0.3: /vfonts/0.0.3:
resolution: {integrity: sha512-nguyw8L6Un8eelg1vQ31vIU2ESxqid7EYmy8V+MDeMaHBqaRSkg3dTBToC1PR00D89UzS/SLkfYPnx0Wf23IQQ==} resolution: {integrity: sha512-nguyw8L6Un8eelg1vQ31vIU2ESxqid7EYmy8V+MDeMaHBqaRSkg3dTBToC1PR00D89UzS/SLkfYPnx0Wf23IQQ==}
dev: false dev: false

View File

@ -8,7 +8,8 @@ import {
Pencil as PencilIcon, Pencil as PencilIcon,
HammerOutline as HammerIcon, HammerOutline as HammerIcon,
DesktopOutline as DesktopOutlineIcon, DesktopOutline as DesktopOutlineIcon,
DownloadOutline as DownloadIcon, Download as DownloadIcon,
DownloadOutline as DownloadOutlineIcon,
Open as OpenIcon, Open as OpenIcon,
Send as SendIcon, Send as SendIcon,
InformationCircleOutline as InformationCircleIcon, InformationCircleOutline as InformationCircleIcon,
@ -122,6 +123,7 @@ const ionicons5 = {
DesktopOutlineIcon, DesktopOutlineIcon,
// 下载 // 下载
DownloadIcon, DownloadIcon,
DownloadOutlineIcon,
// 导出 // 导出
OpenIcon, OpenIcon,
// 导出 // 导出

View File

@ -508,16 +508,6 @@ export const useChartEditStore = defineStore({
} }
}, },
// ---------------- // ----------------
// * 设置页面变换时候的 Class
setPageSizeClass(): void {
const dom = this.getEditCanvas.editContentDom
if (dom) {
dom.classList.add('content-resize')
setTimeout(() => {
dom.classList.remove('content-resize')
}, 600)
}
},
// * 设置页面大小 // * 设置页面大小
setPageSize(scale: number): void { setPageSize(scale: number): void {
this.setPageStyle('height', `${this.editCanvasConfig.height * scale}px`) this.setPageStyle('height', `${this.editCanvasConfig.height * scale}px`)
@ -572,15 +562,16 @@ export const useChartEditStore = defineStore({
} }
return remove return remove
}, },
// * 设置缩放 /**
setScale(scale: number, sys = true): void { * *
if (!this.getEditCanvas.lockScale) { * @param scale 0~1 number ;
this.setPageSizeClass() * @param force boolean
*/
setScale(scale: number, force = false): void {
if (!this.getEditCanvas.lockScale || force) {
this.setPageSize(scale) this.setPageSize(scale)
this.getEditCanvas.userScale = scale this.getEditCanvas.userScale = scale
if (sys) { this.getEditCanvas.scale = scale
this.getEditCanvas.scale = scale
}
} }
} }
} }

View File

@ -30,6 +30,6 @@ $dark: (
// hover 边框颜色 // hover 边框颜色
hover-border-color: $--color-dark-bg-5, hover-border-color: $--color-dark-bg-5,
// 阴影 // 阴影
box-shadow: 0 8px 20px #5252521f box-shadow: 0 8px 10px #1e1e1e1f
); );

View File

@ -32,5 +32,5 @@ $light: (
// hover 边框颜色 // hover 边框颜色
hover-border-color: $--color-light-bg-4, hover-border-color: $--color-light-bg-4,
// 阴影 // 阴影
box-shadow: 0 8px 20px #0000001a box-shadow: 0 8px 10px #00000012
); );

View File

@ -1,5 +1,5 @@
import * as CryptoJS from 'crypto-ts' import * as CryptoJS from 'crypto-ts'
// 加密
const AES_KEY = 'mt' const AES_KEY = 'mt'
export const cryptoEncode = (data: string): string => { export const cryptoEncode = (data: string): string => {

View File

@ -19,25 +19,36 @@ export const readFile = (file: File) => {
} }
/** /**
* * * a
* @param { string } content * @param url
* @param { ?string } filename * @param filename
* @param { ?string } fileSuffix * @param fileSuffix
*/ */
export const downloadFile = ( export const downloadByA = (url: string, filename = new Date().getDate().toString(), fileSuffix?: string) => {
content: string,
filename = new Date().getDate().toString(),
fileSuffix?: string
) => {
const ele = document.createElement('a') // 创建下载链接 const ele = document.createElement('a') // 创建下载链接
ele.download = `${filename}.${fileSuffix}` //设置下载的名称 ele.download = `${filename}.${fileSuffix}` //设置下载的名称
ele.style.display = 'none' // 隐藏的可下载链接 ele.style.display = 'none' // 隐藏的可下载链接
// 字符内容转变成blob地址 // 字符内容转变成blob地址
const blob = new Blob([content]) ele.href = url
ele.href = URL.createObjectURL(blob)
// 绑定点击时间 // 绑定点击时间
document.body.appendChild(ele) document.body.appendChild(ele)
ele.click() ele.click()
// 然后移除 // 然后移除
document.body.removeChild(ele) document.body.removeChild(ele)
} }
/**
*
* @param { string } content
* @param { ?string } filename
* @param { ?string } fileSuffix
*/
export const downloadTextFile = (
content: string,
filename = new Date().getDate().toString(),
fileSuffix?: string
) => {
// 字符内容转变成blob地址
const blob = new Blob([content])
downloadByA(URL.createObjectURL(blob), filename, fileSuffix)
}

View File

@ -3,6 +3,8 @@ import { NIcon } from 'naive-ui'
import screenfull from 'screenfull' import screenfull from 'screenfull'
import throttle from 'lodash/throttle' import throttle from 'lodash/throttle'
import Image_404 from '../assets/images/exception/image-404.png' import Image_404 from '../assets/images/exception/image-404.png'
import html2canvas from 'html2canvas'
import { downloadByA } from './file'
/** /**
* * * *
@ -146,7 +148,7 @@ export const addEventListener = <K extends keyof WindowEventMap>(
type, type,
throttle(listener, 300, { throttle(listener, 300, {
leading: true, leading: true,
trailing: false trailing: false,
}), }),
options options
) )
@ -163,3 +165,21 @@ export const removeEventListener = <K extends keyof WindowEventMap>(
if (!target) return if (!target) return
target.removeEventListener(type, listener) target.removeEventListener(type, listener)
} }
/**
* *
* @param html DOM
*/
export const canvasCut = (html: HTMLElement | null, callback?: Function) => {
if (!html) {
window['$message'].error('导出失败!')
if (callback) callback()
return
}
html2canvas(html).then((canvas: HTMLCanvasElement) => {
window['$message'].success('导出成功!')
downloadByA(canvas.toDataURL(), undefined, 'png')
if (callback) callback()
})
}

View File

@ -1,7 +1,7 @@
import { ref, toRef, nextTick } from 'vue' import { ref, toRef, nextTick } from 'vue'
import { UploadCustomRequestOptions } from 'naive-ui' import { UploadCustomRequestOptions } from 'naive-ui'
import { FileTypeEnum } from '@/enums/fileTypeEnum' import { FileTypeEnum } from '@/enums/fileTypeEnum'
import { readFile, downloadFile } from '@/utils' import { readFile, downloadTextFile } from '@/utils'
export const useFile = (targetData: any) => { export const useFile = (targetData: any) => {
const uploadFileListRef = ref() const uploadFileListRef = ref()
@ -35,7 +35,7 @@ export const useFile = (targetData: any) => {
const download = () => { const download = () => {
try { try {
window['$message'].success('下载中,请耐心等待...') window['$message'].success('下载中,请耐心等待...')
downloadFile(JSON.stringify(targetData.value.option.dataset), undefined, 'json') downloadTextFile(JSON.stringify(targetData.value.option.dataset), undefined, 'json')
} catch (error) { } catch (error) {
window['$message'].error('下载失败,数据错误!') window['$message'].error('下载失败,数据错误!')
} }

View File

@ -54,12 +54,8 @@ const rangeStyle = computed(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
@include go(edit-range) { @include go(edit-range) {
position: relative; position: relative;
border: 1px solid;
border-radius: 15px;
transform-origin: left top; transform-origin: left top;
@include fetch-theme('box-shadow');
@include filter-border-color('hover-border-color'); @include filter-border-color('hover-border-color');
@include fetch-theme-custom('border-color', 'background-color4');
@include filter-bg-color('background-color2'); @include filter-bg-color('background-color2');
} }
</style> </style>

View File

@ -87,20 +87,18 @@ onMounted(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@include goId(chart-edit-layout) { @include goId('chart-edit-layout') {
position: relative; position: relative;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
@include background-image('background-point');
@extend .go-point-bg; @extend .go-point-bg;
@include goId(chart-edit-content) { @include background-image('background-point');
padding: 20px; @include goId('chart-edit-content') {
border: 1px solid rgba(0, 0, 0, 0); border-radius: 5px;
margin: 15px;
overflow: hidden;
@extend .go-transition; @extend .go-transition;
&.content-resize { @include fetch-theme('box-shadow');
border-radius: 15px;
@include hover-border-color('hover-border-color');
}
.edit-content-chart { .edit-content-chart {
position: absolute; position: absolute;
} }

View File

@ -11,14 +11,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { shallowReactive } from 'vue' import { shallowReactive } from 'vue'
import { renderIcon, fetchPathByName, routerTurnByPath,setSessionStorage, getLocalStorage } from '@/utils' import { renderIcon, fetchPathByName, routerTurnByPath, setSessionStorage, getLocalStorage } from '@/utils'
import { PreviewEnum } from '@/enums/pageEnum' import { PreviewEnum } from '@/enums/pageEnum'
import { StorageEnum } from '@/enums/storageEnum' import { StorageEnum } from '@/enums/storageEnum'
import { icon } from '@/plugins' import { icon } from '@/plugins'
import { canvasCut } from '@/utils'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { EditCanvasTypeEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
const { BrowsersOutlineIcon, SendIcon } = icon.ionicons5 const { BrowsersOutlineIcon, SendIcon, DownloadIcon } = icon.ionicons5
const chartEditStore = useChartEditStore() const chartEditStore = useChartEditStore()
// TODO // TODO
@ -53,6 +55,34 @@ const previewHandle = () => {
routerTurnByPath(path, [previewId], undefined, true) routerTurnByPath(path, [previewId], undefined, true)
} }
//
const exportHandle = () => {
const ruler = document.getElementById('mb-ruler')
const range = document.querySelector('.go-edit-range') as HTMLElement
// 线
if (!ruler || !range) {
window['$message'].error('导出失败!')
return
}
//
const scaleTemp = chartEditStore.getEditCanvas.scale
// Dom
ruler.style.display = 'none'
//
chartEditStore.setScale(1, true)
window['$message'].warning('生成截图和数据中, 请耐心等待...')
setTimeout(() => {
canvasCut(range, () => {
// 线
if (ruler) ruler.style.display = 'block'
//
chartEditStore.setScale(scaleTemp, true)
})
}, 600)
}
// //
const sendHandle = () => { const sendHandle = () => {
window['$message'].warning('该功能暂未实现(因为压根没有后台)') window['$message'].warning('该功能暂未实现(因为压根没有后台)')
@ -60,12 +90,17 @@ const sendHandle = () => {
const btnList = shallowReactive([ const btnList = shallowReactive([
{ {
key: '',
select: true, select: true,
title: '预览', title: '预览',
icon: renderIcon(BrowsersOutlineIcon), icon: renderIcon(BrowsersOutlineIcon),
event: previewHandle event: previewHandle
}, },
{
select: true,
title: '下载',
icon: renderIcon(DownloadIcon),
event: exportHandle
},
{ {
select: true, select: true,
title: '发布', title: '发布',

View File

@ -93,7 +93,7 @@ const {
TrashIcon, TrashIcon,
PencilIcon, PencilIcon,
BrowsersOutlineIcon, BrowsersOutlineIcon,
DownloadIcon, DownloadOutlineIcon,
HammerIcon, HammerIcon,
SendIcon SendIcon
} = icon.ionicons5 } = icon.ionicons5
@ -153,7 +153,7 @@ const selectOptions = ref([
{ {
label: renderLang('global.r_download'), label: renderLang('global.r_download'),
key: 'download', key: 'download',
icon: renderIcon(DownloadIcon) icon: renderIcon(DownloadOutlineIcon)
}, },
{ {
type: 'divider', type: 'divider',