// 二维码解码核心逻辑:与 UI、相机、Tauri 解耦,便于单元测试。 // 相机扫码走原生插件,图片扫码走纯 JS 的 jsQR,桌面端也可用。 import jsQR from "jsqr"; /** * 校验扫码内容是否为有效网址(仅接受 http/https)。 * @param {unknown} content 扫码得到的原始内容。 * @returns {string} 合法网址(去除首尾空白);非法时返回空字符串。 */ export function extractUrl(content) { if (typeof content !== "string") return ""; const text = content.trim(); return /^https?:\/\//i.test(text) ? text : ""; } /** * 从像素数据中解码二维码。 * @param {{data: Uint8ClampedArray, width: number, height: number}} imageData * 画布像素数据(RGBA)。 * @returns {string|null} 解码到的文本;未识别到返回 null。 */ export function decodeQrFromImageData(imageData) { if (!imageData || !imageData.data || !imageData.width || !imageData.height) { return null; } // attemptBoth 允许识别反色(浅底深码或深底浅码)二维码,提升清晰码的识别率。 const result = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: "attemptBoth", }); return result ? result.data : null; } /** * 将图片元素绘制到离屏画布并取出像素数据(仅浏览器环境可用)。 * @param {CanvasImageSource & {width:number,height:number}} image 已加载的图片源。 * @param {number} [maxSize=1600] 长边最大像素,过大图片等比缩小以兼顾速度与精度。 * @returns {ImageData} 画布像素数据。 */ export function imageToImageData(image, maxSize = 1600) { const sw = image.naturalWidth || image.width; const sh = image.naturalHeight || image.height; const scale = Math.min(1, maxSize / Math.max(sw, sh)); const w = Math.max(1, Math.round(sw * scale)); const h = Math.max(1, Math.round(sh * scale)); const canvas = document.createElement("canvas"); canvas.width = w; canvas.height = h; const ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, w, h); return ctx.getImageData(0, 0, w, h); } /** * 从文件(用户选择的图片)解码二维码(仅浏览器环境可用)。 * @param {Blob} file 图片文件。 * @returns {Promise} 解码文本;未识别到返回 null。 */ export function decodeQrFromFile(file) { return new Promise((resolve, reject) => { const url = URL.createObjectURL(file); const img = new Image(); img.onload = () => { URL.revokeObjectURL(url); try { resolve(decodeQrFromImageData(imageToImageData(img))); } catch (e) { reject(e); } }; img.onerror = () => { URL.revokeObjectURL(url); reject(new Error("图片加载失败")); }; img.src = url; }); }