|
|
@@ -0,0 +1,391 @@
|
|
|
+# 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 {
|
|
|
+ +<<static>> isRisEnabled(): Promise~boolean~
|
|
|
+ +<<static>> saveAllRisEntries(): Promise~number~
|
|
|
+ +<<static>> 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组件行为差异
|
|
|
+- **影响**:弹框消失时间不一致
|
|
|
+- **解决方案**:
|
|
|
+ - 使用统一的时间管理库
|
|
|
+ - 测试所有目标浏览器
|
|
|
+ - 提供配置化的弹出时长
|