简介
项目上有个需求,需要根据表单填写一些信息,来生成定制的二维码图片,并且支持批量下载二维码图片。
之前的实现方式是直接后端生成二维码图片,点击下载时后端直接返回一个zip包即可。但是项目经理说后端实现方式每次改个东西都要改大半天,所以让前端来实现。
方案
1.后端返回二维码的base64url数据流,就是下图红框中的二维码图片。
2.前端负责展示成交互上的二维码图片样式,如下图。
3.点击批量下载时,用户自己选择下载数量,然后后端返回二维码base64url的数组,前端自己实现下载,且是以zip的形式下载。下载的每张图片都是前端页面上所展示的样子。
思路
比如批量下载50个,首先10个一组处理,每个二维码编号生成一个blob流,塞入生成的zip中,50个二维码编号全部处理完成后,开始下载zip,将zip转为blob流,触发下载等待下载完成。
耗时较久的是每个二维码编号生成一个blob流,需要前端拿到后端返回的二维码base64 url, 通过js代码组装成最终的图片样式的DOM,然后需要塞到页面中,再使用dom-to-image 转成图片形式的blob流。
实现
1.比如选择下载数量是50张,点击下载,触发handleDownload 函数
2.使用jszip 生成一个zip
3.print_set_root 是该页面组件中最外层的div元素
4.分10个一组进行处理
完整代码如下:
import JSZip from 'jszip'
import { chunk } from 'lodash'
import domtoimage from 'dom-to-image'
async handleDownload (val) {
this.downBtnLoading = true
try {
const { data } = await downQuas({ count: Number(val), randomNum: 6, start: this.ruleForm.code })
this.zip = new JSZip()
const rod = document.getElementById('print_set_root')
const arr = chunk(data, 10)
for (let i = 0; i < arr.length; i++) {
await this.usePromiseArr(arr[i], rod)
}
this.downBtnLoading = false
const that = this
this.zip.generateAsync({ type: 'blob' }).then(function (base64) {
const url = URL.createObjectURL(base64)
const link = document.createElement('a')
link.download = `${that.regionName}.zip`
link.href = url
link.click()
setTimeout(() => { window.URL.revokeObjectURL(url) })
})
} catch (e) {
this.downBtnLoading = false
}
},
usePromiseArr (data, rod) {
const allPromise = []
data.forEach(v => {
allPromise.push(this.renderImg(v, rod))
})
return Promise.all(allPromise)
},
renderImg (data, rod) {
return new Promise((resolve, reject) => {
let num = 0
const useSrc = `data:image/png;base64,${data.value}`
const template2 = `
<div class="title-normal">报修电话</div>
<div class="title">${this.ruleForm.phoneNumber || 'xxxxxxxx'}</div>
`
const leftDiv = document.createElement('div')
leftDiv.setAttribute('class', 'left downLeft')
leftDiv.setAttribute('id', 'erweima-common')
const header = document.createElement('div')
header.setAttribute('class', 'left-header')
const large1 = document.createElement('div')
large1.setAttribute('class', 'font-large')
large1.textContent = 'xxxx'
const large2 = document.createElement('div')
large2.setAttribute('class', 'font-large')
large2.textContent = 'xxxx'
const topImage = document.createElement('div')
topImage.setAttribute('class', 'top-img')
const img1 = document.createElement('img')
img1.src = '/xxxxxx.png'
img1.onload = () => {
topImage.appendChild(img1)
num++
this.downloadImg(num, leftDiv, rod, data.key, resolve)
}
header.appendChild(large1)
header.appendChild(topImage)
header.appendChild(large2)
leftDiv.appendChild(header)
const safe = document.createElement('div')
safe.setAttribute('class', 'safe')
safe.textContent = 'xxxxxxxxxx'
const borderDiv = document.createElement('div')
borderDiv.setAttribute('class', 'left-border')
const dashedDiv = document.createElement('div')
dashedDiv.setAttribute('class', 'dashed-border')
const Img2 = document.createElement('img')
Img2.src = '/xxxxxxxx.png'
Img2.onload = () => {
dashedDiv.appendChild(Img2)
num++
this.downloadImg(num, leftDiv, rod, data.key, resolve)
}
const title1 = document.createElement('div')
title1.setAttribute('class', 'title')
title1.textContent = 'xxxx'
const title2 = document.createElement('div')
title2.setAttribute('class', 'title')
title2.textContent = 'xxxxxxxxxx'
const title3 = document.createElement('div')
title3.setAttribute('class', 'title-min')
title3.textContent = 'Area Under 24-hour Monitoring'
borderDiv.appendChild(dashedDiv)
borderDiv.appendChild(title1)
borderDiv.appendChild(title2)
borderDiv.appendChild(title3)
const border2 = document.createElement('div')
border2.setAttribute('class', 'left-border')
border2.innerHTML = template2
const small = document.createElement('div')
small.setAttribute('class', 'title-small')
small.textContent = `${this.ruleForm.producer || 'xxxxxxxxx'}`
const leftcontent = document.createElement('div')
leftcontent.setAttribute('class', 'left-content')
const useImg = document.createElement('img')
useImg.setAttribute('class', 'erwei')
useImg.src = useSrc
useImg.onload = () => {
leftcontent.appendChild(useImg)
leftcontent.appendChild(safe)
leftcontent.appendChild(borderDiv)
leftcontent.appendChild(border2)
leftcontent.appendChild(small)
leftDiv.appendChild(leftcontent)
rod.appendChild(leftDiv)
num++
this.downloadImg(num, leftDiv, rod, data.key, resolve)
}
})
},
downloadImg (num, leftDiv, rod, name, resolve) {
if (num !== 3) return
const that = this
domtoimage.toBlob(leftDiv).then(function (dataUrl) {
rod.removeChild(leftDiv)
that.zip.file(`${name}.jpeg`, dataUrl)
resolve()
})
}
使用技术:dom-to-image JSZip
注意点:
- 元素在appendChild图片时,一定要等到图片onload后再执行appendChild操作。
- 元素最终塞到页面上渲染时,注意下别让用户看到,可以 absolute + z-index 修改显示层级。