# RIS保存本地功能实现方案 ## 概述 RIS(Radiology Information System,放射信息系统)保存本地功能允许医务人员将从RIS系统同步过来的检查请求数据批量保存到本地工作列表中,该功能提升了RIS数据的管理效率,实现本地检查流程的无缝衔接。本文档详细描述了该功能的需求规格、实现设计、数据流和测试方案。 ## 需求规格 ### 功能描述 实现【RIS保存本地】功能,用于将RIS数据批量保存到本地系统,支持医务人员在worklist视图中批量处理RIS检查请求。 ### 业务意义 - **提升效率**:支持批量保存多个RIS条目,减少重复操作 - **标准化流程**:统一RIS数据进入本地工作流程的标准做法 - **数据同步**:确保RIS系统与本地工作站的数据一致性 - **用户体验**:提供直观的操作界面,成功后自动刷新列表 ### 涉及接口 - **后端接口**:`src/API/patient/risActions.ts` 中的 `saveRisBatch` (批量保存本地接口) - **请求参数**:`RisSaveBatchRequest` 类型(entry_id 字符串数组) - **响应**:`StandardApiResponse` ### UI界面需求 - **触发按钮**:在 `src/pages/patient/components/ActionPanel.tsx` 中新增"保存本地"按钮 - **按钮可见性**:仅在 `currentKey` 为 `'worklist'` 时显示该按钮 - **按钮可用性**:worklist列表中选中至少一个RIS数据时才可用(依据:选中项的 `entry_id` 不为空) - **前后端调用**:传递worklist列表中选中所有条目的 `entry_id` 形成的数组给后端 ### 流程需求 1. 用户在worklist中选中具备entry_id的条目 2. 点击"保存本地"按钮 3. 前端调用 `saveRisBatch(entryIds)` 接口 4. 后端处理成功后,前端弹出暂逝提示弹框 5. 自动触发基于当前查询条件的列表刷新 ## 参与者清单(从粗到细) ### 大类参与者: - **用户**:触发功能的发起者 - **前端UI组件**:负责显示按钮、处理点击事件 - **前端状态管理**:Redux store,管理选中项、搜索条件、面板状态 - **前端API调用层**:负责后端接口调用 - **后端服务**:处理批量保存请求,返回结果 - **前端工具函数**:处理自动拉取列表逻辑 ### 具体组件/类/方法: - **组件层**: - `src/pages/patient/components/ActionPanel.tsx` - 主动作面板组件 - `ActionPanel` 组件:包含所有按钮和逻辑 - `ActionButton` 组件:按钮包装组件 - **API层**: - `src/API/patient/risActions.ts`:RIS相关API - `saveRisBatch(entryIds: RisSaveBatchRequest)` - 批量保存接口函数 - **状态管理层**: - Redux slices: - `workSelection` slice: `selectedIds` - worklist选中ID - `historySelection` slice: `selectedIds` - history选中ID - `BusinessFlow` slice: `currentKey` - 当前面板标识 - `workEntities` slice: `data` - worklist条目数据 - `historyEntities` slice: `data` - history条目数据 - `search` slice: 查询条件状态 - **工具函数**: - 触发搜索的函数(将在 `ActionPanel.tsx` 中实现) - **UI元素**: - "保存本地"按钮(新增) - 成功提示弹框(使用 Ant Design Modal 或 message) - **数据模型**: - Worklist条目数据结构:包含 `entry_id`、`StudyID` 等字段 ## 实现任务清单 ### 修改文件: 1. **src/pages/patient/components/ActionPanel.tsx** - 添加新按钮组件定义:"保存本地"按钮 - 修改按钮列表渲染逻辑:只有 `currentKey === 'worklist'` 时显示该按钮 - 添加 `handleRisSave` 方法: - 检查选中项是否有至少一个 `entry_id` 不为空的条目 - 收集所有选中条目的 `entry_id` - 调用 `saveRisBatch(entryIds)` - 处理成功:显示暂逝弹框提示,然后触发自动拉取列表 - 处理失败:显示错误提示 - 添加按钮可用性控制逻辑:基于选中项和 `entry_id` 检查 2. **src/API/patient/risActions.ts** - 无需修改,已有 `saveRisBatch` 函数 3. **src/states/patient/worklist/slices/workSlice.ts** - 无需修改,但依赖其 `fetchWorkThunk` 用于自动拉取 ### 新增方法/类型: - 在 `ActionPanel.tsx` 中新增: - `handleRisSave` - 处理RIS保存逻辑的方法 - `showSaveSuccessModal` - 显示成功提示弹框的方法(类似现有 `showSyncSuccessModal`) - `getRisEnabledSelectedIds` - 获取RIS可用选中ID的方法 - 无需新增类型:使用现有 `RisSaveBatchRequest` 和其他已定义类型 ### 删除: - 无(纯增量功能) ## 参与者交互泳道图 ```mermaid sequenceDiagram participant 用户 participant ActionPanel组件 participant 状态管理 participant API调用层 participant 后端服务 participant 查询系统 用户->>ActionPanel组件: 点击"保存本地"按钮 ActionPanel组件->>ActionPanel组件: 检查可用性(entry_id不为空的选中项) ActionPanel组件->>ActionPanel组件: 收集entry_ids数组 ActionPanel组件->>API调用层: 调用saveRisBatch(entryIds) API调用层->>后端服务: POST /auth/study/ris/batch 后端服务->>API调用层: 返回StandardApiResponse API调用层->>ActionPanel组件: 成功/失败响应 ActionPanel组件->>状态管理: 更新loading状态 ActionPanel组件->>ActionPanel组件: 显示成功弹框(暂逝) ActionPanel组件->>ActionPanel组件: 触发triggerSearch函数 triggerSearch->>状态管理: 重置分页(1,10) triggerSearch->>状态管理: 获取search条件 triggerSearch->>查询系统: 调用fetchWorkThunk 查询系统->>状态管理: 更新workEntities数据 状态管理->>ActionPanel组件: 通知UI更新 ``` ## 数据流模型 ``` 数据流: ======================= 输入数据 ======================= ├── 用户选中项ID数组 ← workSelection.selectedIds ├── 当前面板标识 ← BusinessFlow.currentKey ├── 工作列表实体数据 ← workEntities.data └── 查询条件 ← search slice (id, name, acc_no, start_time, end_time) ======================= 中间处理 ======================= ├── 过滤逻辑:选中ID + workEntities → 筛选entry_id≠null的条目 → entryIds数组 ├── API调用:entryIds → saveRisBatch API → StandardApiResponse ======================= 输出结果 ======================= ├── 成功时:暂逝提示弹框 + 触发 fetchWorkThunk 更新列表 └── 失败时:错误提示弹框 ``` ## 数据结构定义 ### 请求数据结构 ```typescript type RisSaveBatchRequest = string[]; // entry_id 字符串数组 ``` ### 响应数据结构 ```typescript interface StandardApiResponse { code: string; description: string; solution: string; data: EmptyResponseData; } ``` ### 内部使用数据结构 ```typescript // Worklist条目 interface WorkItem { StudyID: string; entry_id?: string; // RIS条目ID,不为空表示RIS数据 PatientName?: string; // ... 其他字段 } // 查询条件 interface SearchState { id?: string; name?: string; acc_no?: string; start_time?: string; end_time?: string; } ``` ## 执行流程详述 ### 起点 **起点:用户操作** - 在worklist视图下,选中一条或多条RIS数据后,点击"保存本地"按钮。 ### 完整执行流程 1. **前提检查**:Confirm in worklist面板(`currentKey === 'worklist'`),选中至少一条含有entry_id的条目 2. **用户触发**:点击"保存本地"按钮 3. **可用性验证**:前端检查是否有entry_id不为空的选中项 4. **数据收集**:从选中项中提取所有entry_id,形成字符串数组 5. **API调用**:调用`saveRisBatch(entryIds)`,传入entry_id数组 6. **请求发送**:POST /auth/study/ris/batch,body包含entryIds 7. **后端处理**:后端接收entryIds,批量保存到本地 8. **响应返回**:返回`StandardApiResponse` 9. **成功处理**: - 显示暂逝成功弹框(显示保存数量) - 触发自动拉取:基于search条件调用`fetchWorkThunk` 10. **失败处理**:显示错误提示弹框 11. **结束**:列表自动刷新,用户看到更新结果 ### 流程异常处理 - **网络超时**:显示连接超时提示,不刷新列表 - **服务器错误**:根据错误码显示对应错误信息 - **权限不足**:显示权限不足提示 - **数据异常**:显示数据处理失败提示 ## 类图设计 ```mermaid classDiagram class ActionPanel { +currentKey: string +workSelectedIds: string[] +workEntities: WorkItem[] +searchState: SearchState +risSaving: boolean +handleRisSave() +getRisSelectedIds() +showSaveSuccessModal(count) +triggerSearch() } class RisManager { +<> isRisEnabled(): Promise~boolean~ +<> saveAllRisEntries(): Promise~number~ +<> syncTodayRis(): Promise~number~ } class RisActions { +saveRisBatch(entryIds): Promise~StandardApiResponse~ +saveRisSingle(params): Promise~RegisterWorkResponse~ +syncRis(params): Promise~RisSyncResponse~ } class ReduxStore { +workSelection: WorkSelectionSlice +workEntities: WorkEntitiesSlice +search: SearchSlice +BusinessFlow: BusinessFlowSlice } ActionPanel --> RisActions : 调用API ActionPanel --> ReduxStore : 读取/更新状态 RisActions --> RisManager : 扩展功能 RisManager --> RisActions : 底层调用 ``` ## 测试方案制定 ### 🧪 测试场景列表 #### 功能测试场景 1. **正常保存场景** - 前置:worklist中有多条RIS数据可选 - 操作:选中2-3条RIS数据,点击"保存本地" - 预期:API调用成功,显示"成功保存X条数据"弹框,弹框自动消失,列表刷新 2. **边界条件测试** - 操作:只有一个选中项,点击保存 - 操作:选中5+条以上数据,点击保存 - 预期:正常处理,无性能问题 3. **可用性控制测试** - 前置:选中非RIS数据(无entry_id) - 预期:按钮禁用,不可用 - 前置:切换到history面板 - 预期:按钮不显示 4. **错误处理测试** - 模拟网络异常或API失败 - 预期:显示错误提示弹框,列表不刷新 5. **自动刷新测试** - 修改查询条件后保存 - 预期:刷新时应用新的查询条件 #### 性能测试场景 6. **大批量保存测试** - 操作:选中50条RIS数据批量保存 - 预期:响应时间<3秒,无卡顿 7. **并发操作测试** - 操作:快速连续点击保存按钮 - 预期:防重复提交,后续操作被忽略 #### 兼容性测试场景 8. **数据兼容测试** - 前置:混合选中RIS和非RIS数据 - 预期:只处理RIS数据,忽略非RIS数据 ### 🔧 测试操作步骤 #### 前置条件 - 应用已启动,登录后进入patient页面 - 当前在worklist tab - worklist中存在RIS数据条目(具备entry_id字段) #### 成功测试步骤 1. **进入worklist视图**:确认当前面板为worklist,显示"保存本地"按钮 2. **选中RIS数据**:选择一条或多条包含entry_id的worklist条目 3. **点击保存本地**:点击"保存本地"按钮(按钮应可用状态) 4. **观察调用过程**:查看控制台日志,确认调用saveRisBatch API 5. **成功场景验证**: - API返回成功(code: "0x000000") - 显示暂逝提示弹框:"成功保存X条RIS数据到本地" - 弹框自动消失(3-5秒) - 列表自动刷新,显示最新数据 #### 失败测试步骤 6. **模拟失败场景**:通过开发者工具阻塞网络或返回错误码 7. **触发保存**:点击按钮触发API调用 8. **验证错误处理**:显示错误提示弹框,包含错误信息;列表不刷新 #### 边界测试步骤 9. **无选中测试**:不选中任何条目,按钮应禁用 10. **非RIS数据测试**:选中无entry_id条目,按钮应禁用 11. **面板切换测试**:切换到history,按钮应消失 ## 🐛 潜在问题分析 ### 1. 并发冲突问题 - **场景**:用户快速连续点击保存按钮 - **影响**:可能引起重复保存或API竞态 - **解决方案**: - 添加deboucing机制,连续请求间隔至少1秒 - 使用loading状态禁用按钮直到请求完成 - 后端增加幂等性检查 ### 2. 数据一致性问题 - **场景**:保存过程中,其他用户修改了相同RIS数据 - **影响**:可能造成数据覆盖或冲突 - **解决方案**: - 获取entry_id时同时获取版本号 - 后端检查版本冲突并返回适当错误 ### 3. 大批量处理性能问题 - **场景**:选中大量条目(>100)进行批量保存 - **影响**:前端/后端响应慢,用户体验差 - **解决方案**: - 前端限制单次最多50个条目 - 显示进度条和处理状态 - 支持分页批量处理 ### 4. 网络异常容错 - **场景**:网络断开或后端服务失效 - **影响**:用户无法知晓保存状态 - **解决方案**: - 实现本地暂存机制,网络恢复后重试 - 提供离线状态指示 - 详细的错误信息提示 ### 5. UI状态同步问题 - **场景**:Redux状态更新与UI渲染不同步 - **影响**:按钮状态显示错误,造成困惑 - **解决方案**: - 使用useEffect监控状态变化 - 統一状态管理逻辑 - 添加loading skeleton或spinner ### 6. 查询条件失效问题 - **场景**:保存后自动拉取使用错误的查询条件 - **影响**:列表刷新后不显示预期数据 - **解决方案**: - 确保从正确的search slice获取条件 - 添加查询条件校验 - 日志记录实际使用的查询参数 ### 7. 权限控制缺失问题 - **场景**:当前用户无RIS保存权限 - **影响**:用户可看到按钮但操作失败 - **解决方案**: - 前端预检用户权限,隐藏按钮 - 后端权限校验,返回适当错误码 - 统一的权限管理框架 ### 8. 浏览器兼容性问题 - **场景**:不同浏览器对Modal组件行为差异 - **影响**:弹框消失时间不一致 - **解决方案**: - 使用统一的时间管理库 - 测试所有目标浏览器 - 提供配置化的弹出时长