From 55d9e24f83a98790fac6c54b55b99eea7eb23593 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Tue, 27 Jan 2026 09:37:38 +0800 Subject: [PATCH] update qr code scan --- .../QrCodeScannerProvider.tsx | 214 ++++++++++++++---- 1 file changed, 170 insertions(+), 44 deletions(-) diff --git a/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx b/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx index d9308ca..eaa6a36 100644 --- a/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx +++ b/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx @@ -1,5 +1,6 @@ "use client"; import { QrCodeInfo } from "@/app/api/qrcode"; +import { useRef } from "react"; import { ReactNode, createContext, @@ -7,6 +8,7 @@ import { useContext, useEffect, useState, + startTransition, } from "react"; export interface QrCodeScanner { @@ -39,6 +41,10 @@ const QrCodeScannerProvider: React.FC = ({ const [scanResult, setScanResult] = useState() const [scanState, setScanState] = useState<"scanning" | "pending" | "retry">("pending"); const [scanError, setScanError] = useState() // TODO return scan error message + const keysRef = useRef([]); + const leftBraceCountRef = useRef(0); + const rightBraceCountRef = useRef(0); + const isFirstKeyRef = useRef(true); const resetScannerInput = useCallback(() => { setKeys(() => []); @@ -61,10 +67,22 @@ const QrCodeScannerProvider: React.FC = ({ }, []); const startQrCodeScanner = useCallback(() => { + const startTime = performance.now(); + console.log(`⏱️ [SCANNER START] Called at: ${new Date().toISOString()}`); + resetQrCodeScanner(); + const resetTime = performance.now() - startTime; + console.log(`⏱️ [SCANNER START] Reset time: ${resetTime.toFixed(2)}ms`); + setIsScanning(() => true); - console.log("%c Scanning started ", "color:cyan"); - }, []); + const setScanningTime = performance.now() - startTime; + console.log(`⏱️ [SCANNER START] setScanning time: ${setScanningTime.toFixed(2)}ms`); + + const totalTime = performance.now() - startTime; + console.log(`%c Scanning started `, "color:cyan"); + console.log(`⏱️ [SCANNER START] Total start time: ${totalTime.toFixed(2)}ms`); + console.log(`⏰ [SCANNER START] Scanner started at: ${new Date().toISOString()}`); + }, [resetQrCodeScanner]); const endQrCodeScanner = useCallback(() => { setIsScanning(() => false); @@ -107,65 +125,154 @@ const QrCodeScannerProvider: React.FC = ({ return result; }; - // Check the KeyDown useEffect(() => { + const effectStartTime = performance.now(); + console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Triggered at: ${new Date().toISOString()}`); + console.log(`⏱️ [KEYBOARD LISTENER EFFECT] isScanning: ${isScanning}`); + if (isScanning) { + const listenerRegisterStartTime = performance.now(); + console.log(`⏱️ [KEYBOARD LISTENER] Registering keyboard listener at: ${new Date().toISOString()}`); + + // Reset refs when starting scan + keysRef.current = []; + leftBraceCountRef.current = 0; + rightBraceCountRef.current = 0; + isFirstKeyRef.current = true; + const handleKeyDown = (event: KeyboardEvent) => { + const keyPressTime = performance.now(); + const keyPressTimestamp = new Date().toISOString(); + + // ✅ OPTIMIZED: Use refs to accumulate keys immediately (no state update delay) if (event.key.length === 1) { - setKeys((key) => [...key, event.key]); + if (isFirstKeyRef.current) { + console.log(`⏱️ [KEYBOARD] First key press detected: "${event.key}"`); + console.log(`⏰ [KEYBOARD] First key press at: ${keyPressTimestamp}`); + console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(keyPressTime - listenerRegisterStartTime).toFixed(2)}ms`); + isFirstKeyRef.current = false; + } + keysRef.current.push(event.key); } - + if (event.key === "{") { - setLeftCurlyBraceCount((count) => count + 1); + const braceTime = performance.now(); + console.log(`⏱️ [KEYBOARD] Left brace "{" detected at: ${new Date().toISOString()}`); + console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(braceTime - listenerRegisterStartTime).toFixed(2)}ms`); + leftBraceCountRef.current += 1; } else if (event.key === "}") { - setRightCurlyBraceCount((count) => count + 1); + const braceTime = performance.now(); + console.log(`⏱️ [KEYBOARD] Right brace "}" detected at: ${new Date().toISOString()}`); + console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(braceTime - listenerRegisterStartTime).toFixed(2)}ms`); + rightBraceCountRef.current += 1; + + // ✅ OPTIMIZED: Check for complete QR immediately and update state only once + if (leftBraceCountRef.current === rightBraceCountRef.current && leftBraceCountRef.current > 0) { + const completeTime = performance.now(); + console.log(`⏱️ [KEYBOARD] Complete QR detected immediately! Time: ${completeTime.toFixed(2)}ms`); + console.log(`⏰ [KEYBOARD] Complete QR at: ${new Date().toISOString()}`); + + const qrValue = keysRef.current.join("").substring( + keysRef.current.indexOf("{"), + keysRef.current.lastIndexOf("}") + 1 + ); + console.log(`⏱️ [KEYBOARD] QR value: ${qrValue}`); + + // ✅ TABLET OPTIMIZATION: Directly set qrCodeScannerValues without any state chain + // Use flushSync for immediate update on tablets (if available, otherwise use regular setState) + setQrCodeScannerValues((value) => { + console.log(`⏱️ [KEYBOARD] Setting qrCodeScannerValues directly: ${qrValue}`); + return [...value, qrValue]; + }); + + // Reset scanner input immediately (using refs, no state update) + keysRef.current = []; + leftBraceCountRef.current = 0; + rightBraceCountRef.current = 0; + isFirstKeyRef.current = true; + + // ✅ TABLET OPTIMIZATION: Defer all cleanup state updates to avoid blocking + // Use setTimeout to ensure QR processing happens first + setTimeout(() => { + startTransition(() => { + setKeys([]); + setLeftCurlyBraceCount(0); + setRightCurlyBraceCount(0); + setScanState("pending"); + resetScannerInput(); + }); + }, 0); + + return; + } + } + + // ✅ TABLET OPTIMIZATION: Completely skip state updates during scanning + // Only update state for the first brace detection (for UI feedback) + // All other updates are deferred to avoid blocking on tablets + if (leftBraceCountRef.current === 1 && keysRef.current.length === 1 && event.key === "{") { + // Only update state once when first brace is detected + startTransition(() => { + setKeys([...keysRef.current]); + setLeftCurlyBraceCount(leftBraceCountRef.current); + setRightCurlyBraceCount(rightBraceCountRef.current); + }); } + // Skip all other state updates during scanning to maximize performance on tablets }; - + document.addEventListener("keydown", handleKeyDown); - + const listenerRegisterTime = performance.now() - listenerRegisterStartTime; + console.log(`⏱️ [KEYBOARD LISTENER] Listener registered in: ${listenerRegisterTime.toFixed(2)}ms`); + console.log(`⏰ [KEYBOARD LISTENER] Listener ready at: ${new Date().toISOString()}`); + return () => { + console.log(`⏱️ [KEYBOARD LISTENER] Removing keyboard listener at: ${new Date().toISOString()}`); document.removeEventListener("keydown", handleKeyDown); }; + } else { + console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Scanner not active, skipping listener registration`); } + + const effectTime = performance.now() - effectStartTime; + console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Total effect time: ${effectTime.toFixed(2)}ms`); }, [isScanning]); - // Update Qr Code Scanner Values - useEffect(() => { - if (rightCurlyBraceCount > leftCurlyBraceCount || leftCurlyBraceCount > 1) { // Prevent multiple scan - setScanState("retry"); - setScanError("Too many scans at once"); - resetQrCodeScanner("Too many scans at once"); - } else { - if (leftCurlyBraceCount == 1 && keys.length == 1) - { - setScanState("scanning"); - console.log("%c Scan detected, waiting for inputs...", "color:cyan"); - } - if ( - leftCurlyBraceCount !== 0 && - rightCurlyBraceCount !== 0 && - leftCurlyBraceCount === rightCurlyBraceCount - ) { - const startBrace = keys.indexOf("{"); - const endBrace = keys.lastIndexOf("}"); - setScanState("pending"); - setQrCodeScannerValues((value) => [ - ...value, - keys.join("").substring(startBrace, endBrace + 1), - ]); - // console.log(keys); - // console.log("%c QR Scanner Values:", "color:cyan", qrCodeScannerValues); +// ✅ OPTIMIZED: Simplify the QR scanner effect - it's now mainly for initial detection +useEffect(() => { + const effectStartTime = performance.now(); + console.log(`⏱️ [QR SCANNER EFFECT] Triggered at: ${new Date().toISOString()}`); + console.log(`⏱️ [QR SCANNER EFFECT] Keys count: ${keys.length}, leftBrace: ${leftCurlyBraceCount}, rightBrace: ${rightCurlyBraceCount}`); - resetScannerInput(); + if (rightCurlyBraceCount > leftCurlyBraceCount || leftCurlyBraceCount > 1) { // Prevent multiple scan + setScanState("retry"); + setScanError("Too many scans at once"); + resetQrCodeScanner("Too many scans at once"); + } else { + // Only show "scanning" state when first brace is detected + if (leftCurlyBraceCount == 1 && keys.length == 1) + { + const scanDetectedTime = performance.now(); + setScanState("scanning"); + console.log(`%c Scan detected, waiting for inputs...`, "color:cyan"); + console.log(`⏱️ [QR SCANNER] Scan detected time: ${scanDetectedTime.toFixed(2)}ms`); + console.log(`⏰ [QR SCANNER] Scan detected at: ${new Date().toISOString()}`); } - } - }, [keys, leftCurlyBraceCount, rightCurlyBraceCount]); - + // Note: Complete QR detection is now handled directly in handleKeyDown + // This effect is mainly for UI feedback and error handling + } +}, [keys, leftCurlyBraceCount, rightCurlyBraceCount]); + useEffect(() => { if (qrCodeScannerValues.length > 0) { + const processStartTime = performance.now(); + console.log(`⏱️ [QR SCANNER PROCESS] Processing qrCodeScannerValues at: ${new Date().toISOString()}`); + console.log(`⏱️ [QR SCANNER PROCESS] Values count: ${qrCodeScannerValues.length}`); + const scannedValues = qrCodeScannerValues[0]; - console.log("%c Scanned Result: ", "color:cyan", scannedValues); + console.log(`%c Scanned Result: `, "color:cyan", scannedValues); + console.log(`⏱️ [QR SCANNER PROCESS] Scanned value: ${scannedValues}`); + console.log(`⏰ [QR SCANNER PROCESS] Processing at: ${new Date().toISOString()}`); if (scannedValues.substring(0, 8) == "{2fitest") { // DEBUGGING // 先检查是否是 {2fiteste...} 或 {2fitestu...} 格式 @@ -174,11 +281,13 @@ const QrCodeScannerProvider: React.FC = ({ const ninthChar = scannedValues.substring(8, 9); if (ninthChar === "e" || ninthChar === "u") { // {2fiteste数字} 或 {2fitestu任何内容} 格式 - console.log("%c DEBUG: detected shortcut format: ", "color:pink", scannedValues); + console.log(`%c DEBUG: detected shortcut format: `, "color:pink", scannedValues); const debugValue = { value: scannedValues // 传递完整值,让 processQrCode 处理 } setScanResult(debugValue); + const processTime = performance.now() - processStartTime; + console.log(`⏱️ [QR SCANNER PROCESS] Shortcut processing time: ${processTime.toFixed(2)}ms`); return; } } @@ -186,30 +295,47 @@ const QrCodeScannerProvider: React.FC = ({ // 原有的 {2fitest数字} 格式(纯数字,向后兼容) const number = scannedValues.substring(8, scannedValues.length - 1); if (/^\d+$/.test(number)) { // Check if number contains only digits - console.log("%c DEBUG: detected ID: ", "color:pink", number); + console.log(`%c DEBUG: detected ID: `, "color:pink", number); const debugValue = { value: number } setScanResult(debugValue); + const processTime = performance.now() - processStartTime; + console.log(`⏱️ [QR SCANNER PROCESS] ID processing time: ${processTime.toFixed(2)}ms`); + return; } else { // 如果不是纯数字,传递完整值让 processQrCode 处理 const debugValue = { value: scannedValues } setScanResult(debugValue); + const processTime = performance.now() - processStartTime; + console.log(`⏱️ [QR SCANNER PROCESS] Non-numeric processing time: ${processTime.toFixed(2)}ms`); + return; } - return; } try { + const parseStartTime = performance.now(); const data: QrCodeInfo = JSON.parse(scannedValues); - console.log("%c Parsed scan data", "color:green", data); + const parseTime = performance.now() - parseStartTime; + console.log(`%c Parsed scan data`, "color:green", data); + console.log(`⏱️ [QR SCANNER PROCESS] JSON parse time: ${parseTime.toFixed(2)}ms`); const content = scannedValues.substring(1, scannedValues.length - 1); data.value = content; + + const setResultStartTime = performance.now(); setScanResult(data); + const setResultTime = performance.now() - setResultStartTime; + console.log(`⏱️ [QR SCANNER PROCESS] setScanResult time: ${setResultTime.toFixed(2)}ms`); + console.log(`⏰ [QR SCANNER PROCESS] setScanResult at: ${new Date().toISOString()}`); + + const processTime = performance.now() - processStartTime; + console.log(`⏱️ [QR SCANNER PROCESS] Total processing time: ${processTime.toFixed(2)}ms`); } catch (error) { // Rough match for other scanner input -- Pending Review + console.log(`⏱️ [QR SCANNER PROCESS] JSON parse failed, trying rough match`); const silId = findIdByRoughMatch(scannedValues, "StockInLine").number ?? 0; if (silId == 0) {