|
|
@@ -0,0 +1,407 @@
|
|
|
+# RIS自动同步功能 - 最终实现方案
|
|
|
+
|
|
|
+## 📋 功能概述
|
|
|
+
|
|
|
+RIS自动同步功能在后台静默运行,定期从RIS系统同步数据到本地,确保worklist显示最新信息。
|
|
|
+
|
|
|
+## 🎯 核心需求
|
|
|
+
|
|
|
+1. **自动同步机制**
|
|
|
+ - worklist组件挂载时启动自动同步
|
|
|
+ - worklist组件卸载时停止自动同步
|
|
|
+ - 根据配置的时间间隔自动触发
|
|
|
+
|
|
|
+2. **配置管理**
|
|
|
+ - 在程序启动时获取RIS配置
|
|
|
+ - 配置存储在全局Redux state中
|
|
|
+ - worklist从全局state读取配置
|
|
|
+
|
|
|
+3. **状态反馈**
|
|
|
+ - 使用Antd Message弹框显示同步结果
|
|
|
+ - 成功/失败信息自动消失
|
|
|
+ - 不显示固定的UI组件
|
|
|
+
|
|
|
+## 🏗️ 实现架构
|
|
|
+
|
|
|
+### 文件结构
|
|
|
+
|
|
|
+```
|
|
|
+src/
|
|
|
+├── states/
|
|
|
+│ └── ris/
|
|
|
+│ └── risSyncSlice.ts # RIS配置和状态管理
|
|
|
+├── hooks/
|
|
|
+│ └── useRisAutoSync.ts # 自动同步Hook
|
|
|
+├── services/
|
|
|
+│ └── risSync/
|
|
|
+│ └── RisSyncService.ts # 同步服务类
|
|
|
+├── pages/
|
|
|
+│ ├── index/
|
|
|
+│ │ └── AppInitializer.tsx # [修改] 启动时获取配置
|
|
|
+│ └── patient/
|
|
|
+│ └── worklist.tsx # [修改] 集成自动同步
|
|
|
+└── states/
|
|
|
+ └── store.ts # [修改] 注册reducer
|
|
|
+```
|
|
|
+
|
|
|
+## 🔄 执行流程
|
|
|
+
|
|
|
+### 1. 程序启动流程
|
|
|
+
|
|
|
+```mermaid
|
|
|
+sequenceDiagram
|
|
|
+ participant App as 应用启动
|
|
|
+ participant Init as AppInitializer
|
|
|
+ participant API
|
|
|
+ participant Redux
|
|
|
+
|
|
|
+ App->>Init: 初始化应用
|
|
|
+ Init->>API: getRisConfig()
|
|
|
+ API-->>Init: RIS配置
|
|
|
+ Init->>Redux: 保存配置到全局state
|
|
|
+ Redux-->>Init: 配置保存成功
|
|
|
+```
|
|
|
+
|
|
|
+### 2. Worklist自动同步流程
|
|
|
+
|
|
|
+```mermaid
|
|
|
+sequenceDiagram
|
|
|
+ participant User
|
|
|
+ participant Worklist
|
|
|
+ participant Hook as useRisAutoSync
|
|
|
+ participant Redux
|
|
|
+ participant Service as RisSyncService
|
|
|
+ participant API
|
|
|
+ participant Message
|
|
|
+
|
|
|
+ User->>Worklist: 进入worklist页面
|
|
|
+ Worklist->>Hook: 组件挂载,启动Hook
|
|
|
+ Hook->>Redux: 读取RIS配置
|
|
|
+ Redux-->>Hook: 返回配置
|
|
|
+
|
|
|
+ alt 自动同步启用
|
|
|
+ Hook->>Service: 启动定时器
|
|
|
+ loop 每N分钟
|
|
|
+ Service->>API: syncRis()
|
|
|
+ API-->>Service: 同步结果
|
|
|
+ Service->>API: fetchTaskList()
|
|
|
+ API-->>Service: 任务列表
|
|
|
+ Service->>Redux: 更新worklist数据
|
|
|
+ Service->>Message: 显示同步结果
|
|
|
+ Note over Message: 3秒后自动消失
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ User->>Worklist: 离开worklist页面
|
|
|
+ Worklist->>Hook: 组件卸载
|
|
|
+ Hook->>Service: 清理定时器
|
|
|
+```
|
|
|
+
|
|
|
+## 📊 数据结构
|
|
|
+
|
|
|
+### Redux State
|
|
|
+
|
|
|
+```typescript
|
|
|
+// RIS同步状态(精简版)
|
|
|
+interface RisSyncState {
|
|
|
+ // 配置信息(程序启动时获取)
|
|
|
+ config: {
|
|
|
+ mwl_enable: boolean;
|
|
|
+ mwl_refresh_enable: boolean;
|
|
|
+ mwl_refresh_interval: number;
|
|
|
+ } | null;
|
|
|
+
|
|
|
+ // 运行时状态(不需要UI显示)
|
|
|
+ isRunning: boolean; // 同步是否正在运行
|
|
|
+ lastSyncTime: string | null; // 最后同步时间
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 💻 代码实现
|
|
|
+
|
|
|
+### 1. risSyncSlice.ts
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
|
|
+import { getRisConfig, RisConfigData } from '@/API/patient/risActions';
|
|
|
+
|
|
|
+interface RisSyncState {
|
|
|
+ config: RisConfigData | null;
|
|
|
+ isRunning: boolean;
|
|
|
+ lastSyncTime: string | null;
|
|
|
+}
|
|
|
+
|
|
|
+const initialState: RisSyncState = {
|
|
|
+ config: null,
|
|
|
+ isRunning: false,
|
|
|
+ lastSyncTime: null,
|
|
|
+};
|
|
|
+
|
|
|
+// 获取配置的thunk(程序启动时调用)
|
|
|
+export const fetchRisConfigThunk = createAsyncThunk(
|
|
|
+ 'risSync/fetchConfig',
|
|
|
+ async () => {
|
|
|
+ const response = await getRisConfig();
|
|
|
+ return response.data;
|
|
|
+ }
|
|
|
+);
|
|
|
+
|
|
|
+const risSyncSlice = createSlice({
|
|
|
+ name: 'risSync',
|
|
|
+ initialState,
|
|
|
+ reducers: {
|
|
|
+ setRunning: (state, action: PayloadAction<boolean>) => {
|
|
|
+ state.isRunning = action.payload;
|
|
|
+ },
|
|
|
+ setLastSyncTime: (state, action: PayloadAction<string>) => {
|
|
|
+ state.lastSyncTime = action.payload;
|
|
|
+ },
|
|
|
+ },
|
|
|
+ extraReducers: (builder) => {
|
|
|
+ builder.addCase(fetchRisConfigThunk.fulfilled, (state, action) => {
|
|
|
+ state.config = action.payload;
|
|
|
+ console.log('[RIS] 配置加载成功:', action.payload);
|
|
|
+ });
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+export const { setRunning, setLastSyncTime } = risSyncSlice.actions;
|
|
|
+export default risSyncSlice.reducer;
|
|
|
+```
|
|
|
+
|
|
|
+### 2. AppInitializer.tsx 修改
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 在现有的 AppInitializer 中添加
|
|
|
+import { fetchRisConfigThunk } from '@/states/ris/risSyncSlice';
|
|
|
+
|
|
|
+// 在 useEffect 中添加
|
|
|
+useEffect(() => {
|
|
|
+ const fetchData = async () => {
|
|
|
+ // ... 现有的初始化代码
|
|
|
+
|
|
|
+ // 获取RIS配置
|
|
|
+ try {
|
|
|
+ await dispatch(fetchRisConfigThunk());
|
|
|
+ console.log('[AppInitializer] RIS配置加载完成');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[AppInitializer] RIS配置加载失败:', error);
|
|
|
+ // 配置加载失败不影响应用启动
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ fetchData();
|
|
|
+}, [dispatch]);
|
|
|
+```
|
|
|
+
|
|
|
+### 3. useRisAutoSync.ts
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { useEffect, useRef } from 'react';
|
|
|
+import { useSelector, useDispatch } from 'react-redux';
|
|
|
+import { message } from 'antd';
|
|
|
+import { RootState, AppDispatch } from '@/states/store';
|
|
|
+import { setRunning, setLastSyncTime } from '@/states/ris/risSyncSlice';
|
|
|
+import { RisSyncService } from '@/services/risSync/RisSyncService';
|
|
|
+import { fetchWorkThunk } from '@/states/patient/worklist/slices/workSlice';
|
|
|
+
|
|
|
+export const useRisAutoSync = () => {
|
|
|
+ const dispatch = useDispatch<AppDispatch>();
|
|
|
+ const config = useSelector((state: RootState) => state.risSync.config);
|
|
|
+ const filters = useSelector((state: RootState) => state.workFilters);
|
|
|
+ const page = useSelector((state: RootState) => state.workPagination.page);
|
|
|
+ const pageSize = useSelector((state: RootState) => state.workPagination.pageSize);
|
|
|
+
|
|
|
+ const serviceRef = useRef<RisSyncService | null>(null);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ // 检查是否应该启动同步
|
|
|
+ if (!config?.mwl_enable || !config?.mwl_refresh_enable) {
|
|
|
+ console.log('[useRisAutoSync] RIS自动同步未启用');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('[useRisAutoSync] 启动RIS自动同步,间隔:', config.mwl_refresh_interval, '分钟');
|
|
|
+
|
|
|
+ // 创建服务实例
|
|
|
+ serviceRef.current = new RisSyncService({
|
|
|
+ interval: config.mwl_refresh_interval * 60 * 1000, // 转换为毫秒
|
|
|
+ onSyncComplete: (success, count) => {
|
|
|
+ if (success) {
|
|
|
+ message.success(`RIS同步成功,同步了 ${count} 条数据`, 3);
|
|
|
+ dispatch(setLastSyncTime(new Date().toISOString()));
|
|
|
+ // 刷新worklist
|
|
|
+ dispatch(fetchWorkThunk({ page, pageSize, filters }));
|
|
|
+ } else {
|
|
|
+ // 错误只在控制台显示,不打扰用户
|
|
|
+ console.error('[useRisAutoSync] 同步失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ // 启动同步
|
|
|
+ serviceRef.current.start();
|
|
|
+ dispatch(setRunning(true));
|
|
|
+
|
|
|
+ // 清理函数
|
|
|
+ return () => {
|
|
|
+ console.log('[useRisAutoSync] 停止RIS自动同步');
|
|
|
+ if (serviceRef.current) {
|
|
|
+ serviceRef.current.stop();
|
|
|
+ serviceRef.current = null;
|
|
|
+ }
|
|
|
+ dispatch(setRunning(false));
|
|
|
+ };
|
|
|
+ }, [config, dispatch, page, pageSize, filters]);
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 4. RisSyncService.ts
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { syncRis, RisTimeUtils } from '@/API/patient/risActions';
|
|
|
+
|
|
|
+interface RisSyncServiceOptions {
|
|
|
+ interval: number;
|
|
|
+ onSyncComplete?: (success: boolean, count: number) => void;
|
|
|
+}
|
|
|
+
|
|
|
+export class RisSyncService {
|
|
|
+ private timer: NodeJS.Timeout | null = null;
|
|
|
+ private isRunning = false;
|
|
|
+ private options: RisSyncServiceOptions;
|
|
|
+
|
|
|
+ constructor(options: RisSyncServiceOptions) {
|
|
|
+ this.options = options;
|
|
|
+ }
|
|
|
+
|
|
|
+ async start() {
|
|
|
+ if (this.isRunning) {
|
|
|
+ console.log('[RisSyncService] 服务已在运行');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.isRunning = true;
|
|
|
+
|
|
|
+ // 立即执行一次同步
|
|
|
+ await this.performSync();
|
|
|
+
|
|
|
+ // 设置定时器
|
|
|
+ this.timer = setInterval(() => {
|
|
|
+ this.performSync();
|
|
|
+ }, this.options.interval);
|
|
|
+
|
|
|
+ console.log('[RisSyncService] 自动同步已启动');
|
|
|
+ }
|
|
|
+
|
|
|
+ stop() {
|
|
|
+ if (this.timer) {
|
|
|
+ clearInterval(this.timer);
|
|
|
+ this.timer = null;
|
|
|
+ }
|
|
|
+ this.isRunning = false;
|
|
|
+ console.log('[RisSyncService] 自动同步已停止');
|
|
|
+ }
|
|
|
+
|
|
|
+ private async performSync() {
|
|
|
+ try {
|
|
|
+ console.log('[RisSyncService] 开始同步RIS数据...');
|
|
|
+
|
|
|
+ // 获取今天的时间范围
|
|
|
+ const timeRange = RisTimeUtils.getTodayRange();
|
|
|
+
|
|
|
+ // 执行同步
|
|
|
+ const result = await syncRis({
|
|
|
+ start_time: timeRange.start,
|
|
|
+ end_time: timeRange.end,
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(`[RisSyncService] 同步成功,共同步 ${result.data.count} 条数据`);
|
|
|
+
|
|
|
+ // 回调通知
|
|
|
+ if (this.options.onSyncComplete) {
|
|
|
+ this.options.onSyncComplete(true, result.data.count);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[RisSyncService] 同步失败:', error);
|
|
|
+
|
|
|
+ // 回调通知
|
|
|
+ if (this.options.onSyncComplete) {
|
|
|
+ this.options.onSyncComplete(false, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 5. worklist.tsx 修改
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 在 import 部分添加
|
|
|
+import { useRisAutoSync } from '@/hooks/useRisAutoSync';
|
|
|
+
|
|
|
+// 在组件内部添加(其他代码保持不变)
|
|
|
+const WorklistPage: React.FC = () => {
|
|
|
+ // ... 现有的代码
|
|
|
+
|
|
|
+ // 启用RIS自动同步
|
|
|
+ useRisAutoSync();
|
|
|
+
|
|
|
+ // ... 现有的代码
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 6. store.ts 修改
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 添加 import
|
|
|
+import risSyncReducer from './ris/risSyncSlice';
|
|
|
+
|
|
|
+// 在 reducer 对象中添加
|
|
|
+const store = configureStore({
|
|
|
+ reducer: {
|
|
|
+ // ... 现有的 reducers
|
|
|
+ risSync: risSyncReducer,
|
|
|
+ },
|
|
|
+ // ... 其他配置
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+## 🧪 测试步骤
|
|
|
+
|
|
|
+### 1. 验证配置加载
|
|
|
+```
|
|
|
+1. 启动应用
|
|
|
+2. 打开浏览器控制台
|
|
|
+3. 查看是否有 "[RIS] 配置加载成功" 日志
|
|
|
+```
|
|
|
+
|
|
|
+### 2. 验证自动同步
|
|
|
+```
|
|
|
+1. 进入worklist页面
|
|
|
+2. 查看控制台是否有 "[useRisAutoSync] 启动RIS自动同步" 日志
|
|
|
+3. 等待配置的时间间隔
|
|
|
+4. 观察是否出现同步成功的消息弹框
|
|
|
+5. 验证列表是否刷新
|
|
|
+```
|
|
|
+
|
|
|
+### 3. 验证生命周期控制
|
|
|
+```
|
|
|
+1. 在worklist页面等待同步启动
|
|
|
+2. 切换到其他页面
|
|
|
+3. 查看控制台是否有 "[useRisAutoSync] 停止RIS自动同步" 日志
|
|
|
+4. 返回worklist页面
|
|
|
+5. 验证同步重新启动
|
|
|
+```
|
|
|
+
|
|
|
+## 📝 注意事项
|
|
|
+
|
|
|
+1. **性能优化**:同步操作不会阻塞UI
|
|
|
+2. **错误处理**:同步失败只在控制台记录,不干扰用户
|
|
|
+3. **内存管理**:组件卸载时正确清理定时器
|
|
|
+4. **配置缓存**:配置在应用启动时加载一次,避免重复请求
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+*文档版本:2.0.0*
|
|
|
+*更新日期:2025-01-31*
|