前端生成二维码并批量打包成zip文件
最近接到一个需求,需要在列表上显示二维码图片,用户还可以勾选多个二维码图片,打包为 zip 文件下载
将 URL 转为二维码图片并不复杂,qrcodejs、node-qrcode 都是很成熟的方案
打包 zip 听起来很恐怖,但用上 jszip 就很简单了
这篇文章介绍的是 node-qrcode + jszip 的解决方案。前端框架用的是 React,不过核心逻辑是通用的
一、生成二维码
首先安装 node-qrcode
yarn add qrcode @types/qrcode
在浏览器环境下,node-qrcode 提供了这几个 API:toCanvas()、toDataURL()、toString()
推荐使用 toDataURL, 然后直接将路径作为 src 用于 <img /> 标签
还可以出传入一些配置项 Option 来设置二维码图片的颜色、格式等
import QRCode from 'qrcode';
const opts: QRCode.QRCodeToDataURLOptions = {
errorCorrectionLevel: 'H',
type: 'image/jpeg',
margin: 1,
color: {
dark:"#010599FF",
light:"#FFBF60FF"
}
}
QRCode.toDataURL('text', opts).then(url => {
const img = document.getElementById('image')
img.src = url
})
基于以上 API,可以封装一个简单的二维码组件:
import React, { useEffect, useState } from 'react';
import QRCode from 'qrcode';
import type { QRCodeToDataURLOptions } from 'qrcode';
export type QrcodeImgProps = {
url: string;
options?: QRCodeToDataURLOptions;
};
/** 二维码图片组件 */
export const QrcodeImg: React.FC<QrcodeImgProps> = ({ url, options }) => {
const [src, setSrc] = useState<string>('');
useEffect(() => {
QRCode.toDataURL(url, options)
.then((x) => {
setSrc(x);
})
.catch(() => {
setSrc('');
console.error('生成二维码失败');
});
}, [url, options]);
return src ? <img className="qrcode" src={src} /> : null;
};
export default QrcodeImg;
二、批量导出 ZIP
安装 jszip
yarn add jszip
基本用法如下:
const zip = new JSZip();
// 直接压缩文件
zip.file("Hello.txt", "Hello World\n");
// 创建文件夹
const img = zip.folder("images");
// 创建图片并放进文件夹
img.file("smile.gif", imgData, {base64: true});
// 生成 zip 文件
zip.generateAsync({type:"blob"}).then(function(content) {
// content 为 'application/zip' 文件流,需要手动下载
});
jszip 可以基于 base64 创建图片文件,而上面通过 qrcode 生成的是一个 dataUrl,所以需要转换一下:
/** 从 dataUrl 截取 Base64 字符串*/
function dataUrlToBase64(dataUrl: string) {
if (typeof dataUrl !== 'string') return '';
const idx = dataUrl.indexOf('base64,') + 'base64,'.length;
return dataUrl.substring(idx);
}
最后一个批量导出二维码的函数就出来了:
import QRCode from "qrcode";
import JSZip from "jszip";
export type QrcodeItem = {
/** 二维码标题 */
name: string;
/** 二维码地址 */
url: string;
};
/**
* 批量下载二维码
* @param data 二维码数据集
* @param name 打包后的文件名
*/
export function downloadBatchQrcodeImg(data: QrcodeItem[], name?: string) {
if (!Array.isArray(data) || !data.length) {
return console.error('二维码数据异常');
}
const zipName = name || '未命名';
const zip = new JSZip();
const folder = zip.folder(zipName);
// 批量创建二维码
Promise.all(data.map((item) => QRCode.toDataURL(item.url))).then((values) => {
values.forEach((v, index) => {
const content = dataUrlToBase64(v);
// qrcode 默认生成 png 图片
folder?.file(`${data[index].name}.png`, content, { base64: true });
});
zip.generateAsync({ type: 'blob' }).then(function (content) {
// 下载文件
downloadBlobFile(content, `${zipName}.zip`, 'application/zip');
});
});
}
最后贴一下 downloadBlobFile 函数,是一个比较粗糙的下载文件方法,仅供参考
export type MsNavigator = {
msSaveOrOpenBlob?: (blob: any, defaultName?: string) => boolean;
};
/** 下载文件流, 默认导出 Excel */
export default function downloadBlobFile(
data: Blob,
fileName: string,
type = 'application/vnd.ms-excel',
) {
const Navigator = window.navigator as MsNavigator;
if (Navigator?.msSaveOrOpenBlob) {
Navigator.msSaveOrOpenBlob(data, fileName);
} else {
const aTag = document.createElement('a');
// 获取 blob 本地文件连接 (blob 为纯二进制对象,不能够直接保存到磁盘上)
const downUrl = window.URL.createObjectURL(new Blob([data], { type }));
// 定义导出文件的命名
aTag.href = downUrl;
aTag.download = fileName;
aTag.click();
window.URL.revokeObjectURL(downUrl);
}
}