# 图像状态控制功能实现文档 ## 1. 功能概述 在图像处理界面(view 模式)的 OperationPanel 底部,实现图像状态控制功能,包括: - **拒绝按钮**:将当前图像标记为拒绝状态 - **恢复按钮**:将已拒绝的图像恢复为接受状态 - **另存为按钮**:保存图像副本(UI 占位,暂不实现功能) ### 业务规则 1. 拒绝和恢复按钮互斥显示,基于当前选中图像的 `judged_status` 2. 仅在单分格模式(1x1)下显示拒绝/恢复按钮 3. 多分格模式下,拒绝/恢复按钮不可见 4. 另存为按钮始终可见但禁用 --- ## 2. 架构设计 ### 2.1 参与者 #### UI 组件层 - **OperationPanel** (`src/pages/view/components/OperationPanel.tsx`) - 职责:父容器组件,Layout 结构 - **ImageStateControl** (`src/pages/view/components/ImageStateControl.tsx`) - **新建** - 职责:图像状态控制组件,渲染按钮并处理用户操作 #### 状态管理层(Redux) - **viewerContainerSlice** (`src/states/view/viewerContainerSlice.ts`) - 职责:管理分格布局、选中的 viewer - 提供:`selectGridLayout`, `selectSelectedViewers` - **bodyPositionListSlice** (`src/states/exam/bodyPositionListSlice.ts`) - **需修改** - 职责:管理体位列表、图像数据、判断状态 - 新增:`judgeImageThunk` 异步操作 - 新增:loading/error 状态字段 #### API 层 - **judgeImage** (`src/API/exam/judgeImage.ts`) - 已存在的接受/拒绝图像 API --- ## 3. 数据流设计 ### 3.1 数据流图 ```mermaid flowchart TD A[用户点击拒绝/恢复按钮] --> B[ImageStateControl 组件] B --> C{获取状态} C --> D[viewerContainerSlice
selectedViewers, gridLayout] C --> E[bodyPositionList
judged_status, sopInstanceUid] B --> F[dispatch judgeImageThunk] F --> G[bodyPositionListSlice] G --> H[调用 judgeImage API] H --> I{API 结果} I -->|成功| J[extraReducers.fulfilled
更新 judged_status] I -->|失败| K[extraReducers.rejected
设置 error] J --> L[Redux 通知状态变更] K --> L L --> B B --> M[重新渲染按钮] ``` ### 3.2 序列图 ```mermaid sequenceDiagram participant User as 用户 participant UI as ImageStateControl participant VCS as viewerContainerSlice participant BPL as bodyPositionListSlice participant API as judgeImage API Note over UI: 组件初始化 UI->>VCS: selectSelectedViewers() VCS-->>UI: [imageUrl] UI->>VCS: selectGridLayout() VCS-->>UI: '1x1' UI->>BPL: 读取 bodyPositions BPL-->>UI: {judged_status: '', sopInstanceUid} Note over UI: 决定按钮可见性 alt gridLayout === '1x1' alt judged_status === 'Reject' UI->>UI: 渲染"恢复"按钮 else UI->>UI: 渲染"拒绝"按钮 end else UI->>UI: 不渲染拒绝/恢复按钮 end Note over User: 用户点击"拒绝"按钮 User->>UI: onClick UI->>BPL: dispatch judgeImageThunk({sopInstanceUid, accept: false}) BPL->>BPL: pending → loading = true BPL->>API: judgeImage(sopInstanceUid, false) API-->>BPL: 成功 BPL->>BPL: fulfilled → 更新 judged_status = 'Reject' BPL->>BPL: loading = false BPL-->>UI: 状态变更通知 UI->>User: 显示成功提示 UI->>UI: 重新渲染,显示"恢复"按钮 ``` --- ## 4. 数据结构设计 ### 4.1 judged_status 类型 ```typescript /** * 图像判断状态 * - 'Accept': 已接受 * - 'Reject': 已拒绝 * - '': 未判断 */ type ImageJudgedStatus = 'Accept' | 'Reject' | ''; ``` ### 4.2 bodyPositionListSlice 新增字段 ```typescript interface BodyPositionListState { bodyPositions: BodyPosition[]; selectedBodyPosition: BodyPosition | null; loading: boolean; // 新增 error: string | null; // 新增 } ``` ### 4.3 judgeImageThunk 参数 ```typescript interface JudgeImagePayload { sopInstanceUid: string; accept: boolean; } interface JudgeImageResult { sopInstanceUid: string; judgedStatus: ImageJudgedStatus; } ``` --- ## 5. 实现细节 ### 5.1 ImageStateControl 组件 **文件**: `src/pages/view/components/ImageStateControl.tsx` **核心逻辑**: ```typescript const ImageStateControl = () => { const dispatch = useDispatch(); // 获取状态 const selectedViewers = useSelector(selectSelectedViewers); const gridLayout = useSelector(selectGridLayout); const bodyPositionList = useSelector((state: RootState) => state.bodyPositionList.bodyPositions ); const loading = useSelector((state: RootState) => state.bodyPositionList.loading ); // 查找当前选中的图像 const selectedImage = useMemo(() => { if (selectedViewers.length === 0) return null; const imageUrl = selectedViewers[0]; return bodyPositionList.find( bp => getDcmImageUrl(bp.sop_instance_uid) === imageUrl ); }, [selectedViewers, bodyPositionList]); // 判断按钮可见性 const isSingleGrid = gridLayout === '1x1'; const judgedStatus = selectedImage?.dview?.judged_status || ''; const showRejectButton = isSingleGrid && judgedStatus !== 'Reject'; const showRestoreButton = isSingleGrid && judgedStatus === 'Reject'; // 按钮处理函数 const handleReject = async () => { if (!selectedImage) return; try { await dispatch(judgeImageThunk({ sopInstanceUid: selectedImage.sop_instance_uid, accept: false })).unwrap(); message.success('图像已拒绝'); } catch (err) { message.error('拒绝图像失败'); } }; const handleRestore = async () => { if (!selectedImage) return; try { await dispatch(judgeImageThunk({ sopInstanceUid: selectedImage.sop_instance_uid, accept: true })).unwrap(); message.success('图像已恢复'); } catch (err) { message.error('恢复图像失败'); } }; return ( {showRejectButton && (