Переглянути джерело

fix(1.34.9 -> 1.34.10): 诊断报告调整。报告基本信息和影像信息从接口获取待完善

szy 1 тиждень тому
батько
коміт
7602cf28e6

+ 7 - 1
src/assets/i18n/messages/en.js

@@ -272,5 +272,11 @@ export default {
   "report.inspectionNumber": "Inspection Number",
   "report.inspectionMethod": "Inspection Method",
   "report.radiologist": "Radiologist",
-  "report.reviewPhysician": "Review Physician"
+  "report.reviewPhysician": "Review Physician",
+  "report.close": "Close",
+  "report.title": "Inspection Report",
+  "report.baseInfo": "Base Info",
+  "report.module.image": "image",
+  "report.module.imageFindings":"imageFindings",
+  "report.module.imagingDiagnosis": "imagingDiagnosis",
 };

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

@@ -272,5 +272,12 @@ export default {
   "report.inspectionNumber": "检查号",
   "report.inspectionMethod": "检查方法",
   "report.radiologist": "放射科医生",
-  "report.reviewPhysician": "审核医师"
+  "report.reviewPhysician": "审核医师",
+  "report.close": "关闭",
+  "report.title": "检查报告",
+  "report.module.baseInfo": "基本信息",
+  "report.module.image": "图像",
+  "report.module.imageFindings":"影像所见",
+  "report.module.imagingDiagnosis": "影像诊断",
+
 };

+ 15 - 5
src/pages/patient/DiagnosticReport/components/AnimalBaseInfo.tsx

@@ -20,9 +20,7 @@ export const AnimalBaseInfo: React.FC = () => {
     (s: RootState) => s.workSelection.selectedIds
   );
   const workEntities = useSelector((s: RootState) => s.workEntities.data);
-  const productName = useSelector(
-    (s: RootState) => s.product.productName
-  );
+  const productName = useSelector((s: RootState) => s.product.productName);
   const intl = useIntl();
   console.log(`【诊断报告】:选中的study id :${selectedIds[0]}`);
   const selectedStudy: Task | TaskAnimal | null =
@@ -81,12 +79,21 @@ export const AnimalBaseInfo: React.FC = () => {
           <Form.Item
             label={
               <FormattedMessage
-                id={productName === 'VETDROS' ? 'animal.register.patientName' : 'register.patientName'}
-                defaultMessage={productName === 'VETDROS' ? 'animal.register.patientName' : 'register.patientName'}
+                id={
+                  productName === 'VETDROS'
+                    ? 'animal.register.patientName'
+                    : 'register.patientName'
+                }
+                defaultMessage={
+                  productName === 'VETDROS'
+                    ? 'animal.register.patientName'
+                    : 'register.patientName'
+                }
               />
             }
           >
             <Input
+              disabled
               value={data.patientName}
               onChange={(e) => onChange('patientName', e.target.value)}
             />
@@ -102,6 +109,7 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Select
+              disabled
               value={data.sex}
               onChange={(v) => onDropdownChange('sex', v)}
             >
@@ -120,6 +128,7 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Input
+              disabled
               value={data.age}
               onChange={(e) => onChange('age', e.target.value)}
             />
@@ -135,6 +144,7 @@ export const AnimalBaseInfo: React.FC = () => {
             }
           >
             <Input
+              disabled
               value={data.ownerName}
               onChange={(e) => onChange('ownerName', e.target.value)}
             />

+ 1 - 1
src/pages/patient/DiagnosticReport/components/DiagnosticReportImageViewer.tsx

@@ -12,7 +12,7 @@ export const DiagnosticReportImageViewer: React.FC<Props> = ({
 }) => (
   <div className="relative w-24 h-24 rounded overflow-hidden group">
     <img src={src} alt="thumb" className="w-full h-full object-cover" />
-    <div className="absolute inset-0 bg-black/45 hidden group:flex items-center justify-center">
+    <div className="absolute inset-0 bg-black/45 group:flex items-center justify-center">
       <CloseOutlined
         className="text-white text-lg bg-black/60 rounded-full p-1 cursor-pointer"
         onClick={onRemove}

+ 9 - 4
src/pages/patient/DiagnosticReport/components/ImageList.tsx

@@ -5,16 +5,21 @@ import { ImageSelection } from './ImageSelection';
 import { DiagnosticReportImageViewer } from './DiagnosticReportImageViewer';
 import { useAppSelector, useAppDispatch } from '@/states/store';
 import { removeImage } from '@/states/patient/DiagnosticReport/imageListSlice';
-import { setCandidates, cancelSelection } from '@/states/patient/DiagnosticReport/imageSelectionSlice';
+import {
+  setCandidates,
+  cancelSelection,
+} from '@/states/patient/DiagnosticReport/imageSelectionSlice';
 import { fetchTaskDetails } from '@/API/patient/workActions';
 import { getExposedImageUrl } from '@/API/bodyPosition';
-
 export const ImageList: React.FC = () => {
   const [isModalVisible, setIsModalVisible] = React.useState(false);
   const [isLoading, setIsLoading] = React.useState(false);
   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 () => {
@@ -29,8 +34,8 @@ export const ImageList: React.FC = () => {
 
       // 提取所有曝光图像的缩略图 URL
       const imageUrls: string[] = [];
-      studyDetails.series.forEach(series => {
-        series.images.forEach(image => {
+      studyDetails.series.forEach((series) => {
+        series.images.forEach((image) => {
           if (image.expose_status === 'Exposed') {
             const thumbnailUrl = getExposedImageUrl(image.sop_instance_uid);
             imageUrls.push(thumbnailUrl);

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

@@ -8,15 +8,17 @@ import { DiagnosisSection } from './DiagnosisSection';
 
 import { useSelector } from 'react-redux';
 import { RootState } from '@/states/store';
+import useIntl from 'react-intl/src/components/useIntl';
 
 export const MainContent: React.FC = () => {
+  const intl = useIntl();
   const productName = useSelector(
     (state: RootState) => state.product.productName
   );
 
   return (
     <div className="flex flex-col gap-2">
-      <Card size="small" title="基本信息">
+      <Card size="small" title={intl.formatMessage({ id: 'patient.baseInfo' })}>
         {productName === 'VETDROS' ? <AnimalBaseInfo /> : <BaseInfo />}
       </Card>
       <Card size="small" title="图像">

+ 4 - 5
src/pages/patient/DiagnosticReport/components/ReportFooter.tsx

@@ -5,19 +5,18 @@ import { useAppDispatch } from '@/states/store';
 import { finishReport } from '@/states/patient/DiagnosticReport/slice';
 import { previewReportThunk } from '@/states/patient/DiagnosticReport/previewReportThunk';
 import { saveReportThunk } from '@/states/patient/DiagnosticReport/saveReportThunk';
-
 export const ReportFooter: React.FC<{ reportData: any }> = ({ reportData }) => {
   const dispatch = useAppDispatch();
   return (
     <div className="p-4 text-right border-t">
       <Space>
         <Button onClick={() => dispatch(previewReportThunk())}>预览</Button>
-        <Button type="primary" onClick={() => dispatch(saveReportThunk(reportData))}>
+        <Button
+          type="primary"
+          onClick={() => dispatch(saveReportThunk(reportData))}
+        >
           保存
         </Button>
-        <Button type="primary" onClick={() => dispatch(finishReport())}>
-          完成
-        </Button>
       </Space>
     </div>
   );

+ 11 - 8
src/pages/patient/DiagnosticReport/components/ReportHeader.tsx

@@ -2,19 +2,22 @@ import React from 'react';
 import { useDispatch } from 'react-redux';
 import { Button } from 'antd';
 import { setVisible } from '@/states/patient/DiagnosticReport/slice';
-
+import useIntl from 'react-intl/src/components/useIntl';
 export const ReportHeader: React.FC = () => {
+  const intl = useIntl();
   const dispatch = useDispatch();
 
-  const handleClose = () => {
-    dispatch(setVisible(false));
-  };
-
   return (
     <div className="flex justify-between items-center">
-      <span className="text-lg font-semibold">报告</span>
-      <Button type="primary" onClick={handleClose} className="ml-auto">
-        Close
+      <span className="text-lg font-semibold">
+        {intl.formatMessage({ id: 'report.title' })}
+      </span>
+      <Button
+        type="primary"
+        onClick={() => dispatch(setVisible(false))}
+        className="ml-auto"
+      >
+        {intl.formatMessage({ id: 'report.close' })}
       </Button>
     </div>
   );

+ 42 - 27
src/pages/patient/DiagnosticReport/components/TemplateManagementModal.tsx

@@ -1,6 +1,18 @@
 /* eslint-disable */
 import React, { useState, useEffect } from 'react';
-import { Modal, Button, Table, Select, Input, Checkbox, message, Form, Space, Row, Col } from 'antd';
+import {
+  Modal,
+  Button,
+  Table,
+  Select,
+  Input,
+  Checkbox,
+  message,
+  Form,
+  Space,
+  Row,
+  Col,
+} from 'antd';
 import { useAppDispatch, useAppSelector } from '@/states/store';
 import { CloseCircleOutlined } from '@ant-design/icons';
 
@@ -19,20 +31,24 @@ interface TemplateManagementModalProps {
   initialTemplate?: Partial<ReportTemplate>; // 用于"另存为模板"功能
 }
 
-export const TemplateManagementModal: React.FC<TemplateManagementModalProps> = ({
-  visible,
-  onClose,
-  initialTemplate,
-}) => {
+export const TemplateManagementModal: React.FC<
+  TemplateManagementModalProps
+> = ({ visible, onClose, initialTemplate }) => {
   const dispatch = useAppDispatch();
   const [form] = Form.useForm();
-  const { templates, loading, selectedTag } = useAppSelector((state) => state.template);
+  const { templates, loading, selectedTag } = useAppSelector(
+    (state) => state.template
+  );
 
-  const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null);
+  const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(
+    null
+  );
   const [isNewTemplate, setIsNewTemplate] = useState(false);
 
   // 获取所有唯一的标签
-  const allTags = Array.from(new Set(templates.map((t) => t.tag).filter(Boolean)));
+  const allTags = Array.from(
+    new Set(templates.map((t) => t.tag).filter(Boolean))
+  );
 
   // 过滤后的模板列表
   const filteredTemplates = selectedTag
@@ -186,12 +202,21 @@ export const TemplateManagementModal: React.FC<TemplateManagementModalProps> = (
     <Modal
       title="诊断报告模板管理"
       open={visible}
+      maskClosable={false}
+      destroyOnHidden
       onCancel={onClose}
       width={1000}
       footer={[
-        <Button key="close" onClick={onClose}>
-          关闭
-        </Button>,
+        <>
+          {!isNewTemplate && selectedTemplateId && (
+            <Button danger onClick={handleDelete}>
+              删除
+            </Button>
+          )}
+          <Button type="primary" onClick={handleSave} disabled={loading}>
+            保存
+          </Button>
+        </>,
       ]}
     >
       <Row gutter={16}>
@@ -204,9 +229,7 @@ export const TemplateManagementModal: React.FC<TemplateManagementModalProps> = (
               </Button>
               {!isNewTemplate && selectedTemplateId && (
                 <>
-                  <Button onClick={handleCopyTemplate}>
-                    复制
-                  </Button>
+                  <Button onClick={handleCopyTemplate}>复制</Button>
                   <Button danger onClick={handleDelete}>
                     删除
                   </Button>
@@ -227,7 +250,10 @@ export const TemplateManagementModal: React.FC<TemplateManagementModalProps> = (
                 type="text"
                 icon={<CloseCircleOutlined />}
                 onClick={() => dispatch(setSelectedTag(''))}
-                style={{ visibility: selectedTag ? 'visible' : 'hidden', transition: 'none' }}
+                style={{
+                  visibility: selectedTag ? 'visible' : 'hidden',
+                  transition: 'none',
+                }}
               />
             </Space>
 
@@ -290,17 +316,6 @@ export const TemplateManagementModal: React.FC<TemplateManagementModalProps> = (
                 <Checkbox>设为常用诊断报告模板</Checkbox>
               </Form.Item>
             </Form>
-
-            <Space>
-              {!isNewTemplate && selectedTemplateId && (
-                <Button danger onClick={handleDelete}>
-                  删除
-                </Button>
-              )}
-              <Button type="primary" onClick={handleSave} loading={loading}>
-                保存
-              </Button>
-            </Space>
           </div>
         </Col>
       </Row>

+ 2 - 6
src/pages/patient/DiagnosticReport/components/TemplatePanel.tsx

@@ -76,8 +76,7 @@ export const TemplatePanel: React.FC = () => {
   };
 
   return (
-    <div className="flex flex-col bg-white rounded">
-
+    <div className="flex flex-col rounded">
       {/* 筛选条 */}
       <div className="flex pb-[10px]">
         <Button
@@ -94,10 +93,7 @@ export const TemplatePanel: React.FC = () => {
         >
           全部
         </Button>
-        <Button
-          size="small"
-          onClick={() => setManagementModalVisible(true)}
-        >
+        <Button size="small" onClick={() => setManagementModalVisible(true)}>
           管理
         </Button>
       </div>

+ 28 - 8
src/pages/patient/DiagnosticReport/index.tsx

@@ -5,10 +5,11 @@ import { ReportMain } from './components/ReportMain';
 import { ReportFooter } from './components/ReportFooter';
 import PdfPreviewModal from '@/states/patient/DiagnosticReport/PdfPreviewModal';
 import { updateField } from '@/states/patient/DiagnosticReport/baseInfoSlice';
-
 const DiagnosticReport: React.FC = () => {
   const dispatch = useAppDispatch();
-  const currentWork = useAppSelector((state) => state.diagnosticReport.currentWork);
+  const currentWork = useAppSelector(
+    (state) => state.diagnosticReport.currentWork
+  );
   const reportData = {}; // Define reportData here or fetch it from state or props
   const currentTheme = useAppSelector((state) => state.theme.currentTheme);
   // 初始化报告数据
@@ -16,12 +17,29 @@ const DiagnosticReport: React.FC = () => {
     if (currentWork) {
       // 初始化 baseInfo slice
       dispatch(updateField({ key: 'studyId', value: currentWork.StudyID }));
-      dispatch(updateField({ key: 'patientName', value: currentWork.PatientName || currentWork.DisplayPatientName }));
+      dispatch(
+        updateField({
+          key: 'patientName',
+          value: currentWork.PatientName || currentWork.DisplayPatientName,
+        })
+      );
       dispatch(updateField({ key: 'patientNo', value: currentWork.PatientID }));
-      dispatch(updateField({ key: 'examTime', value: currentWork.StudyStartDatetime || '' }));
-      dispatch(updateField({ key: 'examDesc', value: currentWork.StudyDescription }));
+      dispatch(
+        updateField({
+          key: 'examTime',
+          value: currentWork.StudyStartDatetime || '',
+        })
+      );
+      dispatch(
+        updateField({ key: 'examDesc', value: currentWork.StudyDescription })
+      );
       dispatch(updateField({ key: 'age', value: currentWork.PatientAge }));
-      dispatch(updateField({ key: 'gender', value: currentWork.PatientSex === 'M' ? '男' : '女' }));
+      dispatch(
+        updateField({
+          key: 'gender',
+          value: currentWork.PatientSex === 'M' ? '男' : '女',
+        })
+      );
       // 暂时注释掉没有的字段,后续根据实际需要添加
       // dispatch(updateField({ key: 'department', value: currentWork.department || '' }));
       // dispatch(updateField({ key: 'examPosition', value: currentWork.body_part || '' }));
@@ -34,8 +52,10 @@ const DiagnosticReport: React.FC = () => {
   }, [currentWork, dispatch]);
 
   return (
-    <div className="flex flex-col h-full"
-      style={{ backgroundColor: currentTheme.token.colorBgContainer }}>
+    <div
+      className="flex flex-col h-full"
+      style={{ backgroundColor: currentTheme.token.colorBgContainer }}
+    >
       <ReportHeader />
       <ReportMain className="flex-1 overflow-scroll" />
       <ReportFooter reportData={reportData} />

+ 197 - 151
src/pages/patient/components/ActionPanel.tsx

@@ -6,28 +6,42 @@ import {
   lockWorkInWorklistThunk,
   fetchWorkThunk,
 } from '@/states/patient/worklist/slices/workSlice';
-import { deleteWorkThunk as deleteWorkThunkFromHistory, lockWorkInhistorylistThunk } from '@/states/patient/worklist/slices/history';
+import {
+  deleteWorkThunk as deleteWorkThunkFromHistory,
+  lockWorkInhistorylistThunk,
+} from '@/states/patient/worklist/slices/history';
 import { switchToSendPanel } from '@/states/patient/worklist/slices/historyPanelSwitchSlice';
 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 } from '@/states/patient/DiagnosticReport/slice';
+import { setVisible, setReport } from '@/states/patient/DiagnosticReport/slice';
 import EditTaskModal from './EditTaskModal';
 import { openEditModal } from '@/states/patient/edit/editFormSlice';
 import DiagnosticReportBatchDownloadModal from '@/components/DiagnosticReportBatchDownloadModal';
 import { setModalVisible } from '@/states/patient/DiagnosticReport/diagnosticReportBatchDownloadSlice';
 import { setBusinessFlow } from '@/states/BusinessFlowSlice';
-import { setSourceTask, setRegisterInfo } from '@/states/patient/reregister/reregisterSlice';
+import {
+  setSourceTask,
+  setRegisterInfo,
+} from '@/states/patient/reregister/reregisterSlice';
 import { mapTaskToRegisterInfo } from '@/domain/patient/taskToRegister';
 import { showNotImplemented } from '@/utils/notificationHelper';
-import { syncRis, RisSyncRequest, saveRisBatch } from '@/API/patient/risActions';
+import {
+  syncRis,
+  RisSyncRequest,
+  saveRisBatch,
+} from '@/API/patient/risActions';
 import { fetchBinThunk } from '@/states/patient/bin/slices/binSlice';
-import { setPage, setPageSize } from '@/states/patient/worklist/slices/searchSlice';
+import {
+  setPage,
+  setPageSize,
+} from '@/states/patient/worklist/slices/searchSlice';
 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';
 const { useToken } = theme;
 
 interface ActionButtonProps {
@@ -61,7 +75,7 @@ const ActionButton: React.FC<ActionButtonProps> = ({
 
 const ActionPanel: React.FC = () => {
   const dispatch = useDispatch<AppDispatch>();
-  
+
   // RIS同步loading状态
   const [risSyncing, setRisSyncing] = useState(false);
   // RIS保存本地loading状态
@@ -70,7 +84,7 @@ const ActionPanel: React.FC = () => {
   const workSelectedIds = useSelector(
     (state: RootState) => state.workSelection.selectedIds
   );
-    const workSelectedSecondaryIdsIds = useSelector(
+  const workSelectedSecondaryIdsIds = useSelector(
     (state: RootState) => state.workSelection.selectedSecondaryIds
   );
   const historySelectedIds = useSelector(
@@ -90,7 +104,9 @@ const ActionPanel: React.FC = () => {
     (state: RootState) => state.historyEntities.data
   );
   const searchState = useSelector((state: RootState) => state.search);
-  const productName = useSelector((state: RootState) => state.product.productName);
+  const productName = useSelector(
+    (state: RootState) => state.product.productName
+  );
 
   const getSelectedWorkIds = () => {
     const selectedIds =
@@ -140,10 +156,10 @@ const ActionPanel: React.FC = () => {
       cancelText: '取消',
       okButtonProps: {
         danger: true,
-        'data-testid': 'modal-confirm-delete'
+        'data-testid': 'modal-confirm-delete',
       },
       cancelButtonProps: {
-        'data-testid': 'modal-cancel-delete'
+        'data-testid': 'modal-cancel-delete',
       },
       centered: true,
       onOk: () => {
@@ -157,7 +173,7 @@ const ActionPanel: React.FC = () => {
     dispatch(switchToSendPanel());
   };
 
-  const handleShowReport = () => {
+  const handleShowReport = async () => {
     const selectedIds = getSelectedWorkIds();
 
     if (selectedIds.length === 0) {
@@ -176,13 +192,19 @@ const ActionPanel: React.FC = () => {
       return;
     }
 
-    dispatch(setVisible({ visible: true, work: selectedWork }));
+    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 || '获取报告失败');
+    }
   };
 
   const getWorksFromWorklistOrHistory = () => {
     return currentKey === 'worklist' ? workEntities : workEntitiesFromHistory;
   };
-  
+
   const handleEdit = () => {
     const selectedIds = getSelectedWorkIds();
 
@@ -204,43 +226,43 @@ const ActionPanel: React.FC = () => {
       dispatch(openEditModal(task));
     }
   };
-  
+
   const handleReRegister = async () => {
     const selectedIds = getSelectedWorkIds();
-    
+
     // 验证选择
     if (selectedIds.length === 0) {
       message.warning('请先选择要重新注册的项目');
       return;
     }
-    
+
     if (selectedIds.length > 1) {
       message.warning('只能重新注册一个项目');
       return;
     }
-    
+
     const works = getWorksFromWorklistOrHistory();
     const task = works.find((item) => item.StudyID === selectedIds[0]);
-    
+
     // 验证任务数据
     if (!task) {
       message.error('找不到选中的任务数据');
       return;
     }
-    
+
     // 验证必要字段
     if (!task.PatientName || !task.PatientID) {
       message.error('任务数据不完整,缺少必要信息');
       return;
     }
-    
+
     try {
       // 设置源任务数据
       await dispatch(setSourceTask(task));
-      
+
       // 将Task数据映射为RegisterInfo
       const registerInfo = mapTaskToRegisterInfo(task);
-      console.log(`study转换后的注册信息:${JSON.stringify(registerInfo)}`)
+      console.log(`study转换后的注册信息:${JSON.stringify(registerInfo)}`);
       // 验证映射后的数据
       if (!registerInfo.patient_name || !registerInfo.patient_id) {
         throw new Error('数据映射失败,缺少必要信息');
@@ -251,11 +273,12 @@ const ActionPanel: React.FC = () => {
       console.log(`开始切换到注册页面`);
       // 切换到注册页面
       await dispatch(setBusinessFlow('register'));
-      
+
       message.success('已切换到注册页面,表单已预填充');
     } catch (error) {
       console.error('ReRegister error:', error);
-      const errorMessage = error instanceof Error ? error.message : '重新注册失败,请重试';
+      const errorMessage =
+        error instanceof Error ? error.message : '重新注册失败,请重试';
       message.error(`重新注册失败: ${errorMessage}`);
     }
   };
@@ -287,24 +310,28 @@ const ActionPanel: React.FC = () => {
   };
 
   // RIS同步成功弹框
-  const showSyncSuccessModal = useCallback((count: number, onOk: () => void) => {
-    Modal.success({
-      title: '🎉 RIS同步成功',
-      content: (
-        <div style={{ padding: '16px 0' }}>
-          <p style={{ fontSize: '16px', margin: '8px 0' }}>
-            共同步 <strong style={{ color: '#52c41a' }}>{count}</strong> 条数据
-          </p>
-          <p style={{ color: '#666', margin: '8px 0' }}>
-            系统将自动刷新列表显示最新数据
-          </p>
-        </div>
-      ),
-      okText: '确定',
-      centered: true,
-      onOk: onOk
-    });
-  }, []);
+  const showSyncSuccessModal = useCallback(
+    (count: number, onOk: () => void) => {
+      Modal.success({
+        title: '🎉 RIS同步成功',
+        content: (
+          <div style={{ padding: '16px 0' }}>
+            <p style={{ fontSize: '16px', margin: '8px 0' }}>
+              共同步 <strong style={{ color: '#52c41a' }}>{count}</strong>{' '}
+              条数据
+            </p>
+            <p style={{ color: '#666', margin: '8px 0' }}>
+              系统将自动刷新列表显示最新数据
+            </p>
+          </div>
+        ),
+        okText: '确定',
+        centered: true,
+        onOk: onOk,
+      });
+    },
+    []
+  );
 
   // RIS同步失败弹框
   const showSyncErrorModal = useCallback((errorMessage: string) => {
@@ -319,7 +346,7 @@ const ActionPanel: React.FC = () => {
         </div>
       ),
       okText: '确定',
-      centered: true
+      centered: true,
     });
   }, []);
 
@@ -341,22 +368,27 @@ const ActionPanel: React.FC = () => {
     // 根据当前页面类型调用相应的搜索
     if (currentKey === 'bin') {
       // 回收站搜索
-      dispatch(fetchBinThunk({
-        page: 1,
-        pageSize: 10,
-        filters: commonFilters as BinFilter,
-      }));
+      dispatch(
+        fetchBinThunk({
+          page: 1,
+          pageSize: 10,
+          filters: commonFilters as BinFilter,
+        })
+      );
     } else {
       // worklist/history 搜索
-      const status = currentKey === 'worklist' ? 'Arrived,InProgress' : 'Completed';
-      dispatch(fetchWorkThunk({
-        page: 1,
-        pageSize: 10,
-        filters: {
-          ...commonFilters,
-          status: status,
-        } as WorkFilter,
-      }));
+      const status =
+        currentKey === 'worklist' ? 'Arrived,InProgress' : 'Completed';
+      dispatch(
+        fetchWorkThunk({
+          page: 1,
+          pageSize: 10,
+          filters: {
+            ...commonFilters,
+            status: status,
+          } as WorkFilter,
+        })
+      );
     }
   }, [dispatch, currentKey, searchState]);
 
@@ -364,32 +396,32 @@ const ActionPanel: React.FC = () => {
   const handleRisSync = useCallback(async () => {
     try {
       setRisSyncing(true);
-      
+
       // 构建同步参数
       const syncParams: RisSyncRequest = {
         start_time: searchState.start_time,
         end_time: searchState.end_time,
         id: searchState.id || undefined,
         name: searchState.name || undefined,
-        acc_no: searchState.acc_no || undefined
+        acc_no: searchState.acc_no || undefined,
       };
 
       console.log('RIS同步参数:', syncParams);
 
       // 调用RIS同步API
       const result = await syncRis(syncParams);
-      
+
       console.log('RIS同步结果:', result);
-      
+
       // 显示成功弹框
       showSyncSuccessModal(result.data.count, () => {
         // 自动触发搜索
         triggerSearch();
       });
-      
     } catch (error) {
       console.error('RIS同步失败:', error);
-      const errorMessage = error instanceof Error ? error.message : '同步过程中发生未知错误';
+      const errorMessage =
+        error instanceof Error ? error.message : '同步过程中发生未知错误';
       showSyncErrorModal(errorMessage);
     } finally {
       setRisSyncing(false);
@@ -404,32 +436,36 @@ const ActionPanel: React.FC = () => {
     if (selectedIds.length === 0) return [];
 
     return workEntities
-      .filter(work => selectedIds.includes(work.entry_id??''))
-      .map(work => work.entry_id!);
+      .filter((work) => selectedIds.includes(work.entry_id ?? ''))
+      .map((work) => work.entry_id!);
   }, [currentKey, workSelectedSecondaryIdsIds, workEntities]);
 
   // RIS保存成功弹框
-  const showSaveSuccessModal = useCallback((count: number) => {
-    Modal.success({
-      title: '🎉 保存成功',
-      content: (
-        <div style={{ padding: '16px 0' }}>
-          <p style={{ fontSize: '16px', margin: '8px 0' }}>
-            成功保存 <strong style={{ color: '#52c41a' }}>{count}</strong> 条RIS数据到本地
-          </p>
-          <p style={{ color: '#666', margin: '8px 0' }}>
-            系统将自动刷新列表显示最新数据
-          </p>
-        </div>
-      ),
-      okText: '确定',
-      centered: true,
-      afterClose: () => {
-        // 弹框自动消失后执行列表刷新
-        triggerSearch();
-      }
-    });
-  }, [triggerSearch]);
+  const showSaveSuccessModal = useCallback(
+    (count: number) => {
+      Modal.success({
+        title: '🎉 保存成功',
+        content: (
+          <div style={{ padding: '16px 0' }}>
+            <p style={{ fontSize: '16px', margin: '8px 0' }}>
+              成功保存 <strong style={{ color: '#52c41a' }}>{count}</strong>{' '}
+              条RIS数据到本地
+            </p>
+            <p style={{ color: '#666', margin: '8px 0' }}>
+              系统将自动刷新列表显示最新数据
+            </p>
+          </div>
+        ),
+        okText: '确定',
+        centered: true,
+        afterClose: () => {
+          // 弹框自动消失后执行列表刷新
+          triggerSearch();
+        },
+      });
+    },
+    [triggerSearch]
+  );
 
   // RIS保存本地处理函数
   const handleRisSave = useCallback(async () => {
@@ -452,10 +488,10 @@ const ActionPanel: React.FC = () => {
 
       // 显示成功弹框(暂逝后自动刷新)
       showSaveSuccessModal(entryIds.length);
-
     } catch (error) {
       console.error('RIS保存本地失败:', error);
-      const errorMessage = error instanceof Error ? error.message : '保存过程中发生未知错误';
+      const errorMessage =
+        error instanceof Error ? error.message : '保存过程中发生未知错误';
       Modal.error({
         title: '❌ 保存失败',
         content: (
@@ -467,7 +503,7 @@ const ActionPanel: React.FC = () => {
           </div>
         ),
         okText: '确定',
-        centered: true
+        centered: true,
       });
     } finally {
       setRisSaving(false);
@@ -485,8 +521,8 @@ const ActionPanel: React.FC = () => {
             theme={themeType}
             size="2x"
             state="normal"
-                        /*控制svg图标的大小,暂时使用这种fontSize方式 */
-            style={{fontSize: '48px'}}
+            /*控制svg图标的大小,暂时使用这种fontSize方式 */
+            style={{ fontSize: '48px' }}
             /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
             className={`text-[${useToken().token.colorPrimary}]`}
           />
@@ -510,15 +546,23 @@ const ActionPanel: React.FC = () => {
             size="2x"
             state="normal"
             /*控制svg图标的大小,暂时使用这种fontSize方式 */
-            style={{fontSize: '48px'}}
+            style={{ fontSize: '48px' }}
             /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
             className={`text-[${useToken().token.colorPrimary}]`}
           />
         }
         tooltip={
           <FormattedMessage
-            id={productName === 'VETDROS' ? 'animal.actionPanel.editPatient' : 'actionPanel.editPatient'}
-            defaultMessage={productName === 'VETDROS' ? 'animal.actionPanel.editPatient' : 'actionPanel.editPatient'}
+            id={
+              productName === 'VETDROS'
+                ? 'animal.actionPanel.editPatient'
+                : 'actionPanel.editPatient'
+            }
+            defaultMessage={
+              productName === 'VETDROS'
+                ? 'animal.actionPanel.editPatient'
+                : 'actionPanel.editPatient'
+            }
           />
         }
         onClick={handleEdit}
@@ -533,7 +577,7 @@ const ActionPanel: React.FC = () => {
             size="2x"
             state="normal"
             /*控制svg图标的大小,暂时使用这种fontSize方式 */
-            style={{fontSize: '48px'}}
+            style={{ fontSize: '48px' }}
             /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
             className={`text-[${useToken().token.colorPrimary}]`}
           />
@@ -546,32 +590,34 @@ const ActionPanel: React.FC = () => {
         }
         onClick={handleLock}
       />
-      {currentKey === 'worklist' && (<ActionButton
-        icon={
-          <Icon
-            module="module-patient"
-            name="RIS"
-            userId="base"
-            theme={themeType}
-            size="2x"
-            state="normal"
-            /*控制svg图标的大小,暂时使用这种fontSize方式 */
-            style={{fontSize: '48px'}}
-            /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
-            className={`text-[${useToken().token.colorPrimary}]`}
-          />
-        }
-        tooltip={
-          <FormattedMessage
-            id="actionPanel.risSync"
-            defaultMessage="RIS同步"
-          />
-        }
-        onClick={handleRisSync}
-        loading={risSyncing}
-        disabled={risSyncing}
-        data-testid="ris-sync-button"
-      />)}
+      {currentKey === 'worklist' && (
+        <ActionButton
+          icon={
+            <Icon
+              module="module-patient"
+              name="RIS"
+              userId="base"
+              theme={themeType}
+              size="2x"
+              state="normal"
+              /*控制svg图标的大小,暂时使用这种fontSize方式 */
+              style={{ fontSize: '48px' }}
+              /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
+              className={`text-[${useToken().token.colorPrimary}]`}
+            />
+          }
+          tooltip={
+            <FormattedMessage
+              id="actionPanel.risSync"
+              defaultMessage="RIS同步"
+            />
+          }
+          onClick={handleRisSync}
+          loading={risSyncing}
+          disabled={risSyncing}
+          data-testid="ris-sync-button"
+        />
+      )}
       {currentKey === 'worklist' && (
         <ActionButton
           icon={
@@ -583,7 +629,7 @@ const ActionPanel: React.FC = () => {
               size="2x"
               state="normal"
               /*控制svg图标的大小,暂时使用这种fontSize方式 */
-              style={{fontSize: '48px'}}
+              style={{ fontSize: '48px' }}
               /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
               className={`text-[${useToken().token.colorPrimary}]`}
             />
@@ -601,29 +647,30 @@ const ActionPanel: React.FC = () => {
         />
       )}
       {currentKey === 'historylist' && (
-      <ActionButton
-        icon={
-          <Icon
-            module="module-patient"
-            name="ReRegister"
-            userId="base"
-            theme={themeType}
-            size="2x"
-            state="normal"
-            /*控制svg图标的大小,暂时使用这种fontSize方式 */
-            style={{fontSize: '48px'}}
-            /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
-            className={`text-[${useToken().token.colorPrimary}]`}
-          />
-        }
-        tooltip={
-          <FormattedMessage
-            id="actionPanel.reRegister"
-            defaultMessage="actionPanel.reRegister"
-          />
-        }
-        onClick={handleReRegister}
-      />)}
+        <ActionButton
+          icon={
+            <Icon
+              module="module-patient"
+              name="ReRegister"
+              userId="base"
+              theme={themeType}
+              size="2x"
+              state="normal"
+              /*控制svg图标的大小,暂时使用这种fontSize方式 */
+              style={{ fontSize: '48px' }}
+              /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
+              className={`text-[${useToken().token.colorPrimary}]`}
+            />
+          }
+          tooltip={
+            <FormattedMessage
+              id="actionPanel.reRegister"
+              defaultMessage="actionPanel.reRegister"
+            />
+          }
+          onClick={handleReRegister}
+        />
+      )}
       {currentKey === 'historylist' && (
         <ActionButton
           icon={
@@ -635,7 +682,7 @@ const ActionPanel: React.FC = () => {
               size="2x"
               state="normal"
               /*控制svg图标的大小,暂时使用这种fontSize方式 */
-              style={{fontSize: '48px'}}
+              style={{ fontSize: '48px' }}
               /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
               className={`text-[${useToken().token.colorPrimary}]`}
             />
@@ -651,7 +698,6 @@ const ActionPanel: React.FC = () => {
         />
       )}
 
-
       <ActionButton
         icon={
           <Icon
@@ -662,7 +708,7 @@ const ActionPanel: React.FC = () => {
             size="2x"
             state="normal"
             /*控制svg图标的大小,暂时使用这种fontSize方式 */
-            style={{fontSize: '48px'}}
+            style={{ fontSize: '48px' }}
             /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
             className={`text-[${useToken().token.colorPrimary}]`}
           />
@@ -686,7 +732,7 @@ const ActionPanel: React.FC = () => {
             size="2x"
             state="normal"
             /*控制svg图标的大小,暂时使用这种fontSize方式 */
-            style={{fontSize: '48px'}}
+            style={{ fontSize: '48px' }}
             /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
             className={`text-[${useToken().token.colorPrimary}]`}
           />
@@ -710,7 +756,7 @@ const ActionPanel: React.FC = () => {
               size="2x"
               state="normal"
               /*控制svg图标的大小,暂时使用这种fontSize方式 */
-              style={{fontSize: '48px'}}
+              style={{ fontSize: '48px' }}
               /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
               className={`text-[${useToken().token.colorPrimary}]`}
             />
@@ -774,7 +820,7 @@ const ActionPanel: React.FC = () => {
             size="2x"
             state="normal"
             /*控制svg图标的大小,暂时使用这种fontSize方式 */
-            style={{fontSize: '48px'}}
+            style={{ fontSize: '48px' }}
             /*控制svg图标的颜色,和主题相关 ,拼接成tailwindcss class*/
             className={`text-[${useToken().token.colorPrimary}]`}
           />

+ 41 - 33
src/states/patient/DiagnosticReport/saveReportThunk.ts

@@ -1,6 +1,8 @@
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { saveDiagnosisReport } from '../../../API/patient/DiagnosisReportActions';
 import { RootState } from '../../../states/store';
+import { setVisible } from '@/states/patient/DiagnosticReport/slice';
+import { message } from 'antd';
 
 export const saveReportThunk = createAsyncThunk(
   'diagnosticReport/saveReport',
@@ -22,41 +24,47 @@ 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,
-            images: images, // 添加图像列表
-          }
+          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, // 添加图像列表
-          };
+          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 saveDiagnosisReport(studyId, report);
-    return response.data;
+    if (response?.code === '0x000000') {
+      await message.success('报告已保存');
+      dispatch(setVisible(false));
+      return response.data;
+    } else {
+      throw new Error(response?.description || '保存失败');
+    }
   }
 );

+ 11 - 2
src/states/patient/DiagnosticReport/slice.ts

@@ -2,6 +2,7 @@ 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';
 
 interface DiagnosticReportState {
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -50,6 +51,9 @@ const diagnosticReportSlice = createSlice({
     setNeedsPreview(state, action: PayloadAction<boolean>) {
       state.needsPreview = action.payload;
     },
+    setReport(state, action: PayloadAction<ReportContent>) {
+      state.report = action.payload;
+    },
   },
   extraReducers: (builder) => {
     builder
@@ -77,6 +81,11 @@ const diagnosticReportSlice = createSlice({
   },
 });
 
-export const { finishReport, setVisible, toggleVisible, setNeedsPreview } =
-  diagnosticReportSlice.actions;
+export const {
+  finishReport,
+  setVisible,
+  toggleVisible,
+  setNeedsPreview,
+  setReport,
+} = diagnosticReportSlice.actions;
 export default diagnosticReportSlice.reducer;