|
|
@@ -0,0 +1,662 @@
|
|
|
+# 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<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;
|
|
|
+```
|
|
|
+
|
|
|
+### 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状态管理、数据映射和表单预填充,用户可以轻松地基于历史记录创建新的注册,大大提高了工作效率。
|