# 传输队列状态管理实现文档 ## 概述 本文档描述了传输队列(Send Job Queue)功能的状态管理实现,包括 Redux 状态设计、API 集成和组件使用方法。 ## 📁 文件结构 ``` src/ ├── domain/output/ │ └── sendJob.ts # 领域模型:发送任务数据类型 ├── API/output/ │ ├── sendJobActions.ts # API 层:传输队列接口(导入领域模型) │ └── pacsNodeActions.ts # API 层:PACS 节点接口 ├── states/output/sendJob/ │ ├── types/ │ │ └── sendJobFilter.ts # 筛选条件类型(状态层特有) │ └── slices/ │ └── sendJobSlice.ts # Redux 状态切片(导入领域模型) ├── states/store.ts # Redux Store 配置 └── pages/patient/ └── OutputList.tsx # 传输列表页面 ``` ### 架构说明 本项目采用**领域驱动设计(DDD)**的分层架构: 1. **领域层(Domain)**:定义核心业务实体,独立于技术实现 2. **API 层**:负责与后端通信,使用领域模型 3. **状态层(State)**:负责状态管理,使用领域模型 4. **UI 层**:负责展示和交互 ## 🎯 核心功能 ### 1. 数据类型定义 #### SendJob(发送任务)- 领域模型 **定义位置**:`src/domain/output/sendJob.ts` ```typescript // 从领域层导入 import { SendJob } from '@/domain/output/sendJob'; interface SendJob { task_id: string; // 任务唯一标识 patient_name: string; // 患者姓名 patient_id: string; // 患者 ID priority: 'High' | 'Medium' | 'Low'; // 优先级 status: 'ARRIVED' | 'SENDING' | 'FAILED' | 'SUCCESS'; // 状态 retry_count?: number; // 重试次数 destination: string; // 目标 PACS 节点名称 } ``` > ⚠️ **重要**:SendJob 是领域模型,定义在 `src/domain/output/sendJob.ts`。API 层和状态层都应该从领域层导入此类型,确保类型定义的单一数据源。 #### SendJobFilter(筛选条件) ```typescript interface SendJobFilter { start_time?: string; // 开始时间 (RFC3339Nano 格式) end_time?: string; // 结束时间 (RFC3339Nano 格式) page: number; // 当前页码 page_size: number; // 每页数量 } ``` ### 2. Redux 状态结构 状态由 5 个 slice 组成: ```typescript { sendJobEntities: { data: SendJob[]; // 任务列表数据 total: number; // 总数量 }, sendJobFilters: { start_time?: string; end_time?: string; page: number; page_size: number; }, sendJobPagination: { page: number; pageSize: number; }, sendJobSelection: { selectedIds: string[]; // 选中的任务 ID 列表 }, sendJobUI: { loading: boolean; // 加载状态 error?: string | null; // 错误信息 } } ``` ### 3. 异步操作(Thunks) #### fetchSendQueueThunk - 获取发送队列 ```typescript import { fetchSendQueueThunk } from '@/states/output/sendJob/slices/sendJobSlice'; // 在组件中使用 dispatch(fetchSendQueueThunk({ page: 1, pageSize: 10, filters: { start_time: '2025-10-10T00:00:00.000+08:00', end_time: '2025-10-10T23:59:59.999+08:00', } })); ``` #### deleteSendJobThunk - 删除任务 ```typescript import { deleteSendJobThunk } from '@/states/output/sendJob/slices/sendJobSlice'; // 删除单个或多个任务 dispatch(deleteSendJobThunk(['task_id_1', 'task_id_2'])); ``` #### retrySendJobThunk - 重试任务 ```typescript import { retrySendJobThunk } from '@/states/output/sendJob/slices/sendJobSlice'; // 重试失败的任务 dispatch(retrySendJobThunk({ taskId: 'task_id', instanceUid: 'sop_instance_uid' // 需要图像实例 UID })); ``` ## 💡 组件使用示例 ### 基础用法 ```typescript import React, { useEffect } from 'react'; import { useAppDispatch, useAppSelector } from '@/states/store'; import { fetchSendQueueThunk, deleteSendJobThunk, retrySendJobThunk, sendJobActions, } from '@/states/output/sendJob/slices/sendJobSlice'; const SendJobList: React.FC = () => { const dispatch = useAppDispatch(); // 获取状态 const { data: sendJobs, total } = useAppSelector(state => state.sendJobEntities); const { loading, error } = useAppSelector(state => state.sendJobUI); const filters = useAppSelector(state => state.sendJobFilters); const { page, pageSize } = useAppSelector(state => state.sendJobPagination); const { selectedIds } = useAppSelector(state => state.sendJobSelection); // 初始加载数据 useEffect(() => { dispatch(fetchSendQueueThunk({ page, pageSize, filters })); }, [dispatch, page, pageSize, filters]); // 刷新数据 const handleRefresh = () => { dispatch(fetchSendQueueThunk({ page, pageSize, filters })); }; // 删除选中的任务 const handleDelete = () => { if (selectedIds.length > 0) { dispatch(deleteSendJobThunk(selectedIds)); } }; // 重试任务 const handleRetry = (taskId: string, instanceUid: string) => { dispatch(retrySendJobThunk({ taskId, instanceUid })); }; // 设置时间范围筛选 const handleDateRangeChange = (startTime: string, endTime: string) => { dispatch(sendJobActions.setStartTime(startTime)); dispatch(sendJobActions.setEndTime(endTime)); }; // 切换页码 const handlePageChange = (newPage: number) => { dispatch(sendJobActions.setPage(newPage)); }; return (