Forráskód Böngészése

feat (1.67.0 -> 1.68.0): 实现扫码枪自动填充患者注册表单功能

- 在 qrCodeDataProcessor.ts 中优化二维码文本解析逻辑,支持优先按制表符分割,提升解析准确性
- 在 register.form.tsx 中新增扫码枪监听功能,支持自动检测扫码输入并填充表单数据
- 添加隐藏输入框用于捕获扫码枪输入,集成 processQRCodeData 和 transformToFormData 处理流程
- 实现扫码枪数据自动同步到 Redux store 并显示成功提示,提升用户操作效率

改动文件:
- src/domain/qrcode/qrCodeDataProcessor.ts
- src/pages/patient/components/register.form.tsx
dengdx 2 napja
szülő
commit
fa44f335c3

+ 13 - 0
CHANGELOG.md

@@ -2,6 +2,19 @@
 
 本项目的所有重要变更都将记录在此文件中.
 
+## [1.68.0] - 2026-01-16 18:43
+
+feat (1.67.0 -> 1.68.0): 实现扫码枪自动填充患者注册表单功能
+
+- 在 qrCodeDataProcessor.ts 中优化二维码文本解析逻辑,支持优先按制表符分割,提升解析准确性
+- 在 register.form.tsx 中新增扫码枪监听功能,支持自动检测扫码输入并填充表单数据
+- 添加隐藏输入框用于捕获扫码枪输入,集成 processQRCodeData 和 transformToFormData 处理流程
+- 实现扫码枪数据自动同步到 Redux store 并显示成功提示,提升用户操作效率
+
+改动文件:
+- src/domain/qrcode/qrCodeDataProcessor.ts
+- src/pages/patient/components/register.form.tsx
+
 ## [1.67.0] - 2026-01-16 16:36
 
 feat (1.66.4 -> 1.67.0): 添加系统模式选择功能

+ 1 - 1
package.json

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

+ 13 - 6
src/domain/qrcode/qrCodeDataProcessor.ts

@@ -176,13 +176,20 @@ const parseFormat_NameIdAgeGender = (parts: string[]) => {
  * @returns 解析后的数据对象
  */
 const parseTabSeparatedText = (text: string) => {
-  // 清理多余的空格和制表符,统一转换为单个空格
-  const cleaned = text.replace(/[\t\s]+/g, ' ').trim();
-  const parts = cleaned.split(/\s+/);
+  console.log('[QRCodeParse] 解析文本:', JSON.stringify(text));
 
-  console.log('[QRCodeParse] 解析文本:', text);
-  console.log('[QRCodeParse] 清理后:', cleaned);
-  console.log('[QRCodeParse] 分割结果:', parts);
+  let parts: string[];
+
+  // 优先尝试按制表符分割(保持Tab分隔符的原始格式)
+  if (text.includes('\t')) {
+    parts = text.split('\t').map(p => p.trim()).filter(p => p);
+    console.log('[QRCodeParse] 按Tab分割结果:', parts);
+  } else {
+    // 如果没有Tab,则按空格分割(向后兼容)
+    const cleaned = text.replace(/[\s]+/g, ' ').trim();
+    parts = cleaned.split(/\s+/);
+    console.log('[QRCodeParse] 按空格分割结果:', parts);
+  }
 
   // 定义解析器列表,按优先级顺序尝试
   const parsers = [

+ 137 - 0
src/pages/patient/components/register.form.tsx

@@ -7,6 +7,7 @@ import {
   Select,
   Radio,
   FormInstance,
+  message,
 } from 'antd';
 import { useIntl, FormattedMessage } from 'react-intl';
 import { registerFormFields } from '@/validation/patient/registerSchema';
@@ -20,6 +21,7 @@ import {
   pregnancyStatusOptions,
 } from '@/domain/patient/pregnancyStatus';
 import { getGenderOptions } from '@/domain/patient/genderOptions';
+import { processQRCodeData, transformToFormData } from '@/domain/qrcode/qrCodeDataProcessor';
 interface BasicInfoFormProps {
   style?: React.CSSProperties;
   form?: FormInstance;
@@ -46,6 +48,12 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({
     useState(false);
   const initializedRef = useRef(false);
 
+  // 扫码枪扫码相关状态
+  const scanInputRef = useRef<HTMLInputElement>(null);
+  const [isComposing, setIsComposing] = useState(false);
+  const bufferRef = useRef('');
+  const timerRef = useRef<number | null>(null);
+
   // 同步初始表单值到 Redux store,并生成登记号
   useEffect(() => {
     if (!initializedRef.current) {
@@ -147,6 +155,116 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({
       console.log('出生日期变化,更新年龄:', newAge);
     }
   }, [patient_dob, form]);
+
+  // 处理扫码枪扫码后的数据:解析文本并自动填充到注册表单
+  const processScannedData = React.useCallback(async (scannedText: string) => {
+    try {
+      // 添加调试日志,输出实际接收到的扫码文本
+      console.log('[扫码枪调试] 得到的扫码数据是:', JSON.stringify(scannedText));
+      console.log('[扫码枪调试] 扫码数据长度:', scannedText.length);
+
+      // 使用二维码数据处理器解析扫码文本(复用现有逻辑)
+      const result = processQRCodeData(scannedText);
+      if (!result.success) {
+        message.error(result.error || '扫码枪数据处理失败');
+        return;
+      }
+
+      // 将解析后的数据转换为表单格式
+      const formData = transformToFormData(result.data!);
+
+      // 自动填充表单
+      form?.setFieldsValue(formData);
+
+      // 同步更新 Redux store
+      dispatch(setFormData(formData));
+
+      // 显示成功提示
+      message.success('扫码枪数据已自动填充到表单');
+    } catch (error) {
+      console.error('扫码枪数据处理失败:', error);
+      message.error('扫码枪数据处理失败');
+    }
+  }, [form, dispatch]);
+
+  // 扫码枪扫码监听器:页面级 keydown 监听,自动切换焦点到隐藏input
+  useEffect(() => {
+    const handleKeyDown = (e: KeyboardEvent) => {
+      const target = e.target as HTMLElement;
+
+      const isEditable =
+        target &&
+        (target.tagName === 'INPUT' ||
+          target.tagName === 'TEXTAREA' ||
+          target.isContentEditable);
+
+      // 如果当前没有可编辑元素获得焦点,认为是扫码枪输入
+      if (!isEditable) {
+        scanInputRef.current?.focus();
+      }
+
+      // 扫码枪通常以 Enter 结束
+      if (e.key === 'Enter') {
+        if (bufferRef.current) {
+          processScannedData(bufferRef.current);
+          bufferRef.current = '';
+          if (scanInputRef.current) {
+            scanInputRef.current.value = '';
+          }
+        }
+        // 阻止Enter键的默认行为(如表单提交)
+        e.preventDefault();
+        e.stopPropagation();
+      } else if (e.key === 'Tab') {
+        // 完全控制Tab键行为,防止焦点切换
+        e.preventDefault();
+        e.stopPropagation();
+
+        // 手动将Tab字符插入到隐藏input中
+        const input = scanInputRef.current;
+        if (input) {
+          const start = input.selectionStart || 0;
+          const end = input.selectionEnd || 0;
+          const value = input.value;
+
+          // 在光标位置插入Tab字符
+          input.value = value.substring(0, start) + '\t' + value.substring(end);
+
+          // 更新光标位置
+          input.selectionStart = input.selectionEnd = start + 1;
+
+          // 手动触发input事件,确保React能检测到变化
+          input.dispatchEvent(new Event('input', { bubbles: true }));
+        }
+      }
+      // 其他字符不阻止,让它们自然输入到隐藏input中
+      // input会自动过滤掉无效字符,只保留有效的文本
+    };
+
+    window.addEventListener('keydown', handleKeyDown);
+    return () => window.removeEventListener('keydown', handleKeyDown);
+  }, [processScannedData]);
+
+  /** input 事件:真正可靠地拿中文 */
+  const handleScanInput = (e: React.ChangeEvent<HTMLInputElement>) => {
+    if (isComposing) return;
+
+    bufferRef.current = e.target.value;
+
+    // 防止没有 Enter 的扫码枪(用时间判断)
+    if (timerRef.current) {
+      clearTimeout(timerRef.current);
+    }
+
+    timerRef.current = window.setTimeout(() => {
+      if (bufferRef.current) {
+        processScannedData(bufferRef.current);
+        bufferRef.current = '';
+        e.target.value = '';
+      }
+    }, 300); // 300ms 内无新输入,认为扫码结束
+  };
+
   return (
     <Form
       form={form}
@@ -611,6 +729,25 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({
           })}
         />
       </Form.Item>
+
+      {/* 扫码枪兜底 input(隐藏) */}
+      <input
+        ref={scanInputRef}
+        type="text"
+        style={{
+          position: 'fixed',
+          opacity: 0,
+          pointerEvents: 'none',
+          left: -9999,
+        }}
+        onCompositionStart={() => setIsComposing(true)}
+        onCompositionEnd={(e) => {
+          setIsComposing(false);
+          bufferRef.current = e.currentTarget.value;
+        }}
+        onInput={handleScanInput}
+        autoComplete="off"
+      />
     </Form>
   );
 };