Browse Source

提交更新

837390164@qq.com 2 months ago
parent
commit
c15e1fba52

+ 38 - 6
src/App.tsx

@@ -169,6 +169,7 @@ const menuTitleMap: Record<string, string> = {
   'dip-core-algorithm': 'DIP算法配置',
   'system-user': '用户管理',
   'system-role': '角色权限',
+  'system-role-manage': '角色管理',
   'system-menu': '菜单配置',
   'system-hospital': '医疗机构管理',
   'system-api': '接口服务配置',
@@ -335,25 +336,55 @@ function App() {
 
   // 使用动态菜单(如果有)或默认菜单
   const menuItems = isLoaded && dynamicMenuItems.length > 0 ? dynamicMenuItems : defaultMenuItems;
+  
+  // ★ 运行时日志:记录当前菜单状态,便于排查角色菜单过滤问题
+  useEffect(() => {
+    console.log('[App] 菜单状态 - isLoaded:', isLoaded, '| dynamicMenuItems.length:', dynamicMenuItems.length, '| 实际使用菜单项数:', menuItems?.length);
+    if (dynamicMenuItems.length > 0) {
+      console.log('[App] ★ 使用角色动态菜单:', JSON.stringify(dynamicMenuItems, null, 2));
+    } else if (!isLoaded) {
+      console.warn('[App] ⚠️ 菜单未加载(isLoaded=false),使用默认全量菜单(所有用户看到所有菜单)');
+    } else {
+      console.warn('[App] ⚠️ 菜单已加载但为空(isLoaded=true, dynamicMenuItems=[]),当前角色无菜单权限');
+    }
+  }, [isLoaded, dynamicMenuItems, menuItems]);
 
   // 获取用户菜单的函数
   const fetchUserMenus = async (session: NonNullable<ReturnType<typeof getSession>>) => {
     try {
+      const groupID = session.groupID || '';
+      const userCode = session.userCode || '';
+      console.log('[App] fetchUserMenus 开始, userCode:', userCode, 'groupID:', groupID);
+      
       const res = await getUserMenus({
-        userCode: session.userCode || '',
-        groupID: session.groupID || ''
+        userCode,
+        groupID
       });
       
+      console.log('[App] getUserMenus原始响应:', JSON.stringify(res, null, 2));
+      
       // getUserMenus 返回的是 MenuItem[] 或 ApiResponse<MenuItem[]>
-      const menus = Array.isArray(res.result) ? res.result : (res.result as any)?.rows;
-      if (String(res.errorCode) === '0' && menus && menus.length > 0) {
-        console.log('[App] 从后端获取到用户菜单:', menus.length, '项');
+      let menus: any[] | null = null;
+      if (String(res.errorCode) === '0' && res.result) {
+        if (Array.isArray(res.result)) {
+          menus = res.result;
+        } else if ((res.result as any)?.rows && Array.isArray((res.result as any).rows)) {
+          menus = (res.result as any).rows;
+        }
+      }
+      
+      if (menus && menus.length > 0) {
+        console.log('[App] 从后端获取到用户菜单:', menus.length, '项, groupID:', groupID);
         loadMenusFromLogin(menus);
       } else {
-        console.warn('[App] 获取用户菜单失败或无权限菜单:', res.errorMessage);
+        console.warn('[App] 获取用户菜单失败或无权限菜单, groupID:', groupID, ', errorMessage:', res.errorMessage);
+        // ★ 即使菜单为空也标记已加载,避免回退到默认全量菜单
+        loadMenusFromLogin([]);
       }
     } catch (error) {
       console.error('[App] 获取用户菜单异常:', error);
+      // 异常时也标记已加载,避免回退到默认菜单
+      loadMenusFromLogin([]);
     }
   };
 
@@ -679,6 +710,7 @@ function App() {
       case 'system-user':
         return <SystemUsers />;
       case 'system-role':
+      case 'system-role-manage':
         return <SystemRoles />;
       case 'system-menu':
         return <SystemMenus />;

+ 153 - 35
src/context/MenuContext.tsx

@@ -50,8 +50,11 @@ export const MenuProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
 
   /** 转换为 Ant Design 菜单格式
    * 支持两种数据结构:
-   * 1. 嵌套结构(有 children 数组)
+   * 1. 嵌套结构(有 children 数组且包含有效子菜单
    * 2. 平铺结构(使用 mainMenuDr 表示父子关系)
+   * 
+   * 注意:后端可能返回字符串类型的 mainMenuDr/menuLevel(如 "1"/"2"),
+   * 需要做类型兼容处理。
    */
   const convertToMenuItems = useCallback((menuList: MenuItem[]): MenuProps['items'] => {
     if (!menuList || menuList.length === 0) {
@@ -59,25 +62,44 @@ export const MenuProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
       return [];
     }
 
-    // 调试日志:输出原始菜单数据
-    console.log('[MenuContext] 原始菜单数据:', menuList);
-    console.log('[MenuContext] 检查 parentCode 字段:', menuList.map(m => ({
+    // 调试日志:输出原始菜单数据及其类型
+    console.log('[MenuContext] ====== 原始菜单数据 ======');
+    console.log('[MenuContext] 菜单总数:', menuList.length);
+    console.log('[MenuContext] 各菜单字段详情:', menuList.map(m => ({
       code: m.code,
       label: m.label,
       mainMenuDr: m.mainMenuDr,
+      'mainMenuDr_type': typeof m.mainMenuDr,
       menuLevel: m.menuLevel,
-      parentCode: m.parentCode
+      'menuLevel_type': typeof m.menuLevel,
+      parentCode: m.parentCode,
+      hasChildren: !!(m.children && m.children.length > 0),
+      menuGroup: m.menuGroup,
+      'menuGroup_type': typeof m.menuGroup,
     })));
 
+    // ★ 辅助函数:兼容数字和字符串类型的层级判断
+    const toNum = (val: any): number => {
+      if (val === null || val === undefined) return 0;
+      return Number(val);
+    };
+
     // 用于判断是否为菜单组的辅助函数
     const isMenuGroup = (menuGroup: any): boolean => {
-      return menuGroup === 'Y' || menuGroup === 1 || menuGroup === '1';
+      const str = String(menuGroup || '').toUpperCase();
+      return str === 'Y' || str === '1';
     };
 
-    // 检查是否为嵌套结构(有 children 数组)
-    const hasNestedStructure = menuList.some(m => m.children && m.children.length > 0);
+    // ★ 检查是否为有效嵌套结构(有 children 数组且非空)
+    const hasNestedStructure = menuList.some(m => {
+      const children = m.children;
+      return Array.isArray(children) && children.length > 0;
+    });
+
+    console.log('[MenuContext] 数据结构判断: hasNestedStructure=', hasNestedStructure);
 
     if (hasNestedStructure) {
+      console.log('[MenuContext] → 使用嵌套结构模式处理');
       // 处理嵌套结构
       const result: MenuProps['items'] = [];
       menuList.forEach(menu => {
@@ -87,48 +109,54 @@ export const MenuProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
             if (isMenuGroup(child.menuGroup)) {
               child.children?.forEach(subChild => {
                 childrenItems.push({
-                  key: subChild.code || subChild.key,
-                  label: subChild.label,
+                  key: subChild.code || subChild.key || '',
+                  label: subChild.label || '',
                 });
               });
             } else {
               childrenItems.push({
-                key: child.code || child.key,
-                label: child.label,
+                key: child.code || child.key || '',
+                label: child.label || '',
               });
             }
           });
           result.push({
-            key: menu.code || menu.key,
-            label: menu.label,
+            key: menu.code || menu.key || '',
+            label: menu.label || '',
             icon: iconMap[menu.icon || ''] || undefined,
             children: childrenItems.length > 0 ? childrenItems : undefined,
           });
         } else if (!isMenuGroup(menu.menuGroup)) {
           result.push({
-            key: menu.code || menu.key,
-            label: menu.label,
+            key: menu.code || menu.key || '',
+            label: menu.label || '',
             icon: iconMap[menu.icon || ''] || undefined,
           });
         }
       });
+      console.log('[MenuContext] 嵌套结构转换结果:', result.length, '项');
       return result;
     } else {
+      console.log('[MenuContext] → 使用平铺结构模式处理(mainMenuDr/parentCode)');
       // 处理平铺结构(使用 mainMenuDr 表示层级关系)
-      // mainMenuDr = 1 表示一级菜单(父菜单),mainMenuDr = 2 表示二级菜单(子菜单)
+      // ★ 兼容数字和字符串:mainMenuDr === 1 或 "1" 表示一级菜单(父菜单)
       const parentMenus: MenuProps['items'] = [];
       const childrenMap: Record<string, MenuProps['items']> = {};
 
-      // 第一步:找出所有父菜单(mainMenuDr = 1)
+      // 第一步:找出所有父菜单(mainMenuDr = 1 或 menuLevel = 1
       menuList.forEach(menu => {
-        const key = menu.code || menu.key;
+        const key = (menu.code || menu.key || '') as string;
         if (!key) return;
         
+        const md = toNum(menu.mainMenuDr);
+        const ml = toNum(menu.menuLevel);
+        
         // mainMenuDr = 1 或 menuLevel = 1 表示父菜单
-        if (menu.mainMenuDr === 1 || menu.menuLevel === 1) {
+        if (md === 1 || ml === 1) {
+          console.log(`[MenuContext] 识别到父菜单: key=${key}, label=${menu.label}, mainMenuDr=${menu.mainMenuDr}(${typeof menu.mainMenuDr}), menuLevel=${menu.menuLevel}(${typeof menu.menuLevel})`);
           const menuItem = {
             key: key,
-            label: menu.label,
+            label: menu.label || '',
             icon: iconMap[menu.icon || ''] || undefined,
           };
           parentMenus.push(menuItem);
@@ -138,22 +166,75 @@ export const MenuProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
         }
       });
 
-      console.log('[MenuContext] 识别到的父菜单:', parentMenus);
+      console.log('[MenuContext] 识别到的父菜单数量:', parentMenus.length);
+
+      // ★ 如果通过 mainMenuDr/menuLevel 没有找到父菜单,尝试另一种策略:
+      // 将所有菜单作为顶级菜单(没有层级信息时兜底显示)
+      if (parentMenus.length === 0) {
+        console.warn('[MenuContext] ⚠️ 未识别到任何父菜单(mainMenuDr/menuLevel均不匹配)!');
+        console.log('[MenuContext] 尝试兜底策略: 将所有菜单作为顶级菜单展示');
+        
+        // 兜底:区分有 parentCode 和无 parentCode 的菜单
+        const topMenus: MenuProps['items'] = [];
+        const childMenus: { key: string; label: string; parentKey: string }[] = [];
+        
+        menuList.forEach(menu => {
+          const key = (menu.code || menu.key || '') as string;
+          if (!key) return;
+          
+          if (menu.parentCode) {
+            childMenus.push({ key, label: menu.label || '', parentKey: menu.parentCode });
+          } else {
+            topMenus.push({
+              key,
+              label: menu.label || '',
+              icon: iconMap[menu.icon || ''] || undefined,
+            });
+          }
+        });
+        
+        // 尝试将 childMenus 关联到正确的父菜单
+        childMenus.forEach(child => {
+          const parent = topMenus.find(p => p!.key === child.parentKey);
+          if (parent) {
+            if (!childrenMap[child.parentKey]) {
+              childrenMap[child.parentKey] = [];
+            }
+            childrenMap[child.parentKey]!.push({ key: child.key, label: child.label });
+          } else {
+            // 父菜单不存在,作为顶级菜单添加
+            topMenus.push({ key: child.key, label: child.label });
+          }
+        });
+        
+        const result = topMenus.map(parent => ({
+          ...parent,
+          children: childrenMap[parent!.key as string] && childrenMap[parent!.key as string]!.length > 0
+            ? childrenMap[parent!.key as string]
+            : undefined,
+        }));
+        
+        console.log('[MenuContext] 兜底转换结果:', result.length, '项');
+        return result;
+      }
 
-      // 第二步:处理子菜单(mainMenuDr = 2),根据 parentCode 关联到父菜单
+      // 第二步:处理子菜单(mainMenuDr = 2 或 menuLevel = 2),根据 parentCode 关联到父菜单
       menuList.forEach(menu => {
-        const key = menu.code || menu.key;
+        const key = (menu.code || menu.key || '') as string;
         if (!key) return;
         
+        const md = toNum(menu.mainMenuDr);
+        const ml = toNum(menu.menuLevel);
+        
         // mainMenuDr = 2 或 menuLevel = 2 表示子菜单
-        if ((menu.mainMenuDr === 2 || menu.menuLevel === 2) && menu.parentCode) {
+        if ((md === 2 || ml === 2) && menu.parentCode) {
           const parentKey = menu.parentCode;
           const menuItem = {
             key: key,
-            label: menu.label,
+            label: menu.label || '',
           };
           
-          console.log(`[MenuContext] 处理子菜单: ${menu.label}, parentCode=${menu.parentCode}, 父菜单key=${parentKey}`);
+          console.log(`[MenuContext] 处理子菜单: key=${key}, label=${menu.label}, parentCode=${parentKey}, mainMenuDr=${menu.mainMenuDr}(${typeof menu.mainMenuDr})`);
           
           if (!childrenMap[parentKey]) {
             childrenMap[parentKey] = [];
@@ -162,7 +243,44 @@ export const MenuProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
         }
       });
 
-      console.log('[MenuContext] childrenMap:', childrenMap);
+      // ★ 处理孤儿子菜单:如果 childrenMap 中有 key 对应的子菜单,但 parentMenus 中没有该父菜单
+      // 则自动创建父菜单组(后端可能只分配了叶子菜单,但没有分配父级菜单组)
+      const orphanParentKeys = Object.keys(childrenMap).filter(
+        pk => !parentMenus.some(p => (p as any)?.key === pk)
+      );
+      
+      if (orphanParentKeys.length > 0) {
+        // 已知父菜单的友好标签映射
+        const parentLabelMap: Record<string, string> = {
+          'user': '用户管理',
+          'basic-data': '数据管理',
+          'system': '系统管理',
+          'drg': 'DRG业务',
+          'dip': 'DIP业务',
+          'warning': '费用预警',
+          'profit': '盈亏分析',
+          'qc': '病案质控',
+          'data': 'HIS数据',
+        };
+        // 父菜单图标映射
+        const parentIconKeyMap: Record<string, string> = {
+          'user': 'UserOutlined',
+          'basic-data': 'TableOutlined',
+          'system': 'SafetyOutlined',
+          'data': 'DatabaseOutlined',
+        };
+        
+        console.warn('[MenuContext] ⚠️ 发现孤儿子菜单,自动创建父菜单:', orphanParentKeys);
+        orphanParentKeys.forEach(pk => {
+          parentMenus.push({
+            key: pk,
+            label: parentLabelMap[pk] || pk,
+            icon: iconMap[parentIconKeyMap[pk] || ''] || undefined,
+          });
+        });
+      }
+
+      console.log('[MenuContext] childrenMap keys:', Object.keys(childrenMap));
 
       // 组装父子关系
       const result = parentMenus.map(parent => ({
@@ -172,17 +290,17 @@ export const MenuProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
           : undefined,
       }));
 
-      console.log('[MenuContext] 最终菜单结果:', result);
+      console.log('[MenuContext] 平铺结构最终菜单结果:', result.length, '项:', JSON.stringify(result));
       return result;
     }
   }, []);
 
-  /** 从登录结果加载菜单 */
+  /** 从登录结果加载菜单(支持空菜单,表示角色无任何菜单权限) */
   const loadMenusFromLogin = useCallback((loginMenus: MenuItem[]) => {
-    if (loginMenus && loginMenus.length > 0) {
-      setMenus(loginMenus);
-      setIsLoaded(true);
-    }
+    console.log('[MenuContext] loadMenusFromLogin 被调用, 菜单数量:', loginMenus?.length || 0);
+    // ★ 即使菜单为空也要标记已加载,避免回退到默认全量菜单
+    setMenus(loginMenus || []);
+    setIsLoaded(true);
   }, []);
 
   /** 清空菜单 */
@@ -311,7 +429,7 @@ export const defaultMenuItems: MenuProps['items'] = [
     label: '用户管理',
     children: [
       { key: 'system-user', label: '用户管理' },
-      { key: 'system-role-manage', label: '角色管理' },
+      { key: 'system-role', label: '角色管理' },
       { key: 'system-menu', label: '菜单配置' },
     ],
   },

+ 42 - 7
src/pages/BasicData/DIPCoreAlgorithmConfig.tsx

@@ -29,6 +29,7 @@ import {
   type DipCoreAlgorithmImportParams,
   type ProvinceItem, type CityItem
 } from '../../api/basicData';
+import { getCurrentGroupName, getSession } from '../../utils/auth';
 
 const { Option } = Select;
 
@@ -83,6 +84,31 @@ const DIPCoreAlgorithmConfig: React.FC = () => {
   const fileContentRef = useRef<string>('');
   const fileNameRef = useRef<string>('');
 
+  // 当前用户是否为管理员
+  const [isAdmin, setIsAdmin] = useState(false);
+
+  // 初始化:根据登录用户角色判断是否为管理员,非管理员默认锁定省份/市/机构等级
+  useEffect(() => {
+    const roleName = getCurrentGroupName();
+    if (roleName === '管理员') {
+      setIsAdmin(true);
+    } else {
+      setIsAdmin(false);
+      const session = getSession();
+      if (session) {
+        if (session.provID) {
+          setProvinceId(session.provID);
+        }
+        if (session.cityID) {
+          setCityId(session.cityID);
+        }
+        if (session.medinsLv) {
+          setMedinsLv(session.medinsLv);
+        }
+      }
+    }
+  }, []);
+
   // 加载省数据(查询)
   const loadProvinces = useCallback(async () => {
     setProvinceLoading(true);
@@ -170,9 +196,16 @@ const DIPCoreAlgorithmConfig: React.FC = () => {
     setPrincipalDiagnosisName('');
     setMajorProcedure('');
     setMajorProcedureName('');
-    setProvinceId('');
-    setCityId('');
-    setMedinsLv('');
+    if (isAdmin) {
+      setProvinceId('');
+      setCityId('');
+      setMedinsLv('');
+    } else {
+      const session = getSession();
+      setProvinceId(session?.provID || '');
+      setCityId(session?.cityID || '');
+      setMedinsLv(session?.medinsLv || '');
+    }
     setYear('');
     setCityList([]);
     setCurrentPage(1);
@@ -760,8 +793,9 @@ const DIPCoreAlgorithmConfig: React.FC = () => {
                   placeholder="请选择省"
                   value={provinceId || undefined}
                   onChange={handleProvinceChange}
-                  allowClear
+                  allowClear={isAdmin}
                   loading={provinceLoading}
+                  disabled={!isAdmin}
                   style={{ width: '100%' }}
                 >
                   {provinceList.map(item => (
@@ -776,8 +810,8 @@ const DIPCoreAlgorithmConfig: React.FC = () => {
                   placeholder="请选择市"
                   value={cityId || undefined}
                   onChange={setCityId}
-                  allowClear
-                  disabled={!provinceId}
+                  allowClear={isAdmin}
+                  disabled={!isAdmin || (!provinceId && isAdmin)}
                   loading={cityLoading}
                   style={{ width: '100%' }}
                 >
@@ -793,7 +827,8 @@ const DIPCoreAlgorithmConfig: React.FC = () => {
                   placeholder="全部"
                   value={medinsLv || undefined}
                   onChange={setMedinsLv}
-                  allowClear
+                  allowClear={isAdmin}
+                  disabled={!isAdmin}
                   style={{ width: '100%' }}
                 >
                   {medinsLvOptions.map(item => (

+ 21 - 2
src/pages/BasicData/DRGCoreAlgorithmConfig.tsx

@@ -26,6 +26,7 @@ import {
   type DrgCoreAlgorithmImportParams
 } from '../../api/basicData';
 import CustomPagination from '../../components/CustomPagination';
+import { getCurrentGroupName, getCurrentHospName } from '../../utils/auth';
 
 const { Option } = Select;
 
@@ -94,12 +95,29 @@ const CoreAlgorithmConfig: React.FC = () => {
   const fileContentRef = useRef<string>('');
   const fileNameRef = useRef<string>('');
 
+  // 当前用户是否为管理员
+  const [isAdmin, setIsAdmin] = useState(false);
+
   // 固定险种选项
   const insuTypeOptions = [
     { code: '310', name: '职工' },
     { code: '390', name: '居民' },
   ];
 
+  // 初始化:根据登录用户角色判断是否为管理员,非管理员默认锁定医疗机构
+  useEffect(() => {
+    const roleName = getCurrentGroupName();
+    if (roleName === '管理员') {
+      setIsAdmin(true);
+    } else {
+      setIsAdmin(false);
+      const hospName = getCurrentHospName();
+      if (hospName) {
+        setFixmedinsName(hospName);
+      }
+    }
+  }, []);
+
   const fetchData = useCallback(async (page = currentPage, size = pageSize) => {
     setLoading(true);
     try {
@@ -192,7 +210,7 @@ const CoreAlgorithmConfig: React.FC = () => {
 
   const handleReset = () => {
     setDrg('');
-    setFixmedinsName('');
+    setFixmedinsName(isAdmin ? '' : getCurrentHospName());
     setProvinceId('');
     setCityId('');
     setQueryInsuType('');
@@ -850,9 +868,10 @@ const CoreAlgorithmConfig: React.FC = () => {
               onChange={v => setFixmedinsName(v || '')}
               loading={queryHospitalLoading}
               style={{ width: 180 }}
-              allowClear
+              allowClear={isAdmin}
               showSearch
               optionFilterProp="children"
+              disabled={!isAdmin}
             >
               {queryHospitalList.map(item => (
                 <Option key={item.code} value={item.descripts}>

+ 45 - 3
src/pages/Login/SelectHospRole.tsx

@@ -22,6 +22,7 @@ import {
   setSession
 } from '../../utils/auth';
 import { useMenu } from '../../context/MenuContext';
+import { getUserMenus } from '../../api/menu';
 import type { UserLogonLocItem } from '../../api/logon';
 import type { MenuItem } from '../../api/menu';
 
@@ -108,7 +109,7 @@ const SelectHospRole: React.FC<SelectHospRoleProps> = ({ onSelectSuccess, onBack
 
       if (String(res.errorCode) === '0' && res.result && res.result.length > 0) {
         const sessionData = res.result[0] as any;
-        console.log('登录接口返回的sessionData:', JSON.stringify(sessionData, null, 2));
+        console.log('[SelectHospRole] 登录接口返回的sessionData:', JSON.stringify(sessionData, null, 2));
         
         // 合并选择页面传入的角色和医院信息到sessionData
         if (permission.groupDesc && !sessionData.groupDesc) {
@@ -124,13 +125,54 @@ const SelectHospRole: React.FC<SelectHospRoleProps> = ({ onSelectSuccess, onBack
           sessionData.hospID = String(permission.hospID);
         }
         
-        console.log('合并后的sessionData:', JSON.stringify(sessionData, null, 2));
+        console.log('[SelectHospRole] 合并后的sessionData:', JSON.stringify(sessionData, null, 2));
         setSession(sessionData);
         clearTempUserInfo();
         
-        // 登录成功后保存用户菜单
+        // ★ 登录成功后确保加载用户角色菜单
+        let menusLoaded = false;
         if (sessionData.menus && sessionData.menus.length > 0) {
+          console.log('[SelectHospRole] 从登录接口返回的menus直接加载菜单:', sessionData.menus.length, '项');
           loadMenusFromLogin(sessionData.menus as MenuItem[]);
+          menusLoaded = true;
+        }
+        
+        // 如果登录接口未返回menus,调用getUserMenus接口获取角色菜单
+        if (!menusLoaded) {
+          console.log('[SelectHospRole] 登录接口未返回menus,调用getUserMenus获取角色菜单...');
+          try {
+            const menuRes = await getUserMenus({
+              userCode: userInfo.userCode,
+              groupID: String(permission.groupID || sessionData.groupID || '')
+            });
+            console.log('[SelectHospRole] getUserMenus原始响应:', JSON.stringify(menuRes, null, 2));
+            
+            // 解析getUserMenus返回的菜单数据
+            let roleMenus: MenuItem[] | null = null;
+            if (String(menuRes.errorCode) === '0' && menuRes.result) {
+              if (Array.isArray(menuRes.result)) {
+                roleMenus = menuRes.result;
+              } else if ((menuRes.result as any)?.rows && Array.isArray((menuRes.result as any).rows)) {
+                roleMenus = (menuRes.result as any).rows;
+              }
+            }
+            
+            if (roleMenus && roleMenus.length > 0) {
+              console.log('[SelectHospRole] getUserMenus获取到角色菜单:', roleMenus.length, '项');
+              // 同时把菜单存入session,便于刷新时恢复
+              sessionData.menus = roleMenus;
+              setSession(sessionData);
+              loadMenusFromLogin(roleMenus);
+            } else {
+              console.warn('[SelectHospRole] getUserMenus未获取到菜单,响应:', menuRes);
+              // 如果getUserMenus也没返回菜单,标记为已加载(空菜单),避免fallback到默认全量菜单
+              loadMenusFromLogin([]);
+            }
+          } catch (menuError) {
+            console.error('[SelectHospRole] getUserMenus调用异常:', menuError);
+            // 异常情况下也标记为已加载,避免显示全量菜单
+            loadMenusFromLogin([]);
+          }
         }
         
         message.success('登录成功');

+ 21 - 2
src/pages/System/Users.tsx

@@ -49,6 +49,7 @@ import {
 } from '../../api/system';
 import { queryHospitals, type HospitalItem } from '../../api/hospital';
 import CustomPagination from '../../components/CustomPagination';
+import { getCurrentGroupName, getCurrentHospId } from '../../utils/auth';
 
 const { Option } = Select;
 const { TabPane } = Tabs;
@@ -115,6 +116,23 @@ const Users: React.FC = () => {
   const [hospitals, setHospitals] = useState<HospitalItem[]>([]);
   const [hospitalLoading, setHospitalLoading] = useState(false);
 
+  // 当前用户是否为管理员
+  const [isAdmin, setIsAdmin] = useState(false);
+
+  // 初始化:根据登录用户角色判断是否为管理员,非管理员默认锁定所属医院
+  useEffect(() => {
+    const roleName = getCurrentGroupName();
+    if (roleName === '管理员') {
+      setIsAdmin(true);
+    } else {
+      setIsAdmin(false);
+      const hospId = getCurrentHospId();
+      if (hospId) {
+        setSearchHosp(hospId);
+      }
+    }
+  }, []);
+
   // 角色列表
   const [groups, setGroups] = useState<GroupOptionItem[]>([]);
   const [groupLoading, setGroupLoading] = useState(false);
@@ -382,7 +400,7 @@ const Users: React.FC = () => {
     setSearchCode('');
     setSearchName('');
     setSearchStatus('');
-    setSearchHosp('');
+    setSearchHosp(isAdmin ? '' : getCurrentHospId());
     setPagination(prev => ({ ...prev, current: 1 }));
     fetchData(1, pagination.pageSize);
   };
@@ -902,9 +920,10 @@ const Users: React.FC = () => {
               value={searchHosp || undefined}
               onChange={v => setSearchHosp(v || '')}
               style={{ width: 180 }}
-              allowClear
+              allowClear={isAdmin}
               loading={hospitalLoading}
               showSearch
+              disabled={!isAdmin}
               filterOption={(input, option) =>
                 String(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
               }