react + umi + antd pro动态路由

1,284 阅读2分钟

react + umi + antd pro动态路由

前言

前一章讲了登录后获取菜单,但有bug,本章是登陆后获取动态路由。

umiJS:3.x

antd pro:v4

本章是使用的页面,根据所需修改即可

屏幕截图 2023-08-10 160729.png

正文

查了很多文档都是写在运行是配置的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

完美解决