使用jsPdf 将页面生成PDF文件并自动上传 DEMO(三)
需求背景
在项目中有这样一个需求,需要调用其他系统业务API生成页面(canvas)渲染图表,并将图表导出成PDF文件,上传到服务器,并将文件地址,传给第三方业务系统,使第三方业务系统能够通过文件服务器下载该图表PDF文件
具体分析
经过上一篇 《使用jsPdf 将页面生成PDF文件并自动上传 DEMO(二)》,我们已经能够正确的将页面图表转成pdf 并上传至文件服务器,将结果同步给第三方了
今天我们就来解决剩下的问题 如何保证很多个图表渲染的时候页面不卡顿???
经过我们简单的分析 页面卡顿甚至崩溃的根本原因是 页面上渲染了太多了图表,也就是 前文 提到的com-a
因为我们是使用v-for
进行渲染生产的图表,那么随着 list
长度的增加 渲染的dom 就会越来越多,页面就会越来越卡,直至浏览器崩溃!
如何解决这一问题呢?
既然 v-for
这个方式存在问题,我们就换个思路,我们一个一个的生成使用组件复用的方式,确保 dom 树中 始终 只有 一份 com-a
的dom, 由最开始的渲染 N个 变成现在的渲染 1 个
为了以这样的方式实现功能,我们需要维护一个队列,去递归消耗这个队列,直到队列被清空,然后 缓存的 canvas 列表,转成 PDF 文件,剩下的就是上传及后续的操作了。
那么 我们来实现一下 这个功能:
参考代码 (较完整)
<template>
<com-a ref="comA" style="dispaly:none;" />
</template>
<script>
export default {
data(){
return {
pageInfo:[], // 存放原始数据
dataList:[], // 存放原始数据的拷贝
imageCacheList:[], // 生成的image (canvas) 存放列表
timer:null, // 定时任务的 timer
}
},
mounted(){
this.dataList = JOSN.parse(JSON.stringify(this.pageInfo))
this.timer = setTimeout(()=>{
this.handleInit()
},0)
},
methods:{
handleInit(){
if(this.dataList.length){
const data = this.dataList.shift()
this.handleRender(data)
}else{
if(this.cacheImageList.length){
this.handleImagesToPdf(this.cacheImageList,callback)
}
}
},
handleRender(data){
const view = this.$refs.comA
view && view.setData(data)
// 由于这个地方不知道 canvas 到底什么时候能够渲染完成,
// 我们使用定时器延时 1s 中后 获取canvas
// 在实际中,comA 会给我们提供一个 渲染完成的 hooks
// 确保我们能够在正确的时机获取到的 canvas 是渲染完成的;
this.$nextTick(()=>{
let timer = setTimeout(()=>{
this.handleCanvasToImage()
},1000)
})
},
handleCanvasToImage(){
const canvas = this.$refs.comA.$el.querySelector('canvas')
const image = cavas.toDataURL('image/jpeg',1,0)
this.cacheImageList.push(image)
// 回调 handleInit() 继续下个月 图表的渲染
this.handleInit()
},
handleImagesToPdf(images,callback){
const pdf = new jsPDF('p','pt','a4')
const width = pdf.internal.pageSize.getWidth()
const height = pdf.internal.pageSize.getHeight()
images.forEach((img,index)=>{
pdf.addImage(img,'JPEG',0,0,width,heihgt)
if(images.length - 1 !== index){
pdf.addPage()
}
})
const filename = 'xxx.pdf'
const blob = pdf.output('blob',{filename})
const file = new File([blob],filename,{type:bolb.type})
if(callback && typeof callback === 'function'){
callback(file)
}
},
handleUpload(file){
const formData = new formData()
formData.append('file',file)
API.upload(formData).then((res)=>{
// TODO 上传完成,重制某些参数,同步给第三方
const fileId = res.data
this.handleAsyncData()
}).catch(()=>{
console.log('上传失败')
}).finally(()=>{
this.handleRestParams()
})
},
handleAsyncData(){
// ....
},
handleRestParams(){
this.imageCacheList = []
this.dataList = []
if(this.timer){
clearTimeout(this.timer)
this.timer = null
}
}
},
}
</script>
截止目前 我们这个 demo 就基本已经完成了
下一步 我们将使用 函数的形式 调用 该组件,直接传递响应的参数,就可以完成剩下的操作;
也可以 emit 一些 事件,比如 upload 的 成功或失败,生成图表的进度等
例如 最终的调用方式 我们希望如下:
<button @click="handleCreatePDF({pageInfo:pageInfo})">{{process}}</button>
process:
生成PDF
正生成第1张图表
正生成第2张图表
正生成第3张图表
正生成第4张图表
正在上传中...
已上传成功/失败
已同步第三方
.....
最后的最后
这一路过来,我们发现在最基本的 domo 中,我们从最开始的基础demo的搭建,功能的验证,以及发现问题,解决问题,优化等,到最终的逐步成型;可以集成进入项目。到最后的 函数式调用,都是 逐步求精的过程。都是在 不断的发现问题,不断的取舍与权衡,不断的解决或规避问题,不断的优化细节;
到这结束了!
后面的以函数形式调用 就 不继续写文章了,感兴趣的同学可以 参考element ui
中 $message 的实现 或者 参阅 vue 官方文档 extend
即可;
最后
欢迎围观:
使用jsPdf 将页面生成PDF文件并自动上传 DEMO(一)
使用jsPdf 将页面生成PDF文件并自动上传 DEMO(二)
最后的最后
有请出我们今天的主角,小趴菜
转载自:https://juejin.cn/post/7163065496282071077