diff --git a/public/wasm/README.txt b/public/wasm/README.txt new file mode 100644 index 0000000..bd7f0d3 --- /dev/null +++ b/public/wasm/README.txt @@ -0,0 +1,2 @@ +zxing_full.wasm + zxing-wasm v2.0.1 diff --git a/public/wasm/zxing_full.wasm b/public/wasm/zxing_full.wasm new file mode 100644 index 0000000..9576840 Binary files /dev/null and b/public/wasm/zxing_full.wasm differ diff --git a/src/assets/js/qr-code.js b/src/assets/js/qr-code.js new file mode 100644 index 0000000..c141e1a --- /dev/null +++ b/src/assets/js/qr-code.js @@ -0,0 +1,252 @@ +import { + prepareZXingModule, + readBarcodes, + writeBarcode, +} from 'zxing-wasm/full'; + +/** + * @desc 二维码读取配置选项 + * @type { import('zxing-wasm').ReaderOptions } + */ +const readerOptions = { + formats: ['QRCode'], + maxNumberOfSymbols: 8, + textMode: 'Plain', + tryDownscale: true, + tryHarder: true, + tryInvert: true, + tryRotate: true, +}; + +/** + * @desc 二维码生成配置选项 + * @type { import('zxing-wasm').WriterOptions } + */ +const writerOptions = { + ecLevel: '', + format: 'QRCode', + scale: 1, +}; + +// 配置 wasm 文件路径 +prepareZXingModule({ + overrides: { + locateFile: (path, prefix) => { + if (path.endsWith('.wasm')) { + return `./wasm/${path}`; + } else { + return (prefix + path); + } + }, + }, +}); + +/** + * @description 转换 Blob 为 DataURL + * @param {Blob} blob + * @param {Callback} callback + */ +export function blobToDataURL(blob, callback) { + + /** @typedef {(data: { error: boolean, result: string }) => void} Callback */ + + let reader = new FileReader(); + + reader.onerror = function () { + callback({ + error: true, + result: reader.result, + }); + }; + + reader.onload = function () { + callback({ + error: false, + result: reader.result, + }); + }; + + reader.readAsDataURL(blob); + +} + +/** + * @description 将 SVG 字符串渲染到 Canvas + * @param {object} options + * @param {HTMLCanvasElement} options.canvas + * @param {string} options.svgString + * @param {number} options.drawLeft + * @param {number} options.drawTop + * @param {number} options.drawWidth + * @param {number} options.drawHeight + * @returns {Promise} + */ +function renderSvgToCanvas(options) { + + let { + canvas, svgString, + drawLeft = 0, drawTop = 0, + drawWidth = 0, drawHeight = 0, + } = options; + + return new Promise((resolve) => { + + let svgBlob = new Blob([svgString], { + type: 'image/svg+xml;charset=utf-8', + }); + let svgUrl = URL.createObjectURL(svgBlob); + let image = new Image(); + + image.onerror = () => { + console.error('加载 SVG 失败'); + URL.revokeObjectURL(svgUrl); + resolve(false); + }; + + image.onload = () => { + let ctx = canvas.getContext('2d'); + ctx.drawImage(image, drawLeft, drawTop, drawWidth, drawHeight); + URL.revokeObjectURL(svgUrl); + resolve(true); + }; + + image.src = svgUrl; + + }); +} + +/** + * @description 解析二维码图片 + * @param {Blob} image 图片二进制 + */ +export function readQrCodeImage(image) { + + /** + * @desc 返回结果 + * @type {{ error: string; image: string; textList: string[]; }} + */ + let returns = { + error: '', + image: '', + textList: [], + }; + + /** 读取图片,转换为 DataURL */ + let fileReader = new FileReader(); + + return new Promise((resolve) => { + + // 处理读取异常 + fileReader.onerror = function () { + console.error('解析二维码失败:读取图片失败'); + returns.error = '读取图片失败'; + resolve(''); + }; + + // 处理读取完成 + fileReader.onload = function () { + resolve(fileReader.result); + }; + + // 开始读取 + fileReader.readAsDataURL(image); + + }).then((dataURL) => { + if (dataURL) { + returns.image = dataURL; + return readBarcodes(image, readerOptions); + } else { + return null; + } + }).then((resultList) => { + + let textList = returns.textList; + + if (resultList.length === 0) { + console.warn('解析二维码失败:未识别到内容'); + return returns; + } else { + console.debug('解析二维码成功:', resultList); + } + + for (let i = 0; i < resultList.length; i++) { + + let item = resultList[i]; + + textList.push(item.text); + + } + + return returns; + + }).catch((error) => { + console.error('解析二维码失败:'); + console.error(error); + returns.error = String(error); + return returns; + });; + +} + +/** + * @description 生成二维码图片 + * @param {object} options + * @param {string} options.content + * @param {number} options.width + * @param {number} options.height + * @returns 二维码图片 DataURL + */ +export function writeQrCodeImage(options = {}) { + + let { content = '', width = 256, height = 256 } = options; + + let canvas = document.createElement('canvas'); + let ctx = canvas.getContext('2d'); + + // 更新画布大小 + canvas.width = width; + canvas.height = height; + + // 设置背景颜色 + ctx.fillStyle = '#FFFFFF'; + ctx.fillRect(0, 0, width, height); + + return writeBarcode(content, writerOptions).then((result) => { + + console.debug('生成二维码', result); + + if (result.error) { + console.error(`生成二维码失败:${result.error}`); + return ''; + } else { + return result.svg; + } + + }).then((svgString) => { + if (svgString) { + return renderSvgToCanvas({ + canvas: canvas, + svgString: svgString, + drawLeft: 0, + drawTop: 0, + drawWidth: width, + drawHeight: height, + }); + } else { + return false; + } + }).then((success) => { + + if (success) { + return canvas.toDataURL('image/png'); + } else { + return ''; + } + + }).catch((error) => { + console.error('生成二维码失败:'); + console.error(error); + return ''; + }); + +}