# Exam 体位列表 - 双击已曝光体位进入处理功能实现文档 ## 📋 功能概述 **功能名称**: Exam 页面体位列表 - 双击已曝光体位进入处理 **实现状态**: 🔄 **待实施** **核心需求**: 区分单击和双击已曝光体位的行为,只有双击才切换到 process 模式 ## 🎯 需求说明 ### 问题背景 **当前行为**(不符合预期): - 在 exam 模式下,**单击**已曝光体位 → 自动切换到 process 模式 **期望行为**: - 在 exam 模式下,**单击**已曝光体位 → 只选中并显示缩略图,**不切换流程** - 在 exam 模式下,**双击**已曝光体位 → 选中并切换到 process 模式 --- ## 📊 完整交互行为表 | 模式 | 体位状态 | 单击行为 | 双击行为 | |------|---------|---------|---------| | exam | Unexposed | 选中 + 同步设备 + 显示示意图 🖼️ | 同左 | | exam | Exposed | **只选中 + 显示缩略图(不切换)** 📷 | **选中 + 切换到 process** ➡️ | | process | Unexposed | 选中 + 切换到 exam + 同步设备 | 同左 | | process | Exposed | 选中 + 保持 process | 同左 | --- ## 🔍 问题根源分析 ### 当前实现问题 在 `src/domain/exam/bodyPositionSelection.ts` 中的 `manualSelectBodyPositionWithFlowSwitch` 函数: ```typescript // 第182-187行 - 问题代码 if (currentKey === 'exam' && isExposed) { targetFlow = 'process'; needSwitch = true; // ❌ 无论单击还是双击都会切换 console.log(`Detected exposed position in exam mode, will switch to process`); } ``` **问题**:函数没有区分是单击还是双击触发,导致单击也会切换流程。 --- ## 💡 解决方案 ### 核心思路 1. **添加参数控制流程切换**:在 `manualSelectBodyPositionWithFlowSwitch` 函数中添加 `allowFlowSwitch` 参数 2. **分离单击和双击处理**:在 `BodyPositionList.tsx` 中分别处理单击和双击事件 3. **支持双击事件**:在 `ImageViewer.tsx` 组件中添加 `onDoubleClick` 属性支持 --- ## 🛠️ 实现方案 ### 修改清单 | 文件 | 修改类型 | 具体内容 | |------|---------|---------| | `bodyPositionSelection.ts` | 🔧 修改函数签名 | 添加 `allowFlowSwitch` 参数 | | `BodyPositionList.tsx` | ➕ 添加双击处理 | 添加 `handleImageDoubleClick` 函数 | | `ImageViewer.tsx` | ➕ 添加属性 | 添加 `onDoubleClick` 属性支持 | --- ### 1️⃣ 修改 `bodyPositionSelection.ts` **文件路径**: `src/domain/exam/bodyPositionSelection.ts` #### 修改函数签名 ```typescript /** * 带流程切换的体位选择(用户点击体位时调用,根据曝光状态自动切换流程) * @param bodyPosition 要选中的体位 * @param dispatch Redux dispatch * @param currentKey 当前业务流程 key * @param allowFlowSwitch 是否允许自动切换流程(默认 true) */ export const manualSelectBodyPositionWithFlowSwitch = async ( bodyPosition: ExtendedBodyPosition, dispatch: AppDispatch, currentKey: string, allowFlowSwitch: boolean = true // 🆕 新增参数,默认 true 保持向后兼容 ): Promise => { const isExposed = bodyPosition.dview.expose_status === 'Exposed'; const isUnexposed = bodyPosition.dview.expose_status === 'Unexposed'; let targetFlow = currentKey; let needSwitch = false; // 🆕 只有在允许流程切换时才判断是否需要切换 if (allowFlowSwitch) { // 判断是否需要切换流程 if (currentKey === 'exam' && isExposed) { targetFlow = 'process'; needSwitch = true; console.log( `[bodyPositionSelection] Detected exposed position in exam mode, will switch to process` ); } else if (currentKey === 'process' && isUnexposed) { targetFlow = 'exam'; needSwitch = true; console.log( `[bodyPositionSelection] Detected unexposed position in process mode, will switch to exam` ); } } else { console.log( `[bodyPositionSelection] Flow switch disabled, staying in ${currentKey} mode` ); } // 后续流程切换和体位选中逻辑保持不变 if (needSwitch) { // ... 流程切换代码 } // 使用正确的流程 key 选中体位 await selectBodyPositionWithFullLogic( bodyPosition, dispatch, targetFlow, true ); }; ``` **关键修改**: - ✅ 添加 `allowFlowSwitch` 参数(默认 `true` 保持向后兼容) - ✅ 只有当 `allowFlowSwitch === true` 时才执行流程切换判断 - ✅ 添加日志记录是否禁用流程切换 --- ### 2️⃣ 修改 `BodyPositionList.tsx` **文件路径**: `src/pages/exam/components/BodyPositionList.tsx` #### 添加双击处理函数 ```typescript // 🆕 新增:处理双击事件 const handleImageDoubleClick = async ( bodyPosition: ExtendedBodyPosition ): Promise => { console.log(`[BodyPositionList] Double-click on: ${bodyPosition.view_name}`); // 双击时,允许自动流程切换 await manualSelectBodyPositionWithFlowSwitch( bodyPosition, dispatch, currentKey, true // allowFlowSwitch = true(允许流程切换) ); }; // 🔧 修改:处理单击事件 const handleImageClick = async ( bodyPosition: ExtendedBodyPosition ): Promise => { console.log(`[BodyPositionList] Single-click on: ${bodyPosition.view_name}`); // 单击时,禁止自动流程切换 await manualSelectBodyPositionWithFlowSwitch( bodyPosition, dispatch, currentKey, false // allowFlowSwitch = false(禁止流程切换) ); }; ``` #### 修改 ImageViewer 调用 ```typescript handleImageClick(bodyPosition)} onDoubleClick={() => handleImageDoubleClick(bodyPosition)} // 🆕 添加双击处理 /> ``` --- ### 3️⃣ 修改 `ImageViewer.tsx` **文件路径**: `src/pages/exam/components/ImageViewer.tsx` #### 添加 onDoubleClick 属性支持 ```typescript interface ImageViewerProps { src: string; alt?: string; className?: string; onClick?: () => void; onDoubleClick?: () => void; // 🆕 添加双击属性 } const ImageViewer: React.FC = ({ src, alt = 'Body Position', className = '', onClick, onDoubleClick, // 🆕 }) => { return (
{alt}
); }; ``` **关键修改**: - ✅ Interface 中添加 `onDoubleClick?: () => void` - ✅ 组件接收 `onDoubleClick` 属性 - ✅ 将 `onDoubleClick` 传递给 `Image` 组件 --- ## 🔄 数据流分析 ### 场景1: Exam 模式 - 单击已曝光体位 ⚠️ **核心场景** ```mermaid sequenceDiagram actor User as 👤 用户 participant IV as ImageViewer participant BPL as BodyPositionList participant BS as bodyPositionSelection participant Redux as Redux Store User->>IV: 单击已曝光体位 IV->>BPL: onClick() 触发 BPL->>BS: manualSelectBodyPositionWithFlowSwitch
(allowFlowSwitch=false) BS->>BS: 检查 allowFlowSwitch === false ✅ Note over BS: 跳过流程切换判断 BS->>BS: targetFlow = 'exam' (保持当前) BS->>Redux: setSelectedBodyPosition(bodyPosition) Redux-->>User: 更新选中状态 + 显示缩略图 📷 Note over User: ❌ 不切换到 process 模式 ``` --- ### 场景2: Exam 模式 - 双击已曝光体位 ⚠️ **核心场景** ```mermaid sequenceDiagram actor User as 👤 用户 participant IV as ImageViewer participant BPL as BodyPositionList participant BS as bodyPositionSelection participant Redux as Redux Store User->>IV: 双击已曝光体位 IV->>BPL: onDoubleClick() 触发 BPL->>BS: manualSelectBodyPositionWithFlowSwitch
(allowFlowSwitch=true) BS->>BS: 检查 allowFlowSwitch === true ✅ BS->>BS: 检查 currentKey='exam' && isExposed ✅ BS->>BS: targetFlow = 'process' (需要切换) BS->>Redux: setBusinessFlow('process') Note over Redux: exam → process 流程切换 BS->>Redux: setSelectedBodyPosition(bodyPosition) Redux-->>User: 切换到 process + 显示缩略图 📷 Note over User: ✅ 成功切换到 process 模式 ``` --- ### 场景3: Exam 模式 - 单击未曝光体位(保持不变) ```mermaid sequenceDiagram actor User as 👤 用户 participant IV as ImageViewer participant BPL as BodyPositionList participant BS as bodyPositionSelection participant API as Device API User->>IV: 单击未曝光体位 IV->>BPL: onClick() 触发 BPL->>BS: manualSelectBodyPositionWithFlowSwitch
(allowFlowSwitch=false) BS->>BS: 检查 allowFlowSwitch === false BS->>BS: 但 isUnexposed,不需要切换 BS->>BS: targetFlow = 'exam' BS->>API: changeBodyPosition() + setExpEnable() API-->>BS: 设备同步成功 Note over User: ✅ 同步设备 + 显示示意图 🖼️ ``` --- ## 🧪 测试方案 ### 测试环境准备 需要准备包含以下体位的测试数据: - 至少 2 个未曝光体位 - 至少 2 个已曝光体位 --- ### 测试场景 #### 场景1: Exam 模式 - 单击已曝光体位 ⚠️ **核心测试** **前置条件**:在 exam 模式 **测试步骤**: 1. 单击一个已曝光体位(显示缩略图的) **预期结果**: - ✅ 体位被选中(高亮显示) - ✅ BodyPositionDetail 显示该体位的**缩略图** - ✅ **保持在 exam 模式**(不切换到 process) - ✅ 控制台输出:`Flow switch disabled, staying in exam mode` **验证方法**: ```javascript // Redux State 检查 currentKey === 'exam' // 没有切换流程 bodyPositionDetail.expose_status === 'Exposed' bodyPositionDetail.sop_instance_uid !== '' // 控制台日志 '[BodyPositionList] Single-click on: ...' '[bodyPositionSelection] Flow switch disabled, staying in exam mode' ``` --- #### 场景2: Exam 模式 - 双击已曝光体位 ⚠️ **核心测试** **前置条件**:在 exam 模式 **测试步骤**: 1. 双击一个已曝光体位(显示缩略图的) **预期结果**: - ✅ 体位被选中 - ✅ **自动切换到 process 模式** - ✅ BodyPositionDetail 显示该体位的缩略图 - ✅ 控制台输出:`Detected exposed position in exam mode, will switch to process` **验证方法**: ```javascript // Redux State 检查 currentKey === 'process' // 已切换流程 bodyPositionDetail.expose_status === 'Exposed' // 控制台日志 '[BodyPositionList] Double-click on: ...' '[bodyPositionSelection] Detected exposed position in exam mode, will switch to process' ``` --- #### 场景3: Exam 模式 - 单击未曝光体位(保持原逻辑) **前置条件**:在 exam 模式 **测试步骤**: 1. 单击一个未曝光体位 **预期结果**: - ✅ 体位被选中 - ✅ 设备同步到该体位 - ✅ 曝光使能 - ✅ 保持在 exam 模式 - ✅ 显示示意图 --- #### 场景4: Exam 模式 - 双击未曝光体位(同单击) **前置条件**:在 exam 模式 **测试步骤**: 1. 双击一个未曝光体位 **预期结果**: - ✅ 与单击行为相同(因为未曝光体位不需要流程切换) --- #### 场景5: Process 模式 - 单击/双击行为(保持原逻辑) **前置条件**:在 process 模式 **测试步骤**: 1. 单击未曝光体位 → 应切换到 exam 2. 单击已曝光体位 → 应保持 process 3. 双击行为应与单击相同 **说明**:process 模式下的行为不受本次修改影响,保持原有逻辑。 --- ### 测试用例总结表 | 模式 | 体位状态 | 操作 | 流程切换 | 设备同步 | 详情显示 | 优先级 | |------|---------|------|---------|---------|---------|--------| | exam | Unexposed | 单击 | ❌ 不切换 | ✅ 同步 | 示意图 🖼️ | P0 | | exam | Unexposed | 双击 | ❌ 不切换 | ✅ 同步 | 示意图 🖼️ | P1 | | exam | Exposed | **单击** | **❌ 不切换** | **❌ 跳过** | **缩略图 📷** | **P0** | | exam | Exposed | **双击** | **✅ 切换到 process** | **❌ 跳过** | **缩略图 📷** | **P0** | | process | Unexposed | 单击/双击 | ✅ 切换到 exam | ✅ 同步 | 示意图 🖼️ | P1 | | process | Exposed | 单击/双击 | ❌ 不切换 | ❌ 跳过 | 缩略图 📷 | P1 | --- ## 🐛 边界情况分析 ### 1. 双击误触发两次单击 **问题描述**:双击时可能先触发单击事件,再触发双击事件 **解决方案**: - React 的 `onDoubleClick` 事件会自动处理这种情况 - 浏览器默认行为会在双击时抑制单击事件的完整执行 - 实际测试中验证是否需要添加防抖逻辑 **风险等级**:🟡 中 --- ### 2. 快速连续单击(非双击) **问题描述**:用户快速连续单击两次,但不是双击(间隔较长) **当前处理**:每次单击都会独立处理,不会误判为双击 **风险等级**:🟢 低 --- ### 3. 流程切换中的状态同步 **问题描述**:双击触发流程切换时,状态可能不一致 **当前处理**:已有事件监听机制(`BUSINESS_FLOW_CHANGED`)确保流程切换完成 **风险等级**:🟢 低(已有保护) --- ## 📈 实现优势 ### 1. 用户体验改善 **之前**: - 单击已曝光体位 → 意外切换到 process(用户困惑) **现在**: - 单击已曝光体位 → 只查看,不切换(符合预期) - 双击已曝光体位 → 明确意图进入处理(操作清晰) --- ### 2. 操作直觉性 - **单击** = 查看/选择(低风险操作) - **双击** = 进入/打开(高风险操作) 符合用户对单击/双击的常规认知。 --- ### 3. 向后兼容 - `allowFlowSwitch` 参数默认 `true` - 现有调用不需要修改 - 渐进式升级 --- ## 🔍 调试建议 ### 控制台日志关键字 ```javascript // 单击已曝光体位 '[BodyPositionList] Single-click on: ...' '[bodyPositionSelection] Flow switch disabled, staying in exam mode' // 双击已曝光体位 '[BodyPositionList] Double-click on: ...' '[bodyPositionSelection] Detected exposed position in exam mode, will switch to process' // 流程切换 '[bodyPositionSelection] Switching flow from exam to process' '[bodyPositionSelection] Flow switch completed' ``` --- ### 断点调试位置 1. **BodyPositionList.tsx:35** - `handleImageClick` 函数 2. **BodyPositionList.tsx:45** - `handleImageDoubleClick` 函数 3. **bodyPositionSelection.ts:182** - `allowFlowSwitch` 判断分支 4. **bodyPositionSelection.ts:187** - `currentKey === 'exam' && isExposed` 判断 --- ## 📚 相关文档 - [bodyPositionSelection.ts](../../src/domain/exam/bodyPositionSelection.ts) - 体位选择逻辑 - [BodyPositionList.tsx](../../src/pages/exam/components/BodyPositionList.tsx) - 体位列表组件 - [ImageViewer.tsx](../../src/pages/exam/components/ImageViewer.tsx) - 图像查看组件 - [exam体位列表-单击已曝光体位优化.md](./exam体位列表-单击已曝光体位优化.md) - 之前的单击优化文档 --- ## ✅ 实施检查清单 - [ ] 理解单击/双击的区别和实现原理 - [ ] 确认 `allowFlowSwitch` 参数的默认值和向后兼容性 - [ ] 准备包含已曝光和未曝光体位的测试数据 - [ ] 通知测试团队新的交互行为(单击不切换,双击切换) - [ ] 验证双击不会误触发两次单击事件 --- ## 🚀 实施后验证 完成代码修改后,需要验证: 1. **功能验证** - ✅ Exam 模式单击已曝光 → 不切换流程 - ✅ Exam 模式双击已曝光 → 切换到 process - ✅ 其他场景行为保持不变 2. **交互验证** - ✅ 单击响应迅速,无延迟 - ✅ 双击不会误触发两次单击 - ✅ 操作反馈清晰 3. **兼容性验证** - ✅ 不影响其他页面功能 - ✅ 不影响自动选中逻辑 - ✅ 向后兼容现有调用 --- **文档版本**: v1.0 **创建日期**: 2025/10/13 **作者**: Cline AI Assistant **状态**: ✅ 待实施