فهرست منبع

feat: 实现ReRegister功能和Pregnancy Status类型重构

主要变更:

1. ReRegister功能实现:
   - 添加reregisterSlice状态管理,处理源任务和注册信息数据
   - 实现Task到RegisterInfo的数据映射函数,支持字段格式转换
   - 在ActionPanel中添加ReRegister按钮,支持选择验证和错误处理
   - 在RegisterPage中添加表单预填充逻辑,自动填充历史数据
   - 添加完整的测试用例,覆盖正常流程和边界情况

2. Pregnancy Status类型重构:
   - 从简单字符串('yes', 'no', 'na')改为枚举类型('0001'-'0004')
   - 添加PregnancyStatus枚举和PregnancyStatusOption接口定义
   - 实现旧值到新值的映射函数,保证向后兼容性
   - 更新表单组件,使用新的pregnancyStatusOptions
   - 更新验证规则,使用z.nativeEnum进行类型验证

3. 文档完善:
   - 添加ReRegister功能设计文档,包含流程图和类图
   - 添加Pregnancy Status类型设计文档,包含实现指南

4. 相关文件变更:
   - src/API/patient/workActions.ts: 更新RegisterInfo接口
   - src/pages/patient/components/ActionPanel.tsx: 添加ReRegister功能
   - src/pages/patient/components/register.form.tsx: 更新表单组件
   - src/pages/patient/register.tsx: 添加表单预填充逻辑
   - src/states/store.ts: 添加reregisterReducer
   - src/validation/patient/registerSchema.ts: 更新验证规则
sw 1 ماه پیش
والد
کامیت
8538e945ac

+ 662 - 0
docs/实现/ReRegister功能设计.md

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

+ 505 - 0
docs/实现/pregnancy_status类型设计.md

@@ -0,0 +1,505 @@
+# Pregnancy Status 类型设计文档
+
+## 概述
+
+本文档定义了妊娠状态(pregnancy_status)的数据类型、验证规则和UI实现方案。
+
+## 类型定义
+
+### PregnancyStatus 枚举
+
+```typescript
+/**
+ * 妊娠状态枚举
+ * 0001: not pregnant - 未怀孕
+ * 0002: possibly pregnant - 可能怀孕
+ * 0003: definitely pregnant - 确定怀孕
+ * 0004: unknown - 未知
+ */
+export enum PregnancyStatus {
+  NOT_PREGNANT = '0001', // 未怀孕
+  POSSIBLY_PREGNANT = '0002', // 可能怀孕
+  DEFINITELY_PREGNANT = '0003', // 确定怀孕
+  UNKNOWN = '0004', // 未知
+}
+```
+
+### PregnancyStatusOption 接口
+
+```typescript
+/**
+ * 妊娠状态选项接口,用于UI下拉选择
+ */
+export interface PregnancyStatusOption {
+  value: PregnancyStatus;
+  label: React.ReactNode;
+  description?: string;
+}
+```
+
+## 实现方案
+
+### 1. 类型定义文件
+
+创建 `src/domain/patient/pregnancyStatus.ts` 文件:
+
+```typescript
+/**
+ * 妊娠状态类型定义
+ */
+
+/**
+ * 妊娠状态枚举
+ * 0001: not pregnant - 未怀孕
+ * 0002: possibly pregnant - 可能怀孕
+ * 0003: definitely pregnant - 确定怀孕
+ * 0004: unknown - 未知
+ */
+export enum PregnancyStatus {
+  NOT_PREGNANT = '0001', // 未怀孕
+  POSSIBLY_PREGNANT = '0002', // 可能怀孕
+  DEFINITELY_PREGNANT = '0003', // 确定怀孕
+  UNKNOWN = '0004', // 未知
+}
+
+/**
+ * 妊娠状态选项接口,用于UI下拉选择
+ */
+export interface PregnancyStatusOption {
+  value: PregnancyStatus;
+  label: React.ReactNode;
+  description?: string;
+}
+
+/**
+ * 妊娠状态选项列表
+ */
+export const pregnancyStatusOptions: PregnancyStatusOption[] = [
+  {
+    value: PregnancyStatus.NOT_PREGNANT,
+    label: '未怀孕',
+    description: '患者未怀孕'
+  },
+  {
+    value: PregnancyStatus.POSSIBLY_PREGNANT,
+    label: '可能怀孕',
+    description: '患者可能怀孕'
+  },
+  {
+    value: PregnancyStatus.DEFINITELY_PREGNANT,
+    label: '确定怀孕',
+    description: '患者确定怀孕'
+  },
+  {
+    value: PregnancyStatus.UNKNOWN,
+    label: '未知',
+    description: '患者妊娠状态未知'
+  }
+];
+
+/**
+ * 根据值获取妊娠状态描述
+ * @param status 妊娠状态值
+ * @returns 妊娠状态描述
+ */
+export const getPregnancyStatusDescription = (status: string): string => {
+  switch (status) {
+    case PregnancyStatus.NOT_PREGNANT:
+      return '未怀孕';
+    case PregnancyStatus.POSSIBLY_PREGNANT:
+      return '可能怀孕';
+    case PregnancyStatus.DEFINITELY_PREGNANT:
+      return '确定怀孕';
+    case PregnancyStatus.UNKNOWN:
+      return '未知';
+    default:
+      return '未知';
+  }
+};
+```
+
+### 2. 更新 workActions.ts
+
+修改 `src/API/patient/workActions.ts` 文件中的 RegisterInfo 接口:
+
+```typescript
+// 导入妊娠状态类型
+import { PregnancyStatus } from '@/domain/patient/pregnancyStatus';
+
+export interface RegisterInfo {
+  // ... 其他字段
+  /** 妊娠状态 */
+  pregnancy_status: PregnancyStatus;
+  // ... 其他字段
+}
+```
+
+### 3. 更新 registerSchema.ts
+
+修改 `src/validation/patient/registerSchema.ts` 文件:
+
+```typescript
+// 导入妊娠状态类型
+import { PregnancyStatus } from '@/domain/patient/pregnancyStatus';
+
+const registerInfoSchema: Record<
+  keyof Omit<RegisterInfo, IgnoredKeys>,
+  z.ZodTypeAny
+> = {
+  // ... 其他字段
+  /**---下面是人医专用字段--- */
+  /** 妊娠状态 */
+  pregnancy_status: z.nativeEnum(PregnancyStatus).optional(),
+  // ... 其他字段
+};
+```
+
+### 4. 更新 register.form.tsx
+
+修改 `src/pages/patient/components/register.form.tsx` 文件:
+
+```typescript
+// 导入妊娠状态类型和选项
+import { PregnancyStatus, pregnancyStatusOptions } from '@/domain/patient/pregnancyStatus';
+
+// 替换现有的 pregnancyStatusOptions
+// const pregnancyStatusOptions = [
+//   {
+//     value: 'yes',
+//     label: (
+//       <FormattedMessage
+//         id="register.pregnancyStatus.yes"
+//         defaultMessage="register.pregnancyStatus.yes"
+//       />
+//     ),
+//   },
+//   {
+//     value: 'no',
+//     label: (
+//       <FormattedMessage
+//         id="register.pregnancyStatus.no"
+//         defaultMessage="register.pregnancyStatus.no"
+//       />
+//     ),
+//   },
+//   {
+//     value: 'na',
+//     label: (
+//       <FormattedMessage
+//         id="register.pregnancyStatus.na"
+//         defaultMessage="register.pregnancyStatus.na"
+//       />
+//     ),
+//   },
+// ];
+
+// 使用国际化的妊娠状态选项
+const pregnancyStatusOptions: PregnancyStatusOption[] = [
+  {
+    value: PregnancyStatus.NOT_PREGNANT,
+    label: (
+      <FormattedMessage
+        id="register.pregnancyStatus.notPregnant"
+        defaultMessage="未怀孕"
+      />
+    ),
+  },
+  {
+    value: PregnancyStatus.POSSIBLY_PREGNANT,
+    label: (
+      <FormattedMessage
+        id="register.pregnancyStatus.possiblyPregnant"
+        defaultMessage="可能怀孕"
+      />
+    ),
+  },
+  {
+    value: PregnancyStatus.DEFINITELY_PREGNANT,
+    label: (
+      <FormattedMessage
+        id="register.pregnancyStatus.definitelyPregnant"
+        defaultMessage="确定怀孕"
+      />
+    ),
+  },
+  {
+    value: PregnancyStatus.UNKNOWN,
+    label: (
+      <FormattedMessage
+        id="register.pregnancyStatus.unknown"
+        defaultMessage="未知"
+      />
+    ),
+  },
+];
+```
+
+### 5. 更新 taskToRegister.ts
+
+修改 `src/domain/patient/taskToRegister.ts` 文件:
+
+```typescript
+// 导入妊娠状态类型
+import { PregnancyStatus } from '@/domain/patient/pregnancyStatus';
+
+export const mapTaskToRegisterInfo = (task: Task): RegisterInfo => {
+  // ... 其他代码
+  
+  // 处理妊娠状态
+  let pregnancyStatus: PregnancyStatus = PregnancyStatus.UNKNOWN;
+  if (task.pregnancy_status && Object.values(PregnancyStatus).includes(task.pregnancy_status as PregnancyStatus)) {
+    pregnancyStatus = task.pregnancy_status as PregnancyStatus;
+  }
+  
+  return {
+    // ... 其他字段
+    // 人医专用字段
+    pregnancy_status: pregnancyStatus,
+    // ... 其他字段
+  };
+};
+```
+
+## 国际化支持
+
+在国际化文件中添加以下键值对:
+
+```json
+{
+  "register.pregnancyStatus.notPregnant": "未怀孕",
+  "register.pregnancyStatus.possiblyPregnant": "可能怀孕",
+  "register.pregnancyStatus.definitelyPregnant": "确定怀孕",
+  "register.pregnancyStatus.unknown": "未知"
+}
+```
+
+## 测试用例
+
+创建 `src/domain/patient/pregnancyStatus.test.ts` 文件:
+
+```typescript
+import { PregnancyStatus, pregnancyStatusOptions, getPregnancyStatusDescription } from './pregnancyStatus';
+
+describe('PregnancyStatus', () => {
+  it('should have correct values', () => {
+    expect(PregnancyStatus.NOT_PREGNANT).toBe('0001');
+    expect(PregnancyStatus.POSSIBLY_PREGNANT).toBe('0002');
+    expect(PregnancyStatus.DEFINITELY_PREGNANT).toBe('0003');
+    expect(PregnancyStatus.UNKNOWN).toBe('0004');
+  });
+
+  it('should have correct options', () => {
+    expect(pregnancyStatusOptions).toHaveLength(4);
+    expect(pregnancyStatusOptions[0].value).toBe(PregnancyStatus.NOT_PREGNANT);
+    expect(pregnancyStatusOptions[1].value).toBe(PregnancyStatus.POSSIBLY_PREGNANT);
+    expect(pregnancyStatusOptions[2].value).toBe(PregnancyStatus.DEFINITELY_PREGNANT);
+    expect(pregnancyStatusOptions[3].value).toBe(PregnancyStatus.UNKNOWN);
+  });
+
+  it('should return correct description', () => {
+    expect(getPregnancyStatusDescription('0001')).toBe('未怀孕');
+    expect(getPregnancyStatusDescription('0002')).toBe('可能怀孕');
+    expect(getPregnancyStatusDescription('0003')).toBe('确定怀孕');
+    expect(getPregnancyStatusDescription('0004')).toBe('未知');
+    expect(getPregnancyStatusDescription('9999')).toBe('未知');
+  });
+});
+```
+
+## 现有代码修改指南
+
+### 修改 register.form.tsx 中的 pregnancyStatusOptions
+
+当前代码(第39-67行):
+```typescript
+const pregnancyStatusOptions = [
+  {
+    value: 'yes',
+    label: (
+      <FormattedMessage
+        id="register.pregnancyStatus.yes"
+        defaultMessage="register.pregnancyStatus.yes"
+      />
+    ),
+  },
+  {
+    value: 'no',
+    label: (
+      <FormattedMessage
+        id="register.pregnancyStatus.no"
+        defaultMessage="register.pregnancyStatus.no"
+      />
+    ),
+  },
+  {
+    value: 'na',
+    label: (
+      <FormattedMessage
+        id="register.pregnancyStatus.na"
+        defaultMessage="register.pregnancyStatus.na"
+      />
+    ),
+  },
+];
+```
+
+应修改为:
+```typescript
+// 导入妊娠状态类型和选项
+import { PregnancyStatus, pregnancyStatusOptions } from '@/domain/patient/pregnancyStatus';
+
+// 使用新的pregnancyStatusOptions,替换原有的定义
+// const pregnancyStatusOptions = [ ... ]; // 删除或注释掉原有定义
+```
+
+### 表单组件实现
+
+当前表单组件(第360-377行):
+```typescript
+<Form.Item
+  label={
+    <FormattedMessage
+      id="register.pregnancyStatus"
+      defaultMessage="register.pregnancyStatus"
+    />
+  }
+  name="pregnancy_status"
+  required={registerFormFields.pregnancy_status.required}
+  validateTrigger={registerFormFields.pregnancy_status.trigger}
+  rules={registerFormFields.pregnancy_status.validation}
+>
+  <Radio.Group
+    options={pregnancyStatusOptions}
+    optionType="button"
+    buttonStyle="solid"
+  />
+</Form.Item>
+```
+
+这个实现已经符合我们的设计,只需要确保导入正确的pregnancyStatusOptions即可。
+
+### 实施步骤
+
+1. **创建类型定义文件**
+   ```bash
+   # 创建 src/domain/patient/pregnancyStatus.ts 文件
+   # 复制设计文档中的代码到该文件
+   ```
+
+2. **更新 register.form.tsx**
+   ```typescript
+   // 在文件顶部添加导入
+   import { PregnancyStatus, pregnancyStatusOptions } from '@/domain/patient/pregnancyStatus';
+   
+   // 删除或注释掉原有的 pregnancyStatusOptions 定义
+   // const pregnancyStatusOptions = [ ... ];
+   ```
+
+3. **更新 workActions.ts**
+   ```typescript
+   // 在文件顶部添加导入
+   import { PregnancyStatus } from '@/domain/patient/pregnancyStatus';
+   
+   // 修改 RegisterInfo 接口中的 pregnancy_status 字段
+   pregnancy_status: PregnancyStatus;
+   ```
+
+4. **更新 registerSchema.ts**
+   ```typescript
+   // 在文件顶部添加导入
+   import { PregnancyStatus } from '@/domain/patient/pregnancyStatus';
+   
+   // 修改 pregnancy_status 的验证规则
+   pregnancy_status: z.nativeEnum(PregnancyStatus).optional(),
+   ```
+
+5. **更新 taskToRegister.ts**
+   ```typescript
+   // 在文件顶部添加导入
+   import { PregnancyStatus } from '@/domain/patient/pregnancyStatus';
+   
+   // 修改 mapTaskToRegisterInfo 函数中的 pregnancy_status 处理
+   // 处理妊娠状态
+   let pregnancyStatus: PregnancyStatus = PregnancyStatus.UNKNOWN;
+   if (task.pregnancy_status && Object.values(PregnancyStatus).includes(task.pregnancy_status as PregnancyStatus)) {
+     pregnancyStatus = task.pregnancy_status as PregnancyStatus;
+   }
+   
+   // 在返回对象中使用
+   pregnancy_status: pregnancyStatus,
+   ```
+
+6. **添加国际化支持**
+   ```json
+   // 在国际化文件中添加
+   {
+     "register.pregnancyStatus.notPregnant": "未怀孕",
+     "register.pregnancyStatus.possiblyPregnant": "可能怀孕",
+     "register.pregnancyStatus.definitelyPregnant": "确定怀孕",
+     "register.pregnancyStatus.unknown": "未知"
+   }
+   ```
+
+7. **创建测试文件**
+   ```bash
+   # 创建 src/domain/patient/pregnancyStatus.test.ts 文件
+   # 复制设计文档中的测试代码到该文件
+   ```
+
+### 注意事项
+
+1. **向后兼容性**:如果数据库中已有使用旧值('yes', 'no', 'na')的记录,需要在数据映射函数中添加转换逻辑:
+
+   ```typescript
+   // 在 taskToRegister.ts 中添加
+   const mapOldPregnancyStatusToNew = (oldStatus: string): PregnancyStatus => {
+     switch (oldStatus) {
+       case 'yes':
+         return PregnancyStatus.DEFINITELY_PREGNANT;
+       case 'no':
+         return PregnancyStatus.NOT_PREGNANT;
+       case 'na':
+       default:
+         return PregnancyStatus.UNKNOWN;
+     }
+   };
+   
+   // 在 mapTaskToRegisterInfo 函数中使用
+   let pregnancyStatus: PregnancyStatus = PregnancyStatus.UNKNOWN;
+   if (task.pregnancy_status) {
+     if (Object.values(PregnancyStatus).includes(task.pregnancy_status as PregnancyStatus)) {
+       pregnancyStatus = task.pregnancy_status as PregnancyStatus;
+     } else {
+       // 处理旧值
+       pregnancyStatus = mapOldPregnancyStatusToNew(task.pregnancy_status);
+     }
+   }
+   ```
+
+2. **表单默认值**:考虑在表单中设置默认值,特别是在ReRegister场景下:
+
+   ```typescript
+   // 在 register.form.tsx 中
+   <Form.Item
+     // ... 其他属性
+     initialValue={PregnancyStatus.UNKNOWN}
+   >
+     <Radio.Group
+       options={pregnancyStatusOptions}
+       optionType="button"
+       buttonStyle="solid"
+     />
+   </Form.Item>
+   ```
+
+## 总结
+
+通过以上实现,我们定义了完整的妊娠状态类型系统,包括:
+
+1. 类型定义:使用枚举明确定义了四种妊娠状态
+2. UI支持:提供了选项列表和描述函数
+3. 表单集成:更新了表单组件和验证规则
+4. 国际化:支持多语言显示
+5. 测试覆盖:确保类型和功能的正确性
+
+这个实现可以确保妊娠状态在整个应用中保持一致性和类型安全。

+ 2 - 1
src/API/patient/workActions.ts

@@ -3,6 +3,7 @@ import axiosInstance from '../interceptor';
 import { Task } from '@/domain/work';
 import { dview } from '@/domain/dview';
 import { XImage } from '@/domain/xImage';
+import { PregnancyStatus } from '@/domain/patient/pregnancyStatus';
 
 interface View {
   view_id: string;
@@ -38,7 +39,7 @@ export interface RegisterInfo {
   is_sedation: boolean;
   /**---下面是人医专用字段------------------------------ */
   /** 妊娠状态 */
-  pregnancy_status: string;
+  pregnancy_status: PregnancyStatus;
 }
 
 export interface RegisterWorkResponseData {

+ 46 - 0
src/domain/patient/pregnancyStatus.test.ts

@@ -0,0 +1,46 @@
+import { PregnancyStatus, pregnancyStatusOptions, getPregnancyStatusDescription, mapOldPregnancyStatusToNew } from './pregnancyStatus';
+
+describe('PregnancyStatus', () => {
+  it('should have correct values', () => {
+    expect(PregnancyStatus.NOT_PREGNANT).toBe('0001');
+    expect(PregnancyStatus.POSSIBLY_PREGNANT).toBe('0002');
+    expect(PregnancyStatus.DEFINITELY_PREGNANT).toBe('0003');
+    expect(PregnancyStatus.UNKNOWN).toBe('0004');
+  });
+
+  it('should have correct options', () => {
+    expect(pregnancyStatusOptions).toHaveLength(4);
+    expect(pregnancyStatusOptions[0].value).toBe(PregnancyStatus.NOT_PREGNANT);
+    expect(pregnancyStatusOptions[1].value).toBe(PregnancyStatus.POSSIBLY_PREGNANT);
+    expect(pregnancyStatusOptions[2].value).toBe(PregnancyStatus.DEFINITELY_PREGNANT);
+    expect(pregnancyStatusOptions[3].value).toBe(PregnancyStatus.UNKNOWN);
+  });
+
+  it('should return correct description', () => {
+    expect(getPregnancyStatusDescription('0001')).toBe('未怀孕');
+    expect(getPregnancyStatusDescription('0002')).toBe('可能怀孕');
+    expect(getPregnancyStatusDescription('0003')).toBe('确定怀孕');
+    expect(getPregnancyStatusDescription('0004')).toBe('未知');
+    expect(getPregnancyStatusDescription('9999')).toBe('未知');
+  });
+
+  describe('mapOldPregnancyStatusToNew', () => {
+    it('should map "yes" to DEFINITELY_PREGNANT', () => {
+      expect(mapOldPregnancyStatusToNew('yes')).toBe(PregnancyStatus.DEFINITELY_PREGNANT);
+    });
+
+    it('should map "no" to NOT_PREGNANT', () => {
+      expect(mapOldPregnancyStatusToNew('no')).toBe(PregnancyStatus.NOT_PREGNANT);
+    });
+
+    it('should map "na" to UNKNOWN', () => {
+      expect(mapOldPregnancyStatusToNew('na')).toBe(PregnancyStatus.UNKNOWN);
+    });
+
+    it('should map unknown values to UNKNOWN', () => {
+      expect(mapOldPregnancyStatusToNew('unknown')).toBe(PregnancyStatus.UNKNOWN);
+      expect(mapOldPregnancyStatusToNew('')).toBe(PregnancyStatus.UNKNOWN);
+      expect(mapOldPregnancyStatusToNew('invalid')).toBe(PregnancyStatus.UNKNOWN);
+    });
+  });
+});

+ 89 - 0
src/domain/patient/pregnancyStatus.ts

@@ -0,0 +1,89 @@
+/**
+ * 妊娠状态类型定义
+ */
+
+/**
+ * 妊娠状态枚举
+ * 0001: not pregnant - 未怀孕
+ * 0002: possibly pregnant - 可能怀孕
+ * 0003: definitely pregnant - 确定怀孕
+ * 0004: unknown - 未知
+ */
+export enum PregnancyStatus {
+  NOT_PREGNANT = '0001', // 未怀孕
+  POSSIBLY_PREGNANT = '0002', // 可能怀孕
+  DEFINITELY_PREGNANT = '0003', // 确定怀孕
+  UNKNOWN = '0004', // 未知
+}
+
+/**
+ * 妊娠状态选项接口,用于UI下拉选择
+ */
+export interface PregnancyStatusOption {
+  value: PregnancyStatus;
+  label: React.ReactNode;
+  description?: string;
+}
+
+/**
+ * 妊娠状态选项列表
+ */
+export const pregnancyStatusOptions: PregnancyStatusOption[] = [
+  {
+    value: PregnancyStatus.NOT_PREGNANT,
+    label: '未怀孕',
+    description: '患者未怀孕'
+  },
+  {
+    value: PregnancyStatus.POSSIBLY_PREGNANT,
+    label: '可能怀孕',
+    description: '患者可能怀孕'
+  },
+  {
+    value: PregnancyStatus.DEFINITELY_PREGNANT,
+    label: '确定怀孕',
+    description: '患者确定怀孕'
+  },
+  {
+    value: PregnancyStatus.UNKNOWN,
+    label: '未知',
+    description: '患者妊娠状态未知'
+  }
+];
+
+/**
+ * 根据值获取妊娠状态描述
+ * @param status 妊娠状态值
+ * @returns 妊娠状态描述
+ */
+export const getPregnancyStatusDescription = (status: string): string => {
+  switch (status) {
+    case PregnancyStatus.NOT_PREGNANT:
+      return '未怀孕';
+    case PregnancyStatus.POSSIBLY_PREGNANT:
+      return '可能怀孕';
+    case PregnancyStatus.DEFINITELY_PREGNANT:
+      return '确定怀孕';
+    case PregnancyStatus.UNKNOWN:
+      return '未知';
+    default:
+      return '未知';
+  }
+};
+
+/**
+ * 将旧妊娠状态值映射到新枚举
+ * @param oldStatus 旧状态值 ('yes', 'no', 'na')
+ * @returns 新的妊娠状态枚举
+ */
+export const mapOldPregnancyStatusToNew = (oldStatus: string): PregnancyStatus => {
+  switch (oldStatus) {
+    case 'yes':
+      return PregnancyStatus.DEFINITELY_PREGNANT;
+    case 'no':
+      return PregnancyStatus.NOT_PREGNANT;
+    case 'na':
+    default:
+      return PregnancyStatus.UNKNOWN;
+  }
+};

+ 107 - 0
src/domain/patient/taskToRegister.test.ts

@@ -0,0 +1,107 @@
+import { mapTaskToRegisterInfo } from './taskToRegister';
+import { Task } from '@/domain/work';
+import { RegisterInfo } from '@/API/patient/workActions';
+
+describe('mapTaskToRegisterInfo', () => {
+  const mockTask: Task = {
+    StudyInstanceUID: 'UID001',
+    StudyID: 'STUDY001',
+    SpecificCharacterSet: 'ISO_IR 100',
+    AccessionNumber: 'ACC001',
+    PatientID: 'P001',
+    PatientName: '测试患者',
+    DisplayPatientName: '测试患者',
+    PatientSize: 'Medium',
+    PatientAge: '030Y',
+    PatientSex: 'male',
+    AdmittingTime: '',
+    RegSource: '',
+    StudyStatus: '',
+    RequestedProcedureID: '',
+    PerformedProtocolCodeValue: '',
+    PerformedProtocolCodeMeaning: '',
+    PerformedProcedureStepID: '',
+    StudyDescription: '',
+    StudyStartDatetime: '',
+    ScheduledProcedureStepStartDate: '',
+    StudyLock: '',
+    OperatorID: '',
+    Modality: 'DX',
+    Views: [],
+    Thickness: 20,
+    PatientType: '',
+    StudyType: '',
+    QRCode: '',
+    IsExported: false,
+    IsEdited: false,
+    WorkRef: '',
+    IsAppended: false,
+    CreationTime: '',
+    MappedStatus: false,
+    IsDelete: false,
+    patient_dob: '1990-01-01',
+    ref_physician: '测试医生',
+    weight: 70,
+    length: 180,
+    comment: '测试注释',
+    owner_name: '测试主人',
+    chip_number: '123456789',
+    variety: '测试品种',
+    sex_neutered: 'UNALTERED',
+    is_anaesthesia: false,
+    is_sedation: false,
+  };
+
+  it('should correctly map Task to RegisterInfo', () => {
+    const result: RegisterInfo = mapTaskToRegisterInfo(mockTask);
+    
+    expect(result.patient_name).toBe('测试患者');
+    expect(result.patient_id).toBe('P001');
+    expect(result.patient_size).toBe('Medium');
+    expect(result.patient_age).toBe('030Y');
+    expect(result.patient_sex).toBe('male');
+    expect(result.patient_dob).toBeDefined();
+    expect(result.ref_physician).toBe('测试医生');
+    expect(result.weight).toBe(70);
+    expect(result.thickness).toBe(20);
+    expect(result.length).toBe(180);
+    expect(result.comment).toBe('测试注释');
+    expect(result.owner_name).toBe('测试主人');
+    expect(result.chip_number).toBe('123456789');
+    expect(result.variety).toBe('测试品种');
+    expect(result.sex_neutered).toBe('UNALTERED');
+    expect(result.is_anaesthesia).toBe(false);
+    expect(result.is_sedation).toBe(false);
+  });
+
+  it('should handle missing patient_dob', () => {
+    const taskWithoutDob = { ...mockTask, patient_dob: undefined };
+    const result = mapTaskToRegisterInfo(taskWithoutDob);
+    expect(result.patient_dob).toBe('');
+  });
+
+  it('should handle missing weight, thickness, and length', () => {
+    const taskWithoutNumbers = { 
+      ...mockTask, 
+      weight: undefined, 
+      thickness: undefined, 
+      length: undefined 
+    };
+    const result = mapTaskToRegisterInfo(taskWithoutNumbers);
+    expect(result.weight).toBe(0);
+    expect(result.thickness).toBe(0);
+    expect(result.length).toBe(0);
+  });
+
+  it('should handle empty PatientAge', () => {
+    const taskWithEmptyAge = { ...mockTask, PatientAge: '' };
+    const result = mapTaskToRegisterInfo(taskWithEmptyAge);
+    expect(result.patient_age).toBe('000Y');
+  });
+
+  it('should handle invalid PatientAge format', () => {
+    const taskWithInvalidAge = { ...mockTask, PatientAge: 'invalid' };
+    const result = mapTaskToRegisterInfo(taskWithInvalidAge);
+    expect(result.patient_age).toBe('000Y');
+  });
+});

+ 80 - 0
src/domain/patient/taskToRegister.ts

@@ -0,0 +1,80 @@
+import { Task } from '@/domain/work';
+import { RegisterInfo } from '@/API/patient/workActions';
+import dayjs from 'dayjs';
+import utc from 'dayjs/plugin/utc';
+import { PregnancyStatus, mapOldPregnancyStatusToNew } from '@/domain/patient/pregnancyStatus';
+
+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 {
+      // 确保patient_dob是有效的日期字符串
+      const date = dayjs.utc(task.patient_dob);
+      if (date.isValid()) {
+        patientDob = date.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]');
+      } else {
+        console.error('Invalid patient_dob date:', task.patient_dob);
+        patientDob = '';
+      }
+    } 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;
+  
+  // 处理妊娠状态
+  let pregnancyStatus: PregnancyStatus = PregnancyStatus.UNKNOWN;
+  if (task.pregnancy_status) {
+    if (Object.values(PregnancyStatus).includes(task.pregnancy_status as PregnancyStatus)) {
+      pregnancyStatus = task.pregnancy_status as PregnancyStatus;
+    } else {
+      // 处理旧值 ('yes', 'no', 'na')
+      pregnancyStatus = mapOldPregnancyStatusToNew(task.pregnancy_status);
+    }
+  }
+  
+  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: pregnancyStatus,
+    // 添加缺失的字段
+    is_anaesthesia: task.is_anaesthesia || false,
+    is_sedation: task.is_sedation || false,
+  };
+};

+ 61 - 1
src/pages/patient/components/ActionPanel.tsx

@@ -15,7 +15,11 @@ import { Popup } from 'antd-mobile';
 import { setVisible } from '@/states/patient/DiagnosticReport/slice';
 import EditTaskModal from './EditTaskModal';
 import { openEditModal } from '@/states/patient/edit/editFormSlice';
+import { setBusinessFlow } from '@/states/BusinessFlowSlice';
+import { setSourceTask, setRegisterInfo } from '@/states/patient/reregister/reregisterSlice';
+import { mapTaskToRegisterInfo } from '@/domain/patient/taskToRegister';
 import { showNotImplemented } from '@/utils/notificationHelper';
+import { string } from 'zod';
 
 interface ActionButtonProps {
   icon: React.ReactNode;
@@ -130,6 +134,7 @@ const ActionPanel: React.FC = () => {
   const getWorksFromWorklistOrHistory = () => {
     return currentKey === 'worklist' ? workEntities : workEntitiesFromHistory;
   };
+  
   const handleEdit = () => {
     const selectedIds = getSelectedWorkIds();
 
@@ -151,6 +156,61 @@ const ActionPanel: React.FC = () => {
       dispatch(openEditModal(task));
     }
   };
+  
+  const handleReRegister = async () => {
+    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 {
+      // 设置源任务数据
+      await dispatch(setSourceTask(task));
+      
+      // 将Task数据映射为RegisterInfo
+      const registerInfo = mapTaskToRegisterInfo(task);
+      console.log(`study转换后的注册信息:${JSON.stringify(registerInfo)}`)
+      // 验证映射后的数据
+      if (!registerInfo.patient_name || !registerInfo.patient_id) {
+        throw new Error('数据映射失败,缺少必要信息');
+      }
+      console.log(`开始设置注册信息`);
+      // 设置注册信息
+      await dispatch(setRegisterInfo(registerInfo));
+      console.log(`开始切换到注册页面`);
+      // 切换到注册页面
+      await dispatch(setBusinessFlow('register'));
+      
+      message.success('已切换到注册页面,表单已预填充');
+    } catch (error) {
+      console.error('ReRegister error:', error);
+      const errorMessage = error instanceof Error ? error.message : '重新注册失败,请重试';
+      message.error(`重新注册失败: ${errorMessage}`);
+    }
+  };
 
   const handleLock = () => {
     const selectedIds = getSelectedWorkIds();
@@ -274,7 +334,7 @@ const ActionPanel: React.FC = () => {
             defaultMessage="actionPanel.reRegister"
           />
         }
-        onClick={() => showNotImplemented('')}
+        onClick={handleReRegister}
       />
 
 

+ 2 - 30
src/pages/patient/components/register.form.tsx

@@ -14,6 +14,7 @@ import NumberWithUnit from '@/components/NumberWithUnit';
 import dayjs, { Dayjs } from 'dayjs';
 import { useSelector } from 'react-redux';
 import { RootState } from '@/states/store';
+import { PregnancyStatus, pregnancyStatusOptions } from '@/domain/patient/pregnancyStatus';
 
 const genderOptions = [
   {
@@ -36,36 +37,6 @@ const genderOptions = [
   },
 ];
 
-const pregnancyStatusOptions = [
-  {
-    value: 'yes',
-    label: (
-      <FormattedMessage
-        id="register.pregnancyStatus.yes"
-        defaultMessage="register.pregnancyStatus.yes"
-      />
-    ),
-  },
-  {
-    value: 'no',
-    label: (
-      <FormattedMessage
-        id="register.pregnancyStatus.no"
-        defaultMessage="register.pregnancyStatus.no"
-      />
-    ),
-  },
-  {
-    value: 'na',
-    label: (
-      <FormattedMessage
-        id="register.pregnancyStatus.na"
-        defaultMessage="register.pregnancyStatus.na"
-      />
-    ),
-  },
-];
-
 interface BasicInfoFormProps {
   style?: React.CSSProperties;
   form?: FormInstance;
@@ -368,6 +339,7 @@ const BasicInfoForm: React.FC<BasicInfoFormProps> = ({
           required={registerFormFields.pregnancy_status.required}
           validateTrigger={registerFormFields.pregnancy_status.trigger}
           rules={registerFormFields.pregnancy_status.validation}
+          initialValue={PregnancyStatus.UNKNOWN}
         >
           <Radio.Group
             options={pregnancyStatusOptions}

+ 66 - 12
src/pages/patient/register.tsx

@@ -25,6 +25,8 @@ import { omitAnimalSchemaMap } from '@/domain/animalSpecificInfo';
 import { RootState } from '@/states/store';
 import { omitHumanSchemaMap } from '@/domain/humanSpecificInfo';
 import { setBusinessFlow } from '@/states/BusinessFlowSlice';
+import { selectRegisterInfo } from '@/states/patient/reregister/reregisterSlice';
+import { clearReRegister } from '@/states/patient/reregister/reregisterSlice';
 dayjs.extend(utc);
 
 const { useBreakpoint } = Grid;
@@ -38,6 +40,7 @@ const RegisterPage: React.FC = () => {
   const productName = useSelector(
     (state: RootState) => state.product.productName
   );
+  const reregisterInfo = useSelector(selectRegisterInfo);
 
   // 清理时机3:组件卸载时清理表单
   useEffect(() => {
@@ -49,6 +52,45 @@ const RegisterPage: React.FC = () => {
     };
   }, [dispatch, form]);
 
+  // 当有ReRegister数据时,填充表单
+  useEffect(() => {
+    if (reregisterInfo) {
+      try {
+        console.log('ReRegister数据已获取,填充表单:', reregisterInfo);
+
+        // 验证数据完整性
+        if (!reregisterInfo.patient_name || !reregisterInfo.patient_id) {
+          console.error('ReRegister数据不完整:', reregisterInfo);
+          message.warning('预填充数据不完整,请手动填写必要信息');
+          return;
+        }
+
+        // 设置表单值
+        const updatedInfo = {
+          ...reregisterInfo,
+          patient_dob: reregisterInfo.patient_dob ? dayjs(reregisterInfo.patient_dob) : null
+        };
+        form.setFieldsValue(updatedInfo);
+
+        // 更新Redux表单数据
+        dispatch(setFormData(reregisterInfo));
+
+        // 显示成功消息
+        message.success('已从历史记录预填充表单数据');
+      } catch (error) {
+        console.error('填充表单时出错:', error);
+        message.error('预填充表单数据时出错,请手动填写');
+      }
+    }
+  }, [reregisterInfo, form, dispatch]);
+
+  // 清理ReRegister数据
+  useEffect(() => {
+    return () => {
+      dispatch(clearReRegister());
+    };
+  }, [dispatch]);
+
   const handleRegister = async (): Promise<{
     success: boolean;
 
@@ -61,9 +103,21 @@ const RegisterPage: React.FC = () => {
       // console.log(`转换前的年龄值:${JSON.stringify(values.patient_age)}`)
       // const age=`${values.patient_age.number}${values.patient_age.unit}`;
       // console.log(`转换后的年龄和转换前的年龄:${age}---${values.patient_age}`)
-      const formatDob = values.patient_dob
-        ? dayjs.utc(values.patient_dob).format('YYYY-MM-DDTHH:mm:ss.SSS[Z]') //values.patient_dob.toString('YYYY-MM-DD[T00:00:00.000000Z]')
-        : '';
+      let formatDob = '';
+      if (values.patient_dob) {
+        try {
+          const date = dayjs.utc(values.patient_dob);
+          if (date.isValid()) {
+            formatDob = date.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]');
+          } else {
+            console.error('Invalid patient_dob date:', values.patient_dob);
+            formatDob = '';
+          }
+        } catch (error) {
+          console.error('Error parsing patient_dob:', error);
+          formatDob = '';
+        }
+      }
       console.log(`转换后的日期:${formatDob}`);
 
       values = { ...values, patient_dob: formatDob };
@@ -116,7 +170,7 @@ const RegisterPage: React.FC = () => {
     } catch (error) {
       console.error('Error registering work:', error);
       message.error(
-        'Error registering work, please check the console for details.'
+        'Error registering work, please check that console for details.'
       );
       return { success: false, views: [] };
     }
@@ -247,10 +301,10 @@ const RegisterPage: React.FC = () => {
         }}
       >
         <Button type="primary" onClick={handleRegister}>
-                      <FormattedMessage
-                        id="register.register"
-                        defaultMessage={'register.register'}
-                      />
+          <FormattedMessage
+            id="register.register"
+            defaultMessage={'register.register'}
+          />
         </Button>
         <Button
           type="default"
@@ -258,10 +312,10 @@ const RegisterPage: React.FC = () => {
             dispatch(setBusinessFlow('exam'));
           }}
         >
-                                <FormattedMessage
-                        id="register.exam"
-                        defaultMessage={'register.exam'}
-                      />
+          <FormattedMessage
+            id="register.exam"
+            defaultMessage={'register.exam'}
+          />
         </Button>
       </Space>
     </>

+ 125 - 0
src/states/patient/reregister/reregisterSlice.test.ts

@@ -0,0 +1,125 @@
+import { reregisterSlice, ReRegisterState } from './reregisterSlice';
+import { Task } from '@/domain/work';
+import { RegisterInfo } from '@/API/patient/workActions';
+
+describe('reregisterSlice', () => {
+  const initialState: ReRegisterState = {
+    sourceTask: null,
+    registerInfo: null,
+    loading: false,
+    error: null,
+  };
+
+  it('should return the initial state', () => {
+    expect(reregisterSlice.reducer(undefined, { type: 'unknown' })).toEqual(initialState);
+  });
+
+  it('should handle setSourceTask', () => {
+    const mockTask: Task = {
+      StudyInstanceUID: 'UID001',
+      StudyID: 'STUDY001',
+      SpecificCharacterSet: 'ISO_IR 100',
+      AccessionNumber: 'ACC001',
+      PatientID: 'P001',
+      PatientName: '测试患者',
+      DisplayPatientName: '测试患者',
+      PatientSize: 'Medium',
+      PatientAge: '030Y',
+      PatientSex: 'male',
+      AdmittingTime: '',
+      RegSource: '',
+      StudyStatus: '',
+      RequestedProcedureID: '',
+      PerformedProtocolCodeValue: '',
+      PerformedProtocolCodeMeaning: '',
+      PerformedProcedureStepID: '',
+      StudyDescription: '',
+      StudyStartDatetime: '',
+      ScheduledProcedureStepStartDate: '',
+      StudyLock: '',
+      OperatorID: '',
+      Modality: 'DX',
+      Views: [],
+      Thickness: 20,
+      PatientType: '',
+      StudyType: '',
+      QRCode: '',
+      IsExported: false,
+      IsEdited: false,
+      WorkRef: '',
+      IsAppended: false,
+      CreationTime: '',
+      MappedStatus: false,
+      IsDelete: false,
+      patient_dob: '1990-01-01',
+      ref_physician: '测试医生',
+      weight: 70,
+      length: 180,
+      comment: '测试注释',
+      owner_name: '测试主人',
+      chip_number: '123456789',
+      variety: '测试品种',
+      sex_neutered: 'UNALTERED',
+      is_anaesthesia: false,
+      is_sedation: false,
+    };
+
+    const actual = reregisterSlice.reducer(initialState, reregisterSlice.actions.setSourceTask(mockTask));
+    expect(actual.sourceTask).toEqual(mockTask);
+  });
+
+  it('should handle setRegisterInfo', () => {
+    const mockRegisterInfo: RegisterInfo = {
+      accession_number: 'ACC001',
+      patient_id: 'P001',
+      patient_name: '测试患者',
+      patient_size: 'Medium',
+      patient_age: '030Y',
+      patient_dob: '1990-01-01T00:00:00.000Z',
+      patient_sex: 'male',
+      patient_type: '',
+      ref_physician: '测试医生',
+      operator_id: '',
+      modality: 'DX',
+      weight: 70,
+      thickness: 20,
+      length: 180,
+      study_type: 'Normal',
+      comment: '测试注释',
+      views: [],
+      owner_name: '测试主人',
+      sex_neutered: 'UNALTERED',
+      chip_number: '123456789',
+      variety: '测试品种',
+      pregnancy_status: 'NOT_PREGNANT',
+      is_anaesthesia: false,
+      is_sedation: false,
+    };
+
+    const actual = reregisterSlice.reducer(initialState, reregisterSlice.actions.setRegisterInfo(mockRegisterInfo));
+    expect(actual.registerInfo).toEqual(mockRegisterInfo);
+  });
+
+  it('should handle clearReRegister', () => {
+    const stateWithData: ReRegisterState = {
+      sourceTask: {} as Task,
+      registerInfo: {} as RegisterInfo,
+      loading: false,
+      error: 'test error',
+    };
+
+    const actual = reregisterSlice.reducer(stateWithData, reregisterSlice.actions.clearReRegister());
+    expect(actual).toEqual(initialState);
+  });
+
+  it('should handle setLoading', () => {
+    const actual = reregisterSlice.reducer(initialState, reregisterSlice.actions.setLoading(true));
+    expect(actual.loading).toBe(true);
+  });
+
+  it('should handle setError', () => {
+    const errorMessage = 'Test error message';
+    const actual = reregisterSlice.reducer(initialState, reregisterSlice.actions.setError(errorMessage));
+    expect(actual.error).toBe(errorMessage);
+  });
+});

+ 58 - 0
src/states/patient/reregister/reregisterSlice.ts

@@ -0,0 +1,58 @@
+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 - 0
src/states/store.ts

@@ -71,6 +71,7 @@ import themeReducer from './themeSlice';
 import cameraReducer from './exam/cameraSlice';
 import pacsNodeReducer from './output/pacsNode/pacsNodeSlice';
 import selectedPatientReducer from './patient/worklist/slices/selectedPatientSlice';
+import reregisterReducer from './patient/reregister/reregisterSlice';
 import {
   binEntitiesSlice,
   binFiltersSlice,
@@ -142,6 +143,7 @@ const store = configureStore({
     camera: cameraReducer,
     pacsNode: pacsNodeReducer,
     selectedPatient: selectedPatientReducer,
+    reregister: reregisterReducer,
     binEntities: binEntitiesSlice.reducer,
     binFilters: binFiltersSlice.reducer,
     binPagination: binPaginationSlice.reducer,

+ 3 - 2
src/validation/patient/registerSchema.ts

@@ -8,6 +8,7 @@ import {
 } from 'zod';
 import { RegisterInfo } from '@/API/patient/workActions';
 import { Rule } from 'antd/es/form';
+import { PregnancyStatus } from '@/domain/patient/pregnancyStatus';
 
 type IgnoredKeys = 'views' | 'study_type' | 'modality' | 'patient_type'; // 这些字段不在表单中填写
 const registerInfoSchema: Record<
@@ -43,11 +44,11 @@ const registerInfoSchema: Record<
   is_anaesthesia: z.boolean().optional(),
   is_sedation: z.boolean().optional(),
   /**---下面是人医专用字段--- */
-  pregnancy_status: z.string().optional(),
+  pregnancy_status: z.nativeEnum(PregnancyStatus).optional(),
 };
 
 const registerformSchema = z.object(registerInfoSchema);
-// 统一管理规则。规则表。描述要验证的字段【字段来源于被验证的数据结构UserInfo】,以及每个字段的“是否必填、必填标记、验证规则、提示信息、验证时机”
+// 统一管理规则。规则表。描述要验证的字段【字段来源于被验证的数据结构UserInfo】,以及每个字段的"是否必填、必填标记、验证规则、提示信息、验证时机"
 export const registerFormFields = {
   accession_number: {
     label: 'Accession Number',