# RIS同步功能实现方案 ## 📖 概述 本文档详细描述了RIS(Radiology Information System)同步功能的简化实现方案。该功能允许用户通过ActionPanel中的RIS按钮,基于当前搜索条件触发后端RIS同步,然后显示同步结果并自动刷新列表。 ## 🔄 核心流程 **简化的RIS同步流程**: 1. 用户点击RIS按钮 2. 获取当前搜索条件 3. 调用syncRis API触发后端同步 4. 显示同步结果弹框(显示同步数据量) 5. 用户确认后自动重新搜索(等同于点击search按钮) ## 🏗️ 架构设计 ### 参与者层级分析 #### 架构层级参与者 - **UI层**: `ActionPanel.tsx` - RIS按钮交互入口 - **状态管理层**: Redux slices - 管理同步状态和数据流 - **业务逻辑层**: Thunk异步操作 - 处理同步逻辑 - **API层**: `risActions.ts` - RIS接口调用 - **数据层**: `work.ts` Task实体和数据结构 #### 组件级参与者 **主要组件**: - `ActionPanel.tsx` - 包含RIS按钮的操作面板 - `SearchPanel.tsx` - 提供同步条件的搜索面板 - `WorklistTable` - 显示同步结果的表格 **辅助组件**: - `WorklistPage` - 工作列表页面容器 - 消息通知组件 - 显示同步结果反馈 #### 方法级参与者 **API方法**: - `syncRis()` - 执行RIS同步(核心方法) - `fetchWorkThunk()` - 重新搜索刷新列表 **UI方法**: - `handleRisSync()` - RIS按钮点击处理 - `triggerSearch()` - 触发自动搜索 - `showSyncSuccessModal()` - 显示成功弹框 - `showSyncErrorModal()` - 显示错误弹框 ## 📊 系统架构图 ```mermaid graph TB subgraph "🎨 UI层" A[ActionPanel RIS按钮] B[SearchPanel 搜索条件] C[WorklistTable 数据显示] D[Modal 结果弹框] end subgraph "🏪 状态管理层" E[searchSlice 搜索状态] F[workSlice 列表数据] end subgraph "🌐 API层" G[syncRis API] H[fetchTaskList API] end subgraph "💾 数据层" I[RIS服务器] J[本地数据库] end A --> G A --> D B --> E D --> H G --> I H --> J F --> C E --> B style A fill:#e1f5fe style D fill:#f1f8e9 style G fill:#e8f5e8 style I fill:#fff3e0 ``` ## 🔄 交互流程 ### 序列图 ```mermaid sequenceDiagram participant User as 👤 用户 participant ActionPanel as 🎛️ ActionPanel participant SearchState as 📋 搜索状态 participant RisAPI as 🌐 RIS API participant Backend as 🏢 后端RIS同步 participant Modal as 📋 结果弹框 participant WorkList as 📊 工作列表 User->>ActionPanel: 点击RIS按钮 ActionPanel->>ActionPanel: setRisSyncing(true) ActionPanel->>SearchState: 读取搜索条件 SearchState-->>ActionPanel: 返回条件参数 ActionPanel->>RisAPI: syncRis(参数) RisAPI->>Backend: 执行RIS数据同步 Backend-->>RisAPI: 返回同步结果 RisAPI-->>ActionPanel: 同步完成 + 数据量 alt 同步成功 ActionPanel->>Modal: showSyncSuccessModal Modal-->>User: 显示"同步X条数据" User->>Modal: 点击确定 Modal->>ActionPanel: onOk回调 ActionPanel->>WorkList: triggerSearch() WorkList-->>User: 显示更新列表 else 同步失败 ActionPanel->>Modal: showSyncErrorModal Modal-->>User: 显示错误信息 end ActionPanel->>ActionPanel: setRisSyncing(false) ``` ### 数据流向图 ```mermaid flowchart TD A[🔍 当前搜索条件] --> B[🎛️ RIS按钮点击] B --> C[🌐 调用syncRis API] C --> D[🏢 后端执行RIS同步] D --> E[📊 返回同步结果] E --> F[📋 显示结果弹框] F --> G[🔍 自动触发搜索] G --> H[📋 刷新列表显示] style B fill:#e3f2fd style F fill:#f1f8e9 style H fill:#fce4ec ``` ## 💾 数据结构 ### 核心数据类型 ```typescript // RIS同步请求参数 interface RisSyncRequest { start_time: string; // RFC3339格式时间 end_time: string; // RFC3339格式时间 id?: string; // 患者ID(可选) name?: string; // 患者姓名(可选) acc_no?: string; // 登记号(可选) } // RIS同步响应 interface RisSyncResponse { code: string; // 响应码 description: string; // 描述信息 solution: string; // 解决方案 data: { '@type': string; // 类型标识 count: number; // 同步的条目数量 }; } // 搜索状态(复用现有结构) interface SearchState { id: string; name: string; acc_no: string; start_time: string; end_time: string; timeRangeType: 'today' | '7days' | 'all'; } // RIS同步状态 interface RisSyncState { loading: boolean; // 同步进行中 lastSyncTime: string; // 最后同步时间 syncCount: number; // 本次同步数量 error: string | null; // 错误信息 } ``` ### 类图 ```mermaid classDiagram class ActionPanel { +handleRisSync() +triggerSearch() +showSyncSuccessModal() +showSyncErrorModal() +risSyncing: boolean } class SearchSlice { +id: string +name: string +acc_no: string +start_time: string +end_time: string +setId() +setName() +setTimeRange() } class RisAPI { +syncRis(params: RisSyncRequest) +getRisConfig() +saveRisSingle() +saveRisBatch() } class WorkSlice { +fetchWorkThunk() +deleteWorkThunk() +lockWorkThunk() } class Modal { +success() +error() } ActionPanel --> SearchSlice : 读取搜索条件 ActionPanel --> RisAPI : 直接调用API ActionPanel --> Modal : 显示结果弹框 ActionPanel --> WorkSlice : 触发搜索刷新 ``` ## 🔧 实现细节 ### 1. 简化的状态管理 #### 无需复杂Redux状态 基于新的需求,我们采用更简单的方案: - **本地状态**: 只在ActionPanel中使用`useState`管理loading状态 - **复用现有逻辑**: 直接复用SearchPanel中的搜索逻辑 - **简单弹框**: 使用antd的Modal.success/error进行结果提示 ```typescript // 在ActionPanel.tsx中 const [risSyncing, setRisSyncing] = useState(false); ``` ### 2. RIS同步结果弹框 #### 简单的Modal实现 ```typescript // 成功弹框 const showSyncSuccessModal = (count: number, onOk: () => void) => { Modal.success({ title: '🎉 RIS同步成功', content: (

共同步 {count} 条数据

系统将自动刷新列表显示最新数据

), okText: '确定', centered: true, onOk: onOk }); }; // 失败弹框 const showSyncErrorModal = (errorMessage: string) => { Modal.error({ title: '❌ RIS同步失败', content: (

{errorMessage}

请检查网络连接或联系技术支持

), okText: '确定', centered: true }); }; ``` ### 3. 自动搜索刷新逻辑 #### 复用SearchPanel的搜索逻辑 ```typescript // 在ActionPanel.tsx中添加搜索触发函数 const triggerSearch = useCallback(() => { // 重置分页 dispatch(setPage(1)); dispatch(setPageSize(10)); // 获取当前搜索条件 const searchState = useSelector((state: RootState) => state.search); const commonFilters = { patient_id: searchState.id, patient_name: searchState.name, access_number: searchState.acc_no, start_time: searchState.start_time, end_time: searchState.end_time, }; // 根据当前页面类型调用相应的搜索 if (currentKey === 'bin') { dispatch(fetchBinThunk({ page: 1, pageSize: 10, filters: commonFilters as BinFilter, })); } else { const status = currentKey === 'worklist' ? 'Arrived,InProgress' : 'Completed'; dispatch(fetchWorkThunk({ page: 1, pageSize: 10, filters: { ...commonFilters, status } as WorkFilter, })); } }, [dispatch, currentKey]); ``` ### 4. ActionPanel完整实现 #### RIS按钮处理函数 ```typescript const handleRisSync = useCallback(async () => { try { setRisSyncing(true); // 获取搜索条件 const searchState = useSelector((state: RootState) => state.search); // 构建同步参数 const syncParams: RisSyncRequest = { start_time: searchState.start_time, end_time: searchState.end_time, id: searchState.id || undefined, name: searchState.name || undefined, acc_no: searchState.acc_no || undefined }; // 调用RIS同步API const result = await syncRis(syncParams); // 显示成功弹框 showSyncSuccessModal(result.data.count, () => { // 自动触发搜索 triggerSearch(); }); } catch (error) { console.error('RIS同步失败:', error); showSyncErrorModal(error.message || '同步过程中发生未知错误'); } finally { setRisSyncing(false); } }, [triggerSearch]); // 按钮组件 } tooltip={} onClick={handleRisSync} loading={risSyncing} disabled={risSyncing} /> ``` ### 5. 实现优势 #### 简化架构的优势 1. **更简单**: 无需复杂的Redux状态管理 2. **更直接**: API调用 → 结果弹框 → 自动搜索 3. **更易维护**: 复用现有的搜索逻辑,减少代码重复 4. **用户体验好**: 清晰的反馈流程和自动刷新 #### 数据流特点 - **后端处理**: RIS同步在后端完成,前端只触发和展示结果 - **自动刷新**: 同步成功后自动重新搜索,无需手动刷新 - **状态简单**: 只需要管理按钮的loading状态 ## 🧪 测试方案 ### 🔍 **功能测试场景** | 测试场景 | 前置条件 | 操作步骤 | 预期结果 | 验收标准 | |---------|---------|---------|---------|---------| | **正常同步流程** | 设置有效搜索条件 | 1. 在搜索面板设置患者姓名"张三"
2. 设置时间范围为今天
3. 点击RIS按钮
4. 在弹框中点击确定 | 1. 按钮显示loading状态
2. 弹框显示"共同步X条数据"
3. 点击确定后列表自动刷新
4. 新同步的数据出现在列表中 | ✅ 完整流程无错误
✅ 数据正确同步和显示 | | **同步无数据** | 设置不匹配的条件 | 1. 设置未来时间范围
2. 点击RIS按钮 | 弹框显示"共同步0条数据" | ✅ 正确显示零条记录
✅ 不影响现有列表 | | **网络异常处理** | 模拟网络断开 | 1. 断开网络
2. 设置搜索条件
3. 点击RIS按钮 | 显示错误弹框,提示网络错误 | ✅ 错误信息清晰
✅ 不执行列表刷新 | | **服务器错误处理** | 模拟API返回错误 | 1. 模拟syncRis返回错误
2. 点击RIS按钮 | 显示具体的错误信息弹框 | ✅ 显示服务器错误详情
✅ 提供解决建议 | | **防重复点击** | 同步进行中 | 1. 点击RIS按钮
2. 在同步完成前再次点击 | 按钮禁用,无法重复点击 | ✅ 按钮disabled状态
✅ 防止重复请求 | | **弹框交互** | 同步成功 | 1. 同步成功后显示弹框
2. 不点击确定,直接关闭弹框 | 弹框关闭,不执行搜索 | ✅ 用户可以取消后续操作 | ### 边界条件测试 | 边界条件 | 测试场景 | 预期行为 | |---------|---------|---------| | **时间格式** | 测试各种时间格式输入 | 正确转换为RFC3339格式 | | **特殊字符** | 患者姓名包含特殊字符 | 正确编码和传输 | | **超长字符串** | 输入超长的患者姓名 | 适当截断或验证 | | **并发操作** | 同时进行多个操作 | 操作互不干扰 | ### 性能测试 | 测试项目 | 测试条件 | 性能要求 | 验收标准 | |---------|---------|---------|---------| | **同步响应时间** | 同步100条数据 | < 3秒 | 用户体验良好 | | **内存使用** | 同步大量数据后 | < 100MB增长 | 内存使用合理 | | **UI响应性** | 同步进行中 | UI保持响应 | 不阻塞用户操作 | ### 兼容性测试 | 测试环境 | 测试内容 | 预期结果 | |---------|---------|---------| | **不同浏览器** | Chrome、Firefox、Edge | 功能正常 | | **不同屏幕尺寸** | 手机、平板、桌面 | 布局适配 | | **不同网络条件** | 快速、慢速、不稳定 | 优雅降级 | ## 🐛 潜在问题分析 ### 边界情况处理 #### 1. 搜索条件验证 **问题**: 用户可能在没有设置任何搜索条件时点击RIS同步 ```typescript // 解决方案:条件验证 const validateSearchConditions = (searchState: SearchState): boolean => { const hasTimeRange = searchState.start_time && searchState.end_time; const hasPatientInfo = searchState.id || searchState.name || searchState.acc_no; if (!hasTimeRange && !hasPatientInfo) { message.warning('请至少设置时间范围或患者信息'); return false; } return true; }; ``` #### 2. 大量数据同步 **问题**: 同步大量数据可能导致内存和性能问题 ```typescript // 解决方案:分批处理和进度提示 const handleLargeDataSync = async (params: RisSyncRequest) => { // 1. 预估数据量 const estimatedCount = await estimateSyncCount(params); if (estimatedCount > 1000) { // 2. 显示确认对话框 const confirmed = await showConfirmDialog( `预计同步 ${estimatedCount} 条数据,可能需要较长时间,是否继续?` ); if (!confirmed) return; // 3. 显示进度条 showProgressModal(); } // 4. 执行同步 return await syncRis(params); }; ``` #### 3. 网络超时处理 **问题**: 网络不稳定可能导致同步超时 ```typescript // 解决方案:超时重试机制 const risSyncWithRetry = async (params: RisSyncRequest, retries = 3) => { for (let i = 0; i < retries; i++) { try { return await syncRis(params); } catch (error) { if (i === retries - 1) throw error; // 等待后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } }; ``` ### 异常处理机制 #### 错误处理层级 ```mermaid flowchart TD A[用户操作] --> B[UI层验证] B --> C[Redux Thunk] C --> D[API调用] D --> E[服务器响应] B --> F[显示验证错误] C --> G[显示业务错误] D --> H[显示网络错误] E --> I[显示服务器错误] F --> J[用户反馈] G --> J H --> J I --> J style F fill:#ffebee style G fill:#fff3e0 style H fill:#e8f5e8 style I fill:#f3e5f5 ``` #### 具体错误处理策略 | 错误类型 | 处理策略 | 用户反馈 | |---------|---------|---------| | **验证错误** | 前端验证,阻止请求 | 友好的提示信息 | | **网络错误** | 自动重试,超时处理 | 网络状态提示 | | **权限错误** | 引导用户登录 | 权限说明 | | **服务器错误** | 记录日志,显示错误 | 技术支持联系方式 | | **数据错误** | 数据回滚,状态恢复 | 操作失败说明 | ### 性能优化考虑 #### 1. 内存管理 - **数据分页**: 避免一次性加载大量数据 - **虚拟滚动**: 大列表使用虚拟滚动 - **内存清理**: 及时清理无用数据 #### 2. 网络优化 - **请求去重**: 防止重复请求 - **请求缓存**: 缓存频繁请求的结果 - **压缩传输**: 启用gzip压缩 #### 3. UI性能 - **异步加载**: 非阻塞UI更新 - **防抖处理**: 防止频繁操作 - **加载状态**: 提供清晰的加载反馈 ## 🔒 安全考虑 ### 数据安全 - **参数验证**: 严格验证所有输入参数 - **SQL注入防护**: 后端参数化查询 - **XSS防护**: 前端数据转义 ### 权限控制 - **接口权限**: 验证用户RIS同步权限 - **数据隔离**: 确保用户只能访问授权数据 - **操作日志**: 记录所有同步操作 ## 📈 监控和日志 ### 操作监控 ```typescript // 监控关键指标 const monitorRisSync = { syncCount: 0, // 同步次数 totalSyncedItems: 0, // 总同步条目数 averageResponseTime: 0, // 平均响应时间 errorRate: 0, // 错误率 // 记录同步操作 logSyncOperation: (params: RisSyncRequest, result: RisSyncResponse) => { console.log('RIS同步操作', { timestamp: new Date().toISOString(), params, result, responseTime: performance.now() }); } }; ``` ### 错误日志 ```typescript // 错误追踪 const logRisSyncError = (error: Error, context: any) => { console.error('RIS同步错误', { error: error.message, stack: error.stack, context, timestamp: new Date().toISOString(), userAgent: navigator.userAgent }); // 发送到监控服务 sendErrorToMonitoring(error, context); }; ``` ## 🚀 部署和维护 ### 部署检查清单 - [ ] API接口测试通过 - [ ] 前端功能测试通过 - [ ] 性能测试满足要求 - [ ] 安全审计通过 - [ ] 文档更新完成 ### 维护指南 - **定期监控**: 关注同步成功率和响应时间 - **日志分析**: 定期分析错误日志,优化问题点 - **用户反馈**: 收集用户使用反馈,持续改进 - **版本更新**: 跟进RIS服务器接口变更 ## 📚 相关文档 - [RIS API 接口文档](src/API/patient/risActions.ts) - [工作列表状态管理](src/states/patient/worklist/slices/workSlice.ts) - [搜索面板组件](src/pages/patient/components/SearchPanel.tsx) - [操作面板组件](src/pages/patient/components/ActionPanel.tsx) ## 🚀 实施步骤 ### **开发顺序建议** 1. **第一步**: 修改ActionPanel.tsx,添加handleRisSync函数 2. **第二步**: 实现Modal弹框显示逻辑 3. **第三步**: 添加triggerSearch自动搜索功能 4. **第四步**: 完善错误处理和loading状态 5. **第五步**: 进行完整的功能测试 ### **代码变更清单** - **修改文件**: `src/pages/patient/components/ActionPanel.tsx` - **新增依赖**: 无(使用现有的antd Modal和RIS API) - **测试文件**: 需要更新ActionPanel的单元测试 ## 📋 总结 ### **核心优势** ✅ **简单高效**: 无复杂状态管理,直接API调用 ✅ **用户友好**: 清晰的反馈流程和自动刷新 ✅ **易于维护**: 复用现有逻辑,代码变更最小 ✅ **稳定可靠**: 完整的错误处理和边界情况考虑 ### **技术亮点** - 复用SearchPanel的搜索逻辑实现自动刷新 - 使用antd Modal提供一致的用户体验 - 本地状态管理避免复杂的Redux配置 - 完整的错误处理和用户反馈机制 ### **实现效果** 用户点击RIS按钮 → 后端同步数据 → 弹框显示结果 → 自动刷新列表,整个流程简洁流畅,符合用户期望。 --- **文档信息** - 📅 最后更新时间: 2025-01-30 - 📋 文档版本: v2.0 (简化版本) - 👥 维护人员: 开发团队 - 🎯 实现状态: 设计完成,待开发实施