Closed dealdot closed 3 years ago
坐等大佬升到V5
坐等大佬发出代码
坐等大佬发出代码
占个位置
~~来了来了,让大家久等了 ^_^
1 我的router.js
export default [
{
path: '/',
//这里的路径是相对于pages目录下
component: '../layouts/BlankLayout',
routes: [
{
path: '/',
component: '../layouts/SecurityLayout',
routes: [
{
path: '/',
component: '../layouts/BasicLayout',
routes: [
//动态从服务器获取并动态加载
]
}
],
},
],
},
];
2 后端返回的路由格式
{
"path": "/",
"component": "ReportPublish/ReportPublish",
"name": "发布报表",
"redirect": "",
"meta": {
"icon": "smile"
},
},
{
"path": "/report/finance",
"component": "Layout",
"name": "财务部",
"redirect": "/report/finance/salary/id/1",
"meta": {
"icon": "heart"
},
"children": [
{
"path": "/report/finance/id/111",
"component": "ReportManage/ReportDetail",
"name": "工资组所有人可看报表",
"redirect": "",
"meta": {
"icon": "heart"
},
},
{
"path": "/report/finance/salary",
"component": "Layout",
"name": "工资组",
"redirect": "/report/finance/salary/id/1",
"meta": {
"icon": "heart"
},
"children": [
{
"path": "/report/finance/salary/id/1",
"component": "ReportManage/ReportDetail",
"name": "1月工资报表",
"redirect": "",
"meta": {
"icon": "heart"
},
},
{
"path": "/report/finance/salary/id/2",
"component": "ReportManage/ReportDetail",
"name": "2月工资报表",
"redirect": "",
"meta": {
"icon": "heart"
},
}
]
}
]
}
3 BasicLayout.jsx 代码
import ProLayout, { DefaultFooter } from '@ant-design/pro-layout';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Link, useIntl, connect, history } from 'umi';
import { SmileOutlined, HeartOutlined } from '@ant-design/icons';
import { Result, Button } from 'antd';
import RightContent from '@/components/GlobalHeader/RightContent';
import logo from '../assets/logo.45a21ca4.png';
const IconMap = {
smile: <SmileOutlined />,
heart: <HeartOutlined />,
};
const generateMenu = (item) => {
const menu = {
path: item.path,
name: item.name,
icon: IconMap[item.meta.icon],
};
return menu;
};
const generateRouter = (item) => {
const router = {
path: item.path,
exact: true,
};
if (item.redirect) {
router.redirect = item.redirect;
}
if (item.component !== 'Layout') {
// eslint-disable-next-line import/no-dynamic-require
router.component = require(`../pages/${item.component}.jsx`).default;
}
return router;
};
const getAsyncRouter = (asyncRouterMap) => {
let Routers = [];
if (asyncRouterMap && asyncRouterMap.length > 0) {
asyncRouterMap.forEach((item) => {
if (item.children) {
Routers.push(generateRouter(item));
Routers = Routers.concat(getAsyncRouter(item.children));
} else {
Routers.push(generateRouter(item));
}
});
}
return Routers;
};
const getRouterMenu = (asyncRouterMap) => {
let RouterMenus = [];
if (asyncRouterMap && asyncRouterMap.length > 0) {
asyncRouterMap.forEach((item) => {
const parent = generateMenu(item);
let children = [];
if (item.children) {
children = getRouterMenu(item.children);
}
if (children.length > 0) {
parent.children = children;
}
RouterMenus.push(parent);
});
}
return RouterMenus;
};
const defaultFooterDom = (
<DefaultFooter copyright={`${new Date().getFullYear()} xx科技(上海)有限公司`} links={[]} />
);
const BasicLayout = (props) => {
console.log(props)
const { asyncRouters, children, settings } = props;
const reactRouters = getAsyncRouter(asyncRouters);
useEffect(() => {
reactRouters.forEach((item) => {
props.route.routes.push(item);
});
// eslint-disable-next-line no-irregular-whitespace
if (reactRouters.length > 0) {
props.route.routes.push({ component: require('../pages/404.jsx').default });
}
// fix bug: 刷新浏览器对应的path不会加载component
history.push(window.location.pathname);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reactRouters.length]);
return (
<ProLayout
logo={logo}
{...props}
{...settings}
onMenuHeaderClick={() => history.push('/')}
menuItemRender={(menuItemProps, defaultDom) => {
return <Link to={menuItemProps.path}>{defaultDom}</Link>;
}}
breadcrumbRender={(routers = []) => [...routers]}
footerRender={() => defaultFooterDom}
route={{ routes: getRouterMenu(asyncRouters) }}
rightContentRender={() => <RightContent />}
>
{children}
</ProLayout>
);
};
export default connect(({ settings, asyncrouter }) => ({
asyncRouters: asyncrouter.asyncRouter,
settings,
}))(BasicLayout);
5 主要逻辑
1 要区分菜单与路由的区别
菜单 在ant design pro v4.5 只有两个属性 path 和 name 就可以确定菜单了
比如把以下数组传给proLayout组件的 route 属性就可以在侧边展示一个发布报表的菜单了,最多再加一个icon,则展示相应icon
[
{
"path": "/",
"name": "发布报表"
}
]
路由 信息 在ant design pro v4.5 只有两个属性 path 和 component 就可以确定相应path(路由)对应的component了, 最多再添加一个redirect属性
因此现在要做的事情就是用户登录成功后获取后端动态路由(菜单)信息,然后把 相应的 path 和name给到proLayout渲染出菜单,再把相应的path和component对应信息动态挂载到BasicLayout组件的props.route.routes上,即暴力修改这个routes就可以了,当然要注意一点,这样的话刷新浏览器对应的path不会加载到对应的component了,需要以下bug fix
// fix bug: 刷新浏览器对应的path不会加载component history.push(window.location.pathname);
另外我这个动态菜单不是在BasicLayout.jsx中dispatch请求的,而是把请求过的数据直接指定给ProLayout的route属性,这样个人感觉好一些,比较简洁,并且不用要多写个loading 来fix 渲染menu的问题
你的做法与 #4691 不同,原文通过在路由中不配置path并缓存 compoents,最后根据接口信息 unShift 路由信息。 所以你的做法存在如下问题:
@dealdot
最后建议将代码写一份到 codesanbox,利于浏览
格式化一下代码吧
安装你的代码实现了,路由权限管理,现在有个问题就是不知道为啥子菜单的icon不显示,大佬你的正常吗
动态路由的意义何在
安装你的代码实现了,路由权限管理,现在有个问题就是不知道为啥子菜单的icon不显示,大佬你的正常吗
and design 默认就不让显示子菜单icon
🧐 ant design pro v4 实现路由,菜单完全由后端返回,不在router.js中配置
估计大家有跟我一样从Vue转过来React使用 ant design pro v4 想实现动态路由,动态菜单的,即不在router.js中配置需要动态变化的路由和菜单信息, 由于ant design的封装比较厉害,实现起来不像Vue那样简单直接,看了很多issue, 结合自己的思路实现了一版,由于这一版的ant design pro V4 支持 patchRoutes,主要就是利用这一点,但我绕开了这个函数,暴力修改routes, 参考了 [#4691] (https://github.com/ant-design/ant-design-pro/issues/4691) @liangyongrui
© 版本信息
"name": "ant-design-pro", "version": "4.5.0", "umi": "^3.2.14", 浏览器环境 chrome 开发环境 windows10
💻 示例代码,稍后贴上 BasicLayout.jsx 代码,代码已贴出,见评论区