# ReRegister功能设计文档 ## 功能概述 ReRegister功能允许用户在historylist表格中选中一个study后,点击"ReRegister"按钮,将当前患者的基本信息获取并填充到注册页面表单中,实现快速重新注册。 ## 参与者分析 ### 组件层 - **ActionPanel** - 包含ReRegister按钮的组件 - **RegisterPage** - 注册页面主组件 - **BasicInfoForm** - 注册表单组件 - **HistoryList** - 历史记录列表页面 ### 状态管理层 - **BusinessFlowSlice** - 管理业务流程状态 - **formSlice** - 管理注册表单数据 - **historySelectionSlice** - 管理历史记录选中状态 - **historyEntitiesSlice** - 管理历史记录实体数据 - **reregisterSlice** - 管理ReRegister功能的状态 ### 数据层 - **Task** - 任务数据结构 - **RegisterInfo** - 注册信息数据结构 - **workActions** - API操作函数 ### 工具层 - **registerLogic** - 注册逻辑处理函数 - **useRegisterState** - 注册状态管理Hook - **mapTaskToRegisterInfo** - 数据映射函数 ## 数据结构分析 ### Task (历史记录数据结构) ```typescript interface Task { StudyInstanceUID: string; StudyID: string; AccessionNumber: string; PatientID: string; PatientName: string; PatientSize: string; PatientAge: string; PatientSex: string; patient_dob?: string; ref_physician?: string; weight?: number; thickness?: number; length?: number; comment?: string; // 宠物专用字段 owner_name?: string; chip_number?: string; variety: string; sex_neutered: string; // 人医专用字段 pregnancy_status?: string; } ``` ### RegisterInfo (注册信息数据结构) ```typescript interface RegisterInfo { accession_number: string; patient_id: string; patient_name: string; patient_size: string; patient_age: string; patient_dob: string; patient_sex: string; patient_type: string; ref_physician: string; operator_id: string; modality: string; weight: number; thickness: number; length: number; study_type: 'Normal' | 'Emergency'; comment: string; views: View[]; // 宠物专用字段 owner_name: string; sex_neutered: string; chip_number: string; variety: string; // 人医专用字段 pregnancy_status: string; is_anaesthesia: boolean; is_sedation: boolean; } ``` ## 实现流程 ### 🔄 执行流程 ```mermaid sequenceDiagram participant User as 用户 participant ActionPanel as ActionPanel组件 participant Redux as Redux Store participant RegisterPage as RegisterPage participant BasicInfoForm as BasicInfoForm User->>ActionPanel: 点击ReRegister按钮 ActionPanel->>Redux: 获取选中的study数据 Redux-->>ActionPanel: 返回Task数据 ActionPanel->>Redux: 触发ReRegister action Redux->>RegisterPage: 切换到注册页面 RegisterPage->>BasicInfoForm: 填充表单数据 BasicInfoForm->>User: 显示预填充的表单 ``` ### 📊 数据流图 ```mermaid graph TD A[HistoryList选中] --> B[ActionPanel] B --> C[获取Task数据] C --> D[数据映射 Task→RegisterInfo] D --> E[更新Redux formSlice] E --> F[切换到RegisterPage] F --> G[BasicInfoForm显示预填充数据] H[BusinessFlow] --> I[设置currentKey为'register'] I --> F ``` ### 🏗️ 类图 ```mermaid classDiagram class ActionPanel { +handleReRegister() +getSelectedWorks() } class RegisterPage { +handleRegister() +useEffect cleanup } class BasicInfoForm { +form: FormInstance +onValuesChange() } class formSlice { +setFormData() +clearFormData() } class BusinessFlowSlice { +setBusinessFlow() } class reregisterSlice { +setSourceTask() +setRegisterInfo() +clearReRegister() } class Task { +StudyID +PatientName +PatientID +PatientSize +PatientAge +PatientSex +patient_dob +ref_physician +weight +thickness +length +comment +owner_name +chip_number +variety +sex_neutered +pregnancy_status } class RegisterInfo { +accession_number +patient_id +patient_name +patient_size +patient_age +patient_dob +patient_sex +patient_type +ref_physician +operator_id +modality +weight +thickness +length +study_type +comment +views +owner_name +sex_neutered +chip_number +variety +pregnancy_status +is_anaesthesia +is_sedation } ActionPanel --> Task : 获取数据 Task --> RegisterInfo : 数据映射 RegisterInfo --> formSlice : 存储数据 formSlice --> BasicInfoForm : 填充表单 ActionPanel --> BusinessFlowSlice : 切换页面 ActionPanel --> reregisterSlice : 存储ReRegister数据 ``` ## 详细实现步骤 ### 1. 创建新的Redux Slice 需要创建一个新的slice来管理ReRegister功能的状态: ```typescript // src/states/patient/reregister/reregisterSlice.ts import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { Task } from '@/domain/work'; import { RegisterInfo } from '@/API/patient/workActions'; interface ReRegisterState { sourceTask: Task | null; registerInfo: RegisterInfo | null; loading: boolean; error: string | null; } const initialState: ReRegisterState = { sourceTask: null, registerInfo: null, loading: false, error: null, }; export const reregisterSlice = createSlice({ name: 'reregister', initialState, reducers: { setSourceTask: (state, action: PayloadAction) => { state.sourceTask = action.payload; }, setRegisterInfo: (state, action: PayloadAction) => { state.registerInfo = action.payload; }, clearReRegister: (state) => { state.sourceTask = null; state.registerInfo = null; state.error = null; }, setLoading: (state, action: PayloadAction) => { state.loading = action.payload; }, setError: (state, action: PayloadAction) => { state.error = action.payload; }, }, }); export const { setSourceTask, setRegisterInfo, clearReRegister, setLoading, setError } = reregisterSlice.actions; // Selectors export const selectSourceTask = (state: { reregister: ReRegisterState }) => state.reregister.sourceTask; export const selectRegisterInfo = (state: { reregister: ReRegisterState }) => state.reregister.registerInfo; export const selectReRegisterLoading = (state: { reregister: ReRegisterState }) => state.reregister.loading; export const selectReRegisterError = (state: { reregister: ReRegisterState }) => state.reregister.error; export type { ReRegisterState }; export default reregisterSlice.reducer; ``` ### 2. 数据映射函数 创建从Task到RegisterInfo的映射函数: ```typescript // src/domain/patient/taskToRegister.ts import { Task } from '@/domain/work'; import { RegisterInfo } from '@/API/patient/workActions'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; dayjs.extend(utc); export const mapTaskToRegisterInfo = (task: Task): RegisterInfo => { // 处理年龄格式转换 let patientAge = task.PatientAge || '000Y'; // 确保年龄格式为3位数字+单位 const ageNumber = parseInt(patientAge.substring(0, 3)) || 0; const ageUnit = patientAge.length > 3 ? patientAge.substring(3) : 'Y'; // 处理日期格式 let patientDob = ''; if (task.patient_dob) { try { patientDob = dayjs.utc(task.patient_dob).format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'); } catch (error) { console.error('Error parsing patient_dob:', error); patientDob = ''; } } // 处理体重、厚度、长度等数值字段 const weight = task.weight ? Number(task.weight) : 0; const thickness = task.Thickness ? Number(task.Thickness) : 0; const length = task.length ? Number(task.length) : 0; return { accession_number: task.AccessionNumber || '', patient_id: task.PatientID || '', patient_name: task.PatientName || '', patient_size: task.PatientSize || 'Medium', patient_age: `${ageNumber.toString().padStart(3, '0')}${ageUnit}`, patient_dob: patientDob, patient_sex: task.PatientSex || '', patient_type: task.PatientType || '', ref_physician: task.ref_physician || '', operator_id: task.OperatorID || '', modality: task.Modality || 'DX', weight: weight, thickness: thickness, length: length, study_type: 'Normal', comment: task.comment || '', views: [], // ReRegister时不保留视图信息 // 宠物专用字段 owner_name: task.owner_name || '', sex_neutered: task.sex_neutered || 'UNALTERED', chip_number: task.chip_number || '', variety: task.variety || '', // 人医专用字段 pregnancy_status: task.pregnancy_status || 'NOT_PREGNANT', // 添加缺失的字段 is_anaesthesia: task.is_anaesthesia || false, is_sedation: task.is_sedation || false, }; }; ``` ### 3. 更新ActionPanel组件 修改ActionPanel组件,添加ReRegister功能: ```typescript // src/pages/patient/components/ActionPanel.tsx import { setSourceTask, setRegisterInfo } from '@/states/patient/reregister/reregisterSlice'; import { mapTaskToRegisterInfo } from '@/domain/patient/taskToRegister'; import { setBusinessFlow } from '@/states/BusinessFlowSlice'; // 在handleReRegister函数中 const handleReRegister = () => { const selectedIds = getSelectedWorkIds(); // 验证选择 if (selectedIds.length === 0) { message.warning('请先选择要重新注册的项目'); return; } if (selectedIds.length > 1) { message.warning('只能重新注册一个项目'); return; } const works = getWorksFromWorklistOrHistory(); const task = works.find((item) => item.StudyID === selectedIds[0]); // 验证任务数据 if (!task) { message.error('找不到选中的任务数据'); return; } // 验证必要字段 if (!task.PatientName || !task.PatientID) { message.error('任务数据不完整,缺少必要信息'); return; } try { // 设置源任务数据 dispatch(setSourceTask(task)); // 将Task数据映射为RegisterInfo const registerInfo = mapTaskToRegisterInfo(task); // 验证映射后的数据 if (!registerInfo.patient_name || !registerInfo.patient_id) { throw new Error('数据映射失败,缺少必要信息'); } // 设置注册信息 dispatch(setRegisterInfo(registerInfo)); // 切换到注册页面 dispatch(setBusinessFlow('register')); message.success('已切换到注册页面,表单已预填充'); } catch (error) { console.error('ReRegister error:', error); const errorMessage = error instanceof Error ? error.message : '重新注册失败,请重试'; message.error(`重新注册失败: ${errorMessage}`); } }; ``` ### 4. 更新RegisterPage组件 修改RegisterPage组件,处理ReRegister数据: ```typescript // src/pages/patient/register.tsx import { selectRegisterInfo } from '@/states/patient/reregister/reregisterSlice'; import { clearReRegister } from '@/states/patient/reregister/reregisterSlice'; const RegisterPage: React.FC = () => { const screens = useBreakpoint(); const [form] = Form.useForm(); const dispatch = useDispatch(); const { selectedViews, currentPatientType } = useRegisterState(); const productName = useSelector( (state: RootState) => state.product.productName ); const reregisterInfo = useSelector(selectRegisterInfo); // 当有ReRegister数据时,填充表单 useEffect(() => { if (reregisterInfo) { try { console.log('ReRegister数据已获取,填充表单:', reregisterInfo); // 验证数据完整性 if (!reregisterInfo.patient_name || !reregisterInfo.patient_id) { console.error('ReRegister数据不完整:', reregisterInfo); message.warning('预填充数据不完整,请手动填写必要信息'); return; } // 设置表单值 form.setFieldsValue(reregisterInfo); // 更新Redux表单数据 dispatch(setFormData(reregisterInfo)); // 显示成功消息 message.success('已从历史记录预填充表单数据'); } catch (error) { console.error('填充表单时出错:', error); message.error('预填充表单数据时出错,请手动填写'); } } }, [reregisterInfo, form, dispatch]); // 清理ReRegister数据 useEffect(() => { return () => { dispatch(clearReRegister()); }; }, [dispatch]); // ... 其他现有代码 }; ``` ## 🧪 测试方案 ### 测试场景 1. **正常流程测试** - 在historylist中选择一个study - 点击ReRegister按钮 - 验证是否切换到注册页面 - 验证表单是否被正确填充 2. **多选测试** - 选择多个study - 点击ReRegister按钮 - 验证是否显示警告信息 3. **空选择测试** - 不选择任何study - 点击ReRegister按钮 - 验证是否显示警告信息 4. **数据映射测试** - 验证Task数据是否正确映射到RegisterInfo - 验证特殊字段(如年龄、日期)是否正确转换 5. **错误处理测试** - 模拟网络错误 - 验证错误信息是否正确显示 ### 测试用例 ```typescript // 测试数据 const mockTask: Task = { StudyID: 'STUDY001', PatientName: '测试患者', PatientID: 'P001', PatientSize: 'Medium', PatientAge: '030Y', PatientSex: 'male', patient_dob: '1990-01-01', ref_physician: '测试医生', weight: 70, thickness: 20, length: 180, comment: '测试注释', owner_name: '测试主人', chip_number: '123456789', variety: '测试品种', sex_neutered: 'UNALTERED', pregnancy_status: 'NOT_PREGNANT', }; // 测试映射函数 test('mapTaskToRegisterInfo should correctly map Task to RegisterInfo', () => { const result = mapTaskToRegisterInfo(mockTask); expect(result.patient_name).toBe('测试患者'); expect(result.patient_age).toBe('030Y'); expect(result.patient_dob).toBeDefined(); }); ``` ## 🐛 潜在问题分析 ### 边界情况 1. **数据缺失处理** - 当Task中某些字段为空时,如何处理默认值 - 解决方案:在映射函数中设置合理的默认值 2. **数据类型转换** - 年龄格式的转换(如"30Y" vs "030Y") - 解决方案:统一使用标准格式 3. **页面切换时机** - 在数据加载完成前切换页面可能导致用户体验问题 - 解决方案:添加loading状态 ### 异常处理 1. **网络错误** - 在获取Task数据时可能发生网络错误 - 解决方案:添加try-catch和错误提示 2. **数据验证失败** - 映射后的数据可能不符合注册表单验证规则 - 解决方案:在映射后进行数据验证 3. **并发操作** - 用户可能在数据加载过程中进行其他操作 - 解决方案:使用loading状态防止重复操作 ## 实现优先级 1. **高优先级** - 基本ReRegister功能实现 - 数据映射功能 - 页面切换逻辑 2. **中优先级** - 错误处理 - 加载状态 - 数据验证 3. **低优先级** - 性能优化 - 高级错误处理 - 日志记录 ## 实现结果 ### 已完成的功能 1. **Redux状态管理** - 创建了 `reregisterSlice` 来管理ReRegister功能的状态 - 实现了设置源任务、注册信息、加载状态和错误处理的功能 - 添加了相应的selector函数 2. **数据映射** - 实现了 `mapTaskToRegisterInfo` 函数,将Task数据转换为RegisterInfo - 处理了特殊字段转换,如年龄格式、日期格式等 - 添加了错误处理和默认值设置 3. **用户界面** - 在ActionPanel中实现了handleReRegister函数 - 添加了选择验证和错误提示 - 实现了从historylist到register页面的导航 4. **表单预填充** - 在RegisterPage中添加了ReRegister数据处理逻辑 - 实现了表单自动预填充功能 - 添加了数据验证和错误处理 5. **测试用例** - 创建了Redux slice的测试用例 - 实现了数据映射函数的测试用例 - 覆盖了正常流程和边界情况 ### 代码文件 1. **状态管理** - `src/states/patient/reregister/reregisterSlice.ts` - Redux slice实现 - `src/states/patient/reregister/reregisterSlice.test.ts` - 测试用例 2. **数据映射** - `src/domain/patient/taskToRegister.ts` - 数据映射函数 - `src/domain/patient/taskToRegister.test.ts` - 测试用例 3. **组件更新** - `src/pages/patient/components/ActionPanel.tsx` - 添加ReRegister功能 - `src/pages/patient/register.tsx` - 添加表单预填充逻辑 4. **Store配置** - `src/states/store.ts` - 添加新的reducer ### 使用流程 1. 用户在historylist中选择一个study 2. 点击ReRegister按钮 3. 系统验证选择并获取Task数据 4. 将Task数据映射为RegisterInfo 5. 更新Redux状态并切换到注册页面 6. 注册页面自动预填充表单数据 ### 错误处理 实现了完善的错误处理机制: - 选择验证(空选择、多选) - 数据完整性验证 - 数据映射错误处理 - 表单填充错误处理 - 用户友好的错误提示 ### 测试覆盖 测试用例覆盖了以下场景: - Redux slice的状态管理 - 数据映射的正确性 - 边界情况处理(缺失字段、无效格式) - 错误情况的处理 ## 总结 ReRegister功能已成功实现,提供了完整的从历史记录快速重新注册的流程。通过Redux状态管理、数据映射和表单预填充,用户可以轻松地基于历史记录创建新的注册,大大提高了工作效率。