体位全曝光-worklist-双击-进入检查.md 9.7 KB

体位全曝光 - Worklist 双击进入检查或处理

需求说明

当 study 的所有体位都已经曝光后,双击 worklist 表格项目时,应该直接进入处理界面(process),而不是进入检查界面(exam)。

涉及的 Redux 参与者

1. Slices(状态切片)

BusinessFlowSlice

  • 文件路径: src/states/BusinessFlowSlice.ts
  • 作用: 管理整个应用的业务流程状态
  • 状态结构:

    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
    • 当前逻辑:
    • 获取 task 详情(包含所有体位信息)
    • 清空缓存
    • 保存 task 到缓存
    • 始终跳转到 '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'
  // ... 其他字段
}

判断条件:

const allExposed = task.Views.every((view) => view.expose_status === 'Exposed');

相关代码参考

src/states/exam/bodyPositionListSlice.ts 中已有类似的判断逻辑:

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

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. 添加曝光状态判断
   const allExposed = updatedTask.Views.every(
     (view) => view.expose_status === 'Exposed'
   );
  1. 条件分支处理

    • 全部曝光: 转换体位列表 → 设置体位数据 → 跳转到 'process'
    • 有未曝光: 直接跳转到 'exam'
  2. 复用现有逻辑

    • 使用 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 数组

// 需要额外检查 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 是异步操作,需要等待完成后再跳转