引言
在接触若依系统时,发现菜单是通过服务端获取的。作为一名前端开发者,我之前一直使用 Ant Design Pro,因此对“如何在 Ant Design Pro 中实现服务端菜单”产生了兴趣。
经过调研和实践,我最终实现了一个简单的服务端菜单渲染方案,过程中也踩了一些坑。在这里总结一下,希望能帮助到有类似需求的小伙伴。
实现步骤
环境版本
- Ant Design Pro v6
- umi v4.4.10
服务端返回的菜单数据结构(示例)
[ { "id": "1", "name": "系统管理", "parentId": "0", "path": "/system", "component": "", "routeName": "system", "icon": "SettingOutlined", "children": [ { "id": "2", "name": "用户管理", "parentId": "1", "path": "/system/user", "component": "/system/user/index", "routeName": "user", "children": []
}
]
}
]
核心代码(src/app.tsx)
import React from 'react';
import type { RuntimeConfig } from '@umijs/max';
import AuthWrapper from './wrappers/auth';
// 服务端菜单数据结构
type Menu = {
id: string;
name: string;
parentId: string;
path: string;
component: string;
query: string | null;
routeName: string;
type: 'D' | 'M' | 'F';
icon: string | null;
children: Menu[];
};
// 替代静态 wrappers 的方式
function withWrapper(Element: React.ReactNode) {
return <AuthWrapper>{Element}</AuthWrapper>;
}
let extraRoutes: Menu[] | undefined;
/**
* 在运行时动态修改路由,把服务端菜单合并进来
*/
export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = ({ routes }) => {
if (!extraRoutes) return;
const mergeRoutes = (menus: Menu[]): any =>
menus.map((menu) => ({
path: menu.path,
name: menu.routeName,
icon: menu.icon ? <IconRender icon={menu.icon} /> : undefined,
element: menu.component
? withWrapper(React.createElement(React.lazy(() => import(`@/pages${menu.component}`))))
: undefined,
children: menu.children?.length ? mergeRoutes(menu.children) : undefined,
}));
// 必须挂载到 / 布局路由下,否则菜单不会显示
const layoutRoute = routes.find((r: any) => r.path === '/');
if (layoutRoute && Array.isArray(layoutRoute.children)) {
layoutRoute.children.push(...mergeRoutes(extraRoutes));
}
};
/**
* 应用渲染前拉取服务端菜单
*/
export const render: RuntimeConfig['render'] = (oldRender) => {
getCurrentUserMenuTree().then((res) => {
extraRoutes = res.data.data as Menu[];
oldRender();
});
};
图标渲染(可选:src/components/IconRender.tsx)
import React from 'react';
// 建议不要整体引入,会增加打包体积,只引入会用到的图标
import { UserOutlined } as Icons from '@ant-design/icons';
const Icons = {
UserOutlined,
};
export default function IconRender({ icon }: { icon?: string | null }) {
if (!icon) return null;
const Cmp = (Icons as any)[icon];
return Cmp ? React.createElement(Cmp) : null;
}
常见坑点与建议
- 菜单必须合并到 / 布局路由:否则 ProLayout 不会渲染菜单。
- 组件路径要与 @/pages 下的实际文件一致:注意首个斜杠与大小写。
- 图标名称需与 @ant-design/icons 导出的组件名一致:如 SettingOutlined、UserOutlined。
- 懒加载与权限包装:React.lazy + 自定义 withWrapper 可替代静态 wrappers。
结语
感谢您的阅读!这是我首次在掘金分享技术经验,文章中可能有不够完善的地方,欢迎大家批评指正。希望我的经验能对您在实际项目中有所帮助,也期待与大家交流更多心得。