Ver Fonte

fix: 修复急诊注销后重新登录直接进入检查页面问题

- 在 ExitModal.tsx 中添加完整的状态清理逻辑
- 重置业务流程到注册页面,避免直接进入检查页面
- 清理工单缓存和体位列表,消除患者数据泄露风险
- 添加详细的问题分析和安全隐患评估文档
- 更新版本号至 1.1.5

改动文件:
- src/components/ExitModal.tsx
- docs/实现/急诊注销后重新登录直接进入检查页面问题修复.md
- package.json
dengdx há 5 dias atrás
pai
commit
400a3ff754

+ 326 - 0
docs/实现/急诊注销后重新登录直接进入检查页面问题修复.md

@@ -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. **安全审计**:
+   - 定期审查是否有其他地方需要清理敏感数据
+   - 确保所有用户切换场景都正确清理数据
+   - 符合医疗数据保护法规要求

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "zsis",
-  "version": "1.1.4",
+  "version": "1.1.5",
   "private": true,
   "description": "医学成像系统",
   "main": "main.js",

+ 17 - 2
src/components/ExitModal.tsx

@@ -8,6 +8,12 @@ import {
 import { useDispatch } from 'react-redux';
 import { clearUserInfo } from '../states/user_info';
 import { setSystemMode, SystemMode } from '../states/systemModeSlice';
+import { setBusinessFlow } from '../states/BusinessFlowSlice';
+import { clearWorks } from '../states/exam/examWorksCacheSlice';
+import {
+  setBodyPositions,
+  setSelectedBodyPosition,
+} from '../states/exam/bodyPositionListSlice';
 
 const { Text } = Typography;
 
@@ -80,9 +86,18 @@ const ExitModal: React.FC<ExitModalProps> = ({ visible, onClose }) => {
           break;
         case 'logout':
           actionName = '注销用户';
-          // 应用级注销:清除 Redux 用户状态并重置系统模式
+          // 应用级注销:清除所有用户相关状态
+          // 1. 清除用户信息
           dispatch(clearUserInfo());
-          dispatch(setSystemMode(SystemMode.Normal)); // 重置系统模式,避免急诊模式注销后黑屏
+          // 2. 重置系统模式,避免急诊模式注销后黑屏
+          dispatch(setSystemMode(SystemMode.Normal));
+          // 3. 重置业务流程到注册页面,避免直接进入检查页面
+          dispatch(setBusinessFlow('register'));
+          // 4. 清理工单缓存,避免患者数据泄露
+          dispatch(clearWorks());
+          // 5. 清理体位列表,避免患者数据泄露
+          dispatch(setBodyPositions([]));
+          dispatch(setSelectedBodyPosition(null));
           message.success('已退出登录');
           onClose();
           return; // 直接返回,不需要后续处理