react + umi + antd pro动态路由
前言
前一章讲了登录后获取菜单,但有bug,本章是登陆后获取动态路由。
umiJS:3.x
antd pro:v4
本章是使用的页面,根据所需修改即可
正文
查了很多文档都是写在运行是配置的patchRoutes中的但我这有个问题就是登陆后默认页面无法显示,切换路由后就好了下面是代码
1.获取路由
// app.ts
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
export async function getInitialState(): Promise<{
routeInfo?: () => Promise<any | undefined>;
routeList?: any
}> {
// 省略其他代码可在获取菜单种查看,用法是一样的
// 获取路由
const routeInfo = async () => {
try {
// const msg = await fetchMenuData();
// 我是用的是假数据格式如下
const msg = [
{
path: '/user',
layout: false,
routes: [
{
path: '/user/login',
layout: false,
name: 'login',
component: 'user/Login',
},
{
path: '/user',
redirect: '/user/login',
},
{
name: 'register-result',
icon: 'smile',
path: '/user/register-result',
component: 'user/register-result',
},
{
name: 'register',
icon: 'smile',
path: '/user/register',
component: 'user/register',
},
{
component: '404',
},
],
},
{
name: "systemsetting",
path: '/systemsetting',
routes: [
{
path: '/systemsetting',
redirect: '/systemsetting/userandrole/usermanager',
},
{
name: "userandrole",
icon: 'smile',
path: '/systemsetting/userandrole',
routes: [
{
name: "usermanager",
icon: 'smile',
path: '/systemsetting/userandrole/usermanager',
component: 'systemsetting/userandrole/usermanager'
},
{
name: "rolemanager",
icon: 'smile',
path: '/systemsetting/userandrole/rolemanager',
component: 'systemsetting/userandrole/rolemanager'
},
]
},
{
name: "menuconfig",
icon: 'smile',
path: '/systemsetting/menuconfig',
routes: [
{
name: "menumanager",
icon: 'smile',
path: '/systemsetting/menuconfig/menumanager',
component: 'systemsetting/menuconfig/menumanager'
},
{
name: "menurole",
icon: 'smile',
path: '/systemsetting/menuconfig/menurole',
component: 'systemsetting/menuconfig/menurole'
},
]
}
]
},
{
path: '/',
redirect: '/systemsetting/userandrole/usermanager',
},
{
component: '404',
},
]
return msg;
} catch (error) {
history.push(loginPath);
}
return undefined;
};
// 如果不是登录页面,执行
if (history.location.pathname !== loginPath) {
const routeList = await routeInfo();
return {
routeInfo,
routeList
};
}
return {
routeInfo,
};
}
2.在layout这个配置用使用获取的路由
// app.ts
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }: any) => {
return {
menuDataRender: () => loopMenuItem(initialState?.menuList?.data),
rightContentRender: () => <RightContent />,
disableContentMargin: false,
footerRender: () => <Footer />,
onPageChange: () => {
const { location } = history;
// 如果没有登录,重定向到 login
if (!initialState?.currentUser && location.pathname !== loginPath) {
history.push(loginPath);
}
},
menuHeaderRender: undefined,
childrenRender: (children: any, props: any) => {
const { location, route } = props;
if (location.pathname !== loginPath && initialState?.routeList) {
// console.log(location); // 当前跳转的路由
// console.log(history.location.pathname); // 当前跳转的路由
// console.log(route); // 当前的路由内容
// console.log(initialState?.routeList); // 获取路由
patchRoutes(route, initialState?.routeList);
}
return (
<>
{children}
{!props.location?.pathname?.includes('/user/login') && (
<div hidden={true}>
<SettingDrawer
disableUrlParams
enableDarkTheme
settings={initialState?.settings}
onSettingChange={(settings) => {
setInitialState((preInitialState: any) => ({
...preInitialState,
settings,
}));
}}
/>
</div>
)}
</>
);
},
...initialState?.settings,
};
};
3.遍历获取的路由并替换原来的routes
// app.ts
export function patchRoutes(routes: any, extraRoutes?: any) {
console.log(routes);
console.log(extraRoutes);
if (extraRoutes) {
const a = parseRoute(extraRoutes).map((item: any) => {
return item
})
// 由于使用的是antd pro所以建议也替换一下children
routes.routes = a;
routes.children = a;
}
}
// 这里的parseRoute函数,我做的主要是简单的对extraRoutes进行遍历,将其中的自己需要的配置项替换routes,这部分要根据服务端给的数据结构进行操作
const parseRoute = (
routess: any,
) => {
return (routess || []).map((item: any) => {
if (item.routes && item.routes.length > 0) {
return {
path: item.path,
name: item.name,
routes: parseRoute(item.routes),
};
}
if (item.redirect) {
return {
exact: true,
path: item.path,
redirect: item.redirect,
}
}
return {
exact: true,
path: item.path,
name: item.name,
component: AsyncComponent(item.component)
// 在这里对比了静态路由和以上写法,如果直接按以上写component,从日志打印中可以看到引入的component是字符串,与静态路由中的component结构不一,需要异步加载组件,使用es6引入模块来引入组件,注意组件需要具体到哪个index,否则会引起编译报错
// component: `@/${item.component}/index.tsx` // 注意:到这一步 你只能动态加载路由,并不能进行切换
}
})
}
4.引入AsyncComponent
// util/AsyncComponent.ts
import { dynamic } from "umi";
const AsyncComponent = (componentPath: string) => {
return dynamic({
loader: async function () {
const {default: AsyncComp } = await import(`@/pages/${componentPath}/index.tsx`)
// const { default: AsyncComp } = await require(`@/pages/${componentPath}/index.tsx`)
return AsyncComp;
},
loading: () => {
return null;
}
})
}
export default AsyncComponent;
后来发现原来是在更改router的时候页面已经加载了所以没有找到对应的页面
修改后的全部带码在此
1.获取路由的方法和上一张的菜单一样
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
export async function getInitialState(): Promise<{
settings?: Partial<LayoutSettings>;
currentUser?: Models.CurrentUser;
loading?: boolean;
fetchUserInfo?: () => Promise<Models.CurrentUser | undefined>;
menuRouteInfo?: () => Promise<Models.FRestResult | undefined>;
menuList?: Models.FRestResult;
routeInfo?: () => Promise<any | undefined>;
routeList?: any
}> {
// 获取个人信息
const fetchUserInfo = async () => {
try {
const msg = await queryCurrentUser();
return msg.dataEntry;
} catch (error) {
history.push(loginPath);
}
return undefined;
};
// 获取菜单
const menuRouteInfo = async () => {
try {
const msg = await fetchMenuData();
return msg;
} catch (error) {
history.push(loginPath);
}
return undefined;
};
// 获取路由
const routeInfo = async () => {
try {
// const msg = await fetchMenuData();
const msg = [
{
path: '/user',
layout: false,
routes: [
{
path: '/user/login',
layout: false,
name: 'login',
component: 'user/Login',
},
{
path: '/user',
redirect: '/user/login',
},
{
name: 'register-result',
icon: 'smile',
path: '/user/register-result',
component: 'user/register-result',
},
{
name: 'register',
icon: 'smile',
path: '/user/register',
component: 'user/register',
},
{
component: '404',
},
],
},
{
name: "systemsetting",
path: '/systemsetting',
routes: [
{
path: '/systemsetting',
redirect: '/systemsetting/userandrole/usermanager',
},
{
name: "userandrole",
icon: 'smile',
path: '/systemsetting/userandrole',
routes: [
{
name: "usermanager",
icon: 'smile',
path: '/systemsetting/userandrole/usermanager',
component: 'systemsetting/userandrole/usermanager'
},
{
name: "rolemanager",
icon: 'smile',
path: '/systemsetting/userandrole/rolemanager',
component: 'systemsetting/userandrole/rolemanager'
},
]
},
{
name: "menuconfig",
icon: 'smile',
path: '/systemsetting/menuconfig',
routes: [
{
name: "menumanager",
icon: 'smile',
path: '/systemsetting/menuconfig/menumanager',
component: 'systemsetting/menuconfig/menumanager'
},
{
name: "menurole",
icon: 'smile',
path: '/systemsetting/menuconfig/menurole',
component: 'systemsetting/menuconfig/menurole'
},
]
}
]
},
{
path: '/',
redirect: '/systemsetting/userandrole/usermanager',
},
{
component: '404',
},
]
return msg;
} catch (error) {
history.push(loginPath);
}
return undefined;
};
// 如果不是登录页面,执行
if (history.location.pathname !== loginPath) {
const currentUser = await fetchUserInfo();
const menuList = await menuRouteInfo();
const routeList = await routeInfo();
return {
fetchUserInfo,
currentUser,
settings: defaultSettings,
menuRouteInfo,
menuList,
routeInfo,
routeList
};
}
return {
fetchUserInfo,
menuRouteInfo,
routeInfo,
settings: defaultSettings,
};
}
2.使用rootContainer方法运行时配置 (umijs.org),舍弃了patchRoutes
// app.ts
// export function patchRoutes(routes: any, extraRoutes?: any) {
// console.log(routes);
// console.log(extraRoutes);
// if (extraRoutes) {
// const a = parseRoute(extraRoutes).map((item: any) => {
// return item
// })
// routes.routes = a;
// routes.children = a;
// }
// }
// 页面创建
export function rootContainer(container: ReactNode) {
return createElement(RootNode, null, container);
}
3.在 RootNode 中监听数据的修改, 并且处理路由
// src/RootNode
import { Router, type IRoute, useModel } from "umi";
import { getMenuData, type MenuDataItem } from "@ant-design/pro-layout";
import { renderRoutes } from "@umijs/renderer-react";
import parseRoute from "@/util/AsyncComponent";
import { FC, useEffect, useState } from "react";
const RootNode: FC<any> = (props) => {
console.log(props);
const plugin = props.children.props.plugin; // 这里是获取 renderRoutes 中所需要的plugin参数
// // 获取保存的用户信息
const { initialState } = useModel("@@initialState");
// renderRoutes 中的routes参数
const [routes, setRoutes] = useState<IRoute[]>([]);
useEffect(() => {
// 获取后端的菜单
const routeinfo = initialState?.routeList;
console.log(routeinfo);
// 获取当前的菜单,这是配置文件中的routes生成的,
const routes = props.routes;
if (routeinfo) {
// 获取通过routeinfo生成的路由信息
const routeData = getMenuData(routeinfo).menuData as (any & MenuDataItem)[];;
// 替换原来的路由信息, 因为目前为止还没有匹配组件,所以必须要把对应的组件匹配上去,页面才能渲染
routes[1].routes = parseRoute(routeData);
routes[1].children = parseRoute(routeData);
console.log("------------");
console.log(routes);
console.log(routeData);
// 路由重定向,由于重定向我写在了获取路由的假数据种了,这里就用不到了
// if (routes[1].routes.length > 0) {
// routes[1].routes.push({
// path: "/",
// redirect: routes[1].routes[0].path,
// exact: true,
// });
// routes[1].children.push({
// path: "/",
// redirect: routes[1].routes[0].path,
// exact: true,
// });
// }
// 设置最终的路由
setRoutes(routes);
}
setRoutes(routes);
}, [initialState?.routeList])
return (
<>
<Router history={props.children.props.history}>
{renderRoutes({ routes, plugin })}
</Router>
</>
);
}
export default RootNode
4.创建一个处理路由菜单的方法 AsyncComponent.ts用来处理路由信息
// 修改后的
// src/util/AsyncComponent
import { dynamic } from "umi";
const AsyncComponent = (componentPath: string) => {
return dynamic({
loader: async function () {
// const { default: AsyncComp } = await import(`@/pages/${componentPath}/index.tsx`)
const { default: AsyncComp } = await require(`@/pages/${componentPath}/index.tsx`)
return AsyncComp;
},
loading: () => {
return null;
}
})
}
function parseRoute(routes: any[]): any[] {
return (routes || []).map((item: any) => {
if (item.routes && item.routes.length > 0) {
return {
path: item.path,
name: item.name,
routes: parseRoute(item.routes),
};
}
if (item.redirect) {
return {
exact: true,
path: item.path,
redirect: item.redirect,
}
}
return {
exact: true,
path: item.path,
name: item.name,
component: AsyncComponent(item.component)
// component: `@/${item.component}/index.tsx`
}
})
}
export default parseRoute
完美解决