前端二维码图片批量打印、批量下载前端实现方案

1,173 阅读3分钟

单张的打印、下载都比较简单,所以稍微记录下批量的坑,以及优化方案(注意:打印和下载都由前端生成并完成,如果是后端生成的那就没必要写这篇文章了,直接调用接口下载就可以)。那为什么不直接后端提供一个接口下载就好呢,因为当需求的二维码需要长得天花乱坠的时候就需要前端来生成渲染。

一、批量打印

效果描述:对应的数据生成图片或者二维码,打印出来

依赖包:npm install --save react-to-print
查看react-to-print

import React, {Component, useRef, useCallback} from 'react';
import {useReactToPrint} from "react-to-print";
import Qrcode from '../Qrcode';  //这个是你的二维码组件或者是图片

const QrCode = () => {
  const qrcodePrintRef = useRef()
  const qrcodeList = []  //批量打印的二维码或图片数据

  const reactToPrintPatchContent = useCallback(() => {
    return qrcodePrintRef.current;
  }, [qrcodePrintRef.current]);
  
  /**
   * 批量打印
   */
  const handlePrint = useReactToPrint({
    content: reactToPrintPatchContent,
    removeAfterPrint: true,
    // copyStyles: false,
    bodyClass: 'set_body_sty' 
  })
  
  
  return (<div>
    <div style={{height: 0, overflow: 'hidden'}}>
      <div ref={qrcodePrintRef}>
        {qrcodeList.map((item, idx) => {
          return <div key={item.id}>
            <div>
              <Qrcode/>
            </div>
          </div>
        })}
      </div>
    </div>
    
    <div onClick={handlePrint}>批量打印</div>
  </div>)
}

遇到的问题

1、批量打印的图片如果不需要在页面上显示出来,需要把dom元素隐藏起来,设置display:none是不行的,需要给div设置height: 0, overflow: 'hidden'并且要批量打印的图片ref实例不能写在当前设置高度为0的div,会导致打出的是空白页。

2、打印页面分页问题怎么控制分页,怎么才能控制打印出来一个图片是一张纸?用marginBottom来调整,就是每张图片的底部边距设置大一点,超出页不影响它不会影响到第二页纸,按照这个调整分页,就不会说打印出来一张里面有图片是一半的。

2022.08.19_肖国垚&701b75df73fdf8fa0b436ab9bedb8e7d.png

3、打印第一张有上边距问题react-to-print有个默认属性copyStyles:true拷贝父窗口的所有样式,因此可能你父窗口的bosy、或者html有样式,会影响到这个打印的效果图,(打印时候react-to-print后台会生成一个iframe,可以看下这个iframe里面的样式,做出对应调整),比如我上面设置了这个bodyClass: set_body_sty,把浏览器的默认边距设置为0.

:global {
  .set_body_sty {
    margin: 0;
  }
}

二、批量下载

效果描述:把对应的数据生成图片或者二维码,然后打包成zip下载下来

插件:html2canvasjszip

import React, {useRef,} from 'react';
import html2canvas from 'html2canvas';
import Qrcode from '../Qrcode';  //这个是你的二维码组件或者是图片

const JSZip = require("jszip");

const zip = new JSZip();

const QrCode = () => {
  const qrcodePrintRef = useRef()
  const qrcodeList = []  //批量打印的二维码或图片数据

  const handleDownload = () => {
    message.info('批量下载打包中,请稍等...')
    const base64Arr = []
    qrcodeList.map(item => {
      html2canvas(document.getElementById(`generate_${item.id}`), {
        allowTaint: false,
        useCORS: true,
      }).then(function (canvas) {
        const base64Data = canvas.toDataURL("image/jpeg").replace("data:image/jpeg;base64,", "");
        // 把数据的指定元素保存为到时候生成图片文件的文件名
        const _name = item.group_name + '-' + item.name;
        base64Arr.push({name: _name, base64: base64Data});
        // 当数据保存完之后,开始执行打包压缩方法
        if (base64Arr.length === qrcodeList.length) {
          data2JSZip(base64Arr);
        }
      });
    })
  }

  const data2JSZip = (base64Arr) => {
    base64Arr.map(function (obj) {
      zip.file(`${obj.name}.jpg`, obj.base64, {base64: true});
    });
    zip.generateAsync({
      type: "blob",
    }).then(function (content) {
      var filename = `批量下载.zip`;
      var eleLink = document.createElement("a");
      eleLink.download = filename;
      eleLink.style.display = "none";
      eleLink.href = URL.createObjectURL(content);
      document.body.appendChild(eleLink);
      eleLink.click();
      document.body.removeChild(eleLink);
    });
  }


  return (<div>
    <div>
      {qrcodeList.map(item => {
        return <div id={`generate_${item.id}`} key={`generate_${item.id}`}>
          <div>
            <Qrcode/>
          </div>
        </div>
      })}
    </div>

    <div onClick={handleDownload}>批量下载</div>
  </div>)
}

思路是现在页面上创建对应需要打印的二维码或者图片,给每个图片dom元素设置对应的id,然后通过html2canvas生成图片,再通过jszip把生成的图片打成zip包下载下来。

优化

但是这样会有个问题,就是当批量下载非常多图片的时候会发生页面阻塞(这时候页面是操作不了的,具体可以自己试一下),这是因为html2canvas会批量在dom上面插入对应的iframe导致页面卡顿。

下面演示了10张图片批量打印的情况插入iframe的dom情况,实际上左侧界面这时候操作不了的。 54545.gif

优化方案是批量生成的时候调用requestIdleCallback

window.requestIdleCallback()方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应

    qrcodeList.map(item => {
    // 调用requestIdleCallback
      window.requestIdleCallback(() => {
        html2canvas(document.getElementById(`generate_${item.id}`), {
          allowTaint: false,
          useCORS: true,
        }).then(function (canvas) {
          const base64Data = canvas.toDataURL("image/jpeg").replace("data:image/jpeg;base64,", "");
          // 把数据的指定元素保存为到时候生成图片文件的文件名
          const _name = item.group_name + '-' + item.name;
          base64Arr.push({name: _name, base64: base64Data});
          // 当数据保存完之后,开始执行打包压缩方法
          if (base64Arr.length === qrcodeList.length) {
            data2JSZip(base64Arr);
          }
        });
      })
    })