浏览代码

feat(large-screen/navigation): implement navigation button availability control, remove resize and button folding on height change

sw 1 月之前
父节点
当前提交
bbacee2ab3
共有 3 个文件被更改,包括 284 次插入32 次删除
  1. 186 0
      src/domain/permissionMap.ts
  2. 93 30
      src/layouts/BusinessZone.tsx
  3. 5 2
      src/pages/security/components/MeButton.tsx

+ 186 - 0
src/domain/permissionMap.ts

@@ -0,0 +1,186 @@
+// 按钮 key 列表(保证拼写一致)
+export type BtnKey =
+  | 'register'
+  | 'worklist'
+  | 'historylist'
+  | 'archivelist'
+  | 'bin'
+  | 'outputlist'
+  | 'exam'
+  | 'process'
+  | 'print'
+  | 'settings'
+  | 'me'
+  | 'patient_management';
+
+// 页面 key 列表
+export type LocationKey =
+  | 'register'
+  | 'worklist'
+  | 'historylist'
+  | 'archivelist'
+  | 'bin'
+  | 'outputlist'
+  | 'exam'
+  | 'process'
+  | 'print';
+
+// 数据状态
+export interface DataState {
+  hasSelection?: boolean; // 任务清单/历史清单
+  hasExposedImage?: boolean; // 检查/处理
+}
+
+// 权限表:Record<location, Record<button, boolean>>
+const PERMISSION_MAP: Record<LocationKey, Record<BtnKey, boolean>> = {
+  register: {
+    register: false,
+    worklist: false,
+    historylist: false,
+    archivelist: false,
+    bin: false,
+    outputlist: false,
+    exam: true,
+    process: false,
+    print: false,
+    settings: false,
+    me: false,
+    patient_management: true,
+  },
+  // eslint-disable-next-line
+    worklist: {} as any, // 下面动态生成
+  // eslint-disable-next-line
+    historylist: {} as any,
+  archivelist: {
+    register: true,
+    worklist: true,
+    historylist: true,
+    archivelist: true,
+    bin: true,
+    outputlist: true,
+    exam: false,
+    process: false,
+    print: false,
+    settings: true,
+    me: true,
+    patient_management: true,
+  },
+  bin: {
+    register: true,
+    worklist: true,
+    historylist: true,
+    archivelist: true,
+    bin: true,
+    outputlist: true,
+    exam: false,
+    process: false,
+    print: false,
+    settings: true,
+    me: true,
+    patient_management: true,
+  },
+  outputlist: {
+    register: true,
+    worklist: true,
+    historylist: true,
+    archivelist: true,
+    bin: true,
+    outputlist: true,
+    exam: false,
+    process: false,
+    print: false,
+    settings: true,
+    me: true,
+    patient_management: true,
+  },
+  // eslint-disable-next-line
+    exam: {} as any,
+  // eslint-disable-next-line
+    process: {} as any,
+  print: {
+    register: true,
+    worklist: true,
+    historylist: true,
+    archivelist: true,
+    bin: true,
+    outputlist: true,
+    exam: true,
+    process: true,
+    print: true,
+    settings: false,
+    me: false,
+    patient_management: true,
+  },
+};
+
+// 动态填充 worklist / historylist
+['worklist', 'historylist'].forEach((loc: LocationKey) => {
+  const base: Record<BtnKey, boolean> = {
+    register: true,
+    worklist: true,
+    historylist: true,
+    archivelist: true,
+    bin: true,
+    outputlist: true,
+    exam: false,
+    process: false,
+    print: false,
+    settings: true,
+    me: true,
+    patient_management: true,
+  };
+  PERMISSION_MAP[loc] = base;
+});
+
+// 动态填充 exam / process
+['exam', 'process'].forEach((loc: LocationKey) => {
+  const base: Record<BtnKey, boolean> = {
+    register: true,
+    worklist: true,
+    historylist: true,
+    archivelist: true,
+    bin: true,
+    outputlist: true,
+    exam: true,
+    process: false,
+    print: false,
+    settings: false,
+    me: false,
+    patient_management: true,
+  };
+  PERMISSION_MAP[loc] = base;
+});
+
+/**
+ * 唯一出口:传入当前 locationKey 与 dataState,返回各按钮可用性
+ */
+export function getBtnAvailability(
+  location: LocationKey,
+  dataState: DataState
+): Record<BtnKey, boolean> {
+  const row = { ...PERMISSION_MAP[location] }; // 先拷贝一份
+
+  // 1. worklist / historylist 根据 hasSelection 开关 exam/process/print
+  if (location === 'worklist' || location === 'historylist') {
+    const ok = Boolean(dataState.hasSelection);
+    console.log(
+      `当前是 ${location} 页面,hasSelection=${dataState.hasSelection},exam/process/print 可用性=${ok}`
+    );
+    row.exam = ok;
+    row.process = ok;
+    row.print = ok;
+  }
+
+  // 2. exam / process 根据 hasExposedImage 开关 process/print
+  if (location === 'exam') {
+    const ok = Boolean(dataState.hasExposedImage);
+    row.process = ok;
+    row.print = ok;
+  }
+  if (location === 'process') {
+    const ok = Boolean(dataState.hasExposedImage);
+    row.print = ok;
+  }
+  console.log('getBtnAvailability', location, dataState, row);
+  return row;
+}

+ 93 - 30
src/layouts/BusinessZone.tsx

@@ -1,7 +1,12 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState } from 'react';
 import { Space } from 'antd';
 import { FormattedMessage } from 'react-intl';
 import { useSelector } from 'react-redux';
+import {
+  DataState,
+  getBtnAvailability,
+  LocationKey,
+} from '@/domain/permissionMap';
 import {
   AlertOutlined,
   AppstoreOutlined,
@@ -10,15 +15,14 @@ import {
   SettingOutlined,
 } from '@ant-design/icons';
 import MeButton from '../pages/security/components/MeButton';
+import { RootState } from '@/states/store';
 
 interface BusinessZoneProps {
   onMenuClick?: (key: string) => void;
 }
 
-const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
-  // eslint-disable-next-line
-  const currentKey = useSelector((state: any) => state.BusinessFlow.currentKey);
-  const items = [
+function useItems(btnAvailability: Record<string, boolean>) {
+  return [
     {
       key: 'patient_management',
       label: (
@@ -28,9 +32,11 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
         />
       ),
       icon: <MailOutlined />,
+      disabled: !btnAvailability['patient_management'],
       children: [
         {
           key: 'register',
+          disabled: !btnAvailability['register'],
           label: (
             <FormattedMessage
               id="register"
@@ -40,6 +46,7 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
         },
         {
           key: 'worklist',
+          disabled: !btnAvailability['worklist'],
           label: (
             <FormattedMessage
               id="tasklist"
@@ -49,6 +56,7 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
         },
         {
           key: 'historylist',
+          disabled: !btnAvailability['historylist'],
           label: (
             <FormattedMessage
               id="historylist"
@@ -58,6 +66,7 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
         },
         {
           key: 'archivelist',
+          disabled: !btnAvailability['archivelist'],
           label: (
             <FormattedMessage
               id="archivelist"
@@ -67,6 +76,7 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
         },
         {
           key: 'bin',
+          disabled: !btnAvailability['bin'],
           label: (
             <FormattedMessage
               id="bin"
@@ -76,6 +86,7 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
         },
         {
           key: 'outputlist',
+          disabled: !btnAvailability['outputlist'],
           label: (
             <FormattedMessage
               id="outputlist"
@@ -89,9 +100,13 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
       key: 'emergency',
       icon: <AlertOutlined />,
       label: '急诊',
+      disabled: false,
     },
     {
       key: 'exam',
+      disabled:
+        (console.log(`我要看看exam对应的值:${!btnAvailability['exam']}`),
+        !btnAvailability['exam']),
       label: (
         <FormattedMessage
           id="exam"
@@ -105,6 +120,11 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
     },
     {
       key: 'process',
+      disabled:
+        (console.log(
+          `我要看看[process]对应的值:${!btnAvailability['process']}`
+        ),
+        !btnAvailability['process']),
       label: (
         <FormattedMessage
           id="process"
@@ -115,6 +135,7 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
     },
     {
       key: 'print',
+      disabled: !btnAvailability['print'],
       label: (
         <FormattedMessage
           id="print"
@@ -124,36 +145,74 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
       icon: <PrinterOutlined />,
     },
   ];
+}
 
-  const [visibleItems, setVisibleItems] = useState(items);
+const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
+  // eslint-disable-next-line
+  const currentKey = useSelector((state: RootState) => state.BusinessFlow.currentKey);
+  console.log('Current Business Flow Key:', currentKey);
+  const dataState: DataState = useSelector((state: RootState) => {
+    if (currentKey === 'worklist') {
+      return {
+        hasSelection: state.workSelection.selectedIds.length > 0,
+        hasExposedImage:
+          state.bodyPositionList.exposureStatus === 'Half Exposed' ||
+          state.bodyPositionList.exposureStatus === 'Fully Exposed',
+      };
+    } else if (currentKey === 'historylist') {
+      return {
+        hasSelection: state.historySelection.selectedIds.length > 0,
+        hasExposedImage:
+          state.bodyPositionList.exposureStatus === 'Half Exposed' ||
+          state.bodyPositionList.exposureStatus === 'Fully Exposed',
+      };
+    } else if (currentKey === 'exam') {
+      return {
+        // hasSelection: state.historySelection.selectedIds.length > 0,
+        hasExposedImage:
+          state.bodyPositionList.exposureStatus === 'Half Exposed' ||
+          state.bodyPositionList.exposureStatus === 'Fully Exposed',
+      };
+    } else {
+      return {};
+    }
+  });
+  const btnAvailability = getBtnAvailability(
+    currentKey as LocationKey,
+    dataState
+  );
+  console.log('Button Availability:', btnAvailability);
+  const items = useItems(btnAvailability);
+
+  // const [visibleItems, setVisibleItems] = useState(items);
   const [floatingMenuVisible, setFloatingMenuVisible] = useState(false);
-  const businessZoneRef = useRef<HTMLDivElement>(null);
+  // const businessZoneRef = useRef<HTMLDivElement>(null);
 
-  useEffect(() => {
-    const handleResize = () => {
-      if (businessZoneRef.current) {
-        const businessZoneHeight = businessZoneRef.current.offsetHeight;
-        const windowHeight = window.innerHeight;
-        const systemZoneHeight = 100; // Assuming SystemZone height is fixed
+  // useEffect(() => {
+  //   const handleResize = () => {
+  //     if (businessZoneRef.current) {
+  //       const businessZoneHeight = businessZoneRef.current.offsetHeight;
+  //       const windowHeight = window.innerHeight;
+  //       const systemZoneHeight = 100; // Assuming SystemZone height is fixed
 
-        if (businessZoneHeight + systemZoneHeight > windowHeight) {
-          const visibleCount = Math.floor(
-            (windowHeight - systemZoneHeight) / 50
-          ); // Assuming each button height is 50px
-          setVisibleItems(items.slice(0, visibleCount));
-        } else {
-          setVisibleItems(items);
-        }
-      }
-    };
+  //       if (businessZoneHeight + systemZoneHeight > windowHeight) {
+  //         const visibleCount = Math.floor(
+  //           (windowHeight - systemZoneHeight) / 50
+  //         ); // Assuming each button height is 50px
+  //         setVisibleItems(items.slice(0, visibleCount));
+  //       } else {
+  //         setVisibleItems(items);
+  //       }
+  //     }
+  //   };
 
-    window.addEventListener('resize', handleResize);
-    handleResize(); // Initial check
+  //   window.addEventListener('resize', handleResize);
+  //   handleResize(); // Initial check
 
-    return () => {
-      window.removeEventListener('resize', handleResize);
-    };
-  }, [items]);
+  //   return () => {
+  //     window.removeEventListener('resize', handleResize);
+  //   };
+  // }, [items]);
 
   const handlePatientManagementClick = () => {
     setFloatingMenuVisible(!floatingMenuVisible);
@@ -166,7 +225,7 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
     >
       <div className="overflow-y-auto">
         <Space direction="vertical">
-          {visibleItems.map((item) =>
+          {items.map((item) =>
             item.type === 'divider' ? (
               <hr
                 key="divider"
@@ -193,6 +252,10 @@ const BusinessZone: React.FC<BusinessZoneProps> = ({ onMenuClick }) => {
                       : () => onMenuClick?.(item.key ?? 'error')
                   }
                   username={item.label}
+                  disabled={
+                    (console.log(`${item.key}=======${item.disabled}`),
+                    item.disabled)
+                  }
                 />
                 {item.key === 'patient_management' && floatingMenuVisible && (
                   <Space direction="vertical" style={{ marginLeft: 20 }}>

+ 5 - 2
src/pages/security/components/MeButton.tsx

@@ -7,7 +7,8 @@ interface MeButtonProps extends AvatarProps {
   avatarUrl?: string;
   username?: React.ReactNode;
   icon?: React.ReactNode;
-  "data-testid"?:string;
+  'data-testid'?: string;
+  disabled?: boolean;
 }
 /**
  * 没有传递isLogin时,当作普通按钮使用
@@ -26,6 +27,7 @@ const MeButton: React.FC<MeButtonProps> = ({
   onClick,
   username,
   icon,
+  disabled,
   ...props
 }) => {
   return (
@@ -35,12 +37,13 @@ const MeButton: React.FC<MeButtonProps> = ({
         display: 'flex',
         alignItems: 'center',
         // border: 'none',
-        background: 'none',
+        background: disabled ? 'transparent' : 'gray',
         padding: 0,
         cursor: 'pointer',
       }}
       className={props.className}
       data-testid={props['data-testid']}
+      disabled={disabled} // 如果传递了disabled属性,则可能禁用按钮
     >
       {isLogin ? (
         <Avatar