ReRegister功能允许用户在historylist表格中选中一个study后,点击"ReRegister"按钮,将当前患者的基本信息获取并填充到注册页面表单中,实现快速重新注册。
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;
}
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;
}
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: 显示预填充的表单
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
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数据
需要创建一个新的slice来管理ReRegister功能的状态:
// 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<Task>) => {
state.sourceTask = action.payload;
},
setRegisterInfo: (state, action: PayloadAction<RegisterInfo>) => {
state.registerInfo = action.payload;
},
clearReRegister: (state) => {
state.sourceTask = null;
state.registerInfo = null;
state.error = null;
},
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
},
setError: (state, action: PayloadAction<string>) => {
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;
创建从Task到RegisterInfo的映射函数:
// 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,
};
};
修改ActionPanel组件,添加ReRegister功能:
// 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}`);
}
};
修改RegisterPage组件,处理ReRegister数据:
// 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]);
// ... 其他现有代码
};
正常流程测试
多选测试
空选择测试
数据映射测试
错误处理测试
// 测试数据
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();
});
数据缺失处理
数据类型转换
页面切换时机
网络错误
数据验证失败
并发操作
高优先级
中优先级
低优先级
Redux状态管理
reregisterSlice 来管理ReRegister功能的状态数据映射
mapTaskToRegisterInfo 函数,将Task数据转换为RegisterInfo用户界面
表单预填充
测试用例
状态管理
src/states/patient/reregister/reregisterSlice.ts - Redux slice实现src/states/patient/reregister/reregisterSlice.test.ts - 测试用例数据映射
src/domain/patient/taskToRegister.ts - 数据映射函数src/domain/patient/taskToRegister.test.ts - 测试用例组件更新
src/pages/patient/components/ActionPanel.tsx - 添加ReRegister功能src/pages/patient/register.tsx - 添加表单预填充逻辑Store配置
src/states/store.ts - 添加新的reducer实现了完善的错误处理机制:
测试用例覆盖了以下场景:
ReRegister功能已成功实现,提供了完整的从历史记录快速重新注册的流程。通过Redux状态管理、数据映射和表单预填充,用户可以轻松地基于历史记录创建新的注册,大大提高了工作效率。