AntDesignPro核心菜单配置全解析

Ant Design Pro 中 menuDataRender 全面解析与应用实例

menuDataRender 是 Ant Design Pro 路由系统的核心配置项,用于动态生成导航菜单。它让你可以完全控制菜单的数据源、格式和渲染逻辑,取代默认的静态路由配置。


🧩 基本用法
// src/app.tsx
import type { RunTimeLayoutConfig } from '@umijs/max';

export const layout: RunTimeLayoutConfig = ({ initialState }) => ({
  menuDataRender: () => {
    // 返回菜单数据数组
    return initialState?.menus || [];
  }
});

🔍 核心功能与使用场景
功能使用场景代码示例
动态菜单加载从后端API获取菜单return await fetch('/api/menus')
权限过滤不同角色显示不同菜单filter(item => hasPermission(item))
菜单格式转换兼容不同数据格式map(item => ({...item, name: item.label}))
状态控制实时更新菜单结合useState/initialState
多级菜单处理递归转换子菜单递归处理children字段

📚 完整企业级实现
1. 数据准备 (getInitialState)
export async function getInitialState() {
  // 从API获取菜单数据
  const fetchMenus = async () => {
    const { data } = await fetch('/api/system/menus');
    
    // 格式化菜单数据
    return data.map(item => ({
      path: item.url,
      name: item.title,
      icon: item.iconType,
      access: item.permission,
      children: item.children || null
    }));
  };
  
  return {
    menus: await fetchMenus(),
    // 其他全局状态...
  };
}
2. 完整menuDataRender实现
export const layout: RunTimeLayoutConfig = ({ initialState }) => ({
  menuDataRender: () => {
    const { menus = [], currentUser } = initialState || {};
    const { permissions = [] } = currentUser || {};
    
    // 1. 菜单权限过滤
    const filterMenuByPermission = (menuItems: MenuDataItem[]) => {
      return menuItems.filter(item => {
        // 无权限要求或用户有权限
        const hasAccess = !item.access || permissions.includes(item.access);
        
        // 递归处理子菜单
        if (item.children) {
          item.children = filterMenuByPermission(item.children);
        }
        
        return hasAccess && (!item.children || item.children.length > 0);
      });
    };
    
    // 2. 菜单排序
    const sortMenu = (menuItems: MenuDataItem[]) => {
      return [...menuItems].sort((a, b) => {
        const weightA = typeof a.weight === 'number' ? a.weight : 100;
        const weightB = typeof b.weight === 'number' ? b.weight : 100;
        return weightA - weightB;
      });
    };
    
    // 3. 特殊菜单项处理
    const processedMenus = menus.map(item => {
      // 添加外部链接处理
      if (item.isExternal) {
        return {
          ...item,
          target: '_blank',
        };
      }
      
      // 添加新窗口打开
      if (item.newTab) {
        return {
          ...item,
          target: '_blank',
          rel: 'noopener noreferrer',
        };
      }
      
      return item;
    });
    
    // 4. 返回过滤和排序后的菜单
    return sortMenu(filterMenuByPermission(processedMenus));
  },
});

🚀 高级应用场景
场景1:多租户菜单隔离
menuDataRender: () => {
  const { menus, currentTenant } = initialState || {};
  return menus.filter(item => 
    !item.tenant || item.tenant === currentTenant.id
  );
}
场景2:灰度发布控制
menuDataRender: () => {
  return initialState.menus.filter(item => 
    !item.featureFlag || isFeatureEnabled(item.featureFlag)
  );
}
场景3:动态图标加载
menuDataRender: () => {
  const icons = {
    dashboard: <DashboardOutlined />,
    user: <UserOutlined />,
    // ...其他图标映射
  };

  return initialState.menus.map(item => ({
    ...item,
    icon: icons[item.icon] || <DefaultIcon />
  }));
}
场景4:菜单缓存处理
menuDataRender: () => {
  const now = Date.now();
  
  // 检查本地缓存
  const cachedMenus = JSON.parse(
    localStorage.getItem('menuCache') || 'null'
  );
  
  const cacheExpire = localStorage.getItem('menuExpire') || '0';
  
  if (cachedMenus && now < Number(cacheExpire)) {
    return cachedMenus;
  }
  
  // 更新缓存
  localStorage.setItem('menuCache', JSON.stringify(initialState.menus));
  localStorage.setItem('menuExpire', String(now + 300000)); // 5分钟有效
  
  return initialState.menus;
}

📊 菜单数据结构示例
[
  {
    "path": "/dashboard",
    "name": "控制台",
    "icon": "dashboard",
    "access": "dashboard.view",
    "weight": 10,
    "children": [
      {
        "path": "/dashboard/analysis",
        "name": "分析页",
        "access": "analysis.view",
        "weight": 1
      },
      {
        "path": "/dashboard/workplace",
        "name": "工作台",
        "access": "workplace.access"
      }
    ]
  },
  {
    "path": "/admin",
    "name": "系统管理",
    "icon": "setting",
    "access": "system.admin",
    "weight": 100,
    "featureFlag": "ADMIN_PANEL"
  }
]

⚙️ 菜单项属性详解
属性类型说明示例
pathstring路由路径"/user/list"
namestring菜单名称"用户管理"
iconstring | ReactNode菜单图标"user"<UserOutlined />
accessstring权限标识"user.manage"
hideInMenuboolean是否隐藏true
hideChildrenInMenuboolean隐藏子菜单false
weightnumber排序权重10
childrenMenuDataItem[]子菜单[...]
targetstring打开方式"_blank"
featureFlagstring特性开关"NEW_FEATURE"
tenantstring租户标识"tenant_a"

🚨 常见问题解决
  1. 菜单不更新问题

    // 强制刷新菜单
    import { refreshInitialState } from '@umijs/max';
    
    const updateMenus = () => {
      const newMenus = [...(initialState?.menus || [])];
      // 修改newMenus...
      
      setInitialState({
        ...initialState,
        menus: newMenus
      });
      
      // 强制刷新布局
      refreshInitialState();
    }
  2. 性能优化

    // 使用缓存提高性能
    import memoize from 'lodash/memoize';
    
    const processMenus = memoize(menus => {
      // 复杂的菜单处理逻辑
    });
    
    menuDataRender: () => 
      processMenus(initialState?.menus || [])
  3. 菜单空状态处理

    menuDataRender: () => {
      if (!initialState?.menus?.length) {
        return [{
          name: '加载中...',
          path: '/loading',
          icon: <LoadingOutlined />
        }];
      }
      // ...正常处理
    }

🧪 调试技巧
  1. 实时查看菜单数据

    menuDataRender: () => {
      const finalMenus = ...; // 处理逻辑
      console.log('Current Menu Structure:', finalMenus);
      return finalMenus;
    }
  2. 开发环境菜单调试页

    // 只在开发环境添加
    if (process.env.NODE_ENV === 'development') {
      menuDataRender: () => [
        ...initialState.menus,
        {
          name: '菜单调试',
          path: '/menu-debug',
          routes: [
            {
              path: '/menu-debug/data',
              component: './MenuDebugger'
            }
          ]
        }
      ]
    }
  3. 性能监控

    menuDataRender: () => {
      const start = performance.now();
      // 菜单处理逻辑...
      const duration = performance.now() - start;
      reportPerf('menuRender', duration);
      return processedMenus;
    }

🌐 多语言支持
import { useIntl } from '@umijs/max';

// 在layout函数内
const intl = useIntl();

menuDataRender: () => {
  return initialState.menus.map(menu => ({
    ...menu,
    name: menu.localeId ? 
      intl.formatMessage({ id: menu.localeId }) : 
      menu.name
  }))
}

通过 menuDataRender,你可以构建出高度动态化、可扩展的菜单系统,满足企业级应用对权限管理、多租户支持、灰度发布等复杂需求。在生产环境中建议配合缓存策略,保证菜单加载性能的同时保持其动态更新能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值