前端实现生成图片并批量下载,下载成果物是zip包

news2024/11/17 12:50:31

简介

项目上有个需求,需要根据表单填写一些信息,来生成定制的二维码图片,并且支持批量下载二维码图片。
之前的实现方式是直接后端生成二维码图片,点击下载时后端直接返回一个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

注意点:

  1. 元素在appendChild图片时,一定要等到图片onload后再执行appendChild操作。
  2. 元素最终塞到页面上渲染时,注意下别让用户看到,可以 absolute + z-index 修改显示层级。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1508310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

elasticsearch(学习笔记)(分布式搜索引擎)(黑马)(kibana操作)

一、索引库操作 索引库就类似数据库表&#xff0c;mapping映射就类似表的结构。 我们要向es中存储数据&#xff0c;必须先创建“库”和“表”。 1、mapping映射属性 mapping是对索引库中文档的约束&#xff0c;常见的mapping属性包括&#xff1a; type&#xff1a;字段数据类型…

树莓派安装Nginx服务搭建web网站结合内网穿透实现公网访问本地站点

文章目录 1. Nginx安装2. 安装cpolar3.配置域名访问Nginx4. 固定域名访问5. 配置静态站点 安装 Nginx&#xff08;发音为“engine-x”&#xff09;可以将您的树莓派变成一个强大的 Web 服务器&#xff0c;可以用于托管网站或 Web 应用程序。相比其他 Web 服务器&#xff0c;Ngi…

龙迅#LT8711UXE1 适用于Type-C/DP1.2/EDP转HDMI2.0方案,支持音频剥离和HDCP功能。

1. 描述 LT8711UXE1是一款高性能的 Type-C/DP1.2 转 HDMI2.0 转换器&#xff0c;设计用于将 USB Type-C 源或 DP1.2 源连接到 HDMI2.0 接收器。该LT8711UXE1集成了符合 DP1.2 标准的接收器和符合 HDMI2.0 标准的发射器。此外&#xff0c;还包括两个用于 CC 通信的 CC 控制器&a…

Python——读写属性

采用读写属性的目的就是把录入的数据控制在合理区间。 如&#xff1a;学生的年龄&#xff08;age&#xff09;&#xff0c;学生的身高&#xff08;height&#xff09;... 方法一&#xff1a;利用实例方法来控制 class Student:def __init__(self,name"",age0):self.…

MySQL技能树学习

MySQL三大范式&#xff1a; 第一范式主要是确保数据表中每个字段的值必须具有原子性&#xff0c;也就是说数据表中每个字段的值为不可再次拆分的最小数据单元。 第二范式是指在第一范式的基础上&#xff0c;确保数据表中除了主键之外的每个字段都必须依赖主键。 第三范式是在…

SQL 中: 索引的建立和删除

目录 实验过程创建索引修改索引删除索引查询索引查看索引信息分析索引待续、更新中 实验过程 1 在STUDENT表的sno列上创建一个非聚簇索引&#xff0c;索引名为“student_sno_idx”。 CREATE INDEX student_sno_idx ON STUDENT (sno);2.在STUDENT表上按sno的升序&#xff0c;…

Project_Euler-10 题解

Project_Euler-10 题解 题目 思路 没有思路&#xff0c;一个线性筛秒了,只不过最近没发博客有点手生哈哈哈哈哈。 代码 /*************************************************************************> Author: Royi > Mail: royi990001gmail.com > From: > Lan…

ipad电容笔哪个牌子好?五款年度实力派电容笔推荐,小白必看

在数字化时代&#xff0c;电容笔已经成为了许多人日常生活和工作中不可或缺的工具。但是&#xff0c;市场上琳琅满目的电容笔品牌和型号让选择变得有些困难。作为一名资深的数码爱好者&#xff0c;我在选购电容笔上也有一定的经验&#xff0c;下面我来给大家分享一下2024电容笔…

基于RK3588+Codesys+Xenomai的ARM+LINUX实时硬件平台的软PLC解决方案

产品概述 公司推出基于瑞芯微RK3588架构的AI边缘计算主板&#xff0c;RK3588是新一代国产旗舰高性能64位八核处理器&#xff0c;采用8nm工艺&#xff0c;具有高算力、低功耗、超强多媒体、丰富数据接口等特点。搭载四核A76四核A55的八核CPU和ARM G610MP4 GPU&#xff0c;内置6…

Python中starmap有什么用的?

目录 前言 starmap函数的作用 starmap函数的用法 starmap函数的示例 1. 对每个元组元素进行求和 2. 对每个元组元素进行乘积 实际应用场景 1. 批量处理函数参数 2. 并行处理任务 3. 批量更新数据库 总结 前言 在Python中&#xff0c; starmap 是一个非常有用的函数&…

【2024泰迪杯】B 题:基于多模态特征融合的图像文本检索Python代码实现

【2024泰迪杯】B 题&#xff1a;基于多模态特征融合的图像文本检索Python代码实现 1 题目 2024 年&#xff08;第 12 届&#xff09;“泰迪杯”数据挖掘挑战赛—B 题&#xff1a;基于多模态特征融合的图像文本检索 一、问题背景 随着近年来智能终端设备和多媒体社交网络平台…

U盘启动盘 制作Linux Ubuntu CentOS系统启动盘 系统安装

U盘启动盘 制作Linux Ubuntu CentOS系统启动盘 系统安装 准备条件 准备一个U盘&#xff0c;建议容量至少为8GB&#xff0c;以便存放系统镜像文件 一台已经安装好操作系统的计算机&#xff0c;用于制作U盘启动盘 Ubuntu和CentOS的Linux ISO镜像文件。可以从官方网站或相关资源…

Linux -- 线程概念和控制

一 什么是线程 1.1 线程的引出 我们开始理解一下Linux中的线程。我们以前说过&#xff0c;一个进程被创建出来&#xff0c;要有自己对应的进程PCB的&#xff0c;也就是 task_struct&#xff0c;也要有自己的地址空间、页表&#xff0c;经过页表映射到物理内存中。所以在进程角…

JMeter 简介及安装详细教程(全网独家)

JMeter 简介 全名为 Apache JMeter JMeter 是一个软件&#xff0c;使负载测试或业绩为导向的业务&#xff08;功能&#xff09;测试不同的协议或技术。 它是 Apache 软件基金会的Stefano Mazzocchi JMeter 最初开发的。 它主要对 Apache JServ&#xff08;现在称为如 Apache T…

吴恩达机器学习笔记十六 如何debug一个学习算法 模型评估 模型选择和训练 交叉验证测试集

如果算法预测出的结果不太好&#xff0c;可以考虑以下几个方面&#xff1a; 获得更多的训练样本 采用更少的特征 尝试获取更多的特征 增加多项式特征 增大或减小 λ 模型评估(evaluate model) 例如房价预测&#xff0c;用五个数据训练出的模型能很好的拟合这几个数据&am…

虚拟机(KVM)克隆

当需要批量部署虚拟机时&#xff0c;可以使用克隆虚拟机的方式来进行。 使用图形界面来克隆虚拟机。 [rootzhoujunru_node1 zhou]# virsh list --allId Name State ------------------------------ vm01 shut off- vm01-clone shut off克隆完成。

【axios】你的进度条准确吗

1、axios监听进度 上传和下载操作在前端中是非常常见的&#xff0c;当我们想知道上传或下载的进度时也不难&#xff0c;axios已经实现了监听进度的方法 import axios from axios// 上传请求 axios.post(/api/v1/upload, {data: xxx},{// onUploadProgress回调可以获取进度onU…

网络基础aaa

三次握手 四次挥手 网络模型 TCP or UDP 的特点 如何理解 TCP 的5层协议 TCP的5层协议是指计算机网络体系结构中&#xff0c;与TCP&#xff08;传输控制协议&#xff09;相关的五个层次。这五个层次从高到低依次是&#xff1a;应用层、传输层、网络层、数据链路层和物理层。每…

多线程多进程

秋招面试的java八股文知识点补充以及iot 这里有一点阅读补充 线程和进程区别 什么是进程? 进程 (Process) 是计算机中的一个独立执行单元&#xff0c;是操作系统资源分配的基本单位。每个进程有各自独立的内存空间和资源&#xff0c;它们之间相互独立&#xff0c;相互之间…

【保姆级】Protobuf详解及入门指南

目录 Protobuf概述 什么是Protobuf 为什么要使用Protobuf Protobuf实战 环境配置 创建文件 解析/封装数据 附录 AQin.proto 完整代码 Protobuf概述 什么是Protobuf Protobuf&#xff08;Protocol Buffers&#xff09;协议&#x1f609; Protobuf 是一种由 Google 开…