2
0

3 Commits 32b5eae0d0 ... b852977b15

Autor SHA1 Nachricht Datum
  szy b852977b15 (1.49.7 -> 1.50.0):检查报告功能完善,增加性别和申请科室管理的接口 vor 1 Woche
  szy cb456418b0 Merge branch 'master' of http://code.pacsonline.cn/ddx/dros vor 1 Woche
  szy 3c4da3df1b feat(1.49.0 -> 1.50.0):检查报告保存,预览功能 vor 1 Woche
27 geänderte Dateien mit 735 neuen und 471 gelöschten Zeilen
  1. 1 1
      config/dev.ts
  2. 5 5
      src/API/patient/DiagnosisReportActions.ts
  3. 60 5
      src/API/report/ReportActions.ts
  4. 25 0
      src/API/system/options.ts
  5. 1 1
      src/assets/i18n/messages/zh.js
  6. 54 0
      src/hooks/useDiagnosticData.ts
  7. 69 88
      src/pages/patient/DiagnosticReport/components/AnimalBaseInfo.tsx
  8. 160 206
      src/pages/patient/DiagnosticReport/components/BaseInfo.tsx
  9. 136 0
      src/pages/patient/DiagnosticReport/components/DepartmentModal.tsx
  10. 6 14
      src/pages/patient/DiagnosticReport/components/DiagnosisSection.tsx
  11. 11 21
      src/pages/patient/DiagnosticReport/components/FindingsSection.tsx
  12. 2 3
      src/pages/patient/DiagnosticReport/components/ImageList.tsx
  13. 8 6
      src/pages/patient/DiagnosticReport/components/ImageSelection.tsx
  14. 4 1
      src/pages/patient/DiagnosticReport/components/MainContent.tsx
  15. 2 2
      src/pages/patient/DiagnosticReport/components/SidePanel.tsx
  16. 3 5
      src/pages/patient/DiagnosticReport/components/TemplateListItem.tsx
  17. 6 5
      src/pages/patient/DiagnosticReport/components/TemplateManagementModal.tsx
  18. 10 14
      src/pages/patient/DiagnosticReport/components/TemplatePanel.tsx
  19. 1 1
      src/pages/patient/DiagnosticReport/index.tsx
  20. 50 15
      src/pages/patient/components/ActionPanel.tsx
  21. 8 9
      src/states/patient/DiagnosticReport/PdfPreviewModal.tsx
  22. 22 0
      src/states/patient/DiagnosticReport/departmentSlice.ts
  23. 12 38
      src/states/patient/DiagnosticReport/previewReportThunk.ts
  24. 25 28
      src/states/patient/DiagnosticReport/saveReportThunk.ts
  25. 23 3
      src/states/patient/DiagnosticReport/slice.ts
  26. 5 0
      src/states/store.ts
  27. 26 0
      src/states/system/optionSlice.ts

+ 1 - 1
config/dev.ts

@@ -98,7 +98,7 @@ export default {
           // }
         },
       },
-      server: 'https',  // 启用 HTTPS ,为了开发环境测试打开摄像头功能
+      // server: 'https', // 启用 HTTPS ,为了开发环境测试打开摄像头功能
       host: '0.0.0.0', // 监听所有网络接口
       open: false, // 可选:是否自动打开浏览器
       port: 10086, // 可选:指定端口号

+ 5 - 5
src/API/patient/DiagnosisReportActions.ts

@@ -56,11 +56,11 @@ export const saveDiagnosisReport = async (
 export const previewDiagnosisReport = async (
   report: DiagnosisReport | DiagnosisReportAnimal
 ): Promise<DiagnosisReportResponse> => {
-const response = await axiosInstance.post(
-  `/auth/report/preview`,
-  report,
-  { responseType: 'blob' }
-);
+  const response = await axiosInstance.post(
+    `/auth/report/preview`,
+    report,
+    { responseType: 'blob' }
+  );
   return response.data;
 };
 

+ 60 - 5
src/API/report/ReportActions.ts

@@ -1,4 +1,5 @@
 /* eslint-disable */
+import { extend } from 'cypress/types/lodash';
 import axiosInstance from '../interceptor';
 
 // 报告查询参数
@@ -10,7 +11,7 @@ export interface ReportQueryParams {
 }
 
 // 报告列表项
-export interface ReportItem {
+export interface ReportItem extends ReportContent {
   study_instance_uid: string;
   study_id: string;
   headers: {
@@ -31,11 +32,21 @@ export interface ReportItem {
   create_time: string;
 }
 
-// 报告内容详情
-export interface ReportContent {
+export interface ReportResponse extends ReportContent {
   '@type': string;
   study_instance_uid: string;
   study_id: string;
+  create_time: string;
+}
+export type ReportContentValue =
+  | string
+  | string[]
+  | ReportContent['headers']
+  | Partial<ReportContent>
+  | boolean // 如果将来添加布尔类型的字段
+  | number;
+// 报告内容详情
+export interface ReportContent {
   headers: {
     age: string;
     bed_number: string;
@@ -46,13 +57,13 @@ export interface ReportContent {
     name: string;
     requesting_department: string;
     sex: string;
+    patient_type?: string
   };
   findings: string;
   impression: string;
   radiologist: string;
   review_physician: string;
   images: string[];
-  create_time: string;
 }
 
 // 报告内容响应
@@ -97,8 +108,34 @@ export interface ReportPreviewRequest {
   images: string[];
 }
 
+export interface DepartmentItem {
+  id: number;
+  department: string;
+  user_id: number;
+}
+
+// 申请科室列表
+export interface createdDepartmentResponse {
+  code: string;
+  description: string;
+  solution: string;
+  data: {
+    '@type': string;
+    count: number;
+    departments: DepartmentItem[];
+  };
+}
+
+// 报告模板响应
+export interface DepartmentResponse {
+  code: string;
+  description: string;
+  solution: string;
+  data: any;
+}
+
 // 报告预览响应
-export interface ReportPreviewResponse extends Blob {}
+export interface ReportPreviewResponse extends Blob { }
 
 /**
  * 获取报告列表
@@ -137,3 +174,21 @@ export const previewReport = async (
   });
   return response.data;
 };
+// 获取申请科室列表
+export const getDepartment = async (
+  params: { scope?: 'all' | 'mine' }
+): Promise<createdDepartmentResponse> => {
+  const response = await axiosInstance.get('/auth/report/department', {
+    params,
+  });
+  return response.data;
+};
+
+// 新建申请科室
+
+export const createdDepartment = async (
+  params: { departments: string[] }
+): Promise<DepartmentResponse> => {
+  const response = await axiosInstance.post('/auth/report/department', params);
+  return response.data;
+};

+ 25 - 0
src/API/system/options.ts

@@ -0,0 +1,25 @@
+import axiosInstance from '../interceptor';
+export interface OptionsItem {
+  text: string;
+  value: string;
+}
+export interface OptionsResponse {
+  code: string;
+  description: string;
+  solution: string;
+  data: {
+    '@type': string;
+    option: OptionsItem[];
+  };
+}
+
+// 获取配置的可选项
+export const getOption = async (params: {
+  group: string;
+  flag: string;
+}): Promise<OptionsResponse> => {
+  const response = await axiosInstance.get('/auth/resource/options', {
+    params,
+  });
+  return response.data;
+};

+ 1 - 1
src/assets/i18n/messages/zh.js

@@ -164,7 +164,7 @@ export default {
   "register.variety.placeholder": "请输入品种",
   "register.patientType": "患者类型",
   "register.patientType.placeholder": "请输入患者类型",
-  "animal.register.patientType": "宠物类",
+  "animal.register.patientType": "宠物类",
   "register.operatorId": "操作员ID",
   "register.operatorId.placeholder": "请输入操作员ID",
   "register.modality": "物理疗法",

+ 54 - 0
src/hooks/useDiagnosticData.ts

@@ -0,0 +1,54 @@
+import { useDispatch, useSelector } from 'react-redux';
+import { AppDispatch, RootState } from '@/states/store';
+import { setReport } from '@/states/patient/DiagnosticReport/slice';
+
+// 根据路径获取嵌套对象的值
+const getNestedValue = (obj: any, path: string) => {
+  return path.split('.').reduce((current, key) => current?.[key], obj);
+};
+
+// 根据路径设置嵌套对象的值
+const setNestedValue = (obj: any, path: string, value: any) => {
+  const keys = path.split('.');
+
+  // 如果只有一个键(非嵌套),直接设置
+  if (keys.length === 1) {
+    obj[keys[0]] = value;
+    return obj;
+  }
+  // 处理嵌套字段
+  const lastKey = keys.pop()!;
+  const target = keys.reduce(
+    (current, key) => {
+      if (!current[key]) current[key] = {};
+      return current[key];
+    },
+    { ...obj }
+  );
+
+  target[lastKey] = value;
+  return obj;
+};
+
+export const useDiagnosticData = <T>(fieldPath: string) => {
+  const dispatch = useDispatch<AppDispatch>();
+  const stateData = useSelector(
+    (state: RootState) => state.diagnosticReport.report
+  );
+
+  const updateData = (value: T) => {
+    if (stateData) {
+      // 创建一个副本并更新指定字段
+      const updatedData = JSON.parse(JSON.stringify(stateData));
+      setNestedValue(updatedData, fieldPath, value);
+      dispatch(setReport(updatedData));
+    }
+  };
+
+  let value: T | undefined;
+  if (stateData) {
+    value = getNestedValue(stateData, fieldPath);
+  }
+
+  return [value, updateData] as const;
+};

+ 69 - 88
src/pages/patient/DiagnosticReport/components/AnimalBaseInfo.tsx

@@ -1,80 +1,65 @@
 import React from 'react';
 import { Row, Col, Input, Select, Form } from 'antd';
-import { RootState, useAppDispatch } from '@/states/store';
-import {
-  updateField,
-  updateDropdown,
-  AnimalBaseInfoState,
-} from '@/states/patient/DiagnosticReport/animalBaseInfoSlice';
-import { useSelector } from 'react-redux';
-import { useEffect } from 'react';
-import { Task, TaskAnimal } from '@/domain/work';
-import { useIntl, FormattedMessage } from 'react-intl';
-
+import { RootState, useAppSelector, useAppDispatch } from '@/states/store';
+import { FormattedMessage } from 'react-intl';
+import { useDiagnosticData } from '@/hooks/useDiagnosticData';
+import { updateReportField } from '@/states/patient/DiagnosticReport/slice';
 const { Option } = Select;
 
 export const AnimalBaseInfo: React.FC = () => {
-  const data = useSelector((s: RootState) => s.animalBaseInfo);
   const dispatch = useAppDispatch();
-  const selectedIds = useSelector(
+  // 报告信息
+  const [name, setName] = useDiagnosticData('headers.name');
+  const [sex, setSex] = useDiagnosticData('headers.sex');
+  const [age, setAge] = useDiagnosticData('headers.age');
+  const [ownerName, setOwnerName] = useDiagnosticData('headers.owner_name');
+  const [requestingDepartment, setRequestingDepartment] = useDiagnosticData(
+    'headers.requesting_department'
+  );
+  const [inspectionNumber, setInspectionNumber] = useDiagnosticData(
+    'headers.inspection_number'
+  );
+  const [inspectionMethod, setInspectionMethod] = useDiagnosticData(
+    'headers.inspection_method'
+  );
+  const [radiologist, setRadiologist] = useDiagnosticData('radiologist');
+  const [reviewPhysician, setReviewPhysician] =
+    useDiagnosticData('review_physician');
+
+  const reportInfo = useAppSelector((state) => state.diagnosticReport?.report);
+
+  const selectedIds = useAppSelector(
     (s: RootState) => s.workSelection.selectedIds
   );
-  const workEntities = useSelector((s: RootState) => s.workEntities.data);
-  const productName = useSelector((s: RootState) => s.product.productName);
-  const intl = useIntl();
+  const productName = useAppSelector((s: RootState) => s.product.productName);
   console.log(`【诊断报告】:选中的study id :${selectedIds[0]}`);
-  const selectedStudy: Task | TaskAnimal | null =
-    selectedIds.length > 0
-      ? (workEntities.find((t) => t.StudyID === selectedIds[0]) ?? null)
-      : null;
-
-  useEffect(() => {
-    console.log(
-      `【诊断报告】:selectedStudy是空吗?${selectedStudy === null} ${selectedStudy}`
-    );
-    if (selectedStudy) {
-      const selectedStudyAnimal = selectedStudy as TaskAnimal;
-      dispatch(
-        updateField({
-          key: 'patientName',
-          value: selectedStudyAnimal.PatientName,
-        })
-      );
-      dispatch(
-        updateField({ key: 'sex', value: selectedStudyAnimal.PatientSex })
-      );
-      dispatch(
-        updateField({ key: 'age', value: selectedStudyAnimal.PatientAge })
-      );
-      dispatch(
-        updateField({ key: 'ownerName', value: selectedStudyAnimal.owner_name })
-      );
-      // dispatch(
-      //   updateField({ key: 'inspectionNumber', value: selectedStudyAnimal.ins })
-      // );
-      // dispatch(
-      //   updateField({ key: 'inspectionMethod', value: selectedStudy.InspectionMethod })
-      // );
-      // dispatch(
-      //   updateField({ key: 'radiologist', value: selectedStudy.Radiologist })
-      // );
-      // dispatch(
-      //   updateField({ key: 'reviewPhysician', value: selectedStudy.ReviewPhysician })
-      // );
-    }
-  }, [selectedIds]);
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const onChange = (key: keyof AnimalBaseInfoState, val: any) => {
-    dispatch(updateField({ key, value: val }));
-  };
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const onDropdownChange = (key: keyof AnimalBaseInfoState, val: any) => {
-    dispatch(updateDropdown({ key, value: val }));
-  };
 
   return (
     <Form layout="vertical">
       <Row gutter={12}>
+        <Col span={6}>
+          <Form.Item
+            label={
+              <FormattedMessage
+                id={'animal.register.patientType'}
+                defaultMessage={'animal.register.patientType'}
+              />
+            }
+          >
+            <Input
+              // disabled
+              value={reportInfo?.headers?.patient_type}
+              onChange={(e) =>
+                dispatch(
+                  updateReportField({
+                    path: 'report.headers.patient_type',
+                    value: e.target.value,
+                  })
+                )
+              }
+            />
+          </Form.Item>
+        </Col>
         <Col span={6}>
           <Form.Item
             label={
@@ -93,9 +78,9 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Input
-              disabled
-              value={data.patientName}
-              onChange={(e) => onChange('patientName', e.target.value)}
+              // disabled
+              value={name}
+              onChange={(e) => setName(e.target.value)}
             />
           </Form.Item>
         </Col>
@@ -108,11 +93,7 @@ export const AnimalBaseInfo: React.FC = () => {
               />
             }
           >
-            <Select
-              disabled
-              value={data.sex}
-              onChange={(v) => onDropdownChange('sex', v)}
-            >
+            <Select value={sex} onChange={(v) => setSex(v)}>
               <Option value="雄性">雄性</Option>
               <Option value="雌性">雌性</Option>
             </Select>
@@ -128,9 +109,9 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Input
-              disabled
-              value={data.age}
-              onChange={(e) => onChange('age', e.target.value)}
+              // disabled
+              value={age}
+              onChange={(e) => setAge(e.target.value)}
             />
           </Form.Item>
         </Col>
@@ -144,9 +125,9 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Input
-              disabled
-              value={data.ownerName}
-              onChange={(e) => onChange('ownerName', e.target.value)}
+              // disabled
+              value={ownerName as string}
+              onChange={(e) => setOwnerName(e.target.value)}
             />
           </Form.Item>
         </Col>
@@ -160,8 +141,8 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Input
-              value={data.requestingDepartment}
-              onChange={(e) => onChange('requestingDepartment', e.target.value)}
+              value={requestingDepartment}
+              onChange={(e) => setRequestingDepartment(e.target.value)}
             />
           </Form.Item>
         </Col>
@@ -175,8 +156,8 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Input
-              value={data.inspectionNumber}
-              onChange={(e) => onChange('inspectionNumber', e.target.value)}
+              value={inspectionNumber}
+              onChange={(e) => setInspectionNumber(e.target.value)}
             />
           </Form.Item>
         </Col>
@@ -190,8 +171,8 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Input
-              value={data.inspectionMethod}
-              onChange={(e) => onChange('inspectionMethod', e.target.value)}
+              value={inspectionMethod}
+              onChange={(e) => setInspectionMethod(e.target.value)}
             />
           </Form.Item>
         </Col>
@@ -205,8 +186,8 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Input
-              value={data.radiologist}
-              onChange={(e) => onChange('radiologist', e.target.value)}
+              value={radiologist}
+              onChange={(e) => setRadiologist(e.target.value)}
             />
           </Form.Item>
         </Col>
@@ -220,8 +201,8 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Input
-              value={data.reviewPhysician}
-              onChange={(e) => onChange('reviewPhysician', e.target.value)}
+              value={reviewPhysician}
+              onChange={(e) => setReviewPhysician(e.target.value)}
             />
           </Form.Item>
         </Col>

+ 160 - 206
src/pages/patient/DiagnosticReport/components/BaseInfo.tsx

@@ -1,84 +1,63 @@
-import React from 'react';
-import { Row, Col, Input, Select, DatePicker, Button, Form } from 'antd';
-import { RootState, useAppDispatch } from '@/states/store';
-import {
-  updateField,
-  updateDropdown,
-  BaseInfoState,
-} from '@/states/patient/DiagnosticReport/baseInfoSlice';
-import dayjs from 'dayjs';
-import { useSelector } from 'react-redux';
-import { useEffect } from 'react';
-import { Task } from '@/domain/work';
-
-const { Option } = Select;
-
+import React, { useEffect, useState } from 'react';
+import { Row, Col, Input, Select, Form } from 'antd';
+import { useDiagnosticData } from '@/hooks/useDiagnosticData';
+import Space from 'antd/lib/space';
+import { PlusOutlined } from '@ant-design/icons';
+import { DepartmentModal } from './DepartmentModal';
+import { getDepartment } from '@/API/report/ReportActions';
+import { RootState, useAppSelector, useAppDispatch } from '@/states/store';
+import { setDepartments } from '@/states/patient/DiagnosticReport/departmentSlice';
+import { getOption } from '@/API/system/options';
+import { setOptionSex } from '@/states/system/optionSlice';
 export const BaseInfo: React.FC = () => {
-  const data = useSelector((s: RootState) => s.baseInfo);
+  const [departmentOpen, setDepartmentOpen] = useState(false);
+  const [name, setName] = useDiagnosticData<string>('headers.name');
+  const [sex, setSex] = useDiagnosticData<string>('headers.sex');
+  const [age, setAge] = useDiagnosticData<string>('headers.age');
+  const [medical_record_number, setMedicalrecordnumber] = useDiagnosticData<string>('headers.medical_record_number');
+  const [hospitalization_number, setHospitalizationNumber] = useDiagnosticData<string>('headers.hospitalization_number');
+  const [requesting_department, setRequestingDepartment] = useDiagnosticData<string>('headers.requesting_department');
+  const [bed_number, setBedNumber] = useDiagnosticData<string>('headers.bed_number');
+  const [inspection_number, setInspectionNumber] = useDiagnosticData<string>('headers.inspection_number');
+  const [inspection_method, setInspectionMethod] = useDiagnosticData<string>('headers.inspection_method');
+  const [radiologist, setRadiologist] = useDiagnosticData<string>('radiologist');
+  const [reviewPhysician, setReviewPhysician] = useDiagnosticData<string>('review_physician');
   const dispatch = useAppDispatch();
-  const selectedIds = useSelector(
-    (s: RootState) => s.workSelection.selectedIds
-  );
-  const workEntities = useSelector((s: RootState) => s.workEntities.data);
-  console.log(`【诊断报告】:选中的study id :${selectedIds[0]}`);
-  const selectedStudy: Task | null =
-    selectedIds.length > 0
-      ? (workEntities.find((t) => t.StudyID === selectedIds[0]) ?? null)
-      : null;
-
-  useEffect(() => {
-    console.log(
-      `【诊断报告】:selectedStudy是空吗?${selectedStudy === null} ${selectedStudy}`
-    );
-    if (selectedStudy) {
-      dispatch(
-        updateField({ key: 'patientName', value: selectedStudy.PatientName })
-      );
-      dispatch(
-        updateField({ key: 'englishName', value: selectedStudy.PatientName })
-      );
-      dispatch(
-        updateField({ key: 'patientNo', value: selectedStudy.PatientID })
-      );
-      dispatch(updateField({ key: 'gender', value: selectedStudy.PatientSex }));
-      dispatch(updateField({ key: 'age', value: selectedStudy.PatientAge }));
-      // dispatch(updateField({ key: 'visitType', value: selectedStudy.visitType }));// 就诊类型
-      // dispatch(updateField({ key: 'clinicNo', value: selectedStudy.clinicNo }));
-      // dispatch(updateField({ key: 'department', value: selectedStudy.department }));//科室
-      dispatch(
-        updateField({
-          key: 'examTime',
-          value: selectedStudy.StudyStartDatetime,
-        })
-      );
-      dispatch(updateField({ key: 'studyId', value: selectedStudy.StudyID }));
-      // dispatch(updateField({ key: 'bodyPart', value: selectedStudy. }));
-      // dispatch(updateField({ key: 'examDesc', value: selectedStudy.examDesc }));
-      // dispatch(updateField({ key: 'examPosition', value: selectedStudy.examPosition }));
-      // dispatch(updateField({ key: 'clinicalDiag', value: selectedStudy.clinicalDiag }));
+  const departments = useAppSelector((state: RootState) => state.department.departments);
+  const optionSex = useAppSelector((state: RootState) => state.options.sex);
+  const getDepartmentData = async () => {
+    const res = await getDepartment({});
+    if (res.code === '0x000000') {
+      dispatch(setDepartments(res?.data?.departments || []));
     }
-  }, [selectedIds]);
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const onChange = (key: keyof BaseInfoState, val: any) => {
-    dispatch(updateField({ key, value: val }));
   };
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const onDropdownChange = (key: keyof BaseInfoState, val: any) => {
-    dispatch(updateDropdown({ key, value: val }));
+
+  const getSexOption = async () => {
+    const res = await getOption({ group: 'patient', flag: 'sex' });
+    if (res.code === '0x000000') {
+      dispatch(setOptionSex(res?.data?.option || []));
+    }
   };
 
+  useEffect(() => {
+    getDepartmentData();
+    getSexOption();
+  }, []);
+
   return (
-    <Form layout="vertical">
-      <Row gutter={12}>
-        <Col span={6}>
-          <Form.Item label="患者姓名">
-            <Input
-              value={data.patientName}
-              onChange={(e) => onChange('patientName', e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
+    <>
+      <Form layout="vertical">
+        <Row gutter={12}>
+          <Col span={6}>
+            <Form.Item label="患者姓名">
+              <Input
+                disabled
+                value={name}
+                onChange={(e) => setName(e.target.value)}
+              />
+            </Form.Item>
+          </Col>
+          {/* <Col span={6}>
           <Form.Item label="曾用名">
             <Input
               value={data.usedName}
@@ -93,34 +72,37 @@ export const BaseInfo: React.FC = () => {
               onChange={(e) => onChange('englishName', e.target.value)}
             />
           </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item label="患者编号">
-            <Input disabled value={data.patientNo} />
-          </Form.Item>
-        </Col>
-
-        <Col span={4}>
-          <Form.Item label="性别">
-            <Select
-              value={data.gender}
-              onChange={(v) => onDropdownChange('gender', v)}
-            >
-              <Option value="男">男</Option>
-              <Option value="女">女</Option>
-            </Select>
-          </Form.Item>
-        </Col>
-        <Col span={4}>
-          <Form.Item label="年龄">
-            <Input
-              addonAfter="Y"
-              value={data.age}
-              onChange={(e) => onChange('age', e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
+        </Col> */}
+          <Col span={6}>
+            <Form.Item label="患者编号">
+              <Input
+                disabled
+                value={medical_record_number}
+                onChange={(e) => setMedicalrecordnumber(e.target.value)}
+              />
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item label="性别">
+              <Select
+                value={sex}
+                disabled
+                fieldNames={{ label: 'text' }}
+                onChange={(v) => setSex(v)}
+                options={optionSex}
+              />
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item label="年龄">
+              <Input
+                disabled
+                value={age}
+                onChange={(e) => setAge(e.target.value)}
+              />
+            </Form.Item>
+          </Col>
+          {/* <Col span={6}>
           <Form.Item label="就诊类型">
             <Row gutter={4}>
               <Col flex="auto">
@@ -138,111 +120,83 @@ export const BaseInfo: React.FC = () => {
               </Col>
             </Row>
           </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item label="门诊/住院号">
-            <Input
-              value={data.clinicNo}
-              onChange={(e) => onChange('clinicNo', e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item label="科室">
-            <Row gutter={4}>
-              <Col flex="auto">
-                <Select
-                  value={data.department}
-                  onChange={(v) => onDropdownChange('department', v)}
-                >
-                  <Option value="内科">内科</Option>
-                  <Option value="外科">外科</Option>
-                </Select>
-              </Col>
-              <Col>
-                <Button>...</Button>
-              </Col>
-            </Row>
-          </Form.Item>
-        </Col>
-
-        <Col span={6}>
-          <Form.Item label="检查时间">
-            <DatePicker
-              className="w-full"
-              value={data.examTime ? dayjs(data.examTime) : null}
-              onChange={(d) =>
-                onChange('examTime', d?.format('YYYY-MM-DD') || '')
-              }
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item label="身体部位">
-            <Select
-              value={data.bodyPart}
-              onChange={(v) => onDropdownChange('bodyPart', v)}
-            >
-              <Option value="腹部">腹部</Option>
-              <Option value="胸部">胸部</Option>
-              <Option value="头部">头部</Option>
-            </Select>
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item label="检查描述">
-            <Input
-              value={data.examDesc}
-              onChange={(e) => onChange('examDesc', e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item label="检查部位">
-            <Input
-              value={data.examPosition}
-              onChange={(e) => onChange('examPosition', e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-
-        <Col span={6}>
-          <Form.Item label="放射科医生">
-            <Input
-              value={data.radiologist}
-              onChange={(e) => onChange('radiologist', e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-
-        <Col span={6}>
-          <Form.Item label="审核医师">
-            <Input
-              value={data.reviewPhysician}
-              onChange={(e) => onChange('reviewPhysician', e.target.value)}
+        </Col> */}
+          <Col span={6}>
+            <Form.Item label="门诊/住院号">
+              <Input
+                value={hospitalization_number}
+                onChange={(e) => setHospitalizationNumber(e.target.value)}
+              />
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item label="床号">
+              <Input
+                value={bed_number}
+                onChange={(e) => setBedNumber(e.target.value)}
+              />
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item label="检查号">
+              <Input
+                value={inspection_number}
+                onChange={(e) => setInspectionNumber(e.target.value)}
+              />
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item label="检查方法">
+              <Input
+                value={inspection_method}
+                onChange={(e) => setInspectionMethod(e.target.value)}
+              />
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item label="申请科室">
+              <Select
+                style={{ width: '90%' }}
+                value={requesting_department}
+                fieldNames={{ label: 'department', value: 'department' }}
+                onChange={(v) => setRequestingDepartment(`${v}`)}
+                options={departments}
+              />
+            </Form.Item>
+            <PlusOutlined
+              style={{
+                position: 'absolute',
+                top: '40px',
+                right: '10px',
+                cursor: 'pointer',
+                transition: 'all 0.3s',
+              }}
+              onClick={() => setDepartmentOpen(true)}
             />
-          </Form.Item>
-        </Col>
+          </Col>
 
-        <Col span={6}>
-          <Form.Item label="临床诊断">
-            <Row gutter={4}>
-              <Col flex="auto">
-                <Select
-                  value={data.clinicalDiag}
-                  onChange={(v) => onDropdownChange('clinicalDiag', v)}
-                >
-                  <Option value="肺炎">肺炎</Option>
-                  <Option value="肝炎">肝炎</Option>
-                </Select>
-              </Col>
-              <Col>
-                <Button>...</Button>
-              </Col>
-            </Row>
-          </Form.Item>
-        </Col>
-      </Row>
-    </Form>
+          <Col span={6}>
+            <Form.Item label="放射科医生">
+              <Input
+                value={radiologist}
+                onChange={(e) => setRadiologist(e.target.value)}
+              />
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item label="审核医师">
+              <Input
+                value={reviewPhysician}
+                onChange={(e) => setReviewPhysician(e.target.value)}
+              />
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+      <DepartmentModal
+        visible={departmentOpen}
+        onClose={() => setDepartmentOpen(false)}
+      />
+    </>
   );
 };

+ 136 - 0
src/pages/patient/DiagnosticReport/components/DepartmentModal.tsx

@@ -0,0 +1,136 @@
+/* eslint-disable */
+import React, { useState, useEffect } from 'react';
+import {
+  Modal,
+  Button,
+  Table,
+  Select,
+  Input,
+  Checkbox,
+  message,
+  Form,
+  Space,
+  Row,
+  Col,
+} from 'antd';
+import { RootState, useAppDispatch, useAppSelector } from '@/states/store';
+import { CloseCircleOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+
+import {
+  fetchTemplatesThunk,
+  createTemplateThunk,
+  updateTemplateThunk,
+  deleteTemplateThunk,
+  setSelectedTag,
+} from '@/states/patient/DiagnosticReport/templateSlice';
+import { ReportTemplate } from '@/API/report/ReportTemplateActions';
+import { createdDepartment, getDepartment } from '@/API/report/ReportActions';
+import { use } from 'chai';
+
+interface DepartmentModalProps {
+  visible: boolean;
+  onClose: () => void;
+}
+
+export const DepartmentModal: React.FC<
+  DepartmentModalProps
+> = ({ visible, onClose }) => {
+  const dispatch = useAppDispatch();
+  const [form] = Form.useForm();
+  const departments = useAppSelector((state: RootState) => state.department.departments);
+  const onFinish = async (values: any) => {
+    console.log('Received values of form:', values);
+    const res = await createdDepartment(values)
+    if (res.code === '0x000000') {
+      message.success('申请科室成功')
+    } else {
+      message.error('申请科室失败')
+    }
+  };
+
+  useEffect(() => {
+    form.setFieldsValue({
+      departments: departments.map(k => k.department)
+    })
+  }, [visible])
+
+  return (
+    <Modal
+      title="申请科室管理"
+      centered
+      closable={false}
+      open={visible}
+      maskClosable={false}
+      destroyOnHidden
+      onCancel={onClose}
+      styles={{ body: { maxHeight: '50vh', overflow: 'auto' } }}
+      okButtonProps={{ autoFocus: true, htmlType: 'submit', style: { marginRight: '50px' } }}
+      modalRender={(dom) => (
+        <Form
+          layout="vertical"
+          form={form}
+          initialValues={{ modifier: 'public' }}
+          clearOnDestroy
+          onFinish={onFinish}
+        >
+          {dom}
+        </Form>
+      )}
+    >
+      < Form.List
+        name="departments"
+      >
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field) => (
+              <Form.Item
+                label={''}
+                required={false}
+                key={field.key}
+              >
+                <Form.Item
+                  {...field}
+                  validateTrigger={['onChange', 'onBlur']}
+                  rules={[
+                    {
+                      required: true,
+                      whitespace: true,
+                      message: "Please input passenger's name or delete this field.",
+                    },
+                  ]}
+                  noStyle
+                >
+                  <Input placeholder="passenger name" style={{ width: '90%' }} />
+                </Form.Item>
+                <MinusCircleOutlined
+                  style={{
+                    position: 'relative',
+                    top: '4px',
+                    margin: '0 8px',
+                    color: '#999',
+                    fontSize: '24px',
+                    cursor: 'pointer',
+                  }}
+                  onClick={() => remove(field.name)}
+                />
+              </Form.Item>
+            ))}
+            <Form.Item>
+              <Button
+                // disabled={fields.length >= 6}
+                type="dashed"
+                onClick={() => add()}
+                style={{ width: '90%' }}
+                icon={<PlusOutlined />}
+              >
+                增加
+              </Button>
+            </Form.Item>
+          </>
+        )}
+      </Form.List >
+
+
+    </Modal >
+  );
+};

+ 6 - 14
src/pages/patient/DiagnosticReport/components/DiagnosisSection.tsx

@@ -1,19 +1,11 @@
 /* eslint-disable */
 import React from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import { setDiagnosisDescription } from '@/states/patient/DiagnosticReport/diagnosisSlice';
-import { Input, Layout } from 'antd';
-
-const { Content } = Layout;
+import { useDispatch } from 'react-redux';
+import { Input } from 'antd';
+import { useDiagnosticData } from '@/hooks/useDiagnosticData';
 
 export const DiagnosisSection: React.FC = () => {
-  const dispatch = useDispatch();
-
-  const diagnosisDescription = useSelector((state: any) => state.diagnosis.diagnosisDescription);
-
-  const handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
-    dispatch(setDiagnosisDescription(event.target.value));
-  };
+  const [impression, setImpression] = useDiagnosticData('impression');
 
   return (
     <div className="flex flex-col">
@@ -23,8 +15,8 @@ export const DiagnosisSection: React.FC = () => {
 
       <Input.TextArea
         className="w-full border rounded"
-        value={diagnosisDescription}
-        onChange={handleInputChange}
+        value={impression}
+        onChange={(e) => setImpression(e.target.value)}
         placeholder="由用户输入"
         rows={4}
       />

+ 11 - 21
src/pages/patient/DiagnosticReport/components/FindingsSection.tsx

@@ -1,40 +1,30 @@
 /* eslint-disable */
 import React, { useState } from 'react';
 import { Button, Input } from 'antd';
-import { useAppDispatch, useAppSelector } from '@/states/store';
-import { updateInputValue } from '@/states/patient/DiagnosticReport/findingsSlice';
+import { useAppSelector } from '@/states/store';
 import { TemplateManagementModal } from './TemplateManagementModal';
-
+import { useDiagnosticData } from '@/hooks/useDiagnosticData';
 export const FindingsSection: React.FC = () => {
-  const dispatch = useAppDispatch();
-  const { diagnosticDescriptionFromImage } = useAppSelector(
-    (state) => state.findings
-  );
-  const { diagnosisDescription } = useAppSelector((state) => state.diagnosis);
+  const { impression } = useAppSelector(
+    (state) => state.diagnosticReport?.report
+  ) || {};
 
   const [templateModalVisible, setTemplateModalVisible] = useState(false);
-
-  const handleSaveAsTemplate = () => {
-    setTemplateModalVisible(true);
-  };
-
-  const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
-    dispatch(updateInputValue(e.target.value));
-  };
+  const [findings, setFindings] = useDiagnosticData<string>('findings');
 
   return (
     <div className="flex flex-col">
       <div className="flex justify-between items-center">
         <span>影像所见</span>
-        <Button type="primary" onClick={handleSaveAsTemplate}>
+        <Button type="primary" onClick={() => setTemplateModalVisible(true)}>
           另存为诊断报告模板
         </Button>
       </div>
       <Input.TextArea
         placeholder="由用户输入"
         className="mt-2.5 w-full h-24"
-        value={diagnosticDescriptionFromImage}
-        onChange={handleInputChange}
+        value={findings}
+        onChange={(e) => setFindings(e.target.value)}
       />
 
       {/* 诊断报告模板管理对话框 */}
@@ -42,8 +32,8 @@ export const FindingsSection: React.FC = () => {
         visible={templateModalVisible}
         onClose={() => setTemplateModalVisible(false)}
         initialTemplate={{
-          findings: diagnosticDescriptionFromImage,
-          impression: diagnosisDescription,
+          findings: impression,
+          impression: findings,
         }}
       />
     </div>

+ 2 - 3
src/pages/patient/DiagnosticReport/components/ImageList.tsx

@@ -17,9 +17,6 @@ export const ImageList: React.FC = () => {
   const images = useAppSelector((s) => s.imageList.images);
   const studyId = useAppSelector((s) => s.baseInfo.studyId);
   const dispatch = useAppDispatch();
-  const reportInfo = useAppSelector((s) => s.diagnosticReport.report);
-  console.log('reportInfo=====>', reportInfo);
-  console.log('images===========>', images);
 
   // 获取候选图像数据
   const handleOpenModal = async () => {
@@ -85,6 +82,8 @@ export const ImageList: React.FC = () => {
       </div>
       <Modal
         title="选择图像"
+        width={'49vw'}
+        maskClosable={false}
         open={isModalVisible}
         onCancel={handleModalClose}
         footer={null}

+ 8 - 6
src/pages/patient/DiagnosticReport/components/ImageSelection.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { Button, Row } from 'antd';
+import { Button, Row, Space } from 'antd';
 import { useAppSelector, useAppDispatch } from '@/states/store';
 import {
   toggleSelect,
@@ -20,7 +20,7 @@ export const ImageSelection: React.FC<ImageSelectionProps> = ({ onClose }) => {
   // 确认选择
   const handleConfirm = () => {
     // 将选中的图像添加到报告图像列表
-    selected.forEach(imageUrl => {
+    selected.forEach((imageUrl) => {
       dispatch(addImage(imageUrl));
     });
 
@@ -59,10 +59,12 @@ export const ImageSelection: React.FC<ImageSelectionProps> = ({ onClose }) => {
 
       {/* 按钮行 */}
       <Row justify="end" gutter={8}>
-        <Button onClick={handleCancel}>取消</Button>
-        <Button type="primary" onClick={handleConfirm}>
-          确定
-        </Button>
+        <Space>
+          <Button onClick={handleCancel}>取消</Button>
+          <Button type="primary" onClick={handleConfirm}>
+            确定
+          </Button>
+        </Space>
       </Row>
     </div>
   );

+ 4 - 1
src/pages/patient/DiagnosticReport/components/MainContent.tsx

@@ -18,7 +18,10 @@ export const MainContent: React.FC = () => {
 
   return (
     <div className="flex flex-col gap-2">
-      <Card size="small" title={intl.formatMessage({ id: 'patient.baseInfo' })}>
+      <Card
+        size="small"
+        title={intl.formatMessage({ id: 'report.module.baseInfo' })}
+      >
         {productName === 'VETDROS' ? <AnimalBaseInfo /> : <BaseInfo />}
       </Card>
       <Card size="small" title="图像">

+ 2 - 2
src/pages/patient/DiagnosticReport/components/SidePanel.tsx

@@ -5,9 +5,9 @@ import { TemplatePanel } from './TemplatePanel';
 
 export const SidePanel: React.FC = () => (
   <div className="flex flex-col gap-2">
-    <Card size="small" title="检查过滤">
+    {/* <Card size="small" title="检查过滤">
       <StudyFilter />
-    </Card>
+    </Card> */}
     <Card size="small" title="模板" className="flex-1">
       <TemplatePanel />
     </Card>

+ 3 - 5
src/pages/patient/DiagnosticReport/components/TemplateListItem.tsx

@@ -18,7 +18,7 @@ export const TemplateListItem: React.FC<TemplateListItemProps> = ({
       size="small"
       hoverable
       onClick={() => onClick(template)}
-      className="mb-2 cursor-pointer"
+      className="mb-1 cursor-pointer"
       bodyStyle={{ padding: '8px 12px' }}
     >
       <div className="flex items-start justify-between">
@@ -27,12 +27,10 @@ export const TemplateListItem: React.FC<TemplateListItemProps> = ({
             {template.staple && (
               <StarFilled style={{ color: '#faad14', fontSize: '14px' }} />
             )}
-            <span className="font-medium text-sm">{template.name}</span>
+            <span>{template.name}</span>
           </div>
           {template.tag && (
-            <div className="text-xs text-gray-500 mt-1">
-              标签: {template.tag}
-            </div>
+            <div className="text-gray-500 mt-0.5">描述: {template.tag}</div>
           )}
         </div>
       </div>

+ 6 - 5
src/pages/patient/DiagnosticReport/components/TemplateManagementModal.tsx

@@ -117,7 +117,6 @@ export const TemplateManagementModal: React.FC<
   const handleSave = async () => {
     try {
       const values = await form.validateFields();
-
       if (isNewTemplate) {
         // 创建新模板
         await dispatch(createTemplateThunk(values)).unwrap();
@@ -140,6 +139,7 @@ export const TemplateManagementModal: React.FC<
       form.resetFields();
       setIsNewTemplate(false);
       setSelectedTemplateId(null);
+      onClose()
     } catch (error: any) {
       message.error(error || '保存失败');
     }
@@ -184,7 +184,7 @@ export const TemplateManagementModal: React.FC<
       width: '40%',
     },
     {
-      title: '标签',
+      title: '描述',
       dataIndex: 'tag',
       key: 'tag',
       width: '30%',
@@ -201,6 +201,7 @@ export const TemplateManagementModal: React.FC<
   return (
     <Modal
       title="诊断报告模板管理"
+      centered
       open={visible}
       maskClosable={false}
       destroyOnHidden
@@ -239,7 +240,7 @@ export const TemplateManagementModal: React.FC<
 
             <Space>
               <Input
-                placeholder="🔍 按标签搜索..."
+                placeholder="🔍 按描述搜索..."
                 value={selectedTag}
                 onChange={(e) => dispatch(setSelectedTag(e.target.value))}
                 size="small"
@@ -308,8 +309,8 @@ export const TemplateManagementModal: React.FC<
                 <Input.TextArea rows={6} placeholder="请输入诊断意见内容" />
               </Form.Item>
 
-              <Form.Item label="标签" name="tag">
-                <Input placeholder="请输入标签(可选)" />
+              <Form.Item label="描述" name="tag">
+                <Input placeholder="请输入描述(可选)" />
               </Form.Item>
 
               <Form.Item name="staple" valuePropName="checked">

+ 10 - 14
src/pages/patient/DiagnosticReport/components/TemplatePanel.tsx

@@ -2,27 +2,24 @@
 import React, { useState, useEffect, useMemo } from 'react';
 import { Input, Button, Modal, message, Spin, Empty } from 'antd';
 import { SearchOutlined } from '@ant-design/icons';
-import { useAppDispatch, useAppSelector } from '@/states/store';
+import { RootState, useAppDispatch, useAppSelector } from '@/states/store';
 import {
   fetchTemplatesThunk,
   setFilterMode,
   setSearchKeyword,
 } from '@/states/patient/DiagnosticReport/templateSlice';
-import { updateInputValue } from '@/states/patient/DiagnosticReport/findingsSlice';
-import { setDiagnosisDescription } from '@/states/patient/DiagnosticReport/diagnosisSlice';
 import { ReportTemplate } from '@/API/report/ReportTemplateActions';
 import { TemplateListItem } from './TemplateListItem';
 import { TemplateManagementModal } from './TemplateManagementModal';
+import { updateReportField } from '@/states/patient/DiagnosticReport/slice';
 
 export const TemplatePanel: React.FC = () => {
   const dispatch = useAppDispatch();
   const { templates, loading, filterMode, searchKeyword } = useAppSelector(
     (state) => state.template
   );
-  const { diagnosticDescriptionFromImage } = useAppSelector(
-    (state) => state.findings
-  );
-  const { diagnosisDescription } = useAppSelector((state) => state.diagnosis);
+
+  const { findings = '', impression = '' } = useAppSelector((state: RootState) => state.diagnosticReport.report) || {};
 
   const [managementModalVisible, setManagementModalVisible] = useState(false);
 
@@ -53,9 +50,8 @@ export const TemplatePanel: React.FC = () => {
   // 应用诊断报告模板
   const handleApplyTemplate = (template: ReportTemplate) => {
     const hasContent =
-      diagnosticDescriptionFromImage.trim() !== '' ||
-      diagnosisDescription.trim() !== '';
-
+      findings.trim() !== '' ||
+      impression.trim() !== '';
     if (hasContent) {
       Modal.confirm({
         title: '确认应用诊断报告模板',
@@ -63,14 +59,14 @@ export const TemplatePanel: React.FC = () => {
         okText: '确定',
         cancelText: '取消',
         onOk: () => {
-          dispatch(updateInputValue(template.findings));
-          dispatch(setDiagnosisDescription(template.impression));
+          dispatch(updateReportField({ path: 'report.findings', value: template.findings }));
+          dispatch(updateReportField({ path: 'report.impression', value: template.impression }));
           message.success('诊断报告模板已应用');
         },
       });
     } else {
-      dispatch(updateInputValue(template.findings));
-      dispatch(setDiagnosisDescription(template.impression));
+      dispatch(updateReportField({ path: 'report.findings', value: template.findings }));
+      dispatch(updateReportField({ path: 'report.impression', value: template.impression }));
       message.success('诊断报告模板已应用');
     }
   };

+ 1 - 1
src/pages/patient/DiagnosticReport/index.tsx

@@ -64,4 +64,4 @@ const DiagnosticReport: React.FC = () => {
   );
 };
 
-export default DiagnosticReport;
+export default React.memo(DiagnosticReport);

+ 50 - 15
src/pages/patient/components/ActionPanel.tsx

@@ -1,5 +1,5 @@
 import React, { useState, useCallback } from 'react';
-import { Button, Tooltip, Modal, message } from 'antd';
+import { Button, Tooltip, Modal, message, Drawer } from 'antd';
 import { useDispatch, useSelector } from 'react-redux';
 import {
   deleteWorkThunk,
@@ -15,8 +15,7 @@ import { FormattedMessage } from 'react-intl';
 import { AppDispatch, RootState, useAppSelector } from '@/states/store';
 import Icon from '@/components/Icon';
 import DiagnosticReport from '../DiagnosticReport';
-import { Popup } from 'antd-mobile';
-import { setVisible, setReport } from '@/states/patient/DiagnosticReport/slice';
+import { setVisible, setReport, resetReport } from '@/states/patient/DiagnosticReport/slice';
 import EditTaskModal from './EditTaskModal';
 import { openEditModal } from '@/states/patient/edit/editFormSlice';
 import DiagnosticReportBatchDownloadModal from '@/components/DiagnosticReportBatchDownloadModal';
@@ -42,6 +41,12 @@ import { WorkFilter } from '@/states/patient/worklist/types/workfilter';
 import { BinFilter } from '@/states/patient/bin/types/binFilter';
 import { theme } from 'antd';
 import { fetchDiagnosisReportContent } from '@/API/patient/DiagnosisReportActions';
+import {
+  addImage,
+  resetImageList,
+} from '@/states/patient/DiagnosticReport/imageListSlice';
+import { getApiBaseUrl } from '@/API/config';
+import { ReportContent } from '@/API/report';
 const { useToken } = theme;
 
 interface ActionButtonProps {
@@ -192,13 +197,38 @@ const ActionPanel: React.FC = () => {
       return;
     }
 
+    const tempReportHeader = {
+      name: selectedWork.PatientName,
+      sex: selectedWork.PatientSex,
+      age: selectedWork.PatientAge,
+      patientType: selectedWork.PatientType,
+      ownerName: selectedWork.owner_name,
+      medical_record_number: selectedWork.PatientID,
+      inspection_number: selectedWork.AccessionNumber,
+    };
+
     const reportRes = await fetchDiagnosisReportContent(selectedIds[0]);
-    if (reportRes?.code === '0x000000') {
-      dispatch(setReport(reportRes?.data)); // 报告接口数据
-      dispatch(setVisible({ visible: true, work: selectedWork })); //
-    } else {
-      message.error(reportRes?.description || '获取报告失败');
-    }
+    // 重置检查图像列表
+    await dispatch(resetImageList());
+    // 报告接口数据
+    const { headers, ...rest } = reportRes?.data || {};
+    await dispatch(
+      setReport({
+        headers: {
+          ...headers,
+          ...tempReportHeader,
+        },
+        ...rest,
+      })
+    );
+
+    // 单独设置报告影像图片
+    (reportRes?.data?.images || []).forEach((image) => {
+      const API_BASE_URL = getApiBaseUrl();
+      const _url = `${API_BASE_URL}pub/thumbnail/${image}`;
+      dispatch(addImage(_url));
+    });
+    dispatch(setVisible({ visible: true, work: selectedWork })); //
   };
 
   const getWorksFromWorklistOrHistory = () => {
@@ -833,14 +863,19 @@ const ActionPanel: React.FC = () => {
         }
         onClick={handleShowReport}
       />
-      <Popup
-        visible={visible}
-        onMaskClick={() => dispatch(setVisible(false))}
-        position="right"
-        bodyStyle={{ width: '100vw', height: '100vh' }}
+      <Drawer
+        open={visible}
+        closeIcon={null}
+        destroyOnHidden
+        width="100vw"
+        styles={{
+          body: {
+            padding: 0,
+          },
+        }}
       >
         <DiagnosticReport />
-      </Popup>
+      </Drawer>
       <EditTaskModal />
       <DiagnosticReportBatchDownloadModal />
     </div>

+ 8 - 9
src/states/patient/DiagnosticReport/PdfPreviewModal.tsx

@@ -1,5 +1,5 @@
-import { Modal, Result, Button, message } from 'antd';
-import { DownloadOutlined } from '@ant-design/icons';
+import { Drawer, Result, Button, message } from 'antd';
+import { CloseOutlined, DownloadOutlined } from '@ant-design/icons';
 import { useEffect, useState } from 'react';
 import { useSelector } from 'react-redux';
 import { RootState, useAppDispatch } from '@/states/store';
@@ -69,12 +69,11 @@ export default function PdfPreviewModal() {
   };
 
   return (
-    <Modal
+    <Drawer
       title="PDF 预览"
       open={needsPreview}
-      onCancel={() => {
-        dispatch(setNeedsPreview(false));
-      }}
+      closable={false}
+      extra={<CloseOutlined onClick={() => dispatch(setNeedsPreview(false))} />}
       footer={
         <div style={{ textAlign: 'right' }}>
           <Button
@@ -87,8 +86,8 @@ export default function PdfPreviewModal() {
           </Button>
         </div>
       }
-      width="90vw"
-      bodyStyle={{ height: '85vh', padding: 0 }}
+      width="100%"
+      bodyStyle={{ padding: 0 }}
     >
       {!url ? (
         <Result status="warning" title="PDF 已失效" />
@@ -98,6 +97,6 @@ export default function PdfPreviewModal() {
           style={{ width: '100%', height: '100%', border: 'none' }}
         />
       )}
-    </Modal>
+    </Drawer>
   );
 }

+ 22 - 0
src/states/patient/DiagnosticReport/departmentSlice.ts

@@ -0,0 +1,22 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { DefaultOptionType } from 'antd/es/select';
+
+export interface DepartmentState {
+  departments: DefaultOptionType[];
+}
+const initialState: DepartmentState = {
+  departments: [],
+};
+
+const departmentSlice = createSlice({
+  name: 'department',
+  initialState,
+  reducers: {
+    setDepartments(state, action: PayloadAction<DefaultOptionType[]>) {
+      state.departments = action.payload;
+    },
+  },
+});
+
+export const { setDepartments } = departmentSlice.actions;
+export default departmentSlice.reducer;

+ 12 - 38
src/states/patient/DiagnosticReport/previewReportThunk.ts

@@ -14,53 +14,27 @@ export const previewReportThunk = createAsyncThunk(
 
   async (_, { getState }) => {
     const state = getState() as RootState;
-    const productName = state.product.productName;
 
+    const productName = state.product.productName;
+    const optionSex = state?.options?.sex;
     // 提取图像文件名
     const extractImageFilename = (url: string): string => {
       // 从 URL 中提取文件名部分
       const urlParts = url.split('/');
       return urlParts[urlParts.length - 1]; // 获取最后一部分
     };
-
     const images = state.imageList.images.map(extractImageFilename);
+    const { headers, ...rest } = state.diagnosticReport.report || {}
+    const sex = optionSex.find(item => item.value === headers?.sex)?.text
+    const report = {
+      headers: {
+        ...headers,
+        sex
+      },
+      ...rest,
+      images
 
-    const report = productName === 'VETDROS'
-      ? {
-          headers: {
-            name: state.animalBaseInfo.patientName,
-            sex: state.animalBaseInfo.sex,
-            age: state.animalBaseInfo.age,
-            owner_name: state.animalBaseInfo.ownerName,
-            requesting_department: state.animalBaseInfo.requestingDepartment,
-            inspection_number: state.animalBaseInfo.inspectionNumber,
-            inspection_method: state.animalBaseInfo.inspectionMethod,
-          },
-          findings: state.findings.diagnosticDescriptionFromImage,
-          impression: state.diagnosis.diagnosisDescription,
-          radiologist: state.animalBaseInfo.radiologist,
-          review_physician: state.animalBaseInfo.reviewPhysician,
-          images: images, // 添加图像列表
-        }
-      : {
-          headers: {
-            name: state.baseInfo.patientName,
-            sex: state.baseInfo.gender,
-            age: state.baseInfo.age,
-            medical_record_number: state.baseInfo.patientNo,
-            hospitalization_number: state.baseInfo.clinicNo,
-            bed_number: state.baseInfo.department,
-            requesting_department: state.baseInfo.department,
-            inspection_number: state.baseInfo.patientNo, //todo 暂时使用患者编号
-            inspection_method: state.baseInfo.examDesc,
-          },
-          findings: state.findings.diagnosticDescriptionFromImage,
-          impression: state.diagnosis.diagnosisDescription,
-          radiologist: state.baseInfo.radiologist,
-          review_physician: state.baseInfo.reviewPhysician,
-          images: images, // 添加图像列表
-        };
-
+    }
     const response = await previewDiagnosisReport(report);
     console.log('【诊断报告】预览返回', response);
     return response;

+ 25 - 28
src/states/patient/DiagnosticReport/saveReportThunk.ts

@@ -24,46 +24,43 @@ export const saveReportThunk = createAsyncThunk(
     const report =
       productName === 'VETDROS'
         ? {
-          headers: {
-            name: state.animalBaseInfo.patientName,
-            sex: state.animalBaseInfo.sex,
-            age: state.animalBaseInfo.age,
-            owner_name: state.animalBaseInfo.ownerName,
-            requesting_department: state.animalBaseInfo.requestingDepartment,
-            inspection_number: state.animalBaseInfo.inspectionNumber,
-            inspection_method: state.animalBaseInfo.inspectionMethod,
-          },
-          findings: state.findings.diagnosticDescriptionFromImage,
-          impression: state.diagnosis.diagnosisDescription,
-          radiologist: state.animalBaseInfo.radiologist,
-          review_physician: state.animalBaseInfo.reviewPhysician,
+          headers: { ...state.diagnosticReport?.report?.headers },
+          findings: state.diagnosticReport.report?.findings,
+          impression: state.diagnosticReport.report?.impression,
+          radiologist: state.diagnosticReport.report?.radiologist,
+          review_physician: state.diagnosticReport.report?.review_physician,
           images: images, // 添加图像列表
         }
         : {
           headers: {
-            name: state.baseInfo.patientName,
-            sex: state.baseInfo.gender,
-            age: state.baseInfo.age,
-            medical_record_number: state.baseInfo.patientNo,
-            hospitalization_number: state.baseInfo.clinicNo,
-            bed_number: state.baseInfo.department,
-            requesting_department: state.baseInfo.department,
-            inspection_number: state.baseInfo.patientNo, //todo 暂时使用患者编号
-            inspection_method: state.baseInfo.examDesc,
+            name: state.diagnosticReport?.report?.headers.name,
+            sex: state.diagnosticReport?.report?.headers.sex,
+            age: state.diagnosticReport?.report?.headers.age,
+            medical_record_number:
+              state.diagnosticReport?.report?.headers.medical_record_number,
+            hospitalization_number:
+              state.diagnosticReport?.report?.headers.hospitalization_number,
+            bed_number: state.diagnosticReport.report?.headers.bed_number,
+            requesting_department:
+              state.diagnosticReport.report?.headers.requesting_department,
+            inspection_number:
+              state.diagnosticReport.report?.headers.inspection_number, //todo 暂时使用患者编号
+            inspection_method:
+              state.diagnosticReport.report?.headers.inspection_method,
           },
-          findings: state.findings.diagnosticDescriptionFromImage,
-          impression: state.diagnosis.diagnosisDescription,
-          radiologist: state.baseInfo.radiologist,
-          review_physician: state.baseInfo.reviewPhysician,
+          findings: state.diagnosticReport?.report?.findings,
+          impression: state.diagnosticReport?.report?.impression,
+          radiologist: state.diagnosticReport?.report?.radiologist,
+          review_physician: state.diagnosticReport?.report?.review_physician,
           images: images, // 添加图像列表
         };
-
     const response = await saveDiagnosisReport(studyId, report);
     if (response?.code === '0x000000') {
-      await message.success('报告已保存');
+      message.success('报告已保存');
       dispatch(setVisible(false));
       return response.data;
     } else {
+      message.error(response?.description || '保存失败');
       throw new Error(response?.description || '保存失败');
     }
   }

+ 23 - 3
src/states/patient/DiagnosticReport/slice.ts

@@ -2,11 +2,13 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 import { saveReportThunk } from './saveReportThunk';
 import { previewReportThunk } from './previewReportThunk';
 import { Task } from '@/domain/work';
-import { ReportContent } from '@/API/report/ReportActions';
+import { ReportContent, ReportContentValue } from '@/API/report/ReportActions';
+
+import { set } from 'lodash';
 
 interface DiagnosticReportState {
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  report: any;
+  report: ReportContent | null;
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   previewResponse: any;
   isPreviewing: boolean;
@@ -35,7 +37,10 @@ const diagnosticReportSlice = createSlice({
     finishReport(state) {
       state.isFinished = true;
     },
-    setVisible(state, action: PayloadAction<boolean | { visible: boolean; work?: Task }>) {
+    setVisible(
+      state,
+      action: PayloadAction<boolean | { visible: boolean; work?: Task }>
+    ) {
       if (typeof action.payload === 'boolean') {
         state.visible = action.payload;
       } else {
@@ -54,6 +59,19 @@ const diagnosticReportSlice = createSlice({
     setReport(state, action: PayloadAction<ReportContent>) {
       state.report = action.payload;
     },
+    resetReport(state) {
+      state.report = initialState.report;
+    },
+    updateReportField(
+      state,
+      action: PayloadAction<{
+        path: keyof ReportContent | string;
+        value: ReportContentValue;
+      }>
+    ) {
+      const { path, value } = action.payload;
+      set(state, path, value);
+    },
   },
   extraReducers: (builder) => {
     builder
@@ -87,5 +105,7 @@ export const {
   toggleVisible,
   setNeedsPreview,
   setReport,
+  resetReport,
+  updateReportField,
 } = diagnosticReportSlice.actions;
 export default diagnosticReportSlice.reducer;

+ 5 - 0
src/states/store.ts

@@ -78,6 +78,9 @@ 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 departmentReducer from './patient/DiagnosticReport/departmentSlice';
+import optionsReducer from './system/optionSlice';
+
 import {
   binEntitiesSlice,
   binFiltersSlice,
@@ -180,6 +183,8 @@ const store = configureStore({
     serverConfig: serverConfigReducer,
     // annotation: annotationReducer,
     versionUpdate: versionUpdateReducer,
+    department: departmentReducer,
+    options: optionsReducer,
   },
   middleware: (getDefaultMiddleware) =>
     getDefaultMiddleware().concat(

+ 26 - 0
src/states/system/optionSlice.ts

@@ -0,0 +1,26 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+export interface OptionItem {
+  text: string;
+  value: string;
+}
+
+export interface OptionsState {
+  sex: OptionItem[];
+}
+const initialState: OptionsState = {
+  sex: [],
+};
+
+const optionSlice = createSlice({
+  name: 'options',
+  initialState,
+  reducers: {
+    setOptionSex(state, action: PayloadAction<OptionItem[]>) {
+      state.sex = action.payload;
+    },
+  },
+});
+
+export const { setOptionSex } = optionSlice.actions;
+export default optionSlice.reducer;