From 2479653bab03a916afff7429fa8c4023818a265e Mon Sep 17 00:00:00 2001 From: Timothy Yin Date: Thu, 12 Mar 2026 01:07:55 +0800 Subject: [PATCH] feat(qr-scanner): integrate jsQR for QR code scanning fallback --- apps/web/app/dashboard/charge/page.tsx | 34 ++++++++++++++++++-------- apps/web/package.json | 1 + 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/web/app/dashboard/charge/page.tsx b/apps/web/app/dashboard/charge/page.tsx index 20213c4..703fd88 100644 --- a/apps/web/app/dashboard/charge/page.tsx +++ b/apps/web/app/dashboard/charge/page.tsx @@ -12,6 +12,7 @@ import { QrCode, Xmark, } from "@gravity-ui/icons"; +import jsQR from "jsqr"; import { api } from "@/lib/api"; import dayjs from "@/lib/dayjs"; @@ -155,20 +156,34 @@ function QrScanner({ onResult, onClose }: ScannerProps) { if (!mountedRef.current) return; - if (!("BarcodeDetector" in window)) { - if (mountedRef.current) setError("当前浏览器不支持实时扫描,请升级至最新版本"); - return; + const useNative = "BarcodeDetector" in window; + if (useNative) { + detector = new (window as any).BarcodeDetector({ formats: ["qr_code"] }); } - detector = new (window as any).BarcodeDetector({ formats: ["qr_code"] }); + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); const scan = async () => { if (!scanningRef.current || !videoRef.current) return; try { - const codes: Array<{ rawValue: string }> = await detector.detect(videoRef.current); - if (codes.length > 0) { - onResult(codes[0].rawValue); - return; + if (useNative) { + const codes: Array<{ rawValue: string }> = await detector.detect(videoRef.current); + if (codes.length > 0) { + onResult(codes[0].rawValue); + return; + } + } else if (ctx) { + const video = videoRef.current; + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + ctx.drawImage(video, 0, 0); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const code = jsQR(imageData.data, imageData.width, imageData.height); + if (code) { + onResult(code.data); + return; + } } } catch {} requestAnimationFrame(scan); @@ -411,8 +426,7 @@ function ChargePageContent() { .filter((cp) => cp.registrationStatus === "Accepted") .map((cp) => { const online = - !!cp.lastHeartbeatAt && - dayjs().diff(dayjs(cp.lastHeartbeatAt), "second") < 120; + !!cp.lastHeartbeatAt && dayjs().diff(dayjs(cp.lastHeartbeatAt), "second") < 120; const availableCount = cp.connectors.filter( (c) => c.status === "Available", ).length; diff --git a/apps/web/package.json b/apps/web/package.json index d458a77..45fe69b 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -17,6 +17,7 @@ "@types/qrcode": "^1.5.6", "better-auth": "catalog:", "dayjs": "catalog:", + "jsqr": "^1.4.0", "next": "16.1.6", "qrcode.react": "^4.2.0", "react": "19.2.3",