|
@@ -0,0 +1,295 @@
|
|
|
+# 体位全曝光 - 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` 是异步操作,需要等待完成后再跳转
|