FPSMS-frontend
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

QrCodeScannerProvider.tsx 16 KiB

8 месяцев назад
4 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
8 месяцев назад
5 месяцев назад
7 месяцев назад
4 месяцев назад
3 месяцев назад
8 месяцев назад
7 месяцев назад
8 месяцев назад
6 месяцев назад
7 месяцев назад
6 месяцев назад
7 месяцев назад
4 месяцев назад
3 месяцев назад
1 неделю назад
4 месяцев назад
7 месяцев назад
4 месяцев назад
3 месяцев назад
4 месяцев назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
4 месяцев назад
7 месяцев назад
4 месяцев назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
8 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
8 месяцев назад
1 неделю назад
7 месяцев назад
1 неделю назад
4 месяцев назад
1 неделю назад
4 месяцев назад
1 неделю назад
4 месяцев назад
1 неделю назад
4 месяцев назад
1 неделю назад
2 месяцев назад
4 месяцев назад
2 месяцев назад
1 неделю назад
2 месяцев назад
1 неделю назад
2 месяцев назад
1 неделю назад
2 месяцев назад
4 месяцев назад
2 месяцев назад
1 неделю назад
2 месяцев назад
1 неделю назад
2 месяцев назад
4 месяцев назад
2 месяцев назад
4 месяцев назад
1 неделю назад
4 месяцев назад
1 неделю назад
4 месяцев назад
1 неделю назад
4 месяцев назад
1 неделю назад
2 месяцев назад
1 неделю назад
4 месяцев назад
2 месяцев назад
4 месяцев назад
2 месяцев назад
4 месяцев назад
7 месяцев назад
6 месяцев назад
7 месяцев назад
6 месяцев назад
7 месяцев назад
4 месяцев назад
3 месяцев назад
7 месяцев назад
6 месяцев назад
7 месяцев назад
6 месяцев назад
7 месяцев назад
6 месяцев назад
7 месяцев назад
8 месяцев назад
7 месяцев назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. "use client";
  2. import { QrCodeInfo } from "@/app/api/qrcode";
  3. import { useRef } from "react";
  4. import {
  5. ReactNode,
  6. createContext,
  7. useCallback,
  8. useContext,
  9. useEffect,
  10. useState,
  11. startTransition,
  12. } from "react";
  13. export interface QrCodeScanner {
  14. values: string[];
  15. isScanning: boolean;
  16. startScan: () => void;
  17. stopScan: () => void;
  18. resetScan: () => void;
  19. result: QrCodeInfo | undefined;
  20. state: "scanning" | "pending" | "retry";
  21. error: string | undefined;
  22. }
  23. interface QrCodeScannerProviderProps {
  24. children: ReactNode;
  25. }
  26. export const QrCodeScannerContext = createContext<QrCodeScanner | undefined>(
  27. undefined,
  28. );
  29. const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({
  30. children,
  31. }) => {
  32. const [qrCodeScannerValues, setQrCodeScannerValues] = useState<string[]>([]);
  33. const [isScanning, setIsScanning] = useState<boolean>(false);
  34. const [keys, setKeys] = useState<string[]>([]);
  35. const [leftCurlyBraceCount, setLeftCurlyBraceCount] = useState<number>(0);
  36. const [rightCurlyBraceCount, setRightCurlyBraceCount] = useState<number>(0);
  37. const [scanResult, setScanResult] = useState<QrCodeInfo | undefined>()
  38. const [scanState, setScanState] = useState<"scanning" | "pending" | "retry">("pending");
  39. const [scanError, setScanError] = useState<string | undefined>() // TODO return scan error message
  40. const keysRef = useRef<string[]>([]);
  41. const leftBraceCountRef = useRef<number>(0);
  42. const rightBraceCountRef = useRef<number>(0);
  43. const isFirstKeyRef = useRef<boolean>(true);
  44. const resetScannerInput = useCallback(() => {
  45. setKeys(() => []);
  46. setLeftCurlyBraceCount(() => 0);
  47. setRightCurlyBraceCount(() => 0);
  48. }, []);
  49. const resetQrCodeScanner = useCallback((error : string = "") => {
  50. setQrCodeScannerValues(() => []);
  51. setScanResult(undefined);
  52. resetScannerInput();
  53. console.log("%c Scanner Reset", "color:cyan");
  54. if (error.length > 0) {
  55. console.log("%c Error:", "color:red", error);
  56. console.log("%c key:", "color:red", keys);
  57. setScanState("retry");
  58. }
  59. }, []);
  60. const startQrCodeScanner = useCallback(() => {
  61. const startTime = performance.now();
  62. console.log(`⏱️ [SCANNER START] Called at: ${new Date().toISOString()}`);
  63. resetQrCodeScanner();
  64. const resetTime = performance.now() - startTime;
  65. console.log(`⏱️ [SCANNER START] Reset time: ${resetTime.toFixed(2)}ms`);
  66. setIsScanning(() => true);
  67. const setScanningTime = performance.now() - startTime;
  68. console.log(`⏱️ [SCANNER START] setScanning time: ${setScanningTime.toFixed(2)}ms`);
  69. const totalTime = performance.now() - startTime;
  70. console.log(`%c Scanning started `, "color:cyan");
  71. console.log(`⏱️ [SCANNER START] Total start time: ${totalTime.toFixed(2)}ms`);
  72. console.log(`⏰ [SCANNER START] Scanner started at: ${new Date().toISOString()}`);
  73. }, [resetQrCodeScanner]);
  74. const endQrCodeScanner = useCallback(() => {
  75. setIsScanning(() => false);
  76. console.log("%c Scanning stopped ", "color:cyan");
  77. }, []);
  78. // Find by rough match, return 0 if not found
  79. const findIdByRoughMatch = (inputString : string, keyword : string) => {
  80. console.log(`%c Performed rough match for ${keyword} within ${inputString}`, "color:brown");
  81. const keywordIndex = inputString.indexOf(keyword);
  82. let result : {keywordFound: boolean; number: number | null; message: string} = {
  83. keywordFound: false,
  84. number: null,
  85. message: `${keyword} not found in the input`,
  86. };
  87. if (keywordIndex !== -1) {
  88. const substringAfterKeyword = inputString.slice(keywordIndex + keyword.length);
  89. const numberMatch = substringAfterKeyword.match(/\d+/);
  90. if (!numberMatch) {
  91. result = {
  92. keywordFound: true,
  93. number: null,
  94. message: `No valid number found after ${keyword}`,
  95. };
  96. } else {
  97. result = {
  98. keywordFound: true,
  99. number: parseInt(numberMatch[0], 10),
  100. message: `Found ${keyword} at index ${keywordIndex}, first number found after is: ${numberMatch[0]}`,
  101. };
  102. }
  103. }
  104. console.log(`%c ${result.message}`, "color:brown");
  105. return result;
  106. };
  107. useEffect(() => {
  108. const effectStartTime = performance.now();
  109. console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Triggered at: ${new Date().toISOString()}`);
  110. console.log(`⏱️ [KEYBOARD LISTENER EFFECT] isScanning: ${isScanning}`);
  111. if (isScanning) {
  112. const listenerRegisterStartTime = performance.now();
  113. console.log(`⏱️ [KEYBOARD LISTENER] Registering keyboard listener at: ${new Date().toISOString()}`);
  114. // Reset refs when starting scan
  115. keysRef.current = [];
  116. leftBraceCountRef.current = 0;
  117. rightBraceCountRef.current = 0;
  118. isFirstKeyRef.current = true;
  119. const handleKeyDown = (event: KeyboardEvent) => {
  120. const keyPressTime = performance.now();
  121. const keyPressTimestamp = new Date().toISOString();
  122. // ✅ OPTIMIZED: Use refs to accumulate keys immediately (no state update delay)
  123. if (event.key.length === 1) {
  124. if (isFirstKeyRef.current) {
  125. console.log(`⏱️ [KEYBOARD] First key press detected: "${event.key}"`);
  126. console.log(`⏰ [KEYBOARD] First key press at: ${keyPressTimestamp}`);
  127. console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(keyPressTime - listenerRegisterStartTime).toFixed(2)}ms`);
  128. isFirstKeyRef.current = false;
  129. }
  130. keysRef.current.push(event.key);
  131. }
  132. if (event.key === "{") {
  133. const braceTime = performance.now();
  134. console.log(`⏱️ [KEYBOARD] Left brace "{" detected at: ${new Date().toISOString()}`);
  135. console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(braceTime - listenerRegisterStartTime).toFixed(2)}ms`);
  136. leftBraceCountRef.current += 1;
  137. } else if (event.key === "}") {
  138. const braceTime = performance.now();
  139. console.log(`⏱️ [KEYBOARD] Right brace "}" detected at: ${new Date().toISOString()}`);
  140. console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(braceTime - listenerRegisterStartTime).toFixed(2)}ms`);
  141. rightBraceCountRef.current += 1;
  142. // ✅ OPTIMIZED: Check for complete QR immediately and update state only once
  143. if (leftBraceCountRef.current === rightBraceCountRef.current && leftBraceCountRef.current > 0) {
  144. const completeTime = performance.now();
  145. console.log(`⏱️ [KEYBOARD] Complete QR detected immediately! Time: ${completeTime.toFixed(2)}ms`);
  146. console.log(`⏰ [KEYBOARD] Complete QR at: ${new Date().toISOString()}`);
  147. const qrValue = keysRef.current.join("").substring(
  148. keysRef.current.indexOf("{"),
  149. keysRef.current.lastIndexOf("}") + 1
  150. );
  151. console.log(`⏱️ [KEYBOARD] QR value: ${qrValue}`);
  152. // ✅ TABLET OPTIMIZATION: Directly set qrCodeScannerValues without any state chain
  153. // Use flushSync for immediate update on tablets (if available, otherwise use regular setState)
  154. setQrCodeScannerValues((value) => {
  155. console.log(`⏱️ [KEYBOARD] Setting qrCodeScannerValues directly: ${qrValue}`);
  156. return [...value, qrValue];
  157. });
  158. // Reset scanner input immediately (using refs, no state update)
  159. keysRef.current = [];
  160. leftBraceCountRef.current = 0;
  161. rightBraceCountRef.current = 0;
  162. isFirstKeyRef.current = true;
  163. // ✅ TABLET OPTIMIZATION: Defer all cleanup state updates to avoid blocking
  164. // Use setTimeout to ensure QR processing happens first
  165. setTimeout(() => {
  166. startTransition(() => {
  167. setKeys([]);
  168. setLeftCurlyBraceCount(0);
  169. setRightCurlyBraceCount(0);
  170. setScanState("pending");
  171. resetScannerInput();
  172. });
  173. }, 0);
  174. return;
  175. }
  176. }
  177. // ✅ TABLET OPTIMIZATION: Completely skip state updates during scanning
  178. // Only update state for the first brace detection (for UI feedback)
  179. // All other updates are deferred to avoid blocking on tablets
  180. if (leftBraceCountRef.current === 1 && keysRef.current.length === 1 && event.key === "{") {
  181. // Only update state once when first brace is detected
  182. startTransition(() => {
  183. setKeys([...keysRef.current]);
  184. setLeftCurlyBraceCount(leftBraceCountRef.current);
  185. setRightCurlyBraceCount(rightBraceCountRef.current);
  186. });
  187. }
  188. // Skip all other state updates during scanning to maximize performance on tablets
  189. };
  190. document.addEventListener("keydown", handleKeyDown);
  191. const listenerRegisterTime = performance.now() - listenerRegisterStartTime;
  192. console.log(`⏱️ [KEYBOARD LISTENER] Listener registered in: ${listenerRegisterTime.toFixed(2)}ms`);
  193. console.log(`⏰ [KEYBOARD LISTENER] Listener ready at: ${new Date().toISOString()}`);
  194. return () => {
  195. console.log(`⏱️ [KEYBOARD LISTENER] Removing keyboard listener at: ${new Date().toISOString()}`);
  196. document.removeEventListener("keydown", handleKeyDown);
  197. };
  198. } else {
  199. console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Scanner not active, skipping listener registration`);
  200. }
  201. const effectTime = performance.now() - effectStartTime;
  202. console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Total effect time: ${effectTime.toFixed(2)}ms`);
  203. }, [isScanning]);
  204. // ✅ OPTIMIZED: Simplify the QR scanner effect - it's now mainly for initial detection
  205. useEffect(() => {
  206. const effectStartTime = performance.now();
  207. console.log(`⏱️ [QR SCANNER EFFECT] Triggered at: ${new Date().toISOString()}`);
  208. console.log(`⏱️ [QR SCANNER EFFECT] Keys count: ${keys.length}, leftBrace: ${leftCurlyBraceCount}, rightBrace: ${rightCurlyBraceCount}`);
  209. if (rightCurlyBraceCount > leftCurlyBraceCount || leftCurlyBraceCount > 1) { // Prevent multiple scan
  210. setScanState("retry");
  211. setScanError("Too many scans at once");
  212. resetQrCodeScanner("Too many scans at once");
  213. } else {
  214. // Only show "scanning" state when first brace is detected
  215. if (leftCurlyBraceCount == 1 && keys.length == 1)
  216. {
  217. const scanDetectedTime = performance.now();
  218. setScanState("scanning");
  219. console.log(`%c Scan detected, waiting for inputs...`, "color:cyan");
  220. console.log(`⏱️ [QR SCANNER] Scan detected time: ${scanDetectedTime.toFixed(2)}ms`);
  221. console.log(`⏰ [QR SCANNER] Scan detected at: ${new Date().toISOString()}`);
  222. }
  223. // Note: Complete QR detection is now handled directly in handleKeyDown
  224. // This effect is mainly for UI feedback and error handling
  225. }
  226. }, [keys, leftCurlyBraceCount, rightCurlyBraceCount]);
  227. useEffect(() => {
  228. if (qrCodeScannerValues.length > 0) {
  229. const processStartTime = performance.now();
  230. console.log(`⏱️ [QR SCANNER PROCESS] Processing qrCodeScannerValues at: ${new Date().toISOString()}`);
  231. console.log(`⏱️ [QR SCANNER PROCESS] Values count: ${qrCodeScannerValues.length}`);
  232. const scannedValues = qrCodeScannerValues[0];
  233. console.log(`%c Scanned Result: `, "color:cyan", scannedValues);
  234. console.log(`⏱️ [QR SCANNER PROCESS] Scanned value: ${scannedValues}`);
  235. console.log(`⏰ [QR SCANNER PROCESS] Processing at: ${new Date().toISOString()}`);
  236. if (scannedValues.substring(0, 8) == "{2fitest") { // DEBUGGING
  237. // 先检查是否是 {2fiteste...} 或 {2fitestu...} 格式
  238. // 这些格式需要传递完整值给 processQrCode 处理
  239. if (scannedValues.length > 9) {
  240. const ninthChar = scannedValues.substring(8, 9);
  241. if (ninthChar === "e" || ninthChar === "u") {
  242. // {2fiteste数字} 或 {2fitestu任何内容} 格式
  243. console.log(`%c DEBUG: detected shortcut format: `, "color:pink", scannedValues);
  244. const debugValue = {
  245. value: scannedValues // 传递完整值,让 processQrCode 处理
  246. }
  247. setScanResult(debugValue);
  248. const processTime = performance.now() - processStartTime;
  249. console.log(`⏱️ [QR SCANNER PROCESS] Shortcut processing time: ${processTime.toFixed(2)}ms`);
  250. return;
  251. }
  252. }
  253. // 原有的 {2fitest数字} 格式(纯数字,向后兼容)
  254. const number = scannedValues.substring(8, scannedValues.length - 1);
  255. if (/^\d+$/.test(number)) { // Check if number contains only digits
  256. console.log(`%c DEBUG: detected ID: `, "color:pink", number);
  257. const debugValue = {
  258. value: number
  259. }
  260. setScanResult(debugValue);
  261. const processTime = performance.now() - processStartTime;
  262. console.log(`⏱️ [QR SCANNER PROCESS] ID processing time: ${processTime.toFixed(2)}ms`);
  263. return;
  264. } else {
  265. // 如果不是纯数字,传递完整值让 processQrCode 处理
  266. const debugValue = {
  267. value: scannedValues
  268. }
  269. setScanResult(debugValue);
  270. const processTime = performance.now() - processStartTime;
  271. console.log(`⏱️ [QR SCANNER PROCESS] Non-numeric processing time: ${processTime.toFixed(2)}ms`);
  272. return;
  273. }
  274. }
  275. try {
  276. const parseStartTime = performance.now();
  277. const data: QrCodeInfo = JSON.parse(scannedValues);
  278. const parseTime = performance.now() - parseStartTime;
  279. console.log(`%c Parsed scan data`, "color:green", data);
  280. console.log(`⏱️ [QR SCANNER PROCESS] JSON parse time: ${parseTime.toFixed(2)}ms`);
  281. const content = scannedValues.substring(1, scannedValues.length - 1);
  282. data.value = content;
  283. const setResultStartTime = performance.now();
  284. setScanResult(data);
  285. const setResultTime = performance.now() - setResultStartTime;
  286. console.log(`⏱️ [QR SCANNER PROCESS] setScanResult time: ${setResultTime.toFixed(2)}ms`);
  287. console.log(`⏰ [QR SCANNER PROCESS] setScanResult at: ${new Date().toISOString()}`);
  288. const processTime = performance.now() - processStartTime;
  289. console.log(`⏱️ [QR SCANNER PROCESS] Total processing time: ${processTime.toFixed(2)}ms`);
  290. } catch (error) { // Rough match for other scanner input -- Pending Review
  291. console.log(`⏱️ [QR SCANNER PROCESS] JSON parse failed, trying rough match`);
  292. const silId = findIdByRoughMatch(scannedValues, "StockInLine").number ?? 0;
  293. if (silId == 0) {
  294. const whId = findIdByRoughMatch(scannedValues, "warehouseId").number ?? 0;
  295. setScanResult({...scanResult, stockInLineId: whId, value: whId.toString()});
  296. } else { setScanResult({...scanResult, stockInLineId: silId, value: silId.toString()}); }
  297. resetQrCodeScanner(String(error));
  298. }
  299. // resetQrCodeScanner();
  300. }
  301. }, [qrCodeScannerValues]);
  302. return (
  303. <QrCodeScannerContext.Provider
  304. value={{
  305. values: qrCodeScannerValues,
  306. isScanning: isScanning,
  307. startScan: startQrCodeScanner,
  308. stopScan: endQrCodeScanner,
  309. resetScan: resetQrCodeScanner,
  310. result: scanResult,
  311. state: scanState,
  312. error: scanError,
  313. }}
  314. >
  315. {children}
  316. </QrCodeScannerContext.Provider>
  317. );
  318. };
  319. export const useQrCodeScannerContext = (): QrCodeScanner => {
  320. const context = useContext(QrCodeScannerContext);
  321. if (!context) {
  322. throw new Error(
  323. "useQrCodeScanner must be used within a QrCodeScannerProvider",
  324. );
  325. }
  326. return context;
  327. };
  328. export default QrCodeScannerProvider;