|
@@ -0,0 +1,326 @@
|
|
|
|
|
+# 急诊注销后重新登录直接进入检查页面问题修复
|
|
|
|
|
+
|
|
|
|
|
+## 问题描述
|
|
|
|
|
+
|
|
|
|
|
+以急诊身份登录系统后,退出到登录页面,然后使用正常账号登录,会直接进入检查页面,跳过了注册页面。
|
|
|
|
|
+
|
|
|
|
|
+## 问题现象
|
|
|
|
|
+
|
|
|
|
|
+1. 用户以急诊身份登录系统
|
|
|
|
|
+2. 进入检查页面(exam)
|
|
|
|
|
+3. 注销退出到登录页面
|
|
|
|
|
+4. 使用正常账号重新登录
|
|
|
|
|
+5. **系统直接进入检查页面,跳过了注册页面**
|
|
|
|
|
+
|
|
|
|
|
+## 根本原因分析
|
|
|
|
|
+
|
|
|
|
|
+### 急诊登录时设置的状态
|
|
|
|
|
+
|
|
|
|
|
+在 `src/domain/patient/handleEmergencyOperation.ts` 中,急诊登录时会设置多个状态:
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+// Step 2: Set system mode to Emergency
|
|
|
|
|
+dispatch(setSystemMode(SystemMode.Emergency));
|
|
|
|
|
+
|
|
|
|
|
+// Step 3: Set temporary user info with guest token
|
|
|
|
|
+dispatch(setUserInfo({
|
|
|
|
|
+ token: guestToken,
|
|
|
|
|
+ expire: Date.now() + 24 * 60 * 60 * 1000,
|
|
|
|
|
+ uid: -1, // Special uid to identify emergency mode
|
|
|
|
|
+ name: 'Emergency User',
|
|
|
|
|
+ avatar: '',
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+// Step 6: Save registration result to cache
|
|
|
|
|
+const task = mapToTask(registrationResult.data);
|
|
|
|
|
+dispatch(addWork(task)); // ← 添加急诊工单到缓存
|
|
|
|
|
+
|
|
|
|
|
+// Step 7: Proceed to Examination
|
|
|
|
|
+dispatch(setBusinessFlow('exam')); // ← 设置业务流程为检查页面
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 注销时只清除了部分状态
|
|
|
|
|
+
|
|
|
|
|
+在修复前,`src/components/ExitModal.tsx` 的注销逻辑只清除了:
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+case 'logout':
|
|
|
|
|
+ actionName = '注销用户';
|
|
|
|
|
+ dispatch(clearUserInfo()); // ✅ 清除用户信息
|
|
|
|
|
+ dispatch(setSystemMode(SystemMode.Normal)); // ✅ 重置系统模式
|
|
|
|
|
+ message.success('已退出登录');
|
|
|
|
|
+ onClose();
|
|
|
|
|
+ return;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**关键问题**:注销时**没有重置以下状态**:
|
|
|
|
|
+- ❌ `BusinessFlow.currentKey` - 仍然是 'exam'
|
|
|
|
|
+- ❌ `examWorksCache.works` - 仍包含急诊工单数据
|
|
|
|
|
+- ❌ `bodyPositionList.bodyPositions` - 仍包含急诊体位数据
|
|
|
|
|
+- ❌ `bodyPositionList.selectedBodyPosition` - 仍有选中的体位
|
|
|
|
|
+
|
|
|
|
|
+### 问题的原因链
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+注销操作
|
|
|
|
|
+ ↓
|
|
|
|
|
+只清除 userInfo 和 systemMode
|
|
|
|
|
+ ↓
|
|
|
|
|
+BusinessFlow.currentKey 仍然是 'exam'
|
|
|
|
|
+examWorksCache.works 仍包含急诊患者数据
|
|
|
|
|
+bodyPositionList 仍包含急诊体位数据
|
|
|
|
|
+ ↓
|
|
|
|
|
+用户重新登录(普通账号)
|
|
|
|
|
+ ↓
|
|
|
|
|
+系统检测到 BusinessFlow.currentKey === 'exam'
|
|
|
|
|
+ ↓
|
|
|
|
|
+直接渲染检查页面
|
|
|
|
|
+ ↓
|
|
|
|
|
+用户看到上一个急诊患者的数据!(数据泄露)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 各个 Slice 的初始状态
|
|
|
|
|
+
|
|
|
|
|
+#### BusinessFlowSlice
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+const initialState: BusinessFlowState = {
|
|
|
|
|
+ currentKey: 'register', // ← 默认应该是注册页面
|
|
|
|
|
+ lastKey: '',
|
|
|
|
|
+ shouldKeepSelection: false,
|
|
|
|
|
+ keptSelectionSopUid: undefined,
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### examWorksCacheSlice
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+const initialState: ExamWorksCacheState = {
|
|
|
|
|
+ works: [], // ← 默认是空数组
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### bodyPositionListSlice
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+const initialState: BodyPositionListState = {
|
|
|
|
|
+ bodyPositions: [], // ← 默认是空数组
|
|
|
|
|
+ selectedBodyPosition: null, // ← 默认是 null
|
|
|
|
|
+ exposureStatus: null,
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ error: null,
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 安全隐患
|
|
|
|
|
+
|
|
|
|
|
+### 数据泄露风险
|
|
|
|
|
+
|
|
|
|
|
+如果不清理以下状态,会导致严重的患者隐私泄露:
|
|
|
|
|
+
|
|
|
|
|
+1. **examWorksCache.works** - 包含患者信息:
|
|
|
|
|
+ - PatientName(患者姓名)
|
|
|
|
|
+ - PatientID(患者ID)
|
|
|
|
|
+ - AccessionNumber(登记号)
|
|
|
|
|
+ - StudyDescription(检查描述)
|
|
|
|
|
+ - Views(检查视图/体位信息)
|
|
|
|
|
+
|
|
|
|
|
+2. **bodyPositionList.bodyPositions** - 包含详细的检查数据:
|
|
|
|
|
+ - 患者基本信息
|
|
|
|
|
+ - 检查图像数据
|
|
|
|
|
+ - 体位配置参数
|
|
|
|
|
+ - DICOM 实例 UID
|
|
|
|
|
+
|
|
|
|
|
+### 业务逻辑问题
|
|
|
|
|
+
|
|
|
|
|
+1. 新用户登录后直接进入检查页面,跳过注册流程
|
|
|
|
|
+2. 新用户可能看到并误操作其他患者的数据
|
|
|
|
|
+3. 系统状态不一致,可能导致其他功能异常
|
|
|
|
|
+
|
|
|
|
|
+## 解决方案
|
|
|
|
|
+
|
|
|
|
|
+### 方案对比
|
|
|
|
|
+
|
|
|
|
|
+#### 方案 A:使用现有 action(已采用)
|
|
|
|
|
+
|
|
|
|
|
+优点:
|
|
|
|
|
+- ✅ 不需要修改 slice 代码
|
|
|
|
|
+- ✅ 使用现有 action,改动最小
|
|
|
|
|
+- ✅ 功能完全满足需求
|
|
|
|
|
+
|
|
|
|
|
+缺点:
|
|
|
|
|
+- ⚠️ 需要调用多个 dispatch
|
|
|
|
|
+
|
|
|
|
|
+#### 方案 B:添加专用清理 action
|
|
|
|
|
+
|
|
|
|
|
+优点:
|
|
|
|
|
+- ✅ 更优雅,一次调用清理所有状态
|
|
|
|
|
+- ✅ 可以添加日志追踪
|
|
|
|
|
+
|
|
|
|
|
+缺点:
|
|
|
|
|
+- ❌ 需要修改多个 slice
|
|
|
|
|
+- ❌ 改动较大
|
|
|
|
|
+
|
|
|
|
|
+### 最终采用方案 A
|
|
|
|
|
+
|
|
|
|
|
+在 `src/components/ExitModal.tsx` 中完整清理所有相关状态:
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+case 'logout':
|
|
|
|
|
+ actionName = '注销用户';
|
|
|
|
|
+ // 应用级注销:清除所有用户相关状态
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 清除用户信息
|
|
|
|
|
+ dispatch(clearUserInfo());
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 重置系统模式,避免急诊模式注销后黑屏
|
|
|
|
|
+ dispatch(setSystemMode(SystemMode.Normal));
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 重置业务流程到注册页面,避免直接进入检查页面
|
|
|
|
|
+ dispatch(setBusinessFlow('register'));
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 清理工单缓存,避免患者数据泄露
|
|
|
|
|
+ dispatch(clearWorks());
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 清理体位列表,避免患者数据泄露
|
|
|
|
|
+ dispatch(setBodyPositions([]));
|
|
|
|
|
+ dispatch(setSelectedBodyPosition(null));
|
|
|
|
|
+
|
|
|
|
|
+ message.success('已退出登录');
|
|
|
|
|
+ onClose();
|
|
|
|
|
+ return;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 修改文件
|
|
|
|
|
+
|
|
|
|
|
+### src/components/ExitModal.tsx
|
|
|
|
|
+
|
|
|
|
|
+**新增导入**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+import { setBusinessFlow } from '../states/BusinessFlowSlice';
|
|
|
|
|
+import { clearWorks } from '../states/exam/examWorksCacheSlice';
|
|
|
|
|
+import {
|
|
|
|
|
+ setBodyPositions,
|
|
|
|
|
+ setSelectedBodyPosition,
|
|
|
|
|
+} from '../states/exam/bodyPositionListSlice';
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**修改注销逻辑**:添加了3个额外的状态清理操作。
|
|
|
|
|
+
|
|
|
|
|
+## 需要清理的完整状态列表
|
|
|
|
|
+
|
|
|
|
|
+| 序号 | State | 清理方法 | 原因 |
|
|
|
|
|
+|------|-------|----------|------|
|
|
|
|
|
+| 1 | `userInfo` | `clearUserInfo()` | 清除用户登录信息 |
|
|
|
|
|
+| 2 | `systemMode` | `setSystemMode(SystemMode.Normal)` | 重置系统模式,避免黑屏 |
|
|
|
|
|
+| 3 | `BusinessFlow` | `setBusinessFlow('register')` | 重置到注册页面,避免直接进入检查 |
|
|
|
|
|
+| 4 | `examWorksCache` | `clearWorks()` | **清理工单缓存,避免患者数据泄露** |
|
|
|
|
|
+| 5 | `bodyPositionList` | `setBodyPositions([])` + `setSelectedBodyPosition(null)` | **清理体位数据,避免患者数据泄露** |
|
|
|
|
|
+
|
|
|
|
|
+## 测试验证
|
|
|
|
|
+
|
|
|
|
|
+### 测试步骤
|
|
|
|
|
+
|
|
|
|
|
+1. 启动应用
|
|
|
|
|
+2. 点击"急诊"按钮以急诊身份登录
|
|
|
|
|
+3. 验证进入检查页面,能看到急诊患者信息
|
|
|
|
|
+4. 点击退出按钮
|
|
|
|
|
+5. 在退出 Modal 中点击"注销用户"
|
|
|
|
|
+6. 点击确认
|
|
|
|
|
+7. 验证返回登录页面
|
|
|
|
|
+8. 使用正常账号登录(如 admin/123456)
|
|
|
|
|
+
|
|
|
|
|
+### 预期结果
|
|
|
|
|
+
|
|
|
|
|
+- ✅ 显示"已退出登录"消息
|
|
|
|
|
+- ✅ Modal 关闭
|
|
|
|
|
+- ✅ 返回登录页面
|
|
|
|
|
+- ✅ 重新登录后进入**注册页面**(不是检查页面)
|
|
|
|
|
+- ✅ **看不到**上一个急诊患者的任何数据
|
|
|
|
|
+- ✅ 系统完全重置到初始状态
|
|
|
|
|
+
|
|
|
|
|
+### 状态验证
|
|
|
|
|
+
|
|
|
|
|
+注销后应确保:
|
|
|
|
|
+- ✅ `userInfo.token` = ''
|
|
|
|
|
+- ✅ `userInfo.uid` = 0
|
|
|
|
|
+- ✅ `systemMode` = 'Normal'
|
|
|
|
|
+- ✅ `BusinessFlow.currentKey` = 'register'
|
|
|
|
|
+- ✅ `examWorksCache.works` = []
|
|
|
|
|
+- ✅ `bodyPositionList.bodyPositions` = []
|
|
|
|
|
+- ✅ `bodyPositionList.selectedBodyPosition` = null
|
|
|
|
|
+- ✅ `loggedIn` = false
|
|
|
|
|
+
|
|
|
|
|
+## 影响范围
|
|
|
|
|
+
|
|
|
|
|
+### 修改的文件
|
|
|
|
|
+- `src/components/ExitModal.tsx`
|
|
|
|
|
+
|
|
|
|
|
+### 影响的功能
|
|
|
|
|
+- ✅ 急诊注销流程 - **重要改进**
|
|
|
|
|
+- ✅ 普通用户注销流程 - 不受影响
|
|
|
|
|
+- ✅ 其他退出方式(关闭程序、关机)- 不受影响
|
|
|
|
|
+
|
|
|
|
|
+### 解决的问题
|
|
|
|
|
+1. ✅ 注销后重新登录直接进入检查页面 - **已修复**
|
|
|
|
|
+2. ✅ 患者数据泄露风险 - **已消除**
|
|
|
|
|
+3. ✅ 系统状态不一致 - **已修复**
|
|
|
|
|
+
|
|
|
|
|
+## 注意事项
|
|
|
|
|
+
|
|
|
|
|
+1. **数据安全**:
|
|
|
|
|
+ - 此修复消除了严重的患者隐私泄露风险
|
|
|
|
|
+ - 确保注销后所有患者相关数据被清除
|
|
|
|
|
+ - 符合医疗信息安全要求
|
|
|
|
|
+
|
|
|
|
|
+2. **业务逻辑**:
|
|
|
|
|
+ - 注销后系统完全重置到初始状态
|
|
|
|
|
+ - 新用户登录时从注册页面开始,符合正常流程
|
|
|
|
|
+ - 不会看到或误操作其他患者的数据
|
|
|
|
|
+
|
|
|
|
|
+3. **与已有功能的兼容性**:
|
|
|
|
|
+ - 此修改与现有的注销逻辑完全兼容
|
|
|
|
|
+ - 不会影响普通用户的注销体验
|
|
|
|
|
+ - 不会影响关闭程序和关机功能
|
|
|
|
|
+
|
|
|
|
|
+4. **状态管理最佳实践**:
|
|
|
|
|
+ - 使用现有 action 进行状态清理
|
|
|
|
|
+ - 代码改动最小,降低引入新 bug 的风险
|
|
|
|
|
+ - 注释清晰,便于维护
|
|
|
|
|
+
|
|
|
|
|
+## 相关文档
|
|
|
|
|
+
|
|
|
|
|
+- [急诊退出黑屏问题修复](./急诊退出黑屏问题修复.md) - 前一个问题的修复
|
|
|
|
|
+- [注销功能实现总结](../logout-implementation-summary.md) - 注销功能整体设计
|
|
|
|
|
+- [急诊流程](./急诊流程.md) - 急诊功能设计
|
|
|
|
|
+
|
|
|
|
|
+## 完成状态
|
|
|
|
|
+
|
|
|
|
|
+- [x] 问题分析
|
|
|
|
|
+- [x] 安全隐患评估
|
|
|
|
|
+- [x] 解决方案设计
|
|
|
|
|
+- [x] 代码修改
|
|
|
|
|
+- [ ] 测试验证(待手动测试)
|
|
|
|
|
+
|
|
|
|
|
+## 后续优化建议
|
|
|
|
|
+
|
|
|
|
|
+1. **添加 Cypress 测试**:
|
|
|
|
|
+ - 创建急诊注销并重新登录的端到端测试
|
|
|
|
|
+ - 验证所有状态都被正确清理
|
|
|
|
|
+ - 验证无法访问上一个用户的数据
|
|
|
|
|
+
|
|
|
|
|
+2. **考虑添加统一的清理 action**:
|
|
|
|
|
+ - 在未来可以考虑在各个 slice 中添加 `reset` 或 `clear` action
|
|
|
|
|
+ - 提供一个统一的清理接口
|
|
|
|
|
+ - 便于在其他场景下重用
|
|
|
|
|
+
|
|
|
|
|
+3. **添加状态监控**:
|
|
|
|
|
+ - 在开发环境下监控状态清理是否完整
|
|
|
|
|
+ - 记录清理操作的日志
|
|
|
|
|
+ - 便于调试和问题追踪
|
|
|
|
|
+
|
|
|
|
|
+4. **安全审计**:
|
|
|
|
|
+ - 定期审查是否有其他地方需要清理敏感数据
|
|
|
|
|
+ - 确保所有用户切换场景都正确清理数据
|
|
|
|
|
+ - 符合医疗数据保护法规要求
|