瀏覽代碼

feat: 实现任务编辑功能及架构重构

- 新增 EditTaskModal 组件,采用四行响应式布局设计
- 创建 editFormSlice 用于集中管理编辑状态(visible、editingTask、loading)
- 在 workActions.ts 中添加 editTask API
- 重构 ActionPanel 通过 Redux action 传递数据
- workSlice 监听编辑成功事件并更新列表数据
- 在 store.ts 中注册 editForm reducer

涉及文件:
- src/pages/patient/components/EditTaskModal.tsx (新增)
- src/states/patient/edit/editFormSlice.ts (新增)
- src/API/patient/workActions.ts
- src/pages/patient/components/ActionPanel.tsx
- src/states/patient/worklist/slices/workSlice.ts
- src/states/store.ts
- src/domain/work.ts
sw 5 天之前
父節點
當前提交
461adedb29

+ 45 - 40
src/API/patient/workActions.ts

@@ -324,46 +324,6 @@ const registerWork = async (
   }
 };
 
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-const mapToTask = (study: RegisterWorkResponseData): Task => ({
-  StudyInstanceUID: study.study_instance_uid,
-  StudyID: study.study_id,
-  SpecificCharacterSet: study.specific_character_set,
-  AccessionNumber: study.accession_number,
-  PatientID: study.patient_id,
-  PatientName: study.patient_name,
-  DisplayPatientName: study.patient_name,
-  PatientSize: study.patient_size,
-  PatientAge: study.patient_age,
-  PatientSex: study.patient_sex,
-  AdmittingTime: study.admitting_time ?? '',
-  RegSource: study.reg_source,
-  StudyStatus: study.study_status,
-  RequestedProcedureID: '',
-  PerformedProtocolCodeValue: '',
-  PerformedProtocolCodeMeaning: '',
-  PerformedProcedureStepID: '',
-  StudyDescription: study.study_description,
-  StudyStartDatetime: study.study_start_datetime ?? '',
-  ScheduledProcedureStepStartDate:
-    study.scheduled_procedure_step_start_date ?? '',
-  StudyLock: study.study_lock,
-  OperatorID: study.operator_name,
-  Modality: study.modality,
-  Views: [],
-  Thickness: study.thickness,
-  PatientType: study.patient_type,
-  StudyType: study.study_type,
-  QRCode: '',
-  IsExported: study.is_exported,
-  IsEdited: study.is_edited,
-  WorkRef: '',
-  IsAppended: study.is_appended,
-  CreationTime: '',
-  MappedStatus: study.mapped_status,
-  IsDelete: false,
-});
-
 /**
  * 将 FetchTaskListStudy 映射到 Task - fetchTaskList 专用
  */
@@ -404,6 +364,19 @@ const mapFetchTaskListStudyToTask = (study: FetchTaskListStudy): Task => ({
   CreationTime: study.create_time,
   MappedStatus: study.mapped_status,
   IsDelete: false,
+  patient_dob: study.patient_dob,
+  ref_physician: study.ref_physician,
+  weight: study.weight,
+  length: study.length,
+  comment: study.comment,
+  // 宠物专用字段
+  owner_name: study.owner_name,
+  chip_number: study.chip_number,
+  variety: study.variety,
+  pregnancy_status: study.pregnancy_status,
+  sex_neutered: study.sex_neutered,
+  is_anaesthesia: study.is_anaesthesia,
+  is_sedation: study.is_sedation,
 });
 
 const fetchTaskList = async (
@@ -499,6 +472,38 @@ const deleteStudies = async (
 };
 
 export { deleteStudies };
+
+/**
+ * 编辑任务的请求参数类型 - 复用 RegisterInfo 但不包含 views 字段
+ */
+export type EditTaskInfo = Omit<RegisterInfo, 'views'>;
+
+/**
+ * 编辑任务
+ * @param studyId 研究ID
+ * @param taskInfo 任务信息(不包含 views)
+ */
+export const editTask = async (
+  studyId: string,
+  taskInfo: EditTaskInfo
+): Promise<RegisterWorkResponse> => {
+  try {
+    const response = await axiosInstance.put(
+      `/auth/study/${studyId}`,
+      taskInfo
+    );
+    console.log('Task edited successfully:', response.data);
+    if (response.data.code !== '0x000000') {
+      console.error('编辑任务请求失败', response.data.description);
+      throw new Error(`编辑任务请求失败 ${response.data.description}`);
+    }
+    return response.data;
+  } catch (error) {
+    console.error('Error editing task:', error);
+    throw error;
+  }
+};
+
 // eslint-disable-next-line
 const suspendOrCompleteStudy = async (studyId: string, studyStatus: 'InProgress' | 'Completed'): Promise<{ code: string; description: string; solution: string; data: {} }> => {
   try {

+ 20 - 0
src/domain/work.ts

@@ -37,6 +37,26 @@ export interface Task {
   CreationTime: string;
   MappedStatus: boolean;
   IsDelete: boolean;
+  /**
+   * 生日
+   */
+  patient_dob?: string; // 生日
+  /**
+   * // 主管医师
+   */
+  ref_physician?: string;
+  weight?: number; // 体重
+  length?: number; // 长度
+  comment?: string; // 备注
+  /**---下面是宠物专用字段--- */
+  owner_name?: string; // 主人姓名,
+  chip_number?: string; // 芯片号,
+  variety: string; // 品种,
+  sex_neutered: string; // 绝育状态, 'ALTERED' | 'UNALTERED' | 'UNKNOWN'
+  is_anaesthesia?: boolean; // 是否麻醉,
+  is_sedation?: boolean; // 是否镇静,
+  /**---下面是人医专用字段--- */
+  pregnancy_status?: string; // 妊娠状态, 'NOT_PREGNANT' | 'PREGNANT' | 'UNKNOWN'
 }
 
 export type TaskAnimal = Omit<Task, 'pregnancy_status'> & {

+ 27 - 0
src/pages/patient/components/ActionPanel.tsx

@@ -12,6 +12,8 @@ import Icon from '@/components/Icon';
 import DiagnosticReport from '../DiagnosticReport';
 import { Popup } from 'antd-mobile';
 import { setVisible } from '@/states/patient/DiagnosticReport/slice';
+import EditTaskModal from './EditTaskModal';
+import { openEditModal } from '@/states/patient/edit/editFormSlice';
 
 interface ActionButtonProps {
   icon: React.ReactNode;
@@ -31,6 +33,7 @@ const ActionButton: React.FC<ActionButtonProps> = ({
 
 const ActionPanel: React.FC = () => {
   const dispatch = useDispatch<AppDispatch>();
+
   const workSelectedIds = useSelector(
     (state: RootState) => state.workSelection.selectedIds
   );
@@ -89,6 +92,28 @@ const ActionPanel: React.FC = () => {
   const getWorksFromWorklistOrHistory = () => {
     return currentKey === 'worklist' ? workEntities : workEntitiesFromHistory;
   };
+  const handleEdit = () => {
+    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) {
+      // 通过 dispatch action 传递数据到 slice
+      dispatch(openEditModal(task));
+    }
+  };
+
   const handleLock = () => {
     const selectedIds = getSelectedWorkIds();
 
@@ -153,6 +178,7 @@ const ActionPanel: React.FC = () => {
             defaultMessage="actionPanel.editPatient"
           />
         }
+        onClick={handleEdit}
       />
       <ActionButton
         icon={
@@ -401,6 +427,7 @@ const ActionPanel: React.FC = () => {
       >
         <DiagnosticReport />
       </Popup>
+      <EditTaskModal />
     </div>
   );
 };

+ 553 - 0
src/pages/patient/components/EditTaskModal.tsx

@@ -0,0 +1,553 @@
+/* eslint-disable */
+import React, { useEffect, useRef } from 'react';
+import { Form, message, Switch, Row, Col, Input, DatePicker, Select, InputNumber, Button } from 'antd';
+import { CloseOutlined } from '@ant-design/icons';
+import { useDispatch, useSelector } from 'react-redux';
+import { AppDispatch, RootState } from '@/states/store';
+import { FormattedMessage, useIntl } from 'react-intl';
+import dayjs, { Dayjs } from 'dayjs';
+import { EditTaskInfo } from '@/API/patient/workActions';
+import {
+  setEditFormData,
+  closeEditModal,
+  editTaskThunk
+} from '@/states/patient/edit/editFormSlice';
+import { registerFormFields } from '@/validation/patient/registerSchema';
+import NumberWithUnit from '@/components/NumberWithUnit';
+
+const EditTaskModal: React.FC = () => {
+  const [form] = Form.useForm();
+  const dispatch = useDispatch<AppDispatch>();
+  const intl = useIntl();
+
+  // 使用 ref 记录更新源,防止循环触发
+  const updateSourceRef = useRef<'age' | 'dob' | null>(null);
+
+  // 从 Redux 读取状态
+  const visible = useSelector((state: RootState) => state.editForm.visible);
+  const task = useSelector((state: RootState) => state.editForm.editingTask);
+  const loading = useSelector((state: RootState) => state.editForm.loading);
+  const productName = useSelector(
+    (state: RootState) => state.product.productName
+  );
+
+  const patient_age = Form.useWatch('patient_age', form);
+  const patient_dob = Form.useWatch('patient_dob', form);
+
+  // 解析年龄字符串(格式如 "005Y" 或 "012M")
+  const parseAge = (ageStr: string): { number: number; unit: 'D' | 'M' | 'Y' } => {
+    if (!ageStr || ageStr.length < 2) {
+      return { number: 0, unit: 'Y' };
+    }
+    const unit = ageStr.slice(-1) as 'D' | 'M' | 'Y';
+    const number = parseInt(ageStr.slice(0, -1), 10) || 0;
+    return { number, unit };
+  };
+
+  // 格式化年龄为字符串(格式如 "005Y")
+  const formatAge = (age: { number: number; unit: 'D' | 'M' | 'Y' }): string => {
+    const numberStr = age.number.toString().padStart(3, '0');
+    return `${numberStr}${age.unit}`;
+  };
+
+  // 根据年龄计算出生日期
+  const calculateDobFromAge = (age: {
+    number: number;
+    unit: 'D' | 'M' | 'Y';
+  }): Dayjs => {
+    const now = dayjs();
+    switch (age.unit) {
+      case 'D':
+        return now.subtract(age.number, 'day');
+      case 'M':
+        return now.subtract(age.number, 'month');
+      case 'Y':
+        return now.subtract(age.number, 'year');
+      default:
+        return now;
+    }
+  };
+
+  // 根据出生日期计算年龄
+  const calculateAgeFromDob = (
+    dob: Dayjs
+  ): { number: number; unit: 'D' | 'M' | 'Y' } => {
+    const now = dayjs();
+    const years = now.diff(dob, 'year');
+    if (years >= 1) {
+      return { number: years, unit: 'Y' };
+    }
+    const months = now.diff(dob, 'month');
+    if (months >= 1) {
+      return { number: months, unit: 'M' };
+    }
+    const days = now.diff(dob, 'day');
+    return { number: Math.max(0, days), unit: 'D' };
+  };
+
+  // 监听年龄变化
+  useEffect(() => {
+    if (updateSourceRef.current === 'dob') {
+      updateSourceRef.current = null;
+      return;
+    }
+    if (patient_age && patient_age.number >= 0 && form) {
+      updateSourceRef.current = 'age';
+      const newDob = calculateDobFromAge(patient_age);
+      form.setFieldsValue({ patient_dob: newDob });
+    }
+  }, [patient_age, form]);
+
+  // 监听出生日期变化
+  useEffect(() => {
+    if (updateSourceRef.current === 'age') {
+      updateSourceRef.current = null;
+      return;
+    }
+    if (patient_dob && dayjs.isDayjs(patient_dob) && form) {
+      updateSourceRef.current = 'dob';
+      const newAge = calculateAgeFromDob(patient_dob);
+      form.setFieldsValue({ patient_age: newAge });
+    }
+  }, [patient_dob, form]);
+
+  // 当任务数据变化时,填充表单
+  useEffect(() => {
+    if (task && visible) {
+      const taskData = task //as any;
+      form.setFieldsValue({
+        patient_id: task.PatientID,
+        patient_name: task.PatientName,
+        patient_sex: task.PatientSex,
+        patient_age: parseAge(task.PatientAge),
+        patient_dob: taskData.patient_dob ? dayjs(taskData.patient_dob) : null,
+        patient_size: task.PatientSize,
+        accession_number: task.AccessionNumber,
+        ref_physician: taskData.ref_physician || '',
+        operator_id: task.OperatorID,
+        weight: taskData.weight || 0,
+        thickness: task.Thickness,
+        length: taskData.length || 0,
+        comment: taskData.comment || '',
+        // 宠物专用字段
+        owner_name: taskData.owner_name || '',
+        chip_number: taskData.chip_number || '',
+        variety: taskData.variety || '',
+        sex_neutered: taskData.sex_neutered || 'UNALTERED',
+        is_anaesthesia: taskData.is_anaesthesia || false,
+        is_sedation: taskData.is_sedation || false,
+        // 人医专用字段
+        pregnancy_status: taskData.pregnancy_status || 'na',
+      });
+    }
+  }, [task, visible, form]);
+
+  const handleOk = async () => {
+    try {
+      const values = await form.validateFields();
+
+      // 格式化出生日期
+      const formatDob = values.patient_dob
+        ? dayjs(values.patient_dob).format('YYYY-MM-DDTHH:mm:ss.SSS[Z]')
+        : '';
+
+      const editInfo: EditTaskInfo = {
+        accession_number: values.accession_number,
+        patient_id: values.patient_id,
+        patient_name: values.patient_name,
+        patient_size: values.patient_size,
+        patient_age: formatAge(values.patient_age),
+        patient_dob: formatDob,
+        patient_sex: values.patient_sex,
+        patient_type: task?.PatientType || 'Human',
+        ref_physician: values.ref_physician || '',
+        operator_id: values.operator_id || '',
+        modality: 'DX',
+        weight: values.weight || 0,
+        thickness: values.thickness || 0,
+        length: values.length || 0,
+        study_type: task?.StudyType as 'Normal' | 'Emergency' || 'Normal',
+        comment: values.comment || '',
+        // 宠物专用字段
+        owner_name: values.owner_name || '',
+        sex_neutered: values.sex_neutered || '',
+        chip_number: values.chip_number || '',
+        variety: values.variety || '',
+        is_anaesthesia: values.is_anaesthesia || false,
+        is_sedation: values.is_sedation || false,
+        // 人医专用字段
+        pregnancy_status: values.pregnancy_status || '',
+      };
+
+      // dispatch editFormSlice 中的 thunk
+      const result = await dispatch(
+        editTaskThunk({ studyId: task!.StudyID, taskInfo: editInfo })
+      ).unwrap();
+
+      if (result.result.code === '0x000000') {
+        message.success('任务编辑成功');
+        form.resetFields();
+        // Modal 会由 slice 的 extraReducers 自动关闭
+      } else {
+        message.error(`编辑失败: ${result.result.description}`);
+      }
+    } catch (error) {
+      console.error('编辑失败:', error);
+      message.error('编辑失败,请检查表单');
+    }
+  };
+
+  // 表单值变化时自动同步到 Redux
+  const handleValuesChange = (changedValues: any, allValues: any) => {
+    dispatch(setEditFormData(allValues));
+  };
+
+  const handleCancel = () => {
+    form.resetFields();
+    dispatch(closeEditModal()); // 关闭 Modal 并清理状态
+  };
+
+  if (!visible) return null;
+
+  return (
+    <div
+      style={{
+        position: 'fixed',
+        top: 0,
+        left: 0,
+        right: 0,
+        bottom: 0,
+        backgroundColor: 'rgba(0, 0, 0, 0.45)',
+        display: 'flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+        zIndex: 1000,
+      }}
+      onClick={handleCancel}
+    >
+      <div
+        style={{
+          backgroundColor: 'var(--color-bg-layout)',
+          borderRadius: '8px',
+          width: '90%',
+          maxWidth: '1200px',
+          maxHeight: '90vh',
+          display: 'flex',
+          flexDirection: 'column',
+          overflow: 'hidden',
+        }}
+        onClick={(e) => e.stopPropagation()}
+      >
+        {/* 第一行:标题栏 */}
+        <div
+          style={{
+            display: 'flex',
+            justifyContent: 'space-between',
+            alignItems: 'center',
+            padding: '16px 24px',
+            borderBottom: '1px solid #f0f0f0',
+          }}
+        >
+          <h2 style={{ margin: 0, fontSize: '18px', fontWeight: 600 }}>
+            <FormattedMessage id="editTask.title" defaultMessage="编辑患者" />
+          </h2>
+          <Button
+            type="text"
+            icon={<CloseOutlined />}
+            onClick={handleCancel}
+            style={{ fontSize: '16px' }}
+          />
+        </div>
+
+        {/* 表单内容区域 */}
+        <div style={{ flex: 1, overflow: 'auto', padding: '24px' }}>
+          <Form
+            form={form}
+            layout="vertical"
+            onValuesChange={handleValuesChange}
+          >
+            {/* 第二行:患者信息 - 响应式多列布局 */}
+            <div style={{ marginBottom: '24px' }}>
+              <h3 style={{ marginBottom: '16px', fontSize: '16px', fontWeight: 600 }}>
+                <FormattedMessage id="editTask.patientInfo" defaultMessage="患者信息" />
+              </h3>
+              <Row gutter={[16, 16]}>
+                {productName === 'VETDROS' && (
+                  <Col xs={24} sm={12} md={8} lg={6}>
+                    <Form.Item
+                      label={<FormattedMessage id="register.owner_name" defaultMessage="主人" />}
+                      name="owner_name"
+                      required={registerFormFields.owner_name.required}
+                      validateTrigger={registerFormFields.owner_name.trigger}
+                      rules={registerFormFields.owner_name.validation}
+                    >
+                      <Input placeholder={intl.formatMessage({ id: 'register.owner_name.placeholder' })} />
+                    </Form.Item>
+                  </Col>
+                )}
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.patientName" defaultMessage="动物姓名" />}
+                    name="patient_name"
+                    required={registerFormFields.patient_name.required}
+                    validateTrigger={registerFormFields.patient_name.trigger}
+                    rules={registerFormFields.patient_name.validation}
+                  >
+                    <Input placeholder={intl.formatMessage({ id: 'register.patientName.placeholder' })} />
+                  </Form.Item>
+                </Col>
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.patientId" defaultMessage="动物ID" />}
+                    name="patient_id"
+                    required={registerFormFields.patient_id.required}
+                    validateTrigger={registerFormFields.patient_id.trigger}
+                    rules={registerFormFields.patient_id.validation}
+                  >
+                    <Input placeholder={intl.formatMessage({ id: 'register.patientId.placeholder' })} />
+                  </Form.Item>
+                </Col>
+                {productName === 'VETDROS' && (
+                  <Col xs={24} sm={12} md={8} lg={6}>
+                    <Form.Item
+                      label={<FormattedMessage id="register.chipNumber" defaultMessage="芯片号" />}
+                      name="chip_number"
+                      required={registerFormFields.chip_number.required}
+                      validateTrigger={registerFormFields.chip_number.trigger}
+                      rules={registerFormFields.chip_number.validation}
+                    >
+                      <Input placeholder={intl.formatMessage({ id: 'register.chipNumber.placeholder' })} />
+                    </Form.Item>
+                  </Col>
+                )}
+                {productName === 'VETDROS' && (
+                  <Col xs={24} sm={12} md={8} lg={6}>
+                    <Form.Item
+                      label={<FormattedMessage id="register.variety" defaultMessage="品种描述" />}
+                      name="variety"
+                      required={registerFormFields.variety.required}
+                      validateTrigger={registerFormFields.variety.trigger}
+                      rules={registerFormFields.variety.validation}
+                    >
+                      <Input placeholder={intl.formatMessage({ id: 'register.variety.placeholder' })} />
+                    </Form.Item>
+                  </Col>
+                )}
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.dateOfBirth" defaultMessage="出生日期" />}
+                    name="patient_dob"
+                    required={registerFormFields.patient_dob.required}
+                    validateTrigger={registerFormFields.patient_dob.trigger}
+                    rules={registerFormFields.patient_dob.validation}
+                  >
+                    <DatePicker format="YYYY-MM-DD" style={{ width: '100%' }} />
+                  </Form.Item>
+                </Col>
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.age" defaultMessage="年龄" />}
+                    name="patient_age"
+                    required={registerFormFields.patient_age.required}
+                    validateTrigger={registerFormFields.patient_age.trigger}
+                    rules={registerFormFields.patient_age.validation}
+                    initialValue={{ number: 0, unit: 'Y' }}
+                  >
+                    <NumberWithUnit
+                      align="baseline"
+                      defaultUnit="Y"
+                      defaultNumber={0}
+                      unitClassName="w-full"
+                      numberClassName="w-full"
+                      className="w-full"
+                      options={[
+                        { label: '天', value: 'D' },
+                        { label: '月', value: 'M' },
+                        { label: '年', value: 'Y' },
+                      ]}
+                    />
+                  </Form.Item>
+                </Col>
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.gender" defaultMessage="性别" />}
+                    name="patient_sex"
+                    required={registerFormFields.patient_sex.required}
+                    validateTrigger={registerFormFields.patient_sex.trigger}
+                    rules={registerFormFields.patient_sex.validation}
+                  >
+                    <Select
+                      options={[
+                        { value: 'M', label: intl.formatMessage({ id: 'register.gender.male' }) },
+                        { value: 'F', label: intl.formatMessage({ id: 'register.gender.female' }) },
+                        { value: 'O', label: intl.formatMessage({ id: 'register.gender.other' }) },
+                        { value: 'U', label: intl.formatMessage({ id: 'register.gender.unknown' }) },
+                        { value: 'CM', label: intl.formatMessage({ id: 'register.gender.castration' }) },
+                        { value: 'SF', label: intl.formatMessage({ id: 'register.gender.sterilization' }) },
+                      ]}
+                    />
+                  </Form.Item>
+                </Col>
+                {productName === 'VETDROS' && (
+                  <Col xs={24} sm={12} md={8} lg={6}>
+                    <Form.Item
+                      label={<FormattedMessage id="register.sexNeutered" defaultMessage="绝育状态" />}
+                      name="sex_neutered"
+                      required={registerFormFields.sex_neutered.required}
+                      validateTrigger={registerFormFields.sex_neutered.trigger}
+                      rules={registerFormFields.sex_neutered.validation}
+                    >
+                      <Select defaultValue={'UNALTERED'}>
+                        <Select.Option value="ALTERED">ALTERED (绝育)</Select.Option>
+                        <Select.Option value="UNALTERED">UNALTERED (未绝育)</Select.Option>
+                      </Select>
+                    </Form.Item>
+                  </Col>
+                )}
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.patientSize" defaultMessage="体型" />}
+                    name="patient_size"
+                    required={registerFormFields.patient_size.required}
+                    validateTrigger={registerFormFields.patient_size.trigger}
+                    rules={registerFormFields.patient_size.validation}
+                    initialValue="Medium"
+                  >
+                    <Select defaultValue="Medium">
+                      <Select.Option value="Large">Large</Select.Option>
+                      <Select.Option value="Medium">Medium</Select.Option>
+                      <Select.Option value="Small">Small</Select.Option>
+                    </Select>
+                  </Form.Item>
+                </Col>
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.weight" defaultMessage="体重" />}
+                    name="weight"
+                    required={registerFormFields.weight.required}
+                    validateTrigger={registerFormFields.weight.trigger}
+                    rules={registerFormFields.weight.validation}
+                  >
+                    <InputNumber min={0} addonAfter="kg" style={{ width: '100%' }} />
+                  </Form.Item>
+                </Col>
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.height" defaultMessage="身高" />}
+                    name="length"
+                    required={registerFormFields.length.required}
+                    validateTrigger={registerFormFields.length.trigger}
+                    rules={registerFormFields.length.validation}
+                  >
+                    <InputNumber min={0} addonAfter="cm" style={{ width: '100%' }} />
+                  </Form.Item>
+                </Col>
+              </Row>
+            </div>
+
+            {/* 第三行:检查基本信息 */}
+            <div style={{ marginBottom: '24px' }}>
+              <h3 style={{ marginBottom: '16px', fontSize: '16px', fontWeight: 600 }}>
+                <FormattedMessage id="editTask.examInfo" defaultMessage="检查基本信息" />
+              </h3>
+              <Row gutter={[16, 16]}>
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.referringPhysician" defaultMessage="兽医师" />}
+                    name="ref_physician"
+                    required={registerFormFields.ref_physician.required}
+                    validateTrigger={registerFormFields.ref_physician.trigger}
+                    rules={registerFormFields.ref_physician.validation}
+                  >
+                    <Input placeholder={intl.formatMessage({ id: 'register.referringPhysician.placeholder' })} />
+                  </Form.Item>
+                </Col>
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.operatorId" defaultMessage="操作医生" />}
+                    name="operator_id"
+                    required={registerFormFields.operator_id.required}
+                    validateTrigger={registerFormFields.operator_id.trigger}
+                    rules={registerFormFields.operator_id.validation}
+                  >
+                    <Input placeholder={intl.formatMessage({ id: 'register.operatorId.placeholder' })} />
+                  </Form.Item>
+                </Col>
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.accessionNumber" defaultMessage="登记号" />}
+                    name="accession_number"
+                    required={registerFormFields.accession_number.required}
+                    validateTrigger={registerFormFields.accession_number.trigger}
+                    rules={registerFormFields.accession_number.validation}
+                  >
+                    <Input placeholder={intl.formatMessage({ id: 'register.accessionNumber.placeholder' })} />
+                  </Form.Item>
+                </Col>
+                <Col xs={24} sm={12} md={8} lg={6}>
+                  <Form.Item
+                    label={<FormattedMessage id="register.comment" defaultMessage="注释" />}
+                    name="comment"
+                    required={registerFormFields.comment.required}
+                    validateTrigger={registerFormFields.comment.trigger}
+                    rules={registerFormFields.comment.validation}
+                  >
+                    <Input placeholder={intl.formatMessage({ id: 'register.comment.placeholder' })} />
+                  </Form.Item>
+                </Col>
+                {productName === 'VETDROS' && (
+                  <>
+                    <Col xs={24} sm={12} md={8} lg={6}>
+                      <Form.Item
+                        label={<FormattedMessage id="editTask.anaesthesia" defaultMessage="麻醉" />}
+                        name="is_anaesthesia"
+                        valuePropName="checked"
+                        required={registerFormFields.is_anaesthesia?.required}
+                        validateTrigger={registerFormFields.is_anaesthesia?.trigger}
+                        rules={registerFormFields.is_anaesthesia?.validation}
+                      >
+                        <Switch />
+                      </Form.Item>
+                    </Col>
+                    <Col xs={24} sm={12} md={8} lg={6}>
+                      <Form.Item
+                        label={<FormattedMessage id="editTask.sedation" defaultMessage="镇定" />}
+                        name="is_sedation"
+                        valuePropName="checked"
+                        required={registerFormFields.is_sedation?.required}
+                        validateTrigger={registerFormFields.is_sedation?.trigger}
+                        rules={registerFormFields.is_sedation?.validation}
+                      >
+                        <Switch />
+                      </Form.Item>
+                    </Col>
+                  </>
+                )}
+              </Row>
+            </div>
+          </Form>
+        </div>
+
+        {/* 第四行:底部按钮 */}
+        <div
+          style={{
+            padding: '16px 24px',
+            borderTop: '1px solid #f0f0f0',
+            display: 'flex',
+            justifyContent: 'flex-end',
+            gap: '12px',
+          }}
+        >
+          <Button onClick={handleCancel}>
+            <FormattedMessage id="editTask.cancel" defaultMessage="取消" />
+          </Button>
+          <Button type="primary" onClick={handleOk} loading={loading}>
+            <FormattedMessage id="editTask.accept" defaultMessage="接受" />
+          </Button>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default EditTaskModal;

+ 98 - 0
src/states/patient/edit/editFormSlice.ts

@@ -0,0 +1,98 @@
+import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
+import { editTask, EditTaskInfo } from '@/API/patient/workActions';
+import { Task } from '@/domain/work';
+
+interface EditFormState {
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  formData: Record<string, any>;
+  visible: boolean; // Modal 可见性
+  editingTask: Task | null; // 当前编辑的任务
+  loading: boolean; // 提交loading状态
+  error: string | null; // 错误信息
+}
+
+const initialState: EditFormState = {
+  formData: {},
+  visible: false,
+  editingTask: null,
+  loading: false,
+  error: null,
+};
+
+// 编辑任务的 thunk
+export const editTaskThunk = createAsyncThunk(
+  'editForm/editTask',
+  async ({
+    studyId,
+    taskInfo,
+  }: {
+    studyId: string;
+    taskInfo: EditTaskInfo;
+  }) => {
+    console.log(
+      `编辑任务,从 editFormSlice thunk 调用api,目标 studyId是 ${studyId}`
+    );
+    const result = await editTask(studyId, taskInfo);
+    return { studyId, taskInfo, result };
+  }
+);
+
+const editFormSlice = createSlice({
+  name: 'editForm',
+  initialState,
+  reducers: {
+    // 打开编辑Modal,传入要编辑的任务
+    openEditModal: (state, action: PayloadAction<Task>) => {
+      state.visible = true;
+      state.editingTask = action.payload;
+      state.error = null;
+      console.log('打开编辑Modal,任务ID:', action.payload.StudyID);
+    },
+    // 关闭编辑Modal
+    closeEditModal: (state) => {
+      state.visible = false;
+      state.editingTask = null;
+      state.formData = {};
+      state.error = null;
+      console.log('关闭编辑Modal,清空数据');
+    },
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    setEditFormData: (state, action: PayloadAction<Record<string, any>>) => {
+      state.formData = action.payload;
+    },
+    // 清空表单数据
+    clearEditFormData: (state) => {
+      state.formData = {};
+      console.log('Redux editFormSlice: 编辑表单数据已清空');
+    },
+  },
+  extraReducers: (builder) => {
+    builder
+      .addCase(editTaskThunk.pending, (state) => {
+        state.loading = true;
+        state.error = null;
+        console.log('编辑任务提交中...');
+      })
+      .addCase(editTaskThunk.fulfilled, (state) => {
+        state.loading = false;
+        state.visible = false; // 编辑成功后关闭Modal
+        state.editingTask = null;
+        state.formData = {};
+        console.log('编辑任务成功,关闭Modal');
+      })
+      .addCase(editTaskThunk.rejected, (state, action) => {
+        state.loading = false;
+        state.error = action.error.message || '编辑失败';
+        console.error('编辑任务失败:', action.error.message);
+      });
+  },
+});
+
+export const {
+  openEditModal,
+  closeEditModal,
+  setEditFormData,
+  clearEditFormData,
+} = editFormSlice.actions;
+export type { EditFormState };
+export default editFormSlice.reducer;

+ 34 - 0
src/states/patient/worklist/slices/workSlice.ts

@@ -24,6 +24,7 @@ import {
   fetchTaskList,
   lockStudy,
 } from '../../../../API/patient/workActions';
+import { editTaskThunk } from '../../edit/editFormSlice';
 import store from '@/states/store';
 import { Draft } from '@reduxjs/toolkit';
 import { EntitiesState } from '../../../list_template/type.model';
@@ -158,6 +159,39 @@ const {
       thunk: lockWorkInWorklistThunk,
       handlers: createLockHandlers(),
     },
+    // 监听 editFormSlice 的编辑 thunk
+    editFromEditForm: {
+      thunk: editTaskThunk,
+      handlers: {
+        fulfilled: (
+          state: Draft<EntitiesState<work | workAnimal>>,
+          action: PayloadAction<any>
+        ) => {
+          const { studyId, result } = action.payload;
+          console.log(`worklist 监听到编辑成功,更新列表数据,studyId: ${studyId}`);
+          const item = state.data.find((item) => item.StudyID === studyId);
+          if (item && result.code === '0x000000') {
+            // 更新列表中的项目数据
+            const updatedData = result.data;
+            item.PatientName = updatedData.patient_name;
+            item.PatientID = updatedData.patient_id;
+            item.PatientSex = updatedData.patient_sex;
+            item.PatientAge = updatedData.patient_age;
+            item.PatientSize = updatedData.patient_size;
+            item.AccessionNumber = updatedData.accession_number;
+            item.OperatorID = updatedData.operator_name;
+            // 根据产品类型更新特定字段
+            if ('owner_name' in item) {
+              (item as any).owner_name = (updatedData as any).owner_name;
+              (item as any).chip_number = (updatedData as any).chip_number;
+              (item as any).variety = (updatedData as any).variety;
+              (item as any).is_anaesthesia = (updatedData as any).is_anaesthesia;
+              (item as any).is_sedation = (updatedData as any).is_sedation;
+            }
+          }
+        },
+      },
+    },
   }
 );
 

+ 2 - 0
src/states/store.ts

@@ -44,6 +44,7 @@ import panelSwitchForViewReducer from './panelSwitchSliceForView';
 import quotaModalReducer from './security/quotaModalSlice';
 import quotaReducer from './security/quotaSlice';
 import formReducer from './patient/register/formSlice';
+import editFormReducer from './patient/edit/editFormSlice';
 import deviceReducer from './device/deviceSlice';
 import headerReducer from './patient/DiagnosticReport/headerSlice';
 import baseInfoReducer from './patient/DiagnosticReport/baseInfoSlice';
@@ -97,6 +98,7 @@ const store = configureStore({
     quotaModal: quotaModalReducer,
     quota: quotaReducer,
     form: formReducer,
+    editForm: editFormReducer,
     device: deviceReducer,
     header: headerReducer,
     baseInfo: baseInfoReducer,