功能名称: Exam 页面体位列表 - 双击已曝光体位进入处理
实现状态: 🔄 待实施
核心需求: 区分单击和双击已曝光体位的行为,只有双击才切换到 process 模式
当前行为(不符合预期):
期望行为:
模式 | 体位状态 | 单击行为 | 双击行为 |
---|---|---|---|
exam | Unexposed | 选中 + 同步设备 + 显示示意图 🖼️ | 同左 |
exam | Exposed | 只选中 + 显示缩略图(不切换) 📷 | 选中 + 切换到 process ➡️ |
process | Unexposed | 选中 + 切换到 exam + 同步设备 | 同左 |
process | Exposed | 选中 + 保持 process | 同左 |
在 src/domain/exam/bodyPositionSelection.ts
中的 manualSelectBodyPositionWithFlowSwitch
函数:
// 第182-187行 - 问题代码
if (currentKey === 'exam' && isExposed) {
targetFlow = 'process';
needSwitch = true; // ❌ 无论单击还是双击都会切换
console.log(`Detected exposed position in exam mode, will switch to process`);
}
问题:函数没有区分是单击还是双击触发,导致单击也会切换流程。
manualSelectBodyPositionWithFlowSwitch
函数中添加 allowFlowSwitch
参数BodyPositionList.tsx
中分别处理单击和双击事件ImageViewer.tsx
组件中添加 onDoubleClick
属性支持文件 | 修改类型 | 具体内容 |
---|---|---|
bodyPositionSelection.ts |
🔧 修改函数签名 | 添加 allowFlowSwitch 参数 |
BodyPositionList.tsx |
➕ 添加双击处理 | 添加 handleImageDoubleClick 函数 |
ImageViewer.tsx |
➕ 添加属性 | 添加 onDoubleClick 属性支持 |
bodyPositionSelection.ts
文件路径: src/domain/exam/bodyPositionSelection.ts
/**
* 带流程切换的体位选择(用户点击体位时调用,根据曝光状态自动切换流程)
* @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<void> => {
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
时才执行流程切换判断BodyPositionList.tsx
文件路径: src/pages/exam/components/BodyPositionList.tsx
// 🆕 新增:处理双击事件
const handleImageDoubleClick = async (
bodyPosition: ExtendedBodyPosition
): Promise<void> => {
console.log(`[BodyPositionList] Double-click on: ${bodyPosition.view_name}`);
// 双击时,允许自动流程切换
await manualSelectBodyPositionWithFlowSwitch(
bodyPosition,
dispatch,
currentKey,
true // allowFlowSwitch = true(允许流程切换)
);
};
// 🔧 修改:处理单击事件
const handleImageClick = async (
bodyPosition: ExtendedBodyPosition
): Promise<void> => {
console.log(`[BodyPositionList] Single-click on: ${bodyPosition.view_name}`);
// 单击时,禁止自动流程切换
await manualSelectBodyPositionWithFlowSwitch(
bodyPosition,
dispatch,
currentKey,
false // allowFlowSwitch = false(禁止流程切换)
);
};
<ImageViewer
src={
bodyPosition.dview.expose_status === 'Exposed'
? getExposedImageUrl(bodyPosition.sop_instance_uid)
: getViewIconUrl(bodyPosition.view_icon_name)
}
className={`image-viewer-item hover:border-[var(--color-primary)] hover:border-4
${
bodyPosition.sop_instance_uid ===
selectedBodyPosition?.sop_instance_uid
? 'border-4 border-[var(--color-primary)] '
: ''
}`}
onClick={() => handleImageClick(bodyPosition)}
onDoubleClick={() => handleImageDoubleClick(bodyPosition)} // 🆕 添加双击处理
/>
ImageViewer.tsx
文件路径: src/pages/exam/components/ImageViewer.tsx
interface ImageViewerProps {
src: string;
alt?: string;
className?: string;
onClick?: () => void;
onDoubleClick?: () => void; // 🆕 添加双击属性
}
const ImageViewer: React.FC<ImageViewerProps> = ({
src,
alt = 'Body Position',
className = '',
onClick,
onDoubleClick, // 🆕
}) => {
return (
<div className={`image-viewer ${className}`}>
<Image
src={src}
alt={alt}
onClick={onClick}
onDoubleClick={onDoubleClick} // 🆕 传递双击事件
preview={false}
fallback={defaultPosition}
/>
</div>
);
};
关键修改:
onDoubleClick?: () => void
onDoubleClick
属性onDoubleClick
传递给 Image
组件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<br/>(allowFlowSwitch=false)
BS->>BS: 检查 allowFlowSwitch === false ✅
Note over BS: 跳过流程切换判断
BS->>BS: targetFlow = 'exam' (保持当前)
BS->>Redux: setSelectedBodyPosition(bodyPosition)
Redux-->>User: 更新选中状态 + 显示缩略图 📷
Note over User: ❌ 不切换到 process 模式
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<br/>(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 模式
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<br/>(allowFlowSwitch=false)
BS->>BS: 检查 allowFlowSwitch === false
BS->>BS: 但 isUnexposed,不需要切换
BS->>BS: targetFlow = 'exam'
BS->>API: changeBodyPosition() + setExpEnable()
API-->>BS: 设备同步成功
Note over User: ✅ 同步设备 + 显示示意图 🖼️
需要准备包含以下体位的测试数据:
前置条件:在 exam 模式
测试步骤:
预期结果:
Flow switch disabled, staying in exam mode
验证方法:
// Redux State 检查
currentKey === 'exam' // 没有切换流程
bodyPositionDetail.expose_status === 'Exposed'
bodyPositionDetail.sop_instance_uid !== ''
// 控制台日志
'[BodyPositionList] Single-click on: ...'
'[bodyPositionSelection] Flow switch disabled, staying in exam mode'
前置条件:在 exam 模式
测试步骤:
预期结果:
Detected exposed position in exam mode, will switch to process
验证方法:
// Redux State 检查
currentKey === 'process' // 已切换流程
bodyPositionDetail.expose_status === 'Exposed'
// 控制台日志
'[BodyPositionList] Double-click on: ...'
'[bodyPositionSelection] Detected exposed position in exam mode, will switch to process'
前置条件:在 exam 模式
测试步骤:
预期结果:
前置条件:在 exam 模式
测试步骤:
预期结果:
前置条件:在 process 模式
测试步骤:
说明:process 模式下的行为不受本次修改影响,保持原有逻辑。
模式 | 体位状态 | 操作 | 流程切换 | 设备同步 | 详情显示 | 优先级 |
---|---|---|---|---|---|---|
exam | Unexposed | 单击 | ❌ 不切换 | ✅ 同步 | 示意图 🖼️ | P0 |
exam | Unexposed | 双击 | ❌ 不切换 | ✅ 同步 | 示意图 🖼️ | P1 |
exam | Exposed | 单击 | ❌ 不切换 | ❌ 跳过 | 缩略图 📷 | P0 |
exam | Exposed | 双击 | ✅ 切换到 process | ❌ 跳过 | 缩略图 📷 | P0 |
process | Unexposed | 单击/双击 | ✅ 切换到 exam | ✅ 同步 | 示意图 🖼️ | P1 |
process | Exposed | 单击/双击 | ❌ 不切换 | ❌ 跳过 | 缩略图 📷 | P1 |
问题描述:双击时可能先触发单击事件,再触发双击事件
解决方案:
onDoubleClick
事件会自动处理这种情况风险等级:🟡 中
问题描述:用户快速连续单击两次,但不是双击(间隔较长)
当前处理:每次单击都会独立处理,不会误判为双击
风险等级:🟢 低
问题描述:双击触发流程切换时,状态可能不一致
当前处理:已有事件监听机制(BUSINESS_FLOW_CHANGED
)确保流程切换完成
风险等级:🟢 低(已有保护)
之前:
现在:
符合用户对单击/双击的常规认知。
allowFlowSwitch
参数默认 true
// 单击已曝光体位
'[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'
handleImageClick
函数handleImageDoubleClick
函数allowFlowSwitch
判断分支currentKey === 'exam' && isExposed
判断allowFlowSwitch
参数的默认值和向后兼容性完成代码修改后,需要验证:
功能验证
交互验证
兼容性验证
文档版本: v1.0
创建日期: 2025/10/13
作者: Cline AI Assistant
状态: ✅ 待实施