Explorar o código

feat(1.54.0 -> 1.55.0):实现诊断报告科室管理功能

- 在 BaseInfo 和 AnimalBaseInfo 组件中集成科室选择下拉框,支持动态数据加载
- 优化 DepartmentModal 组件,添加防重复提交策略,提高表单安全性
- 重构组件结构,移除未使用导入,改进代码格式和可读性
- 新增科室管理相关的中英文翻译,支持国际化显示

改动文件:
- scripts/output/i18n/en.js
- scripts/output/i18n/zh.js
- src/pages/patient/DiagnosticReport/components/AnimalBaseInfo.tsx
- src/pages/patient/DiagnosticReport/components/BaseInfo.tsx
- src/pages/patient/DiagnosticReport/components/DepartmentModal.tsx
- CHANGELOG.md
- package.json
szy hai 6 días
pai
achega
2d400a539f

+ 18 - 0
CHANGELOG.md

@@ -2,6 +2,24 @@
 
 本项目的所有重要变更都将记录在此文件中。
 
+## [1.55.0] - 2026-01-06 16:56
+
+### 新增 (Added)
+
+- **实现诊断报告科室管理功能** - 增强诊断报告表单,添加科室选择和管理功能,提升用户体验
+  - 在 BaseInfo 和 AnimalBaseInfo 组件中集成科室选择下拉框,支持动态数据加载
+  - 优化 DepartmentModal 组件,添加防重复提交策略,提高表单安全性
+  - 重构组件结构,移除未使用导入,改进代码格式和可读性
+  - 新增科室管理相关的中英文翻译,支持国际化显示
+
+**改动文件:**
+
+- scripts/output/i18n/en.js
+- scripts/output/i18n/zh.js
+- src/pages/patient/DiagnosticReport/components/AnimalBaseInfo.tsx
+- src/pages/patient/DiagnosticReport/components/BaseInfo.tsx
+- src/pages/patient/DiagnosticReport/components/DepartmentModal.tsx
+
 ## [1.54.0] - 2026-01-06 16:57
 
 ### 新增 (Added)

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "zsis",
-  "version": "1.54.0",
+  "version": "1.55.0",
   "private": true,
   "description": "医学成像系统",
   "main": "main.js",

+ 8 - 0
scripts/output/i18n/en.js

@@ -284,6 +284,14 @@
   "report.module.image": "image",
   "report.module.imageFindings": "imageFindings",
   "report.module.imagingDiagnosis": "imagingDiagnosis",
+  "report.department.title": "Application for department management",
+  "report.department.okButtonText": "Sure",
+  "report.department.cancelButtonText": "Cancel",
+  "report.department.addButtonText": "Increase",
+  "report.department.placeholder": "Please enter the department name",
+  "report.department.inputEmptyCheck": "Please enter the department name or delete this field",
+  "report.department.saveSuccess": "The information of the applied department has been successfully saved",
+  "report.department.saveFail": "Please enter the department name or delete this field",
   "thumbnailList.title": "Image Thumbnails",
   "thumbnailList.noImages": "No images to display",
   "functionArea.addLMark": "Add L Mark",

+ 9 - 1
scripts/output/i18n/zh.js

@@ -162,7 +162,7 @@
   "register.variety.placeholder": "请输入品种",
   "register.patientType": "患者类型",
   "register.patientType.placeholder": "请输入患者类型",
-  "animal.register.patientType": "宠物类",
+  "animal.register.patientType": "宠物类",
   "register.operatorId": "操作员ID",
   "register.operatorId.placeholder": "请输入操作员ID",
   "register.modality": "物理疗法",
@@ -284,6 +284,14 @@
   "report.module.image": "图像",
   "report.module.imageFindings": "影像所见",
   "report.module.imagingDiagnosis": "影像诊断",
+  "report.department.title": "申请科室管理",
+  "report.department.okButtonText": "确定",
+  "report.department.cancelButtonText": "取消",
+  "report.department.addButtonText": "增加",
+  "report.department.placeholder": "请输入科室名称",
+  "report.department.inputEmptyCheck": "请输入科室名称或删除此字段",
+  "report.department.saveSuccess": "申请科室信息保存成功",
+  "report.department.saveFail": "申请科室信息保存异常",
   "thumbnailList.title": "图像缩略图",
   "thumbnailList.noImages": "暂无图像显示",
   "functionArea.addLMark": "添加左标记",

+ 212 - 163
src/pages/patient/DiagnosticReport/components/AnimalBaseInfo.tsx

@@ -1,13 +1,19 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import { Row, Col, Input, Select, Form } from 'antd';
 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;
+import { PlusOutlined } from '@ant-design/icons';
+import { DepartmentModal } from './DepartmentModal';
+import { getDepartment } from '@/API/report/ReportActions';
+import { setDepartments } from '@/states/patient/DiagnosticReport/departmentSlice';
+import { getOption } from '@/API/system/options';
+import { setOptionSex } from '@/states/system/optionSlice';
 
 export const AnimalBaseInfo: React.FC = () => {
   const dispatch = useAppDispatch();
+  const [departmentOpen, setDepartmentOpen] = useState(false);
   // 报告信息
   const [name, setName] = useDiagnosticData('headers.name');
   const [sex, setSex] = useDiagnosticData('headers.sex');
@@ -32,181 +38,224 @@ export const AnimalBaseInfo: React.FC = () => {
     (s: RootState) => s.workSelection.selectedIds
   );
   const productName = useAppSelector((s: RootState) => s.product.productName);
+  const departments = useAppSelector(
+    (state: RootState) => state.department.departments
+  );
+  const optionSex = useAppSelector((state: RootState) => state.options.sex);
   console.log(`【诊断报告】:选中的study id :${selectedIds[0]}`);
+  const getDepartmentData = async () => {
+    const res = await getDepartment({});
+    if (res.code === '0x000000') {
+      dispatch(setDepartments(res?.data?.departments || []));
+    }
+  };
+
+  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={
-              <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 layout="vertical">
+        <Row gutter={12}>
+          <Col span={6}>
+            <Form.Item
+              label={
+                <FormattedMessage
+                  id={'animal.register.patientType'}
+                  defaultMessage={'animal.register.patientType'}
+                />
               }
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item
-            label={
-              <FormattedMessage
-                id={
-                  productName === 'VETDROS'
-                    ? 'animal.register.patientName'
-                    : 'register.patientName'
-                }
-                defaultMessage={
-                  productName === 'VETDROS'
-                    ? 'animal.register.patientName'
-                    : 'register.patientName'
+            >
+              <Input
+                disabled
+                value={reportInfo?.headers?.patient_type}
+                onChange={(e) =>
+                  dispatch(
+                    updateReportField({
+                      path: 'report.headers.patient_type',
+                      value: e.target.value,
+                    })
+                  )
                 }
               />
-            }
-          >
-            <Input
-              // disabled
-              value={name}
-              onChange={(e) => setName(e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item
-            label={
-              <FormattedMessage
-                id="register.gender"
-                defaultMessage="register.gender"
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item
+              label={
+                <FormattedMessage
+                  id={
+                    productName === 'VETDROS'
+                      ? 'animal.register.patientName'
+                      : 'register.patientName'
+                  }
+                  defaultMessage={
+                    productName === 'VETDROS'
+                      ? 'animal.register.patientName'
+                      : 'register.patientName'
+                  }
+                />
+              }
+            >
+              <Input
+                disabled
+                value={name}
+                onChange={(e) => setName(e.target.value)}
               />
-            }
-          >
-            <Select value={sex} onChange={(v) => setSex(v)}>
-              <Option value="雄性">雄性</Option>
-              <Option value="雌性">雌性</Option>
-            </Select>
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item
-            label={
-              <FormattedMessage
-                id="register.age"
-                defaultMessage="register.age"
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item
+              label={
+                <FormattedMessage
+                  id="register.gender"
+                  defaultMessage="register.gender"
+                />
+              }
+            >
+              <Select
+                disabled
+                value={sex}
+                onChange={(v) => setSex(v)}
+                fieldNames={{ label: 'text' }}
+                options={optionSex}
+              ></Select>
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item
+              label={
+                <FormattedMessage
+                  id="register.age"
+                  defaultMessage="register.age"
+                />
+              }
+            >
+              <Input
+                disabled
+                value={age}
+                onChange={(e) => setAge(e.target.value)}
               />
-            }
-          >
-            <Input
-              // disabled
-              value={age}
-              onChange={(e) => setAge(e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item
-            label={
-              <FormattedMessage
-                id="register.owner_name"
-                defaultMessage="register.owner_name"
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item
+              label={
+                <FormattedMessage
+                  id="register.owner_name"
+                  defaultMessage="register.owner_name"
+                />
+              }
+            >
+              <Input
+                value={ownerName as string}
+                onChange={(e) => setOwnerName(e.target.value)}
               />
-            }
-          >
-            <Input
-              // disabled
-              value={ownerName as string}
-              onChange={(e) => setOwnerName(e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item
-            label={
-              <FormattedMessage
-                id="report.requestingDepartment"
-                defaultMessage="report.requestingDepartment"
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item
+              label={
+                <FormattedMessage
+                  id="report.requestingDepartment"
+                  defaultMessage="report.requestingDepartment"
+                />
+              }
+            >
+              <Select
+                style={{ width: '90%' }}
+                value={requestingDepartment}
+                fieldNames={{ label: 'department', value: 'department' }}
+                onChange={(v) => setRequestingDepartment(v)}
+                options={departments}
               />
-            }
-          >
-            <Input
-              value={requestingDepartment}
-              onChange={(e) => setRequestingDepartment(e.target.value)}
+            </Form.Item>
+            <PlusOutlined
+              style={{
+                position: 'absolute',
+                top: '40px',
+                right: '10px',
+                cursor: 'pointer',
+                transition: 'all 0.3s',
+              }}
+              onClick={() => setDepartmentOpen(true)}
             />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item
-            label={
-              <FormattedMessage
-                id="report.inspectionNumber"
-                defaultMessage="report.inspectionNumber"
+          </Col>
+          <Col span={6}>
+            <Form.Item
+              label={
+                <FormattedMessage
+                  id="report.inspectionNumber"
+                  defaultMessage="report.inspectionNumber"
+                />
+              }
+            >
+              <Input
+                value={inspectionNumber}
+                onChange={(e) => setInspectionNumber(e.target.value)}
               />
-            }
-          >
-            <Input
-              value={inspectionNumber}
-              onChange={(e) => setInspectionNumber(e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item
-            label={
-              <FormattedMessage
-                id="report.inspectionMethod"
-                defaultMessage="report.inspectionMethod"
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item
+              label={
+                <FormattedMessage
+                  id="report.inspectionMethod"
+                  defaultMessage="report.inspectionMethod"
+                />
+              }
+            >
+              <Input
+                value={inspectionMethod}
+                onChange={(e) => setInspectionMethod(e.target.value)}
               />
-            }
-          >
-            <Input
-              value={inspectionMethod}
-              onChange={(e) => setInspectionMethod(e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item
-            label={
-              <FormattedMessage
-                id="report.radiologist"
-                defaultMessage="report.radiologist"
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item
+              label={
+                <FormattedMessage
+                  id="report.radiologist"
+                  defaultMessage="report.radiologist"
+                />
+              }
+            >
+              <Input
+                value={radiologist}
+                onChange={(e) => setRadiologist(e.target.value)}
               />
-            }
-          >
-            <Input
-              value={radiologist}
-              onChange={(e) => setRadiologist(e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-        <Col span={6}>
-          <Form.Item
-            label={
-              <FormattedMessage
-                id="report.reviewPhysician"
-                defaultMessage="report.reviewPhysician"
+            </Form.Item>
+          </Col>
+          <Col span={6}>
+            <Form.Item
+              label={
+                <FormattedMessage
+                  id="report.reviewPhysician"
+                  defaultMessage="report.reviewPhysician"
+                />
+              }
+            >
+              <Input
+                value={reviewPhysician}
+                onChange={(e) => setReviewPhysician(e.target.value)}
               />
-            }
-          >
-            <Input
-              value={reviewPhysician}
-              onChange={(e) => setReviewPhysician(e.target.value)}
-            />
-          </Form.Item>
-        </Col>
-      </Row>
-    </Form>
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+      <DepartmentModal
+        visible={departmentOpen}
+        onClose={() => setDepartmentOpen(false)}
+      />
+    </>
   );
 };

+ 22 - 11
src/pages/patient/DiagnosticReport/components/BaseInfo.tsx

@@ -1,7 +1,6 @@
 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';
@@ -14,16 +13,28 @@ export const BaseInfo: React.FC = () => {
   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 [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 departments = useAppSelector((state: RootState) => state.department.departments);
+  const departments = useAppSelector(
+    (state: RootState) => state.department.departments
+  );
   const optionSex = useAppSelector((state: RootState) => state.options.sex);
   const getDepartmentData = async () => {
     const res = await getDepartment({});
@@ -159,7 +170,7 @@ export const BaseInfo: React.FC = () => {
                 style={{ width: '90%' }}
                 value={requesting_department}
                 fieldNames={{ label: 'department', value: 'department' }}
-                onChange={(v) => setRequestingDepartment(`${v}`)}
+                onChange={(v) => setRequestingDepartment(v)}
                 options={departments}
               />
             </Form.Item>

+ 22 - 11
src/pages/patient/DiagnosticReport/components/DepartmentModal.tsx

@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
 import { Modal, Button, Input, message, Form } from 'antd';
 import { RootState, useAppDispatch, useAppSelector } from '@/states/store';
 import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
@@ -19,6 +19,7 @@ export const DepartmentModal: React.FC<DepartmentModalProps> = ({
   const intl = useIntl();
   const dispatch = useAppDispatch();
   const [form] = Form.useForm();
+  const [submitting, setSubmitting] = useState(false);
   const departments = useAppSelector(
     (state: RootState) => state.department.departments
   );
@@ -30,16 +31,25 @@ export const DepartmentModal: React.FC<DepartmentModalProps> = ({
     }
   };
   const onFinish = async (values: { departments: string[] }) => {
-    console.log('Received values of form:', values);
-    const res = await createdDepartment(values);
-    if (res.code === '0x000000') {
-      await getDepartmentData();
-      onClose();
-      message.success(
-        intl.formatMessage({ id: 'report.department.saveSuccess' })
-      );
-    } else {
+    // 防止重复提交
+    if (submitting) return;
+
+    setSubmitting(true);
+    try {
+      const res = await createdDepartment(values);
+      if (res.code === '0x000000') {
+        await getDepartmentData();
+        onClose();
+        message.success(
+          intl.formatMessage({ id: 'report.department.saveSuccess' })
+        );
+      } else {
+        message.error(intl.formatMessage({ id: 'report.department.saveFail' }));
+      }
+    } catch {
       message.error(intl.formatMessage({ id: 'report.department.saveFail' }));
+    } finally {
+      setSubmitting(false);
     }
   };
 
@@ -70,6 +80,7 @@ export const DepartmentModal: React.FC<DepartmentModalProps> = ({
       okButtonProps={{
         autoFocus: true,
         htmlType: 'submit',
+        loading: submitting,
         style: { marginRight: '50px' },
       }}
       modalRender={(dom) => (
@@ -121,7 +132,7 @@ export const DepartmentModal: React.FC<DepartmentModalProps> = ({
               <Button
                 // disabled={fields.length >= 6}
                 type="dashed"
-                onClick={add}
+                onClick={() => add()}
                 style={{ width: '90%' }}
                 icon={<PlusOutlined />}
               >