二维码扫码录入功能.md 33 KB

二维码扫码录入病人信息功能 - 完整实现文档

📋 功能概述

本功能允许用户通过扫描二维码快速录入病人信息。如果二维码中包含体位信息,系统将自动创建检查并进入检查页面。

核心功能

  • 📷 在注册页面打开摄像头扫描二维码
  • 📝 自动填充病人信息到注册表单
  • 🔄 如果携带体位信息,自动创建检查
  • ▶️ 自动进入检查页面

📊 需求详细分析

1.1 核心功能需求

1. 二维码扫描功能

  • 在注册页面新增扫码按钮
  • 点击后打开摄像头扫描界面
  • 支持扫描二维码获取患者信息

2. 自动填充表单

  • 解析二维码中的患者信息
  • 自动填充到注册表单的对应字段
  • 验证数据格式和完整性

3. 体位信息处理

  • 检测二维码中是否包含体位信息
  • 如果包含体位,自动选择对应的体位

4. 自动创建检查并进入

  • 如果携带体位信息,自动调用注册 API
  • 注册成功后自动切换到检查(exam)页面
  • 如果不携带体位,仅填充表单,等待用户手动选择体位和注册

1.2 二维码数据格式设计

interface QRCodeData {
  // 基础患者信息(必填)
  patient_id: string;          // 患者ID
  patient_name: string;        // 患者姓名
  patient_sex: string;         // 性别
  patient_age?: {              // 年龄(可选)
    number: number;
    unit: 'D' | 'M' | 'Y';
  };
  patient_dob?: string;        // 出生日期(可选)
  
  // 扩展信息(可选)
  accession_number?: string;   // 检查号
  patient_size?: string;       // 患者尺寸
  weight?: number;             // 体重
  thickness?: number;          // 厚度
  length?: number;             // 身高
  ref_physician?: string;      // 医师
  operator_id?: string;        // 操作员
  comment?: string;            // 备注
  
  // 宠物专用字段(可选)
  owner_name?: string;         // 宠物主人
  variety?: string;            // 品种
  chip_number?: string;        // 芯片号
  sex_neutered?: string;       // 绝育状态
  
  // 人医专用字段(可选)
  pregnancy_status?: string;   // 妊娠状态
  
  // 体位信息(可选,如果存在则自动进入检查)
  views?: Array<{
    view_id: string;           // 体位ID
    procedure_id: string;      // 协议ID
  }>;
}

🏗️ 架构设计

2.1 涉及的参与者

UI 组件层

  1. QRCodeScanButton(新建)

    • 位置:注册页面右下角按钮区域
    • 功能:触发二维码扫描
  2. QRCodeScanModal(新建)

    • 摄像头视频流显示
    • 扫描状态提示
    • 扫描结果预览
    • 取消/确认按钮
  3. BasicInfoForm(修改)

    • 现有注册表单组件
    • 需要支持外部数据填充
  4. RegisterPage(修改)

    • 添加扫码按钮
    • 集成扫码逻辑

业务逻辑层

  1. QRCodeScanner(新建)

    • 调用摄像头
    • 解析二维码
    • 返回解析结果
  2. QRCodeDataProcessor(新建)

    • 验证二维码数据格式
    • 转换数据到表单格式
    • 处理体位信息
  3. AutoRegisterHandler(新建)

    • 判断是否需要自动注册
    • 执行自动注册流程
    • 切换到检查页面

API 和服务层

  1. registerWork (已存在)

    • 注册患者和检查
    • 位置:src/API/patient/workActions.ts
  2. fetchViews (已存在)

    • 获取可用体位列表
    • 验证体位有效性

状态管理层

  1. formSlice (已存在)

    • 管理表单数据
    • 位置:src/states/patient/register/formSlice.ts
  2. viewSelectionSlice (需要确认)

    • 管理选中的体位
  3. qrCodeScanSlice(新建)

    • 管理扫码状态
    • 存储扫码结果
  4. BusinessFlowSlice (已存在)

    • 管理业务流程切换
    • 位置:src/states/BusinessFlowSlice.ts

工具库

  1. html5-qrcode
    • 第三方二维码扫描库
    • 需要通过 npm 安装

📝 TodoList - 文件清单

新建文件

  • src/components/QRCodeScanner/QRCodeScanButton.tsx - 扫码触发按钮
  • src/components/QRCodeScanner/QRCodeScanModal.tsx - 扫码模态框
  • src/components/QRCodeScanner/index.ts - 导出文件
  • src/domain/qrcode/qrCodeDataProcessor.ts - 二维码数据处理器
  • src/domain/qrcode/autoRegisterHandler.ts - 自动注册处理器
  • src/domain/qrcode/qrCodeValidator.ts - 二维码数据验证器
  • src/states/patient/register/qrCodeScanSlice.ts - 扫码状态管理
  • src/types/qrcode.ts - 二维码相关类型定义
  • src/hooks/useQRCodeScanner.ts - 二维码扫描 Hook

修改文件

  • src/pages/patient/register.tsx - 添加扫码按钮和逻辑
  • src/pages/patient/components/register.form.tsx - 支持外部数据填充
  • src/states/store.ts - 注册新的 qrCodeScanSlice
  • package.json - 添加二维码扫描库依赖

可能需要修改的文件

  • src/states/patient/register/viewSelectionSlice.ts - 添加批量选择体位的方法
  • src/validation/patient/registerSchema.ts - 可能需要调整验证规则

📊 交互流程 - 序列图

sequenceDiagram
    participant U as 用户
    participant B as 扫码按钮
    participant M as 扫码模态框
    participant S as QRCodeScanner
    participant P as QRCodeDataProcessor
    participant F as 注册表单
    participant A as AutoRegisterHandler
    participant API as registerWork API
    participant BF as BusinessFlow
    
    U->>B: 点击扫码按钮
    B->>M: 打开模态框
    M->>S: 启动摄像头
    S->>S: 扫描二维码
    S->>M: 返回二维码原始数据
    M->>P: 传递数据进行处理
    P->>P: 验证数据格式
    P->>P: 转换为表单格式
    
    alt 数据验证失败
        P->>M: 返回错误信息
        M->>U: 显示错误提示
    else 数据验证成功
        P->>F: 填充表单数据
        M->>U: 关闭模态框
        
        alt 包含体位信息
            P->>A: 触发自动注册
            A->>API: 调用注册接口
            API->>A: 返回注册结果
            
            alt 注册成功
                A->>BF: 切换到检查页面
                BF->>U: 显示检查页面
            else 注册失败
                A->>U: 显示错误提示
            end
        else 不包含体位信息
            F->>U: 显示填充后的表单
            U->>U: 手动选择体位和注册
        end
    end

🔄 数据流图

flowchart TD
    A[二维码] --> B[QRCodeScanner]
    B --> C[原始字符串数据]
    C --> D[JSON.parse]
    D --> E[QRCodeData 对象]
    E --> F[QRCodeDataProcessor]
    F --> G{验证数据}
    G -->|失败| H[错误提示]
    G -->|成功| I[转换数据格式]
    I --> J[FormData]
    J --> K[填充表单]
    K --> L{是否包含体位?}
    L -->|否| M[等待用户操作]
    L -->|是| N[AutoRegisterHandler]
    N --> O[构建 RegisterInfo]
    O --> P[调用 registerWork API]
    P --> Q{注册成功?}
    Q -->|是| R[切换到 exam 页面]
    Q -->|否| S[显示错误]

🗂️ 组件关系图

classDiagram
    class RegisterPage {
        +Form form
        +handleQRCodeScan()
        +handleRegister()
    }
    
    class QRCodeScanButton {
        +onClick()
    }
    
    class QRCodeScanModal {
        +visible: boolean
        +onScan(data)
        +onCancel()
    }
    
    class QRCodeScanner {
        +startScan()
        +stopScan()
        +onDetected(callback)
    }
    
    class QRCodeDataProcessor {
        +validate(data)
        +transform(data)
        +hasViews(data)
    }
    
    class AutoRegisterHandler {
        +execute(data)
        +selectViews(views)
        +callRegisterAPI()
        +navigateToExam()
    }
    
    class BasicInfoForm {
        +form: Form
        +setFieldsValue(data)
    }
    
    RegisterPage --> QRCodeScanButton
    RegisterPage --> QRCodeScanModal
    RegisterPage --> BasicInfoForm
    QRCodeScanModal --> QRCodeScanner
    QRCodeScanModal --> QRCodeDataProcessor
    QRCodeDataProcessor --> AutoRegisterHandler
    AutoRegisterHandler --> RegisterPage

🔧 数据结构详细定义

QRCodeData(二维码数据格式)

/**
 * 二维码数据格式
 * 用于定义二维码中包含的患者和检查信息
 */
interface QRCodeData {
  // 基础患者信息(必填)
  patient_id: string;          // 患者ID
  patient_name: string;        // 患者姓名
  patient_sex: string;         // 性别: 'M', 'F', 'O'
  
  // 年龄信息(可选,如果提供则会覆盖 patient_dob)
  patient_age?: {
    number: number;            // 数值
    unit: 'D' | 'M' | 'Y';    // 单位:天/月/年
  };
  
  // 出生日期(可选,如果提供 patient_age 则优先使用 age)
  patient_dob?: string;        // ISO 8601 格式
  
  // 扩展信息(可选)
  accession_number?: string;   // 检查号
  patient_size?: string;       // 患者尺寸: 'Small', 'Medium', 'Large'
  weight?: number;             // 体重(kg)
  thickness?: number;          // 厚度(cm)
  length?: number;             // 身高(cm)
  ref_physician?: string;      // 医师
  operator_id?: string;        // 操作员
  comment?: string;            // 备注
  
  // 宠物专用字段(可选)
  owner_name?: string;         // 宠物主人
  variety?: string;            // 品种
  chip_number?: string;        // 芯片号
  sex_neutered?: 'ALTERED' | 'UNALTERED';  // 绝育状态
  
  // 人医专用字段(可选)
  pregnancy_status?: 'NOT_PREGNANT' | 'POSSIBLY_PREGNANT' | 'DEFINITELY_PREGNANT' | 'UNKNOWN';
  
  // 体位信息(可选,如果存在则自动进入检查)
  views?: Array<{
    view_id: string;           // 体位ID
    procedure_id: string;      // 协议ID
  }>;
}

QRCodeScanState(扫码状态)

/**
 * 二维码扫描状态
 * 用于 Redux 状态管理
 */
interface QRCodeScanState {
  isScanning: boolean;              // 是否正在扫描
  isProcessing: boolean;            // 是否正在处理数据
  error: string | null;             // 错误信息
  lastScanData: QRCodeData | null;  // 最后扫描的数据
  autoRegisterPending: boolean;     // 是否等待自动注册
}

ValidationResult(验证结果)

/**
 * 数据验证结果
 */
interface ValidationResult {
  success: boolean;           // 验证是否成功
  errors: string[];          // 错误列表
  warnings: string[];        // 警告列表
}

🚀 执行流程

功能起点

用户操作:用户在注册页面点击"扫码录入"按钮

完整执行流程

flowchart TD
    Start([用户点击扫码按钮]) --> A[打开 QRCodeScanModal]
    A --> B{请求摄像头权限}
    B -->|拒绝| C[显示权限提示]
    B -->|允许| D[启动摄像头]
    D --> E[实时扫描二维码]
    E --> F{检测到二维码?}
    F -->|否| E
    F -->|是| G[解析二维码内容]
    G --> H{JSON 格式有效?}
    H -->|否| I[显示格式错误]
    I --> J{用户选择}
    J -->|重试| E
    J -->|取消| End1([结束])
    H -->|是| K[验证必填字段]
    K --> L{验证通过?}
    L -->|否| M[显示验证错误]
    M --> J
    L -->|是| N[转换数据格式]
    N --> O[填充表单]
    O --> P[关闭模态框]
    P --> Q{是否包含体位?}
    Q -->|否| R[等待用户手动操作]
    R --> End2([结束 - 表单已填充])
    Q -->|是| S[选择体位]
    S --> T[调用注册 API]
    T --> U{注册成功?}
    U -->|否| V[显示注册错误]
    V --> End3([结束 - 保持在注册页])
    U -->|是| W[切换到 exam 页面]
    W --> End4([结束 - 进入检查])

详细步骤说明

1. 打开扫码界面

  • 用户点击扫码按钮
  • 打开 QRCodeScanModal 模态框
  • 请求摄像头权限
  • 启动摄像头视频流

2. 扫描二维码

  • 实时检测二维码
  • 解析二维码内容(JSON 字符串)
  • 显示扫描成功提示

3. 验证和处理数据

  • 解析 JSON 数据为 QRCodeData 对象
  • 验证必填字段(patient_id, patient_name, patient_sex)
  • 验证数据格式(年龄、日期等)
  • 转换为表单数据格式

4. 填充表单

  • 调用 form.setFieldsValue() 填充表单
  • 更新 Redux formSlice 状态
  • 关闭扫码模态框

5. 判断是否自动注册

  • 检查 QRCodeData.views 字段
  • 如果存在体位信息,执行步骤 6
  • 如果不存在,等待用户手动操作

6. 自动注册流程(仅当包含体位时)

  • 验证体位 ID 的有效性
  • 更新 viewSelectionSlice(选中体位)
  • 构建 RegisterInfo 对象
  • 调用 registerWork API
  • 等待注册结果

7. 进入检查页面(注册成功后)

  • 保存注册结果到缓存
  • 调用 dispatch(setBusinessFlow('exam'))
  • 切换到检查页面

8. 错误处理

  • 摄像头权限拒绝:提示用户授权
  • 二维码格式错误:提示重新扫描
  • 数据验证失败:显示具体错误信息
  • 注册失败:显示错误并保持在注册页面

🧪 测试方案

准备工作

1. 生成测试二维码

使用以下 JSON 数据生成测试二维码(可以使用在线二维码生成器,如 qr-code-generator.com

示例 1:仅患者信息(不自动进入检查)

{
  "patient_id": "P001",
  "patient_name": "张三",
  "patient_sex": "M",
  "patient_age": {
    "number": 30,
    "unit": "Y"
  },
  "patient_size": "Medium",
  "weight": 70,
  "thickness": 25,
  "length": 175,
  "ref_physician": "李医生",
  "operator_id": "OP001",
  "comment": "常规检查"
}

示例 2:包含体位信息(自动进入检查)

{
  "patient_id": "P002",
  "patient_name": "李四",
  "patient_sex": "F",
  "patient_age": {
    "number": 25,
    "unit": "Y"
  },
  "patient_size": "Small",
  "weight": 55,
  "views": [
    {
      "view_id": "view_001",
      "procedure_id": "proc_001"
    }
  ]
}

示例 3:宠物患者信息

{
  "patient_id": "PET001",
  "patient_name": "旺财",
  "patient_sex": "M",
  "patient_age": {
    "number": 3,
    "unit": "Y"
  },
  "owner_name": "王主人",
  "variety": "金毛",
  "chip_number": "CHI123456",
  "sex_neutered": "UNALTERED",
  "weight": 30,
  "thickness": 20
}

示例 4:无效数据(测试错误处理)

{
  "patient_name": "赵六",
  "patient_sex": "M"
}

(缺少必填字段 patient_id)

测试场景

场景 1:基础扫码填充表单

测试目标:验证扫码功能能够正确填充表单

前置条件

  • 用户已登录系统
  • 在注册页面

操作步骤

  1. 点击"扫码录入"按钮
  2. 允许摄像头权限(如果首次使用)
  3. 将测试二维码(示例 1)对准摄像头
  4. 等待扫描成功

预期结果

  • ✅ 摄像头正常启动并显示视频流
  • ✅ 成功识别二维码
  • ✅ 表单自动填充以下字段:
    • 患者ID: P001
    • 患者姓名: 张三
    • 性别: 男
    • 年龄: 30岁
    • 体重: 70kg
    • 厚度: 25cm
    • 身高: 175cm
    • 医师: 李医生
    • 操作员: OP001
    • 备注: 常规检查
  • ✅ 扫码模态框自动关闭
  • ✅ 用户可以继续选择体位

实际结果(测试时填写)

测试状态:⬜ 通过 / ⬜ 失败


场景 2:扫码后自动进入检查

测试目标:验证包含体位信息的二维码能够自动创建检查并进入检查页面

前置条件

  • 用户已登录系统
  • 在注册页面
  • 系统中存在体位 view_001 和协议 proc_001

操作步骤

  1. 点击"扫码录入"按钮
  2. 扫描包含体位信息的二维码(示例 2)
  3. 观察系统行为

预期结果

  • ✅ 表单自动填充患者信息
  • ✅ 体位自动选中(view_001)
  • ✅ 自动调用注册接口
  • ✅ 显示注册成功提示
  • ✅ 自动跳转到检查(exam)页面
  • ✅ 在检查页面能看到对应的体位

实际结果(测试时填写)

测试状态:⬜ 通过 / ⬜ 失败


场景 3:错误处理 - 无效二维码格式

测试目标:验证系统能够正确处理非 JSON 格式的二维码

前置条件

  • 用户已登录系统
  • 在注册页面

操作步骤

  1. 点击"扫码录入"按钮
  2. 扫描一个纯文本二维码(如 "Hello World")

预期结果

  • ✅ 显示错误提示:"二维码格式错误,请扫描有效的患者信息二维码"
  • ✅ 不关闭扫码模态框
  • ✅ 允许用户重新扫描
  • ✅ 表单数据未被修改

实际结果(测试时填写)

测试状态:⬜ 通过 / ⬜ 失败


场景 4:错误处理 - 缺少必填字段

测试目标:验证系统能够检测并提示缺少必填字段

前置条件

  • 用户已登录系统
  • 在注册页面

操作步骤

  1. 点击"扫码录入"按钮
  2. 扫描缺少 patient_id 的二维码(示例 4)

预期结果

  • ✅ 显示错误提示:"缺少必填字段:患者ID"
  • ✅ 不填充表单
  • ✅ 不关闭扫码模态框
  • ✅ 允许用户重新扫描

实际结果(测试时填写)

测试状态:⬜ 通过 / ⬜ 失败


场景 5:摄像头权限处理

测试目标:验证系统能够正确处理摄像头权限问题

前置条件

  • 用户已登录系统
  • 在注册页面
  • 浏览器设置拒绝摄像头权限

操作步骤

  1. 在浏览器中拒绝摄像头权限
  2. 点击"扫码录入"按钮

预期结果

  • ✅ 显示权限提示:"需要摄像头权限才能使用扫码功能,请在浏览器设置中允许摄像头访问"
  • ✅ 提供浏览器权限设置指引
  • ✅ 允许用户关闭模态框

实际结果(测试时填写)

测试状态:⬜ 通过 / ⬜ 失败


场景 6:宠物患者信息扫描

测试目标:验证宠物患者信息能够正确填充

前置条件

  • 用户已登录系统(VETDROS 产品)
  • 在注册页面

操作步骤

  1. 点击"扫码录入"按钮
  2. 扫描包含宠物信息的二维码(示例 3)

预期结果

  • ✅ 正确填充宠物专用字段:
    • 宠物主人: 王主人
    • 品种: 金毛
    • 芯片号: CHI123456
    • 绝育状态: 未绝育
  • ✅ 表单验证通过

实际结果(测试时填写)

测试状态:⬜ 通过 / ⬜ 失败


场景 7:年龄和出生日期联动

测试目标:验证扫码填充的年龄能够正确联动出生日期

前置条件

  • 用户已登录系统
  • 在注册页面

操作步骤

  1. 点击"扫码录入"按钮
  2. 扫描包含年龄信息的二维码
  3. 观察出生日期字段

预期结果

  • ✅ 年龄字段正确填充
  • ✅ 出生日期字段根据年龄自动计算并填充
  • ✅ 年龄和出生日期保持联动关系

实际结果(测试时填写)

测试状态:⬜ 通过 / ⬜ 失败


场景 8:取消扫码操作

测试目标:验证用户可以随时取消扫码操作

前置条件

  • 用户已登录系统
  • 在注册页面

操作步骤

  1. 点击"扫码录入"按钮
  2. 摄像头启动后,点击"取消"按钮

预期结果

  • ✅ 摄像头停止工作
  • ✅ 模态框关闭
  • ✅ 表单数据未被修改
  • ✅ 没有错误提示

实际结果(测试时填写)

测试状态:⬜ 通过 / ⬜ 失败


场景 9:注册失败后的处理

测试目标:验证自动注册失败后的错误处理

前置条件

  • 用户已登录系统
  • 在注册页面
  • 后端 API 模拟注册失败

操作步骤

  1. 点击"扫码录入"按钮
  2. 扫描包含体位的二维码
  3. 观察注册失败后的行为

预期结果

  • ✅ 显示注册失败错误提示
  • ✅ 保持在注册页面
  • ✅ 表单数据保留
  • ✅ 体位选择保留
  • ✅ 用户可以修改后重新注册

实际结果(测试时填写)

测试状态:⬜ 通过 / ⬜ 失败


回归测试

回归测试 1:正常注册流程

测试目标:确保手动填写表单注册功能不受影响

操作步骤

  1. 手动填写表单所有字段
  2. 选择体位
  3. 点击注册按钮

预期结果

  • ✅ 注册流程正常
  • ✅ 数据正确提交
  • ✅ 表单清空正常

测试状态:⬜ 通过 / ⬜ 失败


回归测试 2:ReRegister 功能

测试目标:确保从历史记录预填充功能不受影响

操作步骤

  1. 从历史列表双击一条记录
  2. 观察表单填充

预期结果

  • ✅ 表单正确填充历史数据
  • ✅ 功能正常工作

测试状态:⬜ 通过 / ⬜ 失败


回归测试 3:急诊流程

测试目标:确保急诊快速注册功能不受影响

操作步骤

  1. 触发急诊流程
  2. 观察自动注册和跳转

预期结果

  • ✅ 急诊流程正常
  • ✅ 自动进入检查页面

测试状态:⬜ 通过 / ⬜ 失败


性能测试

性能测试 1:扫码响应时间

测试目标:验证扫码识别的响应速度

测试方法

  • 记录从二维码出现到识别成功的时间
  • 测试 10 次取平均值

预期结果

  • ⏱️ 平均识别时间 < 2 秒
  • ⏱️ 最长识别时间 < 5 秒

实际结果(测试时填写)


性能测试 2:表单填充性能

测试目标:验证表单填充的流畅度

测试方法

  • 测试填充包含所有字段的二维码数据
  • 观察页面响应

预期结果

  • ⚡ 填充操作无明显延迟
  • ⚡ UI 无卡顿

实际结果(测试时填写)


兼容性测试

浏览器 版本 摄像头调用 二维码识别 表单填充 测试状态
Chrome 最新 ⬜ 通过 / ⬜ 失败
Edge 最新 ⬜ 通过 / ⬜ 失败
Firefox 最新 ⬜ 通过 / ⬜ 失败
Safari 最新 ⬜ 通过 / ⬜ 失败

🐛 潜在问题分析

1. 摄像头权限问题

问题描述

  • 用户可能拒绝摄像头权限
  • 部分浏览器的权限提示不明确
  • HTTPS 协议要求

风险等级:🔴 高

影响范围:扫码功能完全不可用

解决方案

  1. 提供清晰的权限请求说明
  2. 捕获权限拒绝异常并给出友好提示
  3. 提供浏览器设置指引
  4. 确保应用运行在 HTTPS 环境(开发环境可用 localhost)

代码示例

try {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  // 启动扫描
} catch (error) {
  if (error.name === 'NotAllowedError') {
    message.error('需要摄像头权限才能使用扫码功能,请允许访问摄像头');
  } else if (error.name === 'NotFoundError') {
    message.error('未检测到摄像头设备');
  } else {
    message.error('摄像头启动失败:' + error.message);
  }
}

2. 二维码识别准确性

问题描述

  • 光线条件影响识别率
  • 二维码模糊或损坏
  • 识别速度慢

风险等级:🟡 中

影响范围:用户体验

解决方案

  1. 使用成熟的二维码识别库(html5-qrcode)
  2. 提供识别状态反馈
  3. 允许用户调整摄像头焦距
  4. 提供重新扫描选项

优化建议

// 配置二维码扫描器以提高识别率
const config = {
  fps: 10,  // 每秒扫描帧数
  qrbox: { width: 250, height: 250 },  // 扫描框大小
  aspectRatio: 1.0,
};

3. 数据格式不一致

问题描述

  • 二维码中的数据格式与系统不匹配
  • 字段名称不一致
  • 数据类型错误

风险等级:🟡 中

影响范围:数据填充失败或错误

解决方案

  1. 严格的数据验证
  2. 字段映射和转换
  3. 提供详细的错误提示
  4. 支持多种日期格式

验证示例

const validateQRCodeData = (data: any): ValidationResult => {
  const errors: string[] = [];
  
  // 必填字段检查
  if (!data.patient_id) errors.push('缺少患者ID');
  if (!data.patient_name) errors.push('缺少患者姓名');
  if (!data.patient_sex) errors.push('缺少性别信息');
  
  // 格式检查
  if (data.patient_sex && !['M', 'F', 'O'].includes(data.patient_sex)) {
    errors.push('性别格式错误');
  }
  
  return {
    success: errors.length === 0,
    errors,
    warnings: []
  };
};

4. 体位ID无效

问题描述

  • 二维码中的体位ID在系统中不存在
  • 体位已被禁用
  • 体位不适用于当前患者类型

风险等级:🟡 中

影响范围:自动注册失败

解决方案

  1. 在自动注册前验证体位ID有效性
  2. 调用 fetchViews API 检查体位是否存在
  3. 如果验证失败,仅填充表单,不执行自动注册
  4. 给出明确的错误提示

验证流程

const validateViews = async (views: Array<{view_id: string, procedure_id: string}>): Promise<boolean> => {
  try {
    for (const view of views) {
      const viewDetail = await fetchViewDetail(view.view_id);
      if (!viewDetail || !viewDetail.is_enabled) {
        message.warning(`体位 ${view.view_id} 不可用,已跳过自动注册`);
        return false;
      }
    }
    return true;
  } catch (error) {
    message.error('验证体位信息失败');
    return false;
  }
};

5. 网络请求失败

问题描述

  • 自动注册时网络中断
  • API 响应超时
  • 服务器错误

风险等级:🔴 高

影响范围:自动注册流程中断

解决方案

  1. 完善的错误处理机制
  2. 请求超时控制
  3. 失败后保持表单数据,允许用户重试
  4. 提供清晰的错误信息

错误处理示例

const handleAutoRegister = async (data: QRCodeData) => {
  try {
    dispatch(setQRCodeProcessing(true));
    const result = await registerWork(registerInfo);
    
    if (result.code === '0x000000') {
      message.success('注册成功,正在进入检查页面');
      dispatch(setBusinessFlow('exam'));
    } else {
      message.error(`注册失败:${result.description}`);
    }
  } catch (error) {
    console.error('自动注册失败:', error);
    message.error('网络错误,注册失败,请检查网络连接后重试');
  } finally {
    dispatch(setQRCodeProcessing(false));
  }
};

6. 并发问题

问题描述

  • 用户在扫码过程中进行其他操作
  • 多次快速扫码
  • 扫码和手动填写冲突

风险等级:🟢 低

影响范围:数据覆盖或状态混乱

解决方案

  1. 扫码过程中禁用表单编辑
  2. 处理数据时显示 loading 状态
  3. 使用 debounce 防止重复扫描
  4. 在填充前确认用户意图

防抖示例

const debouncedScan = debounce((decodedText: string) => {
  handleQRCodeScanned(decodedText);
}, 500);

7. 浏览器兼容性

问题描述

  • 部分浏览器不支持 getUserMedia API
  • 旧版本浏览器兼容性
  • 移动端浏览器差异

风险等级:🟡 中

影响范围:部分用户无法使用功能

解决方案

  1. 功能检测,不支持时隐藏扫码按钮
  2. 提供降级方案(手动输入)
  3. 明确支持的浏览器版本
  4. 在文档中说明系统要求

功能检测

const isCameraSupported = (): boolean => {
  return !!(
    navigator.mediaDevices &&
    navigator.mediaDevices.getUserMedia
  );
};

// 在组件中使用
{isCameraSupported() && <QRCodeScanButton />}

8. 性能问题

问题描述

  • 视频流占用内存
  • 持续扫描消耗 CPU
  • 模态框未正确释放资源

风险等级:🟢 低

影响范围:页面性能下降

解决方案

  1. 关闭模态框时停止摄像头
  2. 限制扫描帧率
  3. 及时清理资源
  4. 使用 useEffect cleanup 函数

资源清理

useEffect(() => {
  return () => {
    // 组件卸载时停止摄像头
    if (scannerRef.current) {
      scannerRef.current.stop();
    }
    // 释放媒体流
    if (streamRef.current) {
      streamRef.current.getTracks().forEach(track => track.stop());
    }
  };
}, []);

9. 安全性问题

问题描述

  • 恶意二维码注入
  • XSS 攻击风险
  • 敏感信息泄露

风险等级:🔴 高

影响范围:系统安全

解决方案

  1. 严格的数据验证和消毒
  2. 限制允许的字段
  3. 转义特殊字符
  4. 不在二维码中包含敏感信息
  5. 后端再次验证数据

数据消毒

const sanitizeData = (data: any): QRCodeData => {
  // 只保留允许的字段
  const allowedFields = [
    'patient_id', 'patient_name', 'patient_sex', 
    // ... 其他允许的字段
  ];
  
  const sanitized: any = {};
  allowedFields.forEach(field => {
    if (data[field] !== undefined) {
      // 转义特殊字符
      sanitized[field] = typeof data[field] === 'string' 
        ? escapeHtml(data[field]) 
        : data[field];
    }
  });
  
  return sanitized as QRCodeData;
};

10. 用户体验问题

问题描述

  • 扫码界面不够直观
  • 缺少扫描指引
  • 错误提示不清晰
  • 无法预览扫描结果

风险等级:🟡 中

影响范围:用户满意度

解决方案

  1. 提供清晰的扫描指引动画
  2. 实时显示扫描框
  3. 成功/失败有明确的视觉反馈
  4. 允许用户预览和确认数据
  5. 提供"取消"选项

UX 优化建议

  • 添加扫描框高亮动画
  • 成功时播放提示音(可选)
  • 失败时震动反馈(移动端)
  • 显示"将二维码对准扫描框"提示
  • 提供手电筒开关(移动端)

📚 技术选型

二维码扫描库对比

库名称 Stars 优点 缺点 推荐指数
html5-qrcode 4.5k+ 简单易用、文档完善、持续维护 相对较重 ⭐⭐⭐⭐⭐
react-qr-scanner 1k+ React 友好 文档较少、维护不活跃 ⭐⭐⭐
jsQR 3k+ 轻量级、纯 JS 需要自己处理视频流 ⭐⭐⭐⭐
qrcode-decoder 500+ 轻量 功能有限 ⭐⭐⭐

最终选择html5-qrcode

理由

  1. ✅ 活跃维护,文档完善
  2. ✅ 支持多种扫描模式(摄像头、文件上传)
  3. ✅ 良好的浏览器兼容性
  4. ✅ TypeScript 支持
  5. ✅ 易于集成到 React 项目

🔐 安全考虑

1. 数据验证

服务端验证

  • 即使前端已验证,后端必须再次验证所有数据
  • 检查数据类型、长度、格式
  • 防止 SQL 注入、XSS 攻击

2. 权限控制

摄像头访问

  • 仅在需要时请求权限
  • 用户可以随时撤销权限
  • 不存储摄像头数据

3. 数据隐私

敏感信息

  • 不在二维码中包含密码、身份证号等敏感信息
  • 仅包含注册必需的基本信息
  • 遵守数据保护法规(GDPR、个人信息保护法)

4. 传输安全

HTTPS

  • 强制使用 HTTPS 协议
  • 防止中间人攻击
  • 保护数据传输安全

🚀 未来优化方向

1. 功能增强

  • 支持批量扫码(一次扫描多个患者)
  • 支持扫描条形码
  • 支持从图片文件识别二维码
  • 支持扫码历史记录
  • 支持自定义二维码格式

2. 用户体验优化

  • 添加扫描成功动画
  • 提供语音提示
  • 支持暗光环境扫描
  • 移动端优化(手电筒、自动对焦)
  • 提供扫码教程视频

3. 性能优化

  • 懒加载二维码扫描库
  • 优化扫描算法
  • 减少内存占用
  • 支持 WebAssembly 加速

4. 集成增强

  • 与 RIS 系统集成
  • 支持 HL7 FHIR 标准
  • 与电子病历系统对接
  • 支持多种二维码标准

📖 参考文档

内部文档

外部资源

技术标准


📝 变更日志

版本 日期 作者 变更内容
1.0.0 2025-12-19 AI Assistant 初始版本,完成功能设计和文档编写

✅ 总结

本文档详细设计了二维码扫码录入病人信息的完整功能,包括:

  1. 需求分析:明确了功能需求和二维码数据格式
  2. 架构设计:设计了组件结构和数据流
  3. 实现计划:列出了详细的文件清单和开发任务
  4. 测试方案:提供了全面的测试场景和步骤
  5. 风险分析:识别了潜在问题并提供了解决方案
  6. 安全考虑:强调了数据安全和隐私保护

该功能将极大提升患者注册的效率,特别是在批量患者登记和急诊场景下。通过二维码扫描,可以减少手动输入错误,加快工作流程,提高用户满意度。


📌 下一步行动

  1. 安装 html5-qrcode 依赖
  2. 创建相关组件和工具函数
  3. 实现核心功能
  4. 进行单元测试和集成测试
  5. 用户验收测试