RIS保存本地功能实现方案.md 14 KB

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_idStudyID 等字段

实现任务清单

修改文件:

  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 和其他已定义类型

删除:

  • 无(纯增量功能)

参与者交互泳道图

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 更新列表
└── 失败时:错误提示弹框

数据结构定义

请求数据结构

type RisSaveBatchRequest = string[]; // entry_id 字符串数组

响应数据结构

interface StandardApiResponse {
  code: string;
  description: string;
  solution: string;
  data: EmptyResponseData;
}

内部使用数据结构

// 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. 结束:列表自动刷新,用户看到更新结果

流程异常处理

  • 网络超时:显示连接超时提示,不刷新列表
  • 服务器错误:根据错误码显示对应错误信息
  • 权限不足:显示权限不足提示
  • 数据异常:显示数据处理失败提示

类图设计

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. 自动刷新测试

    • 修改查询条件后保存
    • 预期:刷新时应用新的查询条件

性能测试场景

  1. 大批量保存测试

    • 操作:选中50条RIS数据批量保存
    • 预期:响应时间<3秒,无卡顿
  2. 并发操作测试

    • 操作:快速连续点击保存按钮
    • 预期:防重复提交,后续操作被忽略

兼容性测试场景

  1. 数据兼容测试
    • 前置:混合选中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秒)
    • 列表自动刷新,显示最新数据

失败测试步骤

  1. 模拟失败场景:通过开发者工具阻塞网络或返回错误码
  2. 触发保存:点击按钮触发API调用
  3. 验证错误处理:显示错误提示弹框,包含错误信息;列表不刷新

边界测试步骤

  1. 无选中测试:不选中任何条目,按钮应禁用
  2. 非RIS数据测试:选中无entry_id条目,按钮应禁用
  3. 面板切换测试:切换到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组件行为差异
  • 影响:弹框消失时间不一致
  • 解决方案
    • 使用统一的时间管理库
    • 测试所有目标浏览器
    • 提供配置化的弹出时长