本文档详细说明了从检查页面(exam)退出的完整逻辑,包括曝光状态检查、用户反馈、后端通知等流程。
退出检查是一个关键的业务流程,需要根据当前的曝光状态做出不同的处理,以确保检查数据的完整性和用户操作的安全性。
当用户尝试从检查页面离开时(即当前业务流程是 exam
,目标是其他流程),会触发退出逻辑。
关键判断函数:
function isExitingExam(currentAction: string, currentKey: string): boolean {
return currentAction !== 'exam' && currentKey === 'exam';
}
位置: src/states/businessFlowMiddlewareLogic.ts
系统会检查当前的曝光状态(exposureStatus
),根据不同状态采取不同的处理策略。
状态 | 英文 | 说明 | 处理方式 |
---|---|---|---|
未曝光 | Not Exposed | 所有体位都未曝光 | 直接允许退出 |
半曝光 | Half Exposed | 部分体位已曝光,部分未曝光 | 显示退出反馈弹窗 |
全曝光 | Fully Exposed | 所有体位都已曝光 | 通知后端,允许退出 |
当曝光状态为 "Half Exposed" 时:
ExamExitFeedback
处理逻辑:
dispatch(setFeedbackOpen(false)); // 关闭弹窗
dispatch(setBusinessFlow('exitExamSuspended')); // 挂起检查
type="primary"
typescript
dispatch(setFeedbackOpen(false)); // 关闭弹窗
dispatch(setBusinessFlow('exitExamCompleted')); // 完成检查
type="primary" danger
处理逻辑:
dispatch(setFeedbackOpen(false)); // 仅关闭弹窗
// 注意:这里只关闭弹窗,不改变业务流程
业务流程标识: 'exitExamCompleted'
处理逻辑:
if (action.payload === 'exitExamCompleted') {
// 1. 获取当前检查的 StudyID
const studyId = state.bodyPositionList.selectedBodyPosition.work.StudyID;
// 2. 调用API通知后端 - 标记为已完成
await suspendOrCompleteStudy(studyId, 'Completed');
// 3. 跳转到目标业务流程
return next({ ...action, payload: continueBusinessFlow });
}
API参数:
study_id
: 当前检查的IDstudy_status
: 'Completed'
- 标记为已完成业务流程标识: 'exitExamSuspended'
处理逻辑:
if (action.payload === 'exitExamSuspended') {
// 1. 获取当前检查的 StudyID
const studyId = state.bodyPositionList.selectedBodyPosition.work.StudyID;
// 2. 调用API通知后端 - 标记为进行中
await suspendOrCompleteStudy(studyId, 'InProgress');
// 3. 跳转到目标业务流程
return next({ ...action, payload: continueBusinessFlow });
}
API参数:
study_id
: 当前检查的IDstudy_status
: 'InProgress'
- 标记为进行中全局变量: continueBusinessFlow
作用: 保存用户最初想要跳转的目标业务流程
示例场景:
continueBusinessFlow = 'worklist'
worklist
特殊性: 曝光操作可能导致自动进入图像处理页面
判断函数:
function isFromExamToView(currentAction: string, currentKey: string): boolean {
return currentAction === 'process' && currentKey === 'exam';
}
处理逻辑:
if (isFromExamToView(action.payload, currentKey)) {
// 检查发生器状态
if (store.getState().generatorMonitor.acquisitionState === 1) {
// 发生器正在采集 - 阻止退出
console.log('发生器正在采集,不能退出');
return next(action);
} else {
// 非曝光导致的退出 - 执行清理
console.log('从检查退出到处理');
unprepare(); // 清理检查相关资源
}
}
关键状态: generatorMonitor.acquisitionState
1
: 正在采集 - 阻止退出文件路径 | 作用 | 关键内容 |
---|---|---|
src/states/businessFlowMiddlewareLogic.ts |
业务流程中间件(核心) | 所有退出判断和处理逻辑 |
src/pages/exam/components/ExamExitFeedback.tsx |
退出反馈UI组件 | "检查未完成"弹窗 |
src/pages/exam/LargeScreen.tsx |
大屏幕组件 | 集成退出反馈弹窗 |
src/states/exam/largeScreenSlice.ts |
大屏幕状态管理 | 管理反馈弹窗显示状态 |
src/states/BusinessFlowSlice.ts |
业务流程状态管理 | 管理业务流程切换 |
src/API/patient/workActions.ts |
API接口 | suspendOrCompleteStudy() 函数 |
src/domain/exam/prepare.ts |
检查准备逻辑 | prepare() 和 unprepare() 函数 |
文件: src/states/businessFlowMiddlewareLogic.ts
行数: 约 200-250 行
if (
isExitingExam(action.payload, currentKey) &&
action.payload !== 'exitExamCompleted' &&
action.payload !== 'exitExamSuspended' &&
action.payload !== 'process'
) {
const exposureStatus = store.getState().bodyPositionList.exposureStatus;
if (exposureStatus === 'Half Exposed') {
store.dispatch(setFeedbackOpen(true));
continueBusinessFlow = action.payload;
return; // 阻止退出
}
}
文件: src/pages/exam/LargeScreen.tsx
行数: 约 20-30 行
<ExamExitFeedback
open={isFeedbackOpen}
onContinue={() => {
dispatch(setFeedbackOpen(false));
dispatch(setBusinessFlow('exitExamSuspended'));
}}
onSave={() => {
dispatch(setFeedbackOpen(false));
dispatch(setBusinessFlow('exitExamCompleted'));
}}
onAbort={() => dispatch(setFeedbackOpen(false))}
/>
用户点击离开exam
↓
检查是否从exam退出?
↓ (是)
判断目标页面
├─ process (图像处理)
│ ↓
│ 检查发生器状态
│ ├─ acquisitionState === 1 → 阻止退出
│ └─ 其他 → unprepare() → 允许退出
│
└─ 其他页面 (worklist, register, etc.)
↓
检查曝光状态
├─ Not Exposed (未曝光)
│ ↓
│ 直接允许退出
│
├─ Half Exposed (半曝光)
│ ↓
│ 显示退出反馈弹窗
│ ↓
│ 用户选择
│ ├─ 继续检查 → 关闭弹窗,停留在exam
│ ├─ 保存并完成 → API调用(Completed) → 跳转到目标页面
│ └─ 直接中止 → API调用(InProgress) → 跳转到目标页面
│
└─ Fully Exposed (全曝光)
↓
通知后端 → 允许退出
[Exam Page]
|
| 用户触发退出
↓
[检查曝光状态]
|
├─→ [Not Exposed] ──────────→ [直接退出]
|
├─→ [Half Exposed] ──→ [显示弹窗]
| |
| ├─→ [继续检查] → [停留在Exam]
| ├─→ [保存并完成] → [exitExamCompleted]
| └─→ [直接中止] → [exitExamSuspended]
|
└─→ [Fully Exposed] ─────────→ [通知后端] → [退出]
State路径 | 类型 | 说明 |
---|---|---|
largeScreen.isFeedbackOpen |
boolean | 退出反馈弹窗是否显示 |
bodyPositionList.exposureStatus |
string | 曝光状态:'Not Exposed' / 'Half Exposed' / 'Fully Exposed' |
bodyPositionList.selectedBodyPosition.work.StudyID |
string | 当前检查的Study ID |
generatorMonitor.acquisitionState |
number | 发生器采集状态:1=采集中 |
BusinessFlow.currentKey |
string | 当前业务流程 |
变量名 | 类型 | 作用 | 位置 |
---|---|---|---|
continueBusinessFlow |
string | 保存退出检查后要去的目标业务流程 | businessFlowMiddlewareLogic.ts |
函数签名:
const suspendOrCompleteStudy = async (
studyId: string,
studyStatus: 'InProgress' | 'Completed'
): Promise<{ code: string; description: string; solution: string; data: {} }>
API端点:
POST /auth/task/inspection/leave
请求参数:
{
"study_id": "string",
"study_status": "InProgress" | "Completed"
}
响应示例:
{
"code": "0x000000",
"description": "Success",
"solution": "",
"data": {}
}
调用场景:
studyStatus = 'InProgress'
studyStatus = 'Completed'
前置条件:
测试步骤:
data-testid="exam-page"
存在)预期结果:
前置条件:
测试步骤:
预期结果:
isFeedbackOpen = false
exam
前置条件:
测试步骤:
/auth/task/inspection/leave
study_id
: 当前StudyIDstudy_status
: "Completed"预期结果:
前置条件:
测试步骤:
/auth/task/inspection/leave
study_id
: 当前StudyIDstudy_status
: "InProgress"预期结果:
前置条件:
测试步骤:
预期结果:
前置条件:
acquisitionState = 1
)测试步骤:
预期结果:
前置条件:
acquisitionState != 1
)测试步骤:
unprepare()
清理函数预期结果:
前置条件:
测试步骤:
预期结果:
前置条件:
测试步骤:
预期结果:
Action | 类型 | Payload | 说明 |
---|---|---|---|
setBusinessFlow |
Action | string | 切换业务流程 |
setFeedbackOpen |
Action | boolean | 控制退出反馈弹窗显示 |
推荐创建以下Selectors方便测试和使用:
// 选择曝光状态
const selectExposureStatus = (state: RootState) =>
state.bodyPositionList.exposureStatus;
// 选择当前StudyID
const selectCurrentStudyId = (state: RootState) =>
state.bodyPositionList.selectedBodyPosition?.work?.StudyID;
// 选择反馈弹窗状态
const selectIsFeedbackOpen = (state: RootState) =>
state.largeScreen.isFeedbackOpen;
// 选择发生器状态
const selectAcquisitionState = (state: RootState) =>
state.generatorMonitor.acquisitionState;
API调用失败:
StudyID为空:
按钮禁用状态:
清晰的提示信息:
操作确认:
防抖处理:
状态检查优化:
自动保存:
退出历史:
智能提示:
状态机模式:
抽象公共逻辑:
日期 | 修改人 | 修改内容 |
---|---|---|
2025/10/7 | - | 创建文档,详细说明退出检查的逻辑 |