困扰我好多天的自定义菜单及路由终于完成了,首先虽然看似为一个功能,但在umi4中是两项配置。
先说说我浪费了大量时间使用的处理方式:因为曾经的项目是umi3的,所以我就想原样搬过来,结果发现,布局页BasicLayout的子children不见了!
如果这个子路由能正常显示,那引个ProLayout也就不用大费周章找其他方法。参考:https://blog.youkuaiyun.com/eisha2015/article/details/114831390
后来,我又找到了umi的patchRoutes,但发现怎么重写路由,菜单都没有变化。参考:https://blog.youkuaiyun.com/weixin_43294560/article/details/107447241
最后还是翻到ProComponents菜单的高级用法,才发现菜单直接在app.tsx里配置就行了,相当于这个文件就是当初的ProLayout。
之后还将子路由routes为[]时,把父结点也清除的数据处理,以及使用patchClientRoutes来修改默认路由跳转,而原本routes.ts配置的路转就可以删除了。
所以修改的代码如下:
type Router = {
name?: string,
path: string,
icon?: string | React.ReactNode,
routes?: Router[],
component?: string,
redirect?: string,
}
// routes为[],则删除结点
const deleteroutes = (arr: Router[]) => {
let childs = arr
for (let i = childs.length; i--; i > 0) {
const routes = childs[i].routes;
if (routes) {
if (routes.length) {
// 处理antd的图标,全部引入包的大小爆增,不如用iconfontUrl
// const icon = childs[i].icon;
// if (typeof icon === 'string') {
// childs[i].icon = React.createElement(Icons[icon]);
// }
deleteroutes(routes);
} else {
delete childs[i];
}
}
}
return JSON.parse(JSON.stringify(arr).replace(/null,/g, '').replace(/,null/g, '')) as Router[];
}
let extraRoutes: Router[];
export const patchClientRoutes = ({ routes }: any) => {
const r = extraRoutes;
const to = r![0].routes![0].routes![0].path;
routes.unshift({
path: '/',
element: <Navigate to={to || ''} replace />,
});
r.forEach(element => {
routes.unshift({
path: element.path,
element: <Navigate to={element.routes![0].routes![0].path} replace />,
});
});
};
// 示例方法
export function render(oldRender: any) {
setTimeout(() => {
extraRoutes = deleteroutes([{
name: '一级菜单A',
path: '/a',
routes: [
{
name: '二级菜单AA',
path: '/a/aa',
icon: 'icon1',
routes: [
],
},
{
name: '二级菜单BB',
path: '/a/bb',
icon: 'icon2',
routes: [
{
name: '三级菜单AAA',
path: '/a/aa/aaa',
component: './a/aa/aaa',
},
{
name: '三级菜单BBB',
path: '/a/aa/bbb',
component: './a/aa/bbb',
},
],
},
]
},
{
name: '一级菜单B',
path: '/b',
routes: [
],
}]);
oldRender();
}, 1000);
}
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
return {
menu: {
// 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
// params: {
// userId: initialState?.currentUser?.userid,
// },
request: async (params, defaultMenuData) => {
return extraRoutes;
}
},
.......
};
};
import Footer from '@/components/Footer';
import { UserOutlined } from '@ant-design/icons';
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
import { Navigate, RunTimeLayoutConfig } from '@umijs/max';
import { history, Link } from '@umijs/max';
import defaultSettings from '../config/defaultSettings';
import { AvatarDropdown, AvatarName } from './components/RightContent/AvatarDropdown';
import { errorConfig } from './requestErrorConfig';
import React from 'react';
import { initEnums } from './services/api/global';
import { CurrentUser, fetchUser } from './services/api/login';
import { jumpLogin } from './tools';
import { fetchMenuTree } from './services/api/baseSetting/advanced/menu';
import { stamp } from './services';
import { Notice } from './components/RightContent/Notice';
const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login';
type Router = {
name?: string,
path: string,
icon?: string | React.ReactNode,
children?: Router[],
component?: string,
redirect?: string,
}
// routes为[],则删除结点
const deleteroutes = (arr: Router[]) => {
let childs = arr
for (let i = childs.length; i--; i > 0) {
const routes = childs[i].children;
if (routes) {
if (routes.length) {
// 处理antd的图标,全部引入包直接增加多1MB,不如用iconfontUrl
// const icon = childs[i].icon;
// if (typeof icon === 'string') {
// childs[i].icon = React.createElement(Icons[icon]);
// }
deleteroutes(routes);
} else {
delete childs[i];
}
}
}
return JSON.parse(JSON.stringify(arr).replace(/\[null,/g, '[').replace(/,null\]/g, ']')) as Router[];
}
let extraRoutes: Router[];
export const patchClientRoutes = ({ routes }: any) => {
if (localStorage.getItem('admin-token') && extraRoutes) {
const to = extraRoutes![0].children![0].children![0].path;
routes.unshift({
path: '/',
element: <Navigate to={to} replace />,
});
extraRoutes.forEach(element => {
routes.unshift({
path: element.path,
element: <Navigate to={element.children![0].children![0].path} replace />,
});
});
}
};
export function render(oldRender: any) {
if (localStorage.getItem('admin-token')) {
fetchMenuTree().then((res) => {
if (res.data) {
const { menuTree } = res.data;
menuTree.forEach(m => {
m.children?.forEach(c => {
c.children?.forEach(l => {
l.path = `${l.path}?version=${stamp}`
})
})
})
extraRoutes = deleteroutes(res.data.menuTree);
oldRender();
} else {
if (res.code !== 952) {
jumpLogin();
} else {
extraRoutes = [{
path: '',
children: [{
path: '',
children: [{
path: '/access',
}]
}],
}];
}
oldRender();
}
});
} else {
oldRender();
}
}
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
export async function getInitialState(): Promise<{
settings?: Partial<LayoutSettings>;
currentUser?: CurrentUser;
loading?: boolean;
fetchUserInfo?: () => Promise<CurrentUser | undefined>;
}> {
const fetchUserInfo = async () => {
try {
const msg = await fetchUser();
if (msg.code === 952) {
if (window.location.pathname !== '/access') history.push('/access');
} else {
return msg.data.user;
}
} catch (error) {
console.error(error)
jumpLogin();
}
return undefined;
};
// 如果不是登录页面,执行
const { location } = history;
if (location.pathname !== loginPath) {
// 请求用户信息
const currentUser = await fetchUserInfo();
window.auths = currentUser?.authorityValues || [];
// 请求枚举值
await initEnums();
return {
fetchUserInfo,
currentUser,
settings: defaultSettings as Partial<LayoutSettings>,
};
}
return {
fetchUserInfo,
settings: defaultSettings as Partial<LayoutSettings>,
};
}
// ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout: RunTimeLayoutConfig = ({ initialState }) => {
return {
actionsRender: () => [<Notice key="notice" />],
avatarProps: {
icon: <UserOutlined />,
title: <AvatarName />,
style: { background: '#84B64A' },
render: (_, avatarChildren) => {
return <AvatarDropdown menu={true}>{avatarChildren}</AvatarDropdown>;
},
},
waterMarkProps: isDev ? {
content: initialState?.currentUser?.fullName,
} : undefined,
footerRender: () => <Footer />,
onPageChange: () => {
const { location } = history;
// 如果没有登录,重定向到 login
if (!initialState?.currentUser && location.pathname !== loginPath && location.pathname !== '/access') {
jumpLogin();
}
},
layoutBgImgList: [
{
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr',
bottom: 0,
left: 0,
width: '331px',
},
],
links: isDev ? [] : [],
menu: {
// 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
// params: {
// userId: initialState?.currentUser?.userid,
// },
request: async () => {
return extraRoutes;
}
},
menuHeaderRender: undefined,
// 自定义 403 页面
// unAccessible: <div>unAccessible</div>,
// 增加一个 loading 的状态
childrenRender: (children) => {
// if (initialState?.loading) return <PageLoading />;
return (
<>
{children}
</>
);
},
...initialState?.settings,
token: {
sider: {
colorBgMenuItemSelected: '#e2f9c7',
colorTextMenuSelected: '#84B64A',
colorTextMenuItemHover: '#84B64A',
colorTextMenuActive: '#84B64A',
},
header: {
colorBgHeader: '#292f33',
colorHeaderTitle: '#fff',
colorTextMenu: '#dfdfdf',
colorTextMenuSelected: '#84B64A !important',
colorTextMenuActive: '#84B64A',
colorBgMenuItemSelected: '#22272b',
},
}
};
};
/**
* @name request 配置,可以配置错误处理
* 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
* @doc https://umijs.org/docs/max/request#配置
*/
export const request = {
...errorConfig,
};