最近接到一個需求,需要在列表上顯示二維碼圖片,用戶還可以勾選多個二維碼圖片,打包爲 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);
}
}