|
|
@@ -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),
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|