# 检查流程中的图像状态控制功能实现文档 ## 📋 功能概述 在检查流程(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` - 请求参数: ```typescript { 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' | ''; // ... 其他字段 }; // ... 其他字段 } ``` ### 组件交互序列图 ```mermaid 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: 显示"保存参数功能开发中" ``` ### 数据流图 ```mermaid 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 ``` ### 按钮可见性计算流程 ```mermaid 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 接口 ```typescript 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; } ``` ### 按钮可见性状态 ```typescript // 在 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 请求/响应结构 ```typescript // 请求 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. 进入检查流程 ```mermaid flowchart TD Start([用户进入检查流程]) --> LoadPositions[加载体位列表] LoadPositions --> AutoSelect[自动选中第一个未曝光体位] AutoSelect --> GetStatus[获取体位状态信息] GetStatus --> CalcVisibility[计算按钮可见性] CalcVisibility --> RenderUI[渲染 UI] RenderUI --> WaitUser[等待用户操作] ``` #### 2. 切换体位 ```mermaid flowchart TD Click([用户点击体位]) --> UpdateSelected[更新 selectedBodyPosition] UpdateSelected --> GetNewStatus[获取新体位状态] GetNewStatus --> RecalcVisibility[重新计算按钮可见性] RecalcVisibility --> ReRender[重新渲染按钮区域] ReRender --> Complete([完成]) ``` #### 3. 拒绝图像完整流程 ```mermaid 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. 保存参数流程(待实现) ```mermaid 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 **新增导入**: ```typescript import { removeBodyPositionBySopInstanceUid, setByIndex, judgeImageThunk, // 新增 } from '../../states/exam/bodyPositionListSlice'; ``` **新增处理函数**: 1. **handleReject()** - 拒绝图像 ```typescript 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('拒绝图像失败'); } }; ``` 2. **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('恢复图像失败'); } }; ``` 3. **handleSaveParams()** - 保存参数(待实现) ```typescript 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('保存参数失败'); } }; ``` **新增状态订阅**: ```typescript // 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. 删除按钮 - 添加条件渲染 ```typescript {showDeleteButton && (