# 体位全曝光 - Worklist 双击进入检查或处理 ## 需求说明 当 study 的所有体位都已经曝光后,双击 worklist 表格项目时,应该直接进入处理界面(process),而不是进入检查界面(exam)。 ## 涉及的 Redux 参与者 ### 1. Slices(状态切片) #### BusinessFlowSlice - **文件路径**: `src/states/BusinessFlowSlice.ts` - **作用**: 管理整个应用的业务流程状态 - **状态结构**: ```typescript interface BusinessFlowState { currentKey: string; // 当前页面: 'register', 'exam', 'process' 等 lastKey?: string; // 上一个页面 } ``` - **关键 Reducer**: - `setBusinessFlow(key: string)` - 切换业务流程到指定页面 #### examWorksCacheSlice - **文件路径**: `src/states/exam/examWorksCacheSlice.ts` - **作用**: 缓存进入检查/处理界面的工作数据 - **关键 Reducer**: - `addWork(task: Task)` - 添加工作到缓存 - `clearWorks()` - 清空工作缓存 #### bodyPositionListSlice - **文件路径**: `src/states/exam/bodyPositionListSlice.ts` - **作用**: 管理体位列表数据 - **关键 Reducer**: - `setBodyPositions(positions)` - 设置体位数据 - **关键函数**: - `transformWorksToBodyPositions(works)` - 将 works 转换为体位列表 ### 2. Actions(动作) | Action | 作用 | 参数 | | ------------------ | ---------------- | ----------------------------------- | | `setBusinessFlow` | 切换业务流程页面 | `key: string` ('exam' 或 'process') | | `addWork` | 添加工作到缓存 | `task: Task` | | `clearWorks` | 清空工作缓存 | 无 | | `setBodyPositions` | 设置体位列表 | `positions: BodyPosition[]` | ### 3. Domain 逻辑层 #### worklistToExam - **文件路径**: `src/domain/patient/worklistToExam.ts` - **当前逻辑**: 1. 获取 task 详情(包含所有体位信息) 2. 清空缓存 3. 保存 task 到缓存 4. **始终跳转到 'exam' 检查界面** - **需要修改**: 增加判断所有体位是否已曝光的逻辑 #### worklistToProcess - **文件路径**: `src/domain/patient/worklistToExam.ts`(同文件) - **现有逻辑**: 批量处理选中工单,直接跳转到 'process' - **参考价值**: 展示了如何直接进入处理界面的流程 ### 4. 组件层 #### WorklistTable - **文件路径**: `src/pages/patient/components/WorklistTable.tsx` - **作用**: 渲染 worklist 表格,处理行点击和双击事件 - **关键事件**: `onDoubleClick` - 触发 `handleRowDoubleClick` 回调 #### worklist.tsx - **文件路径**: `src/pages/patient/worklist.tsx` - **作用**: Worklist 主页面 - **关键函数**: - `handleRowDoubleClick(record: Task)` - 调用 `worklistToExam(record)` ## 数据流关系 ``` 用户双击 worklist 表格行 ↓ WorklistTable.onDoubleClick ↓ worklist.handleRowDoubleClick(record) ↓ worklistToExam(task) ↓ prepareWorksForExam(task) - 获取详细数据 ↓ 检查所有 Views 的 expose_status ├─ 所有体位 === 'Exposed' │ ↓ │ dispatch(clearWorks()) │ dispatch(addWork(updatedTask)) │ transformWorksToBodyPositions([updatedTask]) │ dispatch(setBodyPositions(bodyPositions)) │ dispatch(setBusinessFlow('process')) ← 直接进入处理界面 │ └─ 有未曝光体位 ↓ dispatch(clearWorks()) dispatch(addWork(updatedTask)) dispatch(setBusinessFlow('exam')) ← 进入检查界面 ``` ## 判断逻辑 ### 曝光状态判断 在 `Task` 接口中,每个 task 包含 `Views: dview[]` 数组,每个 `dview` 有 `expose_status` 字段: ```typescript interface dview { view_id: string; expose_status: string; // 'Exposed' 或 'Unexposed' // ... 其他字段 } ``` **判断条件**: ```typescript const allExposed = task.Views.every((view) => view.expose_status === 'Exposed'); ``` ### 相关代码参考 在 `src/states/exam/bodyPositionListSlice.ts` 中已有类似的判断逻辑: ```typescript const allExposed = bodyPositions.every( (bp) => bp.dview.expose_status === 'Exposed' ); const allUnExposed = bodyPositions.every( (bp) => bp.dview.expose_status === 'Unexposed' ); ``` ## 实现方案 ### 修改 worklistToExam 函数 **文件**: `src/domain/patient/worklistToExam.ts` ```typescript const worklistToExam = async (task: Task) => { const dispatch = store.dispatch; try { // 1. 使用公共函数准备数据(获取详细信息) const [updatedTask] = await prepareWorksForExam(task); // 2. 判断所有体位是否已曝光 const allExposed = updatedTask.Views.every( (view) => view.expose_status === 'Exposed' ); // 3. 清空现有缓存 dispatch(clearWorks()); // 4. 保存更新后的 task 到缓存 dispatch(addWork(updatedTask)); // 5. 根据曝光状态决定跳转目标 if (allExposed) { // 所有体位已曝光 - 进入处理界面 // 需要先转换为体位列表 const bodyPositions = await transformWorksToBodyPositions([updatedTask]); dispatch(setBodyPositions(bodyPositions)); dispatch(setBusinessFlow('process')); } else { // 有未曝光体位 - 进入检查界面 dispatch(setBusinessFlow('exam')); } } catch (error) { console.error('Error in worklistToExam:', error); throw error; } }; ``` ### 关键改动点 1. **添加曝光状态判断** ```typescript const allExposed = updatedTask.Views.every( (view) => view.expose_status === 'Exposed' ); ``` 2. **条件分支处理** - 全部曝光: 转换体位列表 → 设置体位数据 → 跳转到 'process' - 有未曝光: 直接跳转到 'exam' 3. **复用现有逻辑** - 使用 `transformWorksToBodyPositions` 转换数据(参考 `worklistToProcess`) - 使用 `setBodyPositions` 设置体位数据 ## 优势 1. **用户体验优化**: 已完成曝光的 study 无需再进入检查界面,直接进入处理环节 2. **逻辑清晰**: 基于体位曝光状态自动判断,无需用户手动选择 3. **代码复用**: 充分利用现有的 `worklistToProcess` 逻辑 4. **易于维护**: 修改集中在一个函数中,不影响其他功能 ## 测试场景 ### 场景 1: 所有体位已曝光 - **操作**: 双击一个所有体位都是 'Exposed' 的 study - **预期**: 直接进入处理界面(process),可以看到所有已曝光的图像 ### 场景 2: 存在未曝光体位 - **操作**: 双击一个至少有一个体位是 'Unexposed' 的 study - **预期**: 进入检查界面(exam),可以进行曝光操作 ### 场景 3: 空体位列表 - **操作**: 双击一个 Views 为空数组的 study - **预期**: 进入检查界面(因为 `every()` 对空数组返回 true,需要特殊处理) ### 场景 4: 混合状态 - **操作**: 双击一个部分体位已曝光、部分未曝光的 study - **预期**: 进入检查界面 ## 边界情况处理 ### 1. 空 Views 数组 ```typescript // 需要额外检查 Views 是否为空 const allExposed = updatedTask.Views.length > 0 && updatedTask.Views.every((view) => view.expose_status === 'Exposed'); ``` ### 2. expose_status 字段可能的值 根据代码搜索结果,`expose_status` 的可能值: - `'Exposed'` - 已曝光 - `'Unexposed'` - 未曝光 需要确保判断逻辑只认为值为 `'Exposed'` 的才算已曝光。 ### 3. API 数据异常 如果 `fetchTaskDetails` 失败或返回异常数据,原有的错误处理机制会捕获并抛出错误。 ## 相关文件总结 | 文件路径 | 作用 | 修改内容 | | ------------------------------------------------ | ------------------ | ------------------------------ | | `src/domain/patient/worklistToExam.ts` | Worklist 跳转逻辑 | **需要修改**: 添加曝光状态判断 | | `src/states/BusinessFlowSlice.ts` | 业务流程管理 | 无需修改 | | `src/states/exam/examWorksCacheSlice.ts` | 工作缓存 | 无需修改 | | `src/states/exam/bodyPositionListSlice.ts` | 体位列表管理 | 无需修改,可参考判断逻辑 | | `src/pages/patient/worklist.tsx` | Worklist 页面 | 无需修改 | | `src/pages/patient/components/WorklistTable.tsx` | 表格组件 | 无需修改 | | `src/domain/work.ts` | Task 数据类型定义 | 无需修改 | | `src/domain/dview.ts` | dview 数据类型定义 | 无需修改 | ## 实现步骤 1. ✅ 分析现有代码结构,识别涉及的 Redux 参与者 2. ✅ 梳理数据流关系,确定修改点 3. ✅ 编写实现方案文档 4. ⏳ 修改 `worklistToExam` 函数,添加曝光状态判断 5. ⏳ 测试各种场景,确保功能正常 6. ⏳ 处理边界情况 ## 参考代码位置 - **曝光状态判断参考**: `src/states/exam/bodyPositionListSlice.ts:147-154` - **直接进入处理界面参考**: `src/domain/patient/worklistToExam.ts:69-113` (worklistToProcess) - **体位转换函数**: `src/states/exam/bodyPositionListSlice.ts` 中的 `transformWorksToBodyPositions` ## 注意事项 1. **数据完整性**: 确保 `prepareWorksForExam` 返回的数据包含完整的 Views 信息 2. **状态一致性**: 进入处理界面前必须设置 bodyPositions,否则处理界面可能无法正常显示 3. **错误处理**: 保持现有的错误处理机制,确保任何步骤失败都能正确抛出错误 4. **性能考虑**: `transformWorksToBodyPositions` 是异步操作,需要等待完成后再跳转