Bläddra i källkod

修改和ris相关的数据结构,添加接口实现

dengdx 1 månad sedan
förälder
incheckning
6178f3ae01
3 ändrade filer med 495 tillägg och 0 borttagningar
  1. 454 0
      src/API/patient/risActions.ts
  2. 18 0
      src/API/patient/workActions.ts
  3. 23 0
      src/domain/work.ts

+ 454 - 0
src/API/patient/risActions.ts

@@ -0,0 +1,454 @@
+import axiosInstance from '../interceptor';
+import { RegisterWorkResponse } from './workActions';
+
+// ==================== RIS相关接口类型定义 ====================
+
+interface View {
+  view_id: string;
+  procedure_id: string;
+}
+
+/**
+ * RIS配置数据接口
+ */
+export interface RisConfigData {
+  /** 类型标识 */
+  '@type': 'type.googleapis.com/dr.task.RisConfig';
+  /** RIS是否启用 */
+  mwl_enable: boolean;
+  /** RIS自动刷新是否启用 */
+  mwl_refresh_enable: boolean;
+  /** RIS自动刷新间隔(分钟) */
+  mwl_refresh_interval: number;
+}
+
+/**
+ * RIS配置响应接口
+ */
+export interface RisConfigResponse {
+  /** 响应码 */
+  code: string;
+  /** 描述信息 */
+  description: string;
+  /** 解决方案 */
+  solution: string;
+  /** 配置数据 */
+  data: RisConfigData;
+}
+
+/**
+ * RIS同步请求参数接口
+ */
+export interface RisSyncRequest {
+  /** 开始时间 (RFC3339Nano格式) */
+  start_time: string;
+  /** 结束时间 (RFC3339Nano格式) */
+  end_time: string;
+  /** 患者ID (可选) */
+  id?: string;
+  /** 患者姓名 (可选) */
+  name?: string;
+  /** 登记号 (可选) */
+  acc_no?: string;
+}
+
+/**
+ * RIS同步响应数据接口
+ */
+export interface RisSyncData {
+  /** 类型标识 */
+  '@type': 'type.googleapis.com/dr.task.RisSyncReply';
+  /** 同步的条目数量 */
+  count: number;
+}
+
+/**
+ * RIS同步响应接口
+ */
+export interface RisSyncResponse {
+  /** 响应码 */
+  code: string;
+  /** 描述信息 */
+  description: string;
+  /** 解决方案 */
+  solution: string;
+  /** 同步结果数据 */
+  data: RisSyncData;
+}
+
+/**
+ * RIS保存本地(单个)请求参数接口
+ */
+export interface RisSaveSingleRequest {
+  /** RIS条目ID,用于RIS条目的唯一标识 */
+  entry_id: string;
+  /** 体位列表 */
+  views: View[];
+}
+
+/**
+ * RIS保存本地(批量)请求参数类型
+ * entry_id数组
+ */
+export type RisSaveBatchRequest = string[];
+
+/**
+ * 标准空响应数据接口
+ */
+export interface EmptyResponseData {
+  /** 类型标识 */
+  '@type': 'type.googleapis.com/google.protobuf.Empty';
+  /** 空值对象 */
+  value: Record<string, unknown>;
+}
+
+/**
+ * 标准API响应接口 - 用于无特殊数据的响应
+ */
+export interface StandardApiResponse {
+  /** 响应码 */
+  code: string;
+  /** 描述信息 */
+  description: string;
+  /** 解决方案 */
+  solution: string;
+  /** 空数据对象 */
+  data: EmptyResponseData;
+}
+
+// ==================== RIS相关API函数实现 ====================
+
+/**
+ * 获取RIS配置
+ * @returns Promise<RisConfigResponse> RIS配置响应
+ * @throws {Error} 当请求失败时抛出错误
+ * 
+ * @example
+ * ```typescript
+ * try {
+ *   const config = await getRisConfig();
+ *   console.log('RIS启用状态:', config.data.mwl_enable);
+ *   console.log('自动刷新间隔:', config.data.mwl_refresh_interval);
+ * } catch (error) {
+ *   console.error('获取RIS配置失败:', error);
+ * }
+ * ```
+ */
+export const getRisConfig = async (): Promise<RisConfigResponse> => {
+  try {
+    console.log('正在获取RIS配置...');
+    const response = await axiosInstance.get('/auth/study/ris');
+    
+    if (response.data.code !== '0x000000') {
+      console.error('获取RIS配置失败', response.data.description);
+      throw new Error(`获取RIS配置失败: ${response.data.description}`);
+    }
+    
+    console.log('RIS配置获取成功:', response.data);
+    return response.data;
+  } catch (error) {
+    console.error('Error getting RIS config:', error);
+    throw error;
+  }
+};
+
+/**
+ * 同步RIS数据
+ * @param params RIS同步请求参数
+ * @returns Promise<RisSyncResponse> RIS同步响应
+ * @throws {Error} 当请求失败时抛出错误
+ * 
+ * @example
+ * ```typescript
+ * try {
+ *   const result = await syncRis({
+ *     start_time: '2025-01-01T00:00:00.000+08:00',
+ *     end_time: '2025-01-31T23:59:59.999+08:00',
+ *     id: 'P001',
+ *     name: '张三'
+ *   });
+ *   console.log('同步了', result.data.count, '条RIS数据');
+ * } catch (error) {
+ *   console.error('RIS同步失败:', error);
+ * }
+ * ```
+ */
+export const syncRis = async (params: RisSyncRequest): Promise<RisSyncResponse> => {
+  try {
+    console.log('正在同步RIS数据...', JSON.stringify(params, null, 2));
+    const response = await axiosInstance.post('/auth/study/ris', params);
+    
+    if (response.data.code !== '0x000000') {
+      console.error('RIS同步失败', response.data.description);
+      throw new Error(`RIS同步失败: ${response.data.description}`);
+    }
+    
+    console.log('RIS同步成功,同步条目数:', response.data.data.count);
+    return response.data;
+  } catch (error) {
+    console.error('Error syncing RIS:', error);
+    throw error;
+  }
+};
+
+/**
+ * 保存RIS条目到本地(单个)
+ * @param params RIS保存单个条目的请求参数
+ * @returns Promise<RegisterWorkResponse> 保存成功后返回完整的Study信息
+ * @throws {Error} 当请求失败时抛出错误
+ * 
+ * @example
+ * ```typescript
+ * try {
+ *   const study = await saveRisSingle({
+ *     entry_id: 'ris_entry_001',
+ *     views: [
+ *       { view_id: 'View_DX_T_A_SK_AP_00', procedure_id: 'P0-0002' },
+ *       { view_id: 'View_DX_T_A_SK_LAT_00', procedure_id: 'P0-0002' }
+ *     ]
+ *   });
+ *   console.log('保存成功,Study ID:', study.data.study_id);
+ * } catch (error) {
+ *   console.error('保存RIS条目失败:', error);
+ * }
+ * ```
+ */
+export const saveRisSingle = async (
+  params: RisSaveSingleRequest
+): Promise<RegisterWorkResponse> => {
+  try {
+    console.log('正在保存RIS条目到本地(单个)...', JSON.stringify(params, null, 2));
+    const response = await axiosInstance.post('/auth/study/ris/single', params);
+    
+    if (response.data.code !== '0x000000') {
+      console.error('保存RIS条目失败', response.data.description);
+      throw new Error(`保存RIS条目失败: ${response.data.description}`);
+    }
+    
+    console.log('RIS条目保存成功,Study ID:', response.data.data.study_id);
+    return response.data;
+  } catch (error) {
+    console.error('Error saving RIS single entry:', error);
+    throw error;
+  }
+};
+
+/**
+ * 批量保存RIS条目到本地
+ * @param entryIds RIS条目ID数组
+ * @returns Promise<StandardApiResponse> 标准API响应
+ * @throws {Error} 当请求失败时抛出错误
+ * 
+ * @example
+ * ```typescript
+ * try {
+ *   await saveRisBatch(['entry_001', 'entry_002', 'entry_003']);
+ *   console.log('批量保存RIS条目成功');
+ * } catch (error) {
+ *   console.error('批量保存RIS条目失败:', error);
+ * }
+ * ```
+ */
+export const saveRisBatch = async (
+  entryIds: RisSaveBatchRequest
+): Promise<StandardApiResponse> => {
+  try {
+    console.log('正在批量保存RIS条目到本地...', `共${entryIds.length}个条目`);
+    console.log('Entry IDs:', JSON.stringify(entryIds, null, 2));
+    
+    const response = await axiosInstance.post('/auth/study/ris/batch', entryIds);
+    
+    if (response.data.code !== '0x000000') {
+      console.error('批量保存RIS条目失败', response.data.description);
+      throw new Error(`批量保存RIS条目失败: ${response.data.description}`);
+    }
+    
+    console.log('RIS条目批量保存成功,共保存', entryIds.length, '个条目');
+    return response.data;
+  } catch (error) {
+    console.error('Error saving RIS batch entries:', error);
+    throw error;
+  }
+};
+
+// ==================== RIS工具函数和管理类 ====================
+
+/**
+ * RIS管理类 - 提供RIS相关操作的便捷方法
+ */
+export class RisManager {
+  /**
+   * 检查RIS是否可用
+   * @returns Promise<boolean> RIS是否启用
+   */
+  static async isRisEnabled(): Promise<boolean> {
+    try {
+      const config = await getRisConfig();
+      return config.data.mwl_enable;
+    } catch (error) {
+      console.warn('检查RIS状态失败,默认返回false:', error);
+      return false;
+    }
+  }
+  
+  /**
+   * 获取RIS自动刷新配置
+   * @returns Promise<{enabled: boolean, interval: number}> 自动刷新配置
+   */
+  static async getAutoRefreshConfig(): Promise<{
+    enabled: boolean;
+    interval: number;
+  }> {
+    try {
+      const config = await getRisConfig();
+      return {
+        enabled: config.data.mwl_refresh_enable,
+        interval: config.data.mwl_refresh_interval,
+      };
+    } catch (error) {
+      console.warn('获取RIS自动刷新配置失败,返回默认值:', error);
+      return { enabled: false, interval: 5 };
+    }
+  }
+  
+  /**
+   * 同步今日RIS数据
+   * @param additionalFilters 额外的过滤条件
+   * @returns Promise<number> 同步的条目数量
+   */
+  static async syncTodayRis(additionalFilters?: {
+    id?: string;
+    name?: string;
+    acc_no?: string;
+  }): Promise<number> {
+    const today = new Date();
+    const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
+    const endOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59, 999);
+    
+    // 转换为RFC3339格式,包含时区信息
+    const params: RisSyncRequest = {
+      start_time: startOfDay.toISOString().replace('Z', '+08:00'),
+      end_time: endOfDay.toISOString().replace('Z', '+08:00'),
+      ...additionalFilters,
+    };
+    
+    const result = await syncRis(params);
+    return result.data.count;
+  }
+  
+  /**
+   * 同步指定日期范围的RIS数据
+   * @param startDate 开始日期
+   * @param endDate 结束日期
+   * @param filters 过滤条件
+   * @returns Promise<number> 同步的条目数量
+   */
+  static async syncRisDateRange(
+    startDate: Date,
+    endDate: Date,
+    filters?: {
+      id?: string;
+      name?: string;
+      acc_no?: string;
+    }
+  ): Promise<number> {
+    // 转换为RFC3339格式,包含时区信息
+    const params: RisSyncRequest = {
+      start_time: startDate.toISOString().replace('Z', '+08:00'),
+      end_time: endDate.toISOString().replace('Z', '+08:00'),
+      ...filters,
+    };
+    
+    const result = await syncRis(params);
+    return result.data.count;
+  }
+
+  /**
+   * 批量保存所有未保存的RIS条目
+   * @param entryIds 要保存的RIS条目ID列表
+   * @param batchSize 每批处理的数量,默认为10
+   * @returns Promise<number> 成功保存的条目数量
+   */
+  static async saveAllRisEntries(
+    entryIds: string[],
+    batchSize: number = 10
+  ): Promise<number> {
+    if (entryIds.length === 0) {
+      console.log('没有需要保存的RIS条目');
+      return 0;
+    }
+
+    let savedCount = 0;
+    
+    // 分批处理,避免一次性发送过多数据
+    for (let i = 0; i < entryIds.length; i += batchSize) {
+      const batch = entryIds.slice(i, i + batchSize);
+      try {
+        await saveRisBatch(batch);
+        savedCount += batch.length;
+        console.log(`成功保存第${Math.floor(i / batchSize) + 1}批,共${batch.length}个条目`);
+      } catch (error) {
+        console.error(`保存第${Math.floor(i / batchSize) + 1}批失败:`, error);
+        // 继续处理下一批,不中断整个流程
+      }
+    }
+    
+    console.log(`批量保存完成,总共成功保存${savedCount}/${entryIds.length}个RIS条目`);
+    return savedCount;
+  }
+}
+
+/**
+ * RIS时间工具函数
+ */
+export class RisTimeUtils {
+  /**
+   * 将Date对象转换为RFC3339Nano格式字符串(带时区)
+   * @param date Date对象
+   * @param timezone 时区偏移,默认为+08:00
+   * @returns RFC3339格式的时间字符串
+   */
+  static toRFC3339(date: Date, timezone: string = '+08:00'): string {
+    return date.toISOString().replace('Z', timezone);
+  }
+  
+  /**
+   * 获取今日开始和结束时间的RFC3339格式
+   * @param timezone 时区偏移,默认为+08:00
+   * @returns {start: string, end: string} 今日的开始和结束时间
+   */
+  static getTodayRange(timezone: string = '+08:00'): {
+    start: string;
+    end: string;
+  } {
+    const today = new Date();
+    const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
+    const endOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59, 999);
+    
+    return {
+      start: this.toRFC3339(startOfDay, timezone),
+      end: this.toRFC3339(endOfDay, timezone),
+    };
+  }
+  
+  /**
+   * 获取指定日期范围的RFC3339格式
+   * @param startDate 开始日期
+   * @param endDate 结束日期
+   * @param timezone 时区偏移,默认为+08:00
+   * @returns {start: string, end: string} 指定范围的开始和结束时间
+   */
+  static getDateRange(
+    startDate: Date,
+    endDate: Date,
+    timezone: string = '+08:00'
+  ): {
+    start: string;
+    end: string;
+  } {
+    return {
+      start: this.toRFC3339(startDate, timezone),
+      end: this.toRFC3339(endDate, timezone),
+    };
+  }
+}

+ 18 - 0
src/API/patient/workActions.ts

@@ -286,6 +286,20 @@ export interface FetchTaskListStudy {
   /** 创建时间 */
   create_time: string;
   series: Series[];
+  /**---RIS相关字段--- */
+  /** RIS条目ID,用于RIS条目的唯一标识 */
+  entry_id?: string;
+  /** 调度信息,包含AE标题、医师、步骤ID等 */
+  scheduled?: {
+    scheduled_ae_title: string;
+    scheduled_performing_physician_name: string;
+    scheduled_procedure_step_id: string;
+  };
+  /** 协议代码列表,方便技师选择体位 */
+  protocol_code?: Array<{
+    code_value: string;
+    code_meaning: string;
+  }>;
 }
 
 /**
@@ -399,6 +413,10 @@ const mapFetchTaskListStudyToTask = (study: FetchTaskListStudy): Task => ({
   // 患者照片字段
   portrait_status: study.portrait_status,
   portrait_file: study.portrait_file,
+  // RIS相关字段
+  entry_id: study.entry_id,
+  scheduled: study.scheduled,
+  protocol_code: study.protocol_code,
 });
 
 const fetchTaskList = async (

+ 23 - 0
src/domain/work.ts

@@ -60,6 +60,20 @@ export interface Task {
   /**---患者照片相关字段--- */
   portrait_status?: string; // 患者照片保存状态, 'Saved' | 'NotSaved' | ''
   portrait_file?: string; // 患者照片文件名
+  /**---RIS相关字段--- */
+  /** RIS条目ID,用于RIS条目的唯一标识 */
+  entry_id?: string;
+  /** 调度信息,包含AE标题、医师、步骤ID等 */
+  scheduled?: {
+    scheduled_ae_title: string;
+    scheduled_performing_physician_name: string;
+    scheduled_procedure_step_id: string;
+  };
+  /** 协议代码列表,方便技师选择体位 */
+  protocol_code?: Array<{
+    code_value: string;
+    code_meaning: string;
+  }>;
 }
 
 export type TaskAnimal = Omit<Task, 'pregnancy_status'> & {
@@ -68,3 +82,12 @@ export type TaskAnimal = Omit<Task, 'pregnancy_status'> & {
   chip_number: object;
   variety: string;
 };
+
+/**
+ * 判断是否为RIS条目
+ * @param task 任务对象
+ * @returns true表示是RIS条目,false表示是本地条目
+ */
+export const isRISEntry = (task: Task): boolean => {
+  return !task.StudyID || task.StudyID === '';
+};