RIS同步功能实现方案.md 19 KB

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() - 显示错误弹框

📊 系统架构图

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

🔄 交互流程

序列图

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)

数据流向图

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

💾 数据结构

核心数据类型

// 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;      // 错误信息
}

类图

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进行结果提示
// 在ActionPanel.tsx中
const [risSyncing, setRisSyncing] = useState(false);

2. RIS同步结果弹框

简单的Modal实现

// 成功弹框
const showSyncSuccessModal = (count: number, onOk: () => void) => {
  Modal.success({
    title: '🎉 RIS同步成功',
    content: (
      <div style={{ padding: '16px 0' }}>
        <p style={{ fontSize: '16px', margin: '8px 0' }}>
          共同步 <strong style={{ color: '#52c41a' }}>{count}</strong> 条数据
        </p>
        <p style={{ color: '#666', margin: '8px 0' }}>
          系统将自动刷新列表显示最新数据
        </p>
      </div>
    ),
    okText: '确定',
    centered: true,
    onOk: onOk
  });
};

// 失败弹框
const showSyncErrorModal = (errorMessage: string) => {
  Modal.error({
    title: '❌ RIS同步失败',
    content: (
      <div style={{ padding: '16px 0' }}>
        <p style={{ margin: '8px 0' }}>{errorMessage}</p>
        <p style={{ color: '#666', fontSize: '14px' }}>
          请检查网络连接或联系技术支持
        </p>
      </div>
    ),
    okText: '确定',
    centered: true
  });
};

3. 自动搜索刷新逻辑

复用SearchPanel的搜索逻辑

// 在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按钮处理函数

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]);

// 按钮组件
<ActionButton
  icon={<Icon module="module-patient" name="RIS" />}
  tooltip={<FormattedMessage id="actionPanel.risSync" />}
  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同步

// 解决方案:条件验证
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. 大量数据同步

问题: 同步大量数据可能导致内存和性能问题

// 解决方案:分批处理和进度提示
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. 网络超时处理

问题: 网络不稳定可能导致同步超时

// 解决方案:超时重试机制
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)));
    }
  }
};

异常处理机制

错误处理层级

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同步权限
  • 数据隔离: 确保用户只能访问授权数据
  • 操作日志: 记录所有同步操作

📈 监控和日志

操作监控

// 监控关键指标
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()
    });
  }
};

错误日志

// 错误追踪
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服务器接口变更

📚 相关文档

🚀 实施步骤

开发顺序建议

  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 (简化版本)
  • 👥 维护人员: 开发团队
  • 🎯 实现状态: 设计完成,待开发实施