检查流程图像状态控制功能.md 30 KB

检查流程中的图像状态控制功能实现文档

📋 功能概述

在检查流程(exam 模块)中实现"恢复/拒绝/保存参数"功能,允许用户对已曝光的图像进行判断操作,对未曝光的体位进行参数保存。

实现日期: 2025-01-14
版本: v1.0.0

🎯 需求分析

功能需求

  1. 拒绝按钮

    • 将已曝光且状态为接受的图像标记为拒绝状态
    • 仅在选中体位已曝光且 judged_status 为 'Accept' 或 '' 时显示
  2. 恢复按钮

    • 将已拒绝的图像恢复为接受状态
    • 仅在选中体位已曝光且 judged_status 为 'Reject' 时显示
    • 与拒绝按钮占据同一位置(互斥显示)
  3. 保存参数按钮

    • 保存当前 APR 参数到未曝光的体位
    • 仅在选中体位未曝光时显示
    • 注意: 当前 API 暂未提供,使用 TODO 标记
  4. 删除按钮可见性调整

    • 当选中体位已曝光时,隐藏"删除选择的体位"按钮
    • 防止误删已曝光的图像

显示逻辑表

体位状态 expose_status judged_status 拒绝按钮 恢复按钮 保存参数 删除按钮
未曝光 Unexposed - 隐藏 隐藏 显示 显示
已曝光-接受 Exposed Accept 显示 隐藏 隐藏 隐藏
已曝光-拒绝 Exposed Reject 隐藏 显示 隐藏 隐藏
已曝光-未判断 Exposed '' 显示 隐藏 隐藏 隐藏

🏗️ 架构设计

参与者列表

1. UI 层

  • ContentAreaLarge.tsx - 主容器组件,包含所有控制按钮

    • 添加处理函数:handleReject(), handleRestore(), handleSaveParams()
    • 计算按钮可见性逻辑
    • 管理按钮的显示/隐藏
  • BodyPositionList.tsx - 体位列表组件

    • 提供选中体位信息
    • 显示体位的曝光和判断状态徽章

2. 状态管理层

  • bodyPositionListSlice.ts - 体位列表状态管理
    • selectedBodyPosition - 当前选中的体位
    • judgeImageThunk - 判断图像的异步 Thunk
    • loading - 加载状态

关键 Reducer:

  • judgeImageThunk.fulfilled - 更新 judged_status
  • judgeImageThunk.rejected - 处理错误

  • aprSlice.ts - APR 参数状态管理

    • aprConfig - 当前 APR 参数配置(用于保存参数功能)

3. 业务逻辑层

  • paraSettingCoordinator.ts - APR 参数协调器
    • 协调设备参数的增减操作
    • (保存参数功能待实现时会用到)

4. API 层

  • judgeImage.ts - 图像判断 API

    • judgeImage(instanceUid, accept) - 接受/拒绝图像
    • 端点: POST /auth/task/inspection/judge
    • 请求参数:

      {
      instance_uid: string;  // SOP 实例 UID
      accept: boolean;       // true: 接受, false: 拒绝
      }
      

      5. 数据模型

      • ExtendedBodyPosition - 扩展体位信息 typescript interface ExtendedBodyPosition { sop_instance_uid: string; dview: { expose_status: 'Exposed' | 'Unexposed'; judged_status: 'Accept' | 'Reject' | ''; // ... 其他字段 }; // ... 其他字段 }

组件交互序列图

sequenceDiagram
    participant User as 👤 用户
    participant UI as ContentAreaLarge
    participant Redux as Redux Store
    participant API as judgeImage API
    
    Note over User,API: 场景1: 拒绝图像
    User->>UI: 点击拒绝按钮
    UI->>UI: 检查 selectedBodyPosition 是否存在
    UI->>Redux: dispatch(judgeImageThunk({sopInstanceUid, accept: false}))
    Redux->>Redux: judgeImageThunk.pending
    Redux->>UI: loading = true
    UI->>User: 按钮显示 loading 状态
    Redux->>API: POST /auth/task/inspection/judge
    API-->>Redux: 返回成功 {code: '0x000000'}
    Redux->>Redux: judgeImageThunk.fulfilled
    Redux->>Redux: 更新 bodyPositions[i].dview.judged_status = 'Reject'
    Redux->>Redux: 更新 selectedBodyPosition.dview.judged_status = 'Reject'
    Redux-->>UI: 状态更新, loading = false
    UI->>UI: 重新计算按钮可见性
    UI-->>User: 显示恢复按钮(隐藏拒绝按钮)
    UI-->>User: 显示成功消息
    
    Note over User,API: 场景2: 恢复图像
    User->>UI: 点击恢复按钮
    UI->>Redux: dispatch(judgeImageThunk({sopInstanceUid, accept: true}))
    Redux->>API: POST /auth/task/inspection/judge
    API-->>Redux: 返回成功
    Redux->>Redux: 更新 judged_status = 'Accept'
    Redux-->>UI: 状态更新
    UI-->>User: 显示拒绝按钮(隐藏恢复按钮)
    UI-->>User: 显示成功消息
    
    Note over User,API: 场景3: 保存参数(待实现)
    User->>UI: 点击保存参数按钮
    UI->>UI: 读取当前 APR 参数
    UI->>UI: TODO: 调用保存参数 API
    UI-->>User: 显示"保存参数功能开发中"

数据流图

flowchart TD
    Start[用户操作] --> Check{检查操作类型}
    
    Check -->|拒绝| Reject[handleReject]
    Check -->|恢复| Restore[handleRestore]
    Check -->|保存参数| Save[handleSaveParams]
    
    Reject --> ValidateReject{检查体位存在?}
    ValidateReject -->|否| WarnReject[显示警告: 请先选择一个体位]
    ValidateReject -->|是| DispatchReject[dispatch judgeImageThunk accept=false]
    
    Restore --> ValidateRestore{检查体位存在?}
    ValidateRestore -->|否| WarnRestore[显示警告: 请先选择一个体位]
    ValidateRestore -->|是| DispatchRestore[dispatch judgeImageThunk accept=true]
    
    DispatchReject --> APICall[调用 judgeImage API]
    DispatchRestore --> APICall
    
    APICall --> APIResult{API 返回}
    APIResult -->|成功| UpdateState[更新 Redux 状态]
    APIResult -->|失败| ShowError[显示错误消息]
    
    UpdateState --> UpdatePositions[更新 bodyPositions 中的 judged_status]
    UpdatePositions --> UpdateSelected[更新 selectedBodyPosition.judged_status]
    UpdateSelected --> Rerender[触发 UI 重新渲染]
    
    Rerender --> CalcVisibility[重新计算按钮可见性]
    CalcVisibility --> UpdateButtons[更新按钮显示/隐藏]
    UpdateButtons --> ShowSuccess[显示成功消息]
    
    Save --> ValidateSave{检查体位存在?}
    ValidateSave -->|否| WarnSave[显示警告: 请先选择一个体位]
    ValidateSave -->|是| TODOSave[TODO: 调用保存参数 API]
    TODOSave --> InfoSave[显示: 保存参数功能开发中]
    
    WarnReject --> End[结束]
    WarnRestore --> End
    WarnSave --> End
    ShowError --> End
    ShowSuccess --> End
    InfoSave --> End

按钮可见性计算流程

flowchart TD
    Start[获取 selectedBodyPosition] --> CheckNull{体位是否存在?}
    
    CheckNull -->|否| HideAll[隐藏所有按钮]
    CheckNull -->|是| GetStatus[获取 expose_status 和 judged_status]
    
    GetStatus --> CheckExpose{expose_status?}
    
    CheckExpose -->|Unexposed| ShowUnexposed[显示: 保存参数, 删除]
    ShowUnexposed --> HideUnexposed[隐藏: 拒绝, 恢复]
    
    CheckExpose -->|Exposed| CheckJudged{judged_status?}
    
    CheckJudged -->|'Reject'| ShowRestore[显示: 恢复]
    ShowRestore --> HideRestore[隐藏: 拒绝, 保存参数, 删除]
    
    CheckJudged -->|'Accept' 或 ''| ShowReject[显示: 拒绝]
    ShowReject --> HideReject[隐藏: 恢复, 保存参数, 删除]
    
    HideAll --> End[结束]
    HideUnexposed --> End
    HideRestore --> End
    HideReject --> End

📊 数据结构

ExtendedBodyPosition 接口

interface ExtendedBodyPosition {
  // 基本信息
  patient_name: string;
  patient_id: string;
  registration_number: string;
  study_description: string;
  
  // 体位信息
  view_name: string;
  view_description: string;
  view_icon_name: string;
  body_position_image: string;
  
  // 唯一标识符
  sop_instance_uid: string;
  series_instance_uid?: string;
  study_instance_uid?: string;
  study_id?: string;
  
  // 核心状态对象
  dview: {
    expose_status: 'Exposed' | 'Unexposed';  // 曝光状态
    judged_status: 'Accept' | 'Reject' | ''; // 判断状态
    PrimarySopUID: string;
    thumbnail_file: string;
    image_file: string;
    // ... 其他字段
  };
  
  // 其他配置
  collimator_length: number | string;
  collimator_width: number | string;
  sid: string;
  work: Work;
}

按钮可见性状态

// 在 ContentAreaLarge 组件中计算
const exposeStatus = selectedBodyPosition?.dview.expose_status;
const judgedStatus = selectedBodyPosition?.dview.judged_status || '';

const isExposed = exposeStatus === 'Exposed';
const isUnexposed = exposeStatus === 'Unexposed';

// 按钮可见性
const showRejectButton = isExposed && judgedStatus !== 'Reject';
const showRestoreButton = isExposed && judgedStatus === 'Reject';
const showSaveParamsButton = isUnexposed;
const showDeleteButton = isUnexposed;

API 请求/响应结构

// 请求
interface JudgeImageRequest {
  instance_uid: string;  // SOP 实例 UID
  accept: boolean;       // true: 接受, false: 拒绝
}

// 响应
interface JudgeImageResponse {
  code: string;          // '0x000000' 表示成功
  description: string;
  solution: string;
  data: {
    '@type': string;
    value: object;
  };
}

🔄 执行流程

用户操作起点

1. 进入检查流程

flowchart TD
    Start([用户进入检查流程]) --> LoadPositions[加载体位列表]
    LoadPositions --> AutoSelect[自动选中第一个未曝光体位]
    AutoSelect --> GetStatus[获取体位状态信息]
    GetStatus --> CalcVisibility[计算按钮可见性]
    CalcVisibility --> RenderUI[渲染 UI]
    RenderUI --> WaitUser[等待用户操作]

2. 切换体位

flowchart TD
    Click([用户点击体位]) --> UpdateSelected[更新 selectedBodyPosition]
    UpdateSelected --> GetNewStatus[获取新体位状态]
    GetNewStatus --> RecalcVisibility[重新计算按钮可见性]
    RecalcVisibility --> ReRender[重新渲染按钮区域]
    ReRender --> Complete([完成])

3. 拒绝图像完整流程

flowchart TD
    Start([用户点击拒绝按钮]) --> Check{selectedBodyPosition 存在?}
    Check -->|否| Warning[显示警告消息]
    Check -->|是| Dispatch[dispatch judgeImageThunk]
    
    Dispatch --> Pending[judgeImageThunk.pending]
    Pending --> SetLoading[设置 loading = true]
    SetLoading --> ShowLoading[按钮显示 loading 状态]
    
    ShowLoading --> CallAPI[调用 POST /auth/task/inspection/judge]
    CallAPI --> CheckResponse{API 响应}
    
    CheckResponse -->|成功| Fulfilled[judgeImageThunk.fulfilled]
    CheckResponse -->|失败| Rejected[judgeImageThunk.rejected]
    
    Fulfilled --> UpdateJudged[更新 judged_status = 'Reject']
    UpdateJudged --> UpdateList[更新 bodyPositions 列表]
    UpdateList --> UpdateSelection[更新 selectedBodyPosition]
    UpdateSelection --> ClearLoading[设置 loading = false]
    ClearLoading --> Rerender[触发 UI 重新渲染]
    Rerender --> HideReject[隐藏拒绝按钮]
    HideReject --> ShowRestore[显示恢复按钮]
    ShowRestore --> Success[显示成功消息]
    
    Rejected --> SetError[设置 error 信息]
    SetError --> ClearLoadingError[设置 loading = false]
    ClearLoadingError --> ErrorMsg[显示错误消息]
    
    Warning --> End([结束])
    Success --> End
    ErrorMsg --> End

4. 恢复图像流程

与拒绝流程类似,但参数 accept: true,结果是 judged_status = 'Accept'

5. 保存参数流程(待实现)

flowchart TD
    Start([用户点击保存参数按钮]) --> Check{selectedBodyPosition 存在?}
    Check -->|否| Warning[显示警告消息]
    Check -->|是| ReadAPR[读取当前 APR 配置]
    ReadAPR --> LogInfo[记录日志]
    LogInfo --> TODO[TODO: 调用保存参数 API]
    TODO --> ShowInfo[显示: 保存参数功能开发中]
    
    Warning --> End([结束])
    ShowInfo --> End

📝 实现清单

已修改的文件

1. src/pages/exam/ContentAreaLarge.tsx

新增导入:

import {
  removeBodyPositionBySopInstanceUid,
  setByIndex,
  judgeImageThunk,  // 新增
} from '../../states/exam/bodyPositionListSlice';

新增处理函数:

  1. handleReject() - 拒绝图像

    const handleReject = async () => {
     if (!selectedBodyPosition) {
       message.warning('请先选择一个体位');
       return;
     }
    
     try {
       await dispatch(
         judgeImageThunk({
           sopInstanceUid: selectedBodyPosition.sop_instance_uid,
           accept: false,
         })
       ).unwrap();
    
       message.success('图像已拒绝');
     } catch (err) {
       console.error('拒绝图像失败:', err);
       message.error('拒绝图像失败');
     }
    };
    
    1. handleRestore() - 恢复图像 ```typescript const handleRestore = async () => { if (!selectedBodyPosition) { message.warning('请先选择一个体位'); return; }

    try { await dispatch(

     judgeImageThunk({
       sopInstanceUid: selectedBodyPosition.sop_instance_uid,
       accept: true,
     })
    

    ).unwrap();

    message.success('图像已恢复'); } catch (err) { console.error('恢复图像失败:', err); message.error('恢复图像失败'); } }; ```

  2. handleSaveParams() - 保存参数(待实现)

    const handleSaveParams = async () => {
     if (!selectedBodyPosition) {
       message.warning('请先选择一个体位');
       return;
     }
    
     try {
       // TODO: 实现保存参数 API 调用
       console.log('保存参数功能待实现,当前 APR 配置:', aprConfig);
       console.log('选中体位:', selectedBodyPosition.sop_instance_uid);
           
       message.info('保存参数功能开发中');
     } catch (err) {
       console.error('保存参数失败:', err);
       message.error('保存参数失败');
     }
    };
    

    新增状态订阅:

    // 4. 计算按钮可见性
    const loading = useSelector(
    (state: RootState) => state.bodyPositionList.loading
    );
    const exposeStatus = selectedBodyPosition?.dview.expose_status;
    const judgedStatus = selectedBodyPosition?.dview.judged_status || '';
    
    const isExposed = exposeStatus === 'Exposed';
    const isUnexposed = exposeStatus === 'Unexposed';
    
    // 按钮可见性逻辑
    const showRejectButton = isExposed && judgedStatus !== 'Reject';
    const showRestoreButton = isExposed && judgedStatus === 'Reject';
    const showSaveParamsButton = isUnexposed;
    const showDeleteButton = isUnexposed;
    

修改的按钮渲染:

  1. 删除按钮 - 添加条件渲染

    {showDeleteButton && (
     <Tooltip title="删除选择的体位">
       <Button ... />
     </Tooltip>
    )}
    
    1. 保存参数按钮 - 添加条件渲染和点击处理 typescript {showSaveParamsButton && ( <Tooltip title="保存参数"> <Button onClick={handleSaveParams} ... /> </Tooltip> )}
  2. 拒绝按钮 - 添加条件渲染、loading 状态和点击处理

    {showRejectButton && (
     <Tooltip title="拒绝">
       <Button 
         loading={loading}
         onClick={handleReject}
         ... 
       />
     </Tooltip>
    )}
    
    1. 恢复按钮 - 新增(与拒绝按钮互斥) typescript {showRestoreButton && ( <Tooltip title="恢复"> <Button loading={loading} onClick={handleRestore} icon={<Icon name="btn_RestoreImage" ... />} /> </Tooltip> )}

未修改的文件

以下文件已经具备所需功能,无需修改:

  • src/states/exam/bodyPositionListSlice.ts - judgeImageThunk 已存在
  • src/API/exam/judgeImage.ts - API 已就绪
  • src/pages/exam/components/BodyPositionList.tsx - 体位列表无需修改
  • src/domain/exam/bodyPositionSelection.ts - 体位选择逻辑无需修改

🧪 测试方案

功能测试用例

1. 按钮显示逻辑测试

用例编号 测试场景 前置条件 操作步骤 期望结果
TC-001 未曝光体位 选中未曝光体位 查看按钮区域 显示:保存参数、删除
隐藏:拒绝、恢复
TC-002 已曝光-接受 选中已曝光且接受的体位 查看按钮区域 显示:拒绝
隐藏:恢复、保存参数、删除
TC-003 已曝光-拒绝 选中已曝光且拒绝的体位 查看按钮区域 显示:恢复
隐藏:拒绝、保存参数、删除
TC-004 已曝光-未判断 选中已曝光但未判断的体位 查看按钮区域 显示:拒绝
隐藏:恢复、保存参数、删除
TC-005 无选中体位 清空选中状态 查看按钮区域 所有判断相关按钮隐藏

2. 拒绝功能测试

用例编号 测试场景 前置条件 操作步骤 期望结果
TC-101 拒绝成功 选中已曝光-接受体位 1. 点击拒绝按钮
2. 等待响应
1. 按钮显示 loading
2. API 调用成功
3. judged_status 更新为 'Reject'
4. 显示恢复按钮
5. 显示"图像已拒绝"
TC-102 拒绝失败 选中已曝光体位,模拟 API 错误 点击拒绝按钮 1. 显示错误消息
2. 状态不变
3. 按钮显示不变
TC-103 无选中体位 未选中任何体位 点击拒绝按钮(如果可见) 显示警告"请先选择一个体位"
TC-104 加载状态 选中已曝光体位 点击拒绝按钮,立即再次点击 第二次点击无效(按钮 disabled)

3. 恢复功能测试

用例编号 测试场景 前置条件 操作步骤 期望结果
TC-201 恢复成功 选中已曝光-拒绝体位 1. 点击恢复按钮
2. 等待响应
1. 按钮显示 loading
2. API 调用成功
3. judged_status 更新为 'Accept'
4. 显示拒绝按钮
5. 显示"图像已恢复"
TC-202 恢复失败 选中已拒绝体位,模拟 API 错误 点击恢复按钮 1. 显示错误消息
2. 状态不变
TC-203 拒绝后恢复 选中已曝光-接受体位 1. 点击拒绝
2. 等待成功
3. 点击恢复
最终状态恢复为接受

4. 保存参数功能测试(当前版本)

用例编号 测试场景 前置条件 操作步骤 期望结果
TC-301 点击保存参数 选中未曝光体位 点击保存参数按钮 1. 控制台输出 APR 配置
2. 显示"保存参数功能开发中"
TC-302 无选中体位 未选中任何体位 点击保存参数按钮(如果可见) 显示警告"请先选择一个体位"

5. 删除按钮可见性测试

用例编号 测试场景 前置条件 操作步骤 期望结果
TC-401 未曝光体位 选中未曝光体位 查看删除按钮 删除按钮可见
TC-402 已曝光体位 选中已曝光体位 查看删除按钮 删除按钮隐藏

6. 体位切换测试

用例编号 测试场景 前置条件 操作步骤 期望结果
TC-501 未曝光→已曝光 当前选中未曝光体位 点击已曝光-接受体位 按钮更新:显示拒绝,隐藏保存参数和删除
TC-502 已曝光接受→已曝光拒绝 当前选中已曝光-接受体位 点击已曝光-拒绝体位 按钮更新:显示恢复,隐藏拒绝
TC-503 已曝光拒绝→未曝光 当前选中已曝光-拒绝体位 点击未曝光体位 按钮更新:显示保存参数和删除,隐藏恢复

集成测试场景

场景 1: 完整工作流

进入检查流程 
→ 选中未曝光体位(显示保存参数、删除)
→ 进行曝光(MQTT 更新状态)
→ 体位变为已曝光-未判断(显示拒绝,隐藏保存参数、删除)
→ 点击拒绝(显示恢复,隐藏拒绝)
→ 点击恢复(显示拒绝,隐藏恢复)
→ 退出

场景 2: 多体位混合状态

进入检查流程
→ 体位列表包含:2个未曝光,2个已曝光-接受,1个已曝光-拒绝
→ 依次点击每个体位
→ 验证每个体位的按钮显示正确
→ 对已曝光体位进行拒绝/恢复操作
→ 验证状态更新正确

场景 3: 边界条件

  • 只有1个体位(未曝光)
  • 只有1个体位(已曝光)
  • 所有体位已曝光
  • 所有体位未曝光

性能测试

测试场景 操作 性能指标
按钮切换响应 快速点击不同体位 UI 更新延迟 < 100ms
API 响应时间 执行拒绝/恢复操作 总耗时 < 2s
并发操作 在 loading 期间尝试其他操作 按钮正确禁用,无重复请求

🐛 潜在问题与异常处理

1. 边界情况处理

问题 1: selectedBodyPosition 为 null

  • 场景: 体位列表为空或未选中任何体位
  • 影响: 访问 selectedBodyPosition.dview 会导致运行时错误
  • 解决方案:

    const exposeStatus = selectedBodyPosition?.dview.expose_status;
    const judgedStatus = selectedBodyPosition?.dview.judged_status || '';
    

    使用可选链操作符 ?. 和默认值 || ''

    问题 2: judged_status 未定义

    • 场景: 旧数据或新创建的体位可能没有 judged_status 字段
    • 影响: 按钮可见性判断可能出错
    • 解决方案: typescript const judgedStatus = selectedBodyPosition?.dview.judged_status || '';

提供空字符串作为默认值

问题 3: 只有一个体位且已曝光

  • 场景: 列表中只有一个已曝光的体位
  • 影响: 用户无法删除该体位(删除按钮被隐藏)
  • 处理: 这是预期行为,已曝光的体位不应被删除,保护已采集的数据

问题 4: expose_status 状态不一致

  • 场景: 前端状态与后端状态不同步
  • 影响: 按钮显示可能不正确
  • 解决方案: 依赖 MQTT 事件自动更新状态,确保状态同步

2. API 调用异常处理

网络错误

  • 错误类型:
    • 连接超时
    • 网络中断
    • DNS 解析失败
  • 处理方案:

    try {
    await dispatch(judgeImageThunk(...)).unwrap();
    message.success('操作成功');
    } catch (err) {
    console.error('操作失败:', err);
    message.error('操作失败,请检查网络连接');
    }
    
    • 用户反馈: 显示友好的错误消息,建议用户检查网络

    服务器错误

    • 错误类型:
    • 500 Internal Server Error
    • 503 Service Unavailable
    • 处理方案:
    • 捕获错误并显示通用错误消息
    • 记录详细错误日志供调试
    • 不修改当前 UI 状态

    API 返回非成功码

    • 场景: response.code !== '0x000000'
    • 处理: judgeImage API 内部已处理,会抛出错误
    • 结果: 在 catch 块中捕获并显示错误消息

    3. 状态一致性问题

    Redux 状态与后端不同步

    • 场景:
    • API 调用成功但 Redux 状态未更新
    • 多个客户端同时操作同一体位
    • 解决方案:
    • 使用 judgeImageThunk.fulfilled 确保 API 成功后才更新状态
    • 失败时保持原状态不变
    • 考虑添加刷新功能重新拉取最新数据

    并发操作冲突

    • 场景: 用户在 loading 期间快速点击其他按钮
    • 解决方案: typescript <Button loading={loading} ... />
    • 使用 loading 属性禁用按钮
    • 防止重复提交请求

4. 用户体验优化

Loading 状态管理

  • 实现:

    const loading = useSelector(
    (state: RootState) => state.bodyPositionList.loading
    );
    
    • 效果: 按钮显示加载动画,用户清楚操作正在进行

    操作反馈

    • 成功: message.success('图像已拒绝')
    • 失败: message.error('拒绝图像失败')
    • 警告: message.warning('请先选择一个体位')

    按钮互斥显示

    • 设计: 拒绝和恢复按钮同一时间只显示一个
    • 好处: 界面简洁,避免用户混淆

    5. 性能考虑

    状态更新优化

    • 当前实现: React 自动优化,使用 useSelector 订阅特定状态
    • 优化点:
    • 按钮可见性计算逻辑简单,不需要额外的 useMemo
    • Redux 的 selector 已经做了浅比较优化

    图标加载

    • 处理: Icon 组件内部处理图标缓存
    • 性能: 首次加载后图标被缓存,后续渲染快速

    频繁切换体位

    • 场景: 用户快速点击不同体位
    • 影响: 触发多次重渲染
    • 优化: React 的批量更新机制自动优化,实际性能影响小

    6. 数据完整性

    SOP Instance UID 验证

    • 处理: 在调用 API 前检查 selectedBodyPosition 存在性
    • 代码: typescript if (!selectedBodyPosition) { message.warning('请先选择一个体位'); return; }

状态字段缺失

  • 处理: 使用可选链和默认值
  • 示例: judgedStatus || ''

🔧 后续优化建议

1. 保存参数功能完整实现

待实现内容:

  1. 定义保存参数 API

    // src/API/exam/saveParams.ts
    const saveBodyPositionParams = async (
     sopInstanceUid: string, 
     params: AprConfig
    ): Promise<void> => {
     await axiosInstance.post('/auth/exam/save-params', {
       sop_instance_uid: sopInstanceUid,
       apr_config: params
     });
    };
    
    1. 创建 Redux Thunk typescript // src/states/exam/bodyPositionListSlice.ts export const saveParamsThunk = createAsyncThunk( 'bodyPositionList/saveParams', async (payload: { sopInstanceUid: string; aprConfig: AprConfig }) => { await saveBodyPositionParams( payload.sopInstanceUid, payload.aprConfig ); return payload; } );
  2. 更新 handleSaveParams

    const handleSaveParams = async () => {
     if (!selectedBodyPosition) {
       message.warning('请先选择一个体位');
       return;
     }
    
     try {
       await dispatch(
         saveParamsThunk({
           sopInstanceUid: selectedBodyPosition.sop_instance_uid,
           aprConfig: aprConfig
         })
       ).unwrap();
           
       message.success('参数保存成功');
     } catch (err) {
       console.error('保存参数失败:', err);
       message.error('保存参数失败');
     }
    };
    

    2. 添加操作确认对话框

    对于某些关键操作,可以考虑添加确认对话框:

    // 拒绝操作确认(可选)
    const handleRejectWithConfirm = () => {
    Modal.confirm({
    title: '确认拒绝',
    content: '确定要拒绝这张图像吗?',
    onOk: handleReject,
    });
    };
    

3. 批量操作支持

未来可能需要支持批量拒绝/恢复:

// 批量拒绝
const handleBatchReject = async (sopInstanceUids: string[]) => {
  for (const uid of sopInstanceUids) {
    await dispatch(judgeImageThunk({ sopInstanceUid: uid, accept: false }));
  }
};

4. 操作历史记录

记录用户的判断操作历史:

interface JudgeHistory {
  sopInstanceUid: string;
  action: 'reject' | 'restore';
  timestamp: number;
  operator: string;
}

5. 快捷键支持

为常用操作添加快捷键:

  • R - 拒绝图像
  • A - 恢复图像(Accept)
  • S - 保存参数

6. 状态同步机制

考虑实现 WebSocket 或轮询机制,确保多客户端状态同步:

// 定期检查状态
useEffect(() => {
  const interval = setInterval(async () => {
    // 重新获取选中体位的最新状态
    await refreshBodyPositionStatus(selectedBodyPosition?.sop_instance_uid);
  }, 30000); // 每30秒

  return () => clearInterval(interval);
}, [selectedBodyPosition]);

📚 参考资料

相关文件

  • src/pages/view/components/ImageStateControl.tsx - 类似功能的参考实现
  • src/states/exam/bodyPositionListSlice.ts - 状态管理
  • src/API/exam/judgeImage.ts - API 定义
  • src/domain/exam/bodyPositionSelection.ts - 体位选择逻辑

技术栈

  • React + TypeScript
  • Redux Toolkit (状态管理)
  • Ant Design (UI 组件)
  • Axios (HTTP 请求)

API 端点

  • POST /auth/task/inspection/judge - 接受/拒绝图像
  • POST /auth/exam/save-params - 保存参数(待实现)

📋 检查清单

实施前检查:

  • 理解业务需求和显示逻辑
  • 确认 API 可用性(judgeImage 已就绪)
  • 设计组件交互流程
  • 规划错误处理策略

实施中检查:

  • 导入必要的依赖
  • 实现处理函数
  • 添加状态订阅
  • 更新按钮渲染逻辑
  • 添加 loading 状态
  • 实现错误处理

实施后检查:

  • 手动测试所有按钮显示逻辑
  • 测试拒绝/恢复功能
  • 测试边界情况
  • 测试错误处理
  • 性能测试
  • 代码审查
  • 文档更新

🎯 总结

本次实现成功为检查流程添加了图像状态控制功能,包括:

  1. 拒绝按钮 - 将已曝光图像标记为拒绝状态
  2. 恢复按钮 - 将已拒绝图像恢复为接受状态
  3. 保存参数按钮 - 预留保存 APR 参数功能(待 API 就绪)
  4. 删除按钮控制 - 根据曝光状态控制删除按钮的显示

关键特性

  • ✅ 按钮互斥显示(拒绝/恢复)
  • ✅ 基于体位状态动态显示
  • ✅ 完整的错误处理
  • ✅ Loading 状态反馈
  • ✅ 用户友好的提示信息
  • ✅ 边界情况处理

技术亮点

  • 使用 Redux Toolkit 的 createAsyncThunk 处理异步操作
  • 可选链操作符确保代码健壮性
  • 条件渲染优化 UI 显示
  • 统一的错误处理策略

后续工作

  • 实现保存参数 API 并集成
  • 添加更多测试用例
  • 考虑添加快捷键支持
  • 优化用户体验细节

文档版本: v1.0.0
最后更新: 2025-01-14
维护者: 开发团队