Browse Source

fix (1.44.0 -> 1.44.1): 修复二维码扫描模态框稳定性问题->注册页面不能再次扫码录入的问题,修复了扫码页面重新扫码失败的问题

重构扫描器实例管理,每次打开模态框新建实例,关闭时正确清理,解决了注册页面不能再次扫码录入的问题,修复了扫码页面重新扫码失败的问题

改动文件:
- src/components/QRCodeScanner/QRCodeScanModal.tsx
dengdx 1 week ago
parent
commit
c6af160763
3 changed files with 106 additions and 152 deletions
  1. 9 0
      CHANGELOG.md
  2. 1 1
      package.json
  3. 96 151
      src/components/QRCodeScanner/QRCodeScanModal.tsx

+ 9 - 0
CHANGELOG.md

@@ -2,6 +2,15 @@
 
 本项目的所有重要变更都将记录在此文件中。
 
+## [1.44.1] - 2026-01-03 17:32
+
+### 修复 (Fixed)
+- 注册页面不能再次扫码录入的问题,修复了扫码页面重新扫码失败的问题
+- **修复二维码扫描模态框稳定性问题** - 重构扫描器实例管理,每次打开模态框新建实例,关闭时正确清理,避免重复扫描和实例冲突
+
+**改动文件:**
+- src/components/QRCodeScanner/QRCodeScanModal.tsx
+
 ## [1.44.0] - 2026-01-03 16:40
 
 ### 新增 (Added)

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "zsis",
-  "version": "1.44.0",
+  "version": "1.44.1",
   "private": true,
   "description": "医学成像系统",
   "main": "main.js",

+ 96 - 151
src/components/QRCodeScanner/QRCodeScanModal.tsx

@@ -1,10 +1,11 @@
 /**
- * 二维码扫描模态框
+ * 二维码扫描模态框 - 稳定版
  * 使用 html5-qrcode 库实现摄像头扫描功能
+ * 核心原则:每次 Modal 打开 → 新建实例,关闭 → stop + clear
  */
 
-import React, { useEffect, useRef, useState } from 'react';
-import { Modal, Button, Alert, Spin, Typography, Space } from 'antd';
+import React, { useEffect, useRef, useState, useMemo } from 'react';
+import { Modal, Button, Alert, Spin, Typography } from 'antd';
 import { Html5Qrcode } from 'html5-qrcode';
 import { useDispatch, useSelector } from 'react-redux';
 import { FormInstance } from 'antd';
@@ -30,149 +31,95 @@ interface QRCodeScanModalProps {
 const QRCodeScanModal: React.FC<QRCodeScanModalProps> = ({ visible, onCancel, form }) => {
   const dispatch = useDispatch();
   const { isScanning, isProcessing, error } = useSelector((state: RootState) => state.qrCodeScan);
-  const [scanner, setScanner] = useState<Html5Qrcode | null>(null);
-  const [scannedText, setScannedText] = useState<string>(''); // 新增:存储扫描到的文本
+  const [scannedText, setScannedText] = useState<string>('');
   const scannerRef = useRef<Html5Qrcode | null>(null);
   const readerRef = useRef<HTMLDivElement>(null);
 
-  // 初始化扫描器 - 只在模态框首次打开时初始化一次
-  useEffect(() => {
-    let isMounted = true;
-
-    if (visible && !scanner) {
-      const initScanner = () => {
-        // 检查组件是否还挂载着
-        if (!isMounted) return;
-
-        const element = document.getElementById('qr-reader');
-        if (element) {
-          // DOM 元素存在,初始化扫描器
-          try {
-            const qrScanner = new Html5Qrcode('qr-reader');
-            if (isMounted) {
-              setScanner(qrScanner);
-              scannerRef.current = qrScanner;
-              console.log('[QRCodeScan] 扫描器初始化成功');
-            }
-          } catch (error) {
-            console.error('初始化扫描器失败:', error);
-            if (isMounted) {
-              dispatch(setError('初始化扫描器失败,请重试'));
-            }
-          }
-        } else {
-          // DOM 还没准备好,延迟重试
-          console.log('[QRCodeScan] DOM 元素未准备好,延迟重试');
-          setTimeout(initScanner, 100);
-        }
-      };
+  // 生成唯一的扫描器ID,避免多个实例冲突
+  const qrReaderId = useMemo(() => `qr-reader-${Math.random().toString(36).substr(2, 9)}`, []);
 
-      // 初始延迟 100ms 确保模态框完全渲染
-      setTimeout(initScanner, 100);
-    }
-
-    // 清理函数
-    return () => {
-      isMounted = false;
-      if (scannerRef.current && scannerRef.current.isScanning) {
-        scannerRef.current.stop().catch(err => console.error('停止扫描器失败:', err));
-      }
-      setScanner(null);
-      scannerRef.current = null;
-    };
-  }, [visible]); // 只依赖 visible,移除 scanner 和 dispatch 依赖
-
-  // 启动扫描 - 确保扫描器已初始化
+  // 🎯 核心逻辑:每次 visible=true → new Html5Qrcode,关闭 → stop + clear
   useEffect(() => {
-    if (visible && scanner && !isScanning) {
-      startQRScanner();
-    }
-  }, [visible, scanner, isScanning]);
+    if (!visible) return;
+    if (!readerRef.current) return;
 
-  const startQRScanner = async () => {
-    if (!scanner) return;
-
-    try {
-      dispatch(startScanning());
-      dispatch(clearError());
+    // 每次打开都新建实例 - html5-qrcode 需要字符串 ID
+    const qr = new Html5Qrcode(qrReaderId);
+    scannerRef.current = qr;
 
-      await scanner.start(
-        { facingMode: 'environment' }, // 使用后置摄像头
-        {
-          fps: 10, // 每秒扫描帧数
-          qrbox: { width: 250, height: 250 }, // 扫描框大小
-        },
-        onScanSuccess,
-        onScanError
-      );
-    } catch (err) {
-      console.error('启动扫描失败:', err);
-      const errorMsg = err instanceof Error ? err.message : '摄像头启动失败';
-      dispatch(setError(errorMsg));
-      dispatch(stopScanning());
-    }
-  };
-
-  const stopQRScanner = async () => {
-    if (scanner && scanner.isScanning) {
+    // 直接启动扫描
+    const startScan = async () => {
       try {
-        await scanner.stop();
-        dispatch(stopScanning());
+        await qr.start(
+          { facingMode: 'environment' },
+          { fps: 10, qrbox: 250 },
+          onScanSuccess,
+          onScanError
+        );
       } catch (err) {
-        console.error('停止扫描失败:', err);
+        console.error('启动扫描失败:', err);
+        const errorMsg = err instanceof Error ? err.message : '摄像头启动失败';
+        dispatch(setError(errorMsg));
       }
-    }
-  };
+    };
+
+    startScan();
+
+    // 清理函数:关闭时 stop + clear
+    return () => {
+      const cleanup = async () => {
+        try {
+          await qr.stop();
+          await qr.clear();
+        } catch (e) {
+          // 忽略清理错误
+        }
+        scannerRef.current = null;
+        setScannedText('');
+        dispatch(clearError());
+      };
+      cleanup();
+    };
+  }, [visible, qrReaderId]);
 
   const onScanSuccess = async (decodedText: string) => {
     console.log('[QRCodeScan] 扫描成功:', decodedText);
-    console.log('[QRCodeScan] 文本长度:', decodedText.length);
 
     // 停止扫描
-    await stopQRScanner();
+    if (scannerRef.current) {
+      await scannerRef.current.stop().catch(() => { });
+    }
 
-    // 显示扫描到的文本,让用户确认
-    console.log('[QRCodeScan] 设置 scannedText:', decodedText);
     setScannedText(decodedText);
   };
 
   const onScanError = (errorMessage: string) => {
-    // 扫描错误不需要显示,只在控制台记录
-    // console.log('[QRCodeScan] 扫描错误:', errorMessage);
+    // 扫描错误不显示,只记录
   };
 
   const handleConfirmScan = async () => {
     if (!scannedText) return;
 
-    // 处理数据
     dispatch(startProcessing());
 
     try {
-      // 1. 处理二维码数据
       const result = processQRCodeData(scannedText);
-
       if (!result.success) {
         dispatch(setError(result.error || '数据处理失败'));
         dispatch(stopProcessing());
         return;
       }
 
-      // 2. 转换为表单数据
       const formData = transformToFormData(result.data!);
-
-      // 3. 填充表单
       form.setFieldsValue(formData);
       dispatch(setFormData(formData));
 
-      // 4. 关闭模态框
       onCancel();
       dispatch(stopProcessing());
 
-      // 5. 如果包含体位信息,执行自动注册
       if (hasViews(result.data!)) {
         console.log('[QRCodeScan] 检测到体位信息,开始自动注册');
         const registerResult = await executeAutoRegister(result.data!, formData);
-
         if (!registerResult.success) {
           console.log('[QRCodeScan] 自动注册失败,用户可手动注册');
         }
@@ -185,45 +132,43 @@ const QRCodeScanModal: React.FC<QRCodeScanModalProps> = ({ visible, onCancel, fo
     }
   };
 
+  // 🔄 重新扫描:stop + clear + start(不重新 new)
   const handleRescan = async () => {
-    // 1. 清除扫描结果,显示扫描框
-    setScannedText('');
-    dispatch(clearError());
-
-    // 2. 停止当前扫描器
-    if (scanner && scanner.isScanning) {
-      await stopQRScanner();
-    }
+    if (!scannerRef.current) return;
 
-    // 3. 重新创建扫描器实例(关键修复)
     try {
-      const element = document.getElementById('qr-reader');
-      if (element) {
-        const newScanner = new Html5Qrcode('qr-reader');
-        setScanner(newScanner);
-        scannerRef.current = newScanner;
-
-        // 4. 启动新扫描器
-        await startQRScanner();
-      } else {
-        dispatch(setError('扫描器初始化失败,请重试'));
+      setScannedText('');
+      dispatch(clearError());
+
+      // stop + clear + start
+      if (scannerRef.current.isScanning) {
+        await scannerRef.current.stop();
+        await scannerRef.current.clear();
       }
+      // 2. 创建新实例
+      const qr = new Html5Qrcode(qrReaderId);
+      scannerRef.current = qr;
+      await scannerRef.current.start(
+        { facingMode: 'environment' },
+        { fps: 10, qrbox: 250 },
+        onScanSuccess,
+        onScanError
+      );
+
+      console.log('[QRCodeScan] 重新扫描启动成功');
     } catch (error) {
-      console.error('重新创建扫描器失败:', error);
-      dispatch(setError('重新启动扫描失败,请刷新页面重试'));
+      console.error('重新扫描失败:', error);
+      const errorMsg = error instanceof Error ? error.message : '重新启动扫描失败';
+      dispatch(setError(errorMsg));
     }
   };
 
-  const handleCancel = async () => {
-    await stopQRScanner();
-    setScannedText(''); // 清除扫描结果
+  const handleCancel = () => {
     onCancel();
   };
 
-  // 根据状态决定footer按钮
   const getFooterButtons = () => {
     if (scannedText) {
-      // 已扫描到内容,显示确认和重新扫描按钮
       return [
         <Button key="rescan" onClick={handleRescan}>
           重新扫描
@@ -233,7 +178,6 @@ const QRCodeScanModal: React.FC<QRCodeScanModalProps> = ({ visible, onCancel, fo
         </Button>,
       ];
     } else {
-      // 还在扫描中,只显示取消按钮
       return [
         <Button key="cancel" onClick={handleCancel}>
           取消
@@ -249,7 +193,6 @@ const QRCodeScanModal: React.FC<QRCodeScanModalProps> = ({ visible, onCancel, fo
       onCancel={handleCancel}
       footer={getFooterButtons()}
       width={600}
-      destroyOnClose
       maskClosable={false} // 防止误点击关闭
     >
       <div style={{ textAlign: 'center' }}>
@@ -272,27 +215,30 @@ const QRCodeScanModal: React.FC<QRCodeScanModalProps> = ({ visible, onCancel, fo
         )}
 
         {/* 扫描区域 */}
-        {!scannedText && (
-          <>
-            <div
-              id="qr-reader"
-              style={{
-                width: '100%',
-                maxWidth: 500,
-                margin: '0 auto',
-                border: isScanning ? '2px solid #1890ff' : '2px solid #d9d9d9',
-                borderRadius: 8,
-                padding: 8,
-              }}
-            />
-            <div style={{ marginTop: 16, color: '#666' }}>
-              <p>请将二维码对准扫描框</p>
-              <p style={{ fontSize: 12 }}>
-                提示:确保光线充足,二维码清晰可见
-              </p>
-            </div>
-          </>
-        )}
+
+        <>
+          <div
+            id={qrReaderId}
+            ref={readerRef}
+            style={{
+              width: '100%',
+              maxWidth: 500,
+              margin: '0 auto',
+              border: '2px solid #1890ff',
+              borderRadius: 8,
+              padding: 8,
+              minHeight: 300, // 给扫描区域固定高度
+              display: scannedText ? 'none' : 'block',
+            }}
+          />
+          <div style={{ marginTop: 16, color: '#666' }}>
+            <p>请将二维码对准扫描框</p>
+            <p style={{ fontSize: 12 }}>
+              提示:确保光线充足,二维码清晰可见
+            </p>
+          </div>
+        </>
+
 
         {/* 扫描结果显示 */}
         {scannedText && (
@@ -309,7 +255,6 @@ const QRCodeScanModal: React.FC<QRCodeScanModalProps> = ({ visible, onCancel, fo
               <Typography.Title level={5}>扫描结果:</Typography.Title>
               <div
                 style={{
-                  backgroundColor: '#f5f5f5',
                   padding: 12,
                   borderRadius: 4,
                   border: '1px solid #d9d9d9',