Browse Source

refactor: 重构响应式断点Hook以修复水平滚动时的布局问题

- 新增 useEffectiveBreakpoint Hook 包装 antd Grid.useBreakpoint
- 修复窗口宽度小于1700px时水平滚动导致的布局切换问题
- 当窗口宽度 < 1700px 时强制使用大屏断点,防止布局错乱
- 在所有页面组件中统一使用新 Hook 替代原生 Grid.useBreakpoint
- 包含代码格式化优化

改动文件:
- src/hooks/useEffectiveBreakpoint.ts(新增)
- src/pages/exam/ExamPage.tsx
- src/pages/patient/ArchiveList.tsx
- src/pages/patient/Bin.tsx
- src/pages/patient/HistoryList.tsx
- src/pages/patient/OutputList.tsx
- src/pages/patient/register.tsx
- src/pages/patient/worklist.tsx
- src/pages/patient/components/register.protocol.list.tsx
- src/pages/patient/components/register.selected.view.list.tsx
- src/pages/view/ImageProcessingPage.tsx
- package.json(版本号更新至1.2.2)
dengdx 1 ngày trước cách đây
mục cha
commit
c9fa32a87e

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "zsis",
-  "version": "1.1.5",
+  "version": "1.2.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "zsis",
-      "version": "1.1.5",
+      "version": "1.2.1",
       "dependencies": {
         "@babel/runtime": "^7.24.4",
         "@cornerstonejs/core": "^3.28.0",

+ 1 - 1
package.json

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

+ 46 - 0
src/hooks/useEffectiveBreakpoint.ts

@@ -0,0 +1,46 @@
+import { Grid } from 'antd';
+import { useEffect, useState } from 'react';
+
+const { useBreakpoint } = Grid;
+
+/**
+ * A wrapper around antd's Grid.useBreakpoint that respects the application's minimum width.
+ * If the window width is less than 1700px (the min-width set in CSS), it forces the breakpoints
+ * to behave as if the screen is large (xxl), preventing the layout from switching to mobile/tablet modes
+ * when the user scrolls horizontally.
+ */
+const useEffectiveBreakpoint = () => {
+    const screens = useBreakpoint();
+    const [isForcedLarge, setIsForcedLarge] = useState(false);
+
+    useEffect(() => {
+        const handleResize = () => {
+            // Check if window width is less than the min-width (1700px)
+            // We use a slightly smaller buffer or exact match depending on needs, 
+            // but strictly < 1700 matches the CSS min-width logic.
+            setIsForcedLarge(window.innerWidth < 1700);
+        };
+
+        // Initial check
+        handleResize();
+
+        window.addEventListener('resize', handleResize);
+        return () => window.removeEventListener('resize', handleResize);
+    }, []);
+
+    if (isForcedLarge) {
+        // Return a breakpoint object that mimics a very large screen
+        return {
+            xs: true,
+            sm: true,
+            md: true,
+            lg: true,
+            xl: true,
+            xxl: true,
+        };
+    }
+
+    return screens;
+};
+
+export default useEffectiveBreakpoint;

+ 2 - 2
src/pages/exam/ExamPage.tsx

@@ -1,11 +1,11 @@
-import { Grid } from 'antd';
 import LargeScreen from './LargeScreen';
 import MediumScreen from './MediumScreen';
 import SmallScreen from './SmallScreen';
+import useEffectiveBreakpoint from '../../hooks/useEffectiveBreakpoint';
 
 const ExamPage = () => {
   console.log('ExamPage component rendered');
-  const screens = Grid.useBreakpoint();
+  const screens = useEffectiveBreakpoint();
 
   return (
     <div data-testid="exam-page" className="h-full">

+ 3 - 4
src/pages/patient/ArchiveList.tsx

@@ -1,14 +1,13 @@
 import React, { useState } from 'react';
-import { Row, Col, Button, Drawer, Grid, Pagination } from 'antd';
+import { Row, Col, Button, Drawer, Pagination } from 'antd';
 import { SettingOutlined } from '@ant-design/icons';
 import { FormattedMessage } from 'react-intl';
 import WorklistTable from './components/WorklistTable';
 import ArchiveOperationPanel from './components/ArchiveOperationPanel';
-
-const { useBreakpoint } = Grid;
+import useEffectiveBreakpoint from '../../hooks/useEffectiveBreakpoint';
 
 const ArchivelistPage: React.FC = () => {
-  const screens = useBreakpoint();
+  const screens = useEffectiveBreakpoint();
   const [drawerVisible, setDrawerVisible] = useState(false);
 
   return (

+ 7 - 8
src/pages/patient/Bin.tsx

@@ -1,5 +1,5 @@
 import React, { useState, useEffect } from 'react';
-import { Row, Col, Button, Drawer, Grid } from 'antd';
+import { Row, Col, Button, Drawer } from 'antd';
 import { SettingOutlined } from '@ant-design/icons';
 import { FormattedMessage } from 'react-intl';
 import WorklistTable from './components/WorklistTable';
@@ -16,11 +16,10 @@ import {
 import { fetchDiskInfoThunk } from '../../states/patient/bin/slices/binDiskInfoSlice';
 import { Task } from '@/domain/work';
 import { useMultiSelection } from '@/hooks/useMultiSelection';
-
-const { useBreakpoint } = Grid;
+import useEffectiveBreakpoint from '../../hooks/useEffectiveBreakpoint';
 
 const BinPage: React.FC = () => {
-  const screens = useBreakpoint();
+  const screens = useEffectiveBreakpoint();
   const [drawerVisible, setDrawerVisible] = useState(false);
   const [selectedPatientForPortrait, setSelectedPatientForPortrait] =
     useState<Task | null>(null);
@@ -61,7 +60,7 @@ const BinPage: React.FC = () => {
     selectedIds,
     onSelectionChange: (newIds) => {
       console.log('Bin selected IDs changed:', newIds);
-      
+
       // 如果只有一个选中项,设置选中患者用于显示照片
       if (newIds.length === 1) {
         const selectedRecord = binData.find(item => item.StudyID === newIds[0]);
@@ -72,7 +71,7 @@ const BinPage: React.FC = () => {
         // 多选时清空患者照片
         setSelectedPatientForPortrait(null);
       }
-      
+
       // 更新 Redux 状态
       dispatch(binSelectionSlice.actions.setSelectedIds(newIds));
     },
@@ -103,7 +102,7 @@ const BinPage: React.FC = () => {
               pageSize={pageSize}
               selectedIds={selectedIds}
               handleRowClick={handleRowClickInternal}
-              handleRowDoubleClick={() => {}}
+              handleRowDoubleClick={() => { }}
             />
           </div>
           <GenericPagination
@@ -150,7 +149,7 @@ const BinPage: React.FC = () => {
                   pageSize={pageSize}
                   selectedIds={selectedIds}
                   handleRowClick={handleRowClickInternal}
-                  handleRowDoubleClick={() => {}}
+                  handleRowDoubleClick={() => { }}
                 />
               </div>
               <GenericPagination

+ 9 - 10
src/pages/patient/HistoryList.tsx

@@ -1,5 +1,5 @@
 import React, { useState, useEffect } from 'react';
-import { Row, Col, Button, Drawer, Grid } from 'antd';
+import { Row, Col, Button, Drawer } from 'antd';
 import { SettingOutlined } from '@ant-design/icons';
 import { FormattedMessage } from 'react-intl';
 import { useSelector, useDispatch } from 'react-redux';
@@ -21,11 +21,10 @@ import { Task } from '@/domain/work';
 import worklistToExam from '../../domain/patient/worklistToExam';
 import { ColumnConfig, columnConfigService } from '@/config/tableColumns';
 import { useMultiSelection } from '@/hooks/useMultiSelection';
-
-const { useBreakpoint } = Grid;
+import useEffectiveBreakpoint from '../../hooks/useEffectiveBreakpoint';
 
 const HistorylistPage: React.FC = () => {
-  const screens = useBreakpoint();
+  const screens = useEffectiveBreakpoint();
   const [drawerVisible, setDrawerVisible] = useState(false);
   const [selectedPatientForPortrait, setSelectedPatientForPortrait] = useState<Task | null>(null); // 照片显示用的选中患者
 
@@ -44,7 +43,7 @@ const HistorylistPage: React.FC = () => {
   const currentPanel = useSelector(
     (state: RootState) => state.historyPanelSwitch.currentPanel
   );
-   const [columnConfig, setColumnConfig] = useState<ColumnConfig[]>([]);
+  const [columnConfig, setColumnConfig] = useState<ColumnConfig[]>([]);
   // 获取和应用列配置
   useEffect(() => {
     columnConfigService
@@ -90,7 +89,7 @@ const HistorylistPage: React.FC = () => {
     selectedIds,
     onSelectionChange: (newIds) => {
       console.log('Selected IDs changed:', newIds);
-      
+
       // 如果只有一个选中项,设置选中患者用于显示照片
       if (newIds.length === 1) {
         const selectedRecord = historylistData.find(item => item.StudyID === newIds[0]);
@@ -101,7 +100,7 @@ const HistorylistPage: React.FC = () => {
         // 多选时清空患者照片
         setSelectedPatientForPortrait(null);
       }
-      
+
       // 更新 Redux 状态(用于其他功能)
       dispatch(historySelectionSlice.actions.setSelectedIds(newIds));
       dispatch(updateThumbnailsFromHistorySelection(newIds));
@@ -125,11 +124,11 @@ const HistorylistPage: React.FC = () => {
   return (
     <div className="h-full">
       {/* 患者照片浮动组件 */}
-      <PatientPortraitFloat 
+      <PatientPortraitFloat
         patient={selectedPatientForPortrait}
         onClose={() => setSelectedPatientForPortrait(null)}
       />
-      
+
       {screens.xs ? (
         <>
           <div className="flex-1 overflow-auto">
@@ -185,7 +184,7 @@ const HistorylistPage: React.FC = () => {
             <div className="flex-1 flex flex-col">
               <div className="flex-1 overflow-auto">
                 <WorklistTable
-                columnConfig={columnConfig}
+                  columnConfig={columnConfig}
                   worklistData={historylistData}
                   filters={filters}
                   page={page}

+ 5 - 6
src/pages/patient/OutputList.tsx

@@ -1,5 +1,5 @@
 import React, { useState, useEffect } from 'react';
-import { Row, Col, Button, Drawer, Grid } from 'antd';
+import { Row, Col, Button, Drawer } from 'antd';
 import { SettingOutlined } from '@ant-design/icons';
 import { FormattedMessage } from 'react-intl';
 import { GenericDataTable, ColumnDefinition } from '@/components/GenericDataTable';
@@ -12,8 +12,7 @@ import {
   sendJobPaginationSlice,
 } from '@/states/output/sendJob/slices/sendJobSlice';
 import { SendJob } from '@/domain/output/sendJob';
-
-const { useBreakpoint } = Grid;
+import useEffectiveBreakpoint from '../../hooks/useEffectiveBreakpoint';
 
 // 定义传输队列的列
 const sendJobColumns: ColumnDefinition<SendJob>[] = [
@@ -81,9 +80,9 @@ const sendJobColumns: ColumnDefinition<SendJob>[] = [
 ];
 
 const OutputlistPage: React.FC = () => {
-  const screens = useBreakpoint();
+  const screens = useEffectiveBreakpoint();
   const [drawerVisible, setDrawerVisible] = useState(false);
-  
+
   // Redux 状态和 dispatch
   const dispatch = useAppDispatch();
   const { data: sendJobs } = useAppSelector((state) => state.sendJobEntities);
@@ -91,7 +90,7 @@ const OutputlistPage: React.FC = () => {
   const filters = useAppSelector((state) => state.sendJobFilters);
   const { page, pageSize } = useAppSelector((state) => state.sendJobPagination);
   const selectedIds = useAppSelector((state) => state.sendJobSelection.selectedIds);
-  
+
   // 组件挂载时拉取数据
   useEffect(() => {
     dispatch(

+ 4 - 5
src/pages/patient/components/register.protocol.list.tsx

@@ -1,15 +1,14 @@
 import React from 'react';
-import { Grid, Row } from 'antd';
+import { Row } from 'antd';
 import { useSelector } from 'react-redux';
 // import { FormattedMessage } from 'react-intl';
 
 import type { RootState } from '@/states/store'; // 假设RootState已定义
 import ProcedureCard from './ProcedureCard'; // 若已实现
-
-const { useBreakpoint } = Grid;
+import useEffectiveBreakpoint from '../../../hooks/useEffectiveBreakpoint';
 
 // 卡片尺寸配置
-const getCardSize = (screens: ReturnType<typeof useBreakpoint>) => {
+const getCardSize = (screens: any) => {
   if (screens.xl || screens.xxl) {
     return { width: 50, height: 100 };
   }
@@ -20,7 +19,7 @@ const getCardSize = (screens: ReturnType<typeof useBreakpoint>) => {
 };
 
 const ProtocolSelectionList: React.FC = () => {
-  const screens = useBreakpoint();
+  const screens = useEffectiveBreakpoint();
   const cardSize = getCardSize(screens);
 
   // 监听redux中的protocols

+ 4 - 5
src/pages/patient/components/register.selected.view.list.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { Card, Grid, Button, Row, Flex } from 'antd';
+import { Card, Button, Row, Flex } from 'antd';
 import { CloseOutlined } from '@ant-design/icons';
 import { getViewIconUrl } from '@/API/bodyPosition';
 import { Image } from 'antd';
@@ -8,11 +8,10 @@ import { useSelector, useDispatch } from 'react-redux';
 import type { RootState } from '@/states/store';
 import type { ExtendedView } from '@/states/patient/viewSelection';
 import { removeSelectedView } from '@/states/patient/viewSelection';
-
-const { useBreakpoint } = Grid;
+import useEffectiveBreakpoint from '../../../hooks/useEffectiveBreakpoint';
 
 // 卡片尺寸配置
-const getCardSize = (screens: ReturnType<typeof useBreakpoint>) => {
+const getCardSize = (screens: any) => {
   if (screens.xl || screens.xxl) {
     return { width: 150, height: 100 };
   }
@@ -27,7 +26,7 @@ interface SelectedProtocolListProps {
 }
 const SelectedProtocolList: React.FC<SelectedProtocolListProps> = () => {
   const dispatch = useDispatch();
-  const screens = useBreakpoint();
+  const screens = useEffectiveBreakpoint();
   const cardSize = getCardSize(screens);
 
   // 从redux获取selectedViews

+ 3 - 3
src/pages/patient/register.tsx

@@ -1,5 +1,5 @@
 import React, { useEffect } from 'react';
-import { Row, Col, Collapse, Grid, Button, Space, Form, message } from 'antd';
+import { Row, Col, Collapse, Button, Space, Form, message } from 'antd';
 import { FormattedMessage } from 'react-intl';
 import { useDispatch, useSelector } from 'react-redux';
 import {
@@ -27,13 +27,13 @@ import { omitHumanSchemaMap } from '@/domain/humanSpecificInfo';
 import { setBusinessFlow } from '@/states/BusinessFlowSlice';
 import { selectRegisterInfo } from '@/states/patient/reregister/reregisterSlice';
 import { clearReRegister } from '@/states/patient/reregister/reregisterSlice';
+import useEffectiveBreakpoint from '../../hooks/useEffectiveBreakpoint';
 dayjs.extend(utc);
 
-const { useBreakpoint } = Grid;
 const { Panel } = Collapse;
 
 const RegisterPage: React.FC = () => {
-  const screens = useBreakpoint();
+  const screens = useEffectiveBreakpoint();
   const [form] = Form.useForm();
   const dispatch = useDispatch();
   const { selectedViews, currentPatientType } = useRegisterState();

+ 11 - 12
src/pages/patient/worklist.tsx

@@ -1,5 +1,5 @@
 import React, { useState, useEffect } from 'react';
-import { Row, Col, Button, Drawer, Grid } from 'antd';
+import { Row, Col, Button, Drawer } from 'antd';
 import { SettingOutlined } from '@ant-design/icons';
 import { FormattedMessage } from 'react-intl';
 import { useSelector, useDispatch } from 'react-redux';
@@ -24,11 +24,10 @@ import { columnConfigService } from '@/config/tableColumns';
 import { ColumnConfig } from '@/config/tableColumns/types/columnConfig';
 import { useMultiSelection } from '@/hooks/useMultiSelection';
 import AppendViewModal from '../exam/components/AppendViewModal';
-
-const { useBreakpoint } = Grid;
+import useEffectiveBreakpoint from '../../hooks/useEffectiveBreakpoint';
 
 const WorklistPage: React.FC = () => {
-  const screens = useBreakpoint();
+  const screens = useEffectiveBreakpoint();
   const [drawerVisible, setDrawerVisible] = useState(false);
   const [columnConfig, setColumnConfig] = useState<ColumnConfig[]>([]); // 新增:列配置状态
   const [selectedPatientForPortrait, setSelectedPatientForPortrait] = useState<Task | null>(null); // 照片显示用的选中患者
@@ -98,12 +97,12 @@ const WorklistPage: React.FC = () => {
   const { handleRowClick } = useMultiSelection({
     selectedIds,
     selectedSecondaryIds,
-    onSelectionChange: (newIds,secondNewIds) => {
-      console.log('Selected IDs changed:', newIds,secondNewIds);
-      
+    onSelectionChange: (newIds, secondNewIds) => {
+      console.log('Selected IDs changed:', newIds, secondNewIds);
+
       // 如果只有一个选中项,设置选中患者用于显示照片
       if (newIds.length === 1) {
-        const selectedRecord = worklistData.find(item => item.StudyID === newIds[0]&&item.entry_id===secondNewIds?.[0]);
+        const selectedRecord = worklistData.find(item => item.StudyID === newIds[0] && item.entry_id === secondNewIds?.[0]);
         if (selectedRecord) {
           setSelectedPatientForPortrait(selectedRecord);
         }
@@ -111,10 +110,10 @@ const WorklistPage: React.FC = () => {
         // 多选时清空患者照片
         setSelectedPatientForPortrait(null);
       }
-      
+
       // 更新 Redux 状态(用于其他功能)
       dispatch(workSelectionSlice.actions.setSelectedIds(newIds));
-      dispatch(workSelectionSlice.actions.setSelectedSecondaryIds(secondNewIds??[]));
+      dispatch(workSelectionSlice.actions.setSelectedSecondaryIds(secondNewIds ?? []));
       dispatch(updateThumbnailsFromWorkSelection(newIds));
     },
     enableMultiSelect: true,
@@ -148,11 +147,11 @@ const WorklistPage: React.FC = () => {
   return (
     <div className="h-full">
       {/* 患者照片浮动组件 */}
-      <PatientPortraitFloat 
+      <PatientPortraitFloat
         patient={selectedPatientForPortrait}
         onClose={() => setSelectedPatientForPortrait(null)}
       />
-      
+
       {screens.xs ? (
         <>
           <div className="flex-1 overflow-auto">

+ 2 - 2
src/pages/view/ImageProcessingPage.tsx

@@ -1,11 +1,11 @@
 import React from 'react';
-import { Grid } from 'antd';
 import ImageProcessingPageLarge from './ImageProcessingPageLarge';
 import ImageProcessingPageMedium from './ImageProcessingPageMedium';
 import ImageProcessingPageSmall from './ImageProcessingPageSmall';
+import useEffectiveBreakpoint from '../../hooks/useEffectiveBreakpoint';
 
 const ImageProcessingPage = () => {
-  const screens = Grid.useBreakpoint();
+  const screens = useEffectiveBreakpoint();
 
   return (
     <>