简介
项目上有个需求,需要根据表单填写一些信息,来生成定制的二维码图片,并且支持批量下载二维码图片。
之前的实现方式是直接后端生成二维码图片,点击下载时后端直接返回一个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 = truetry {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 = falseconst that = thisthis.zip.generateAsync({ type: 'blob' }).then(function (base64) {const url = URL.createObjectURL(base64)const link = document.createElement('a')link.download = `${that.regionName}.zip`link.href = urllink.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 = 0const 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 = template2const 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 = useSrcuseImg.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) returnconst that = thisdomtoimage.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 修改显示层级。