【前端umi-动态路由菜单】Umi Max项目的动态路由菜单方案-bysking

一、核心代码:

参考:umijs.org/docs/api/ru...

js 复制代码
let extraRoutes;


export function patchClientRoutes({ routes }) {
  // 根据 extraRoutes 对 routes 做一些修改
  patch(routes, extraRoutes);
}


export function render(oldRender) {
  fetch('/api/routes')
    .then((res) => res.json())
    .then((res) => {
      extraRoutes = res.routes;
      oldRender();
    });
}

二、app.tsx 中获取数据,修改路由

ts 复制代码
type typeMenuItem = {
  id: string;
  path: string;
  name: string;
  component: string; // 路由对应的组件路径,比如:'./TableList/index',
  hideInMenu: boolean; // 是否是隐藏路由,菜单不展示在页面上,但是通过路径能找到,比如详情页面
  routes?: typeMenuItem[];
  children?: typeMenuItem[];
};

type typeUmiRoute = {
    id: '@@/global-layout'; // proLayout的顶层组件
    path: '/';
    isLayout: true;
    element: React.ReactNode
    routes: {
        id: string;
        parentId: '@@/global-layout' | string; // 第一层是'@@/global-layout',其余是各自的id
        name: string;
        path: string; // '/welcome'
        element: React.ReactNode;
        hideInMenu: boolean;
    }[]
}

let apiMenus = []; // 接口菜单

export const getInitialState = async () => {

    // 从接口api获取你的路由菜单配置,然后格式化成`typeMenuItem`
    let menus = await reqMenusDataFromApi<Promise<typeMenuItem[]>>(); // 通过接口获取你的路由
    apiMenus = menus;
    
    return {
        menus
    }
}

const handleApiMenus = (menus: typeMenuItem[], parentId: number | string) => {
  // 扁平化全部路由菜单
  return menus.flatMap((menuItem) => {
    let RouteComponent: React.ComponentType | null = null;

    // 根据每个菜单配置的路由组件去加懒载组件
    if (menuItem.component) {
      RouteComponent = React.lazy(
        () =>
          new Promise((resolve) => {
            import(`@/pages/${menuItem.component}`)
              .then((module) => resolve(module))
              .catch(() => resolve(import(`@/pages/Error/404` as string))); // 处理查找兜底,需要对应路径下有404兜底组件
          }),
      );
    }

    // 递归处理子路由
    if (menuItem.children) {
      return [
        {
          ...menuItem,
          parentId,
          children: [
            {
              path: menuItem.path,
              element: <Navigate to={menuItem.children[0].path} replace />,
            },
            ...handleApiMenus(menuItem.children, menuItem.id),
          ],
        },
      ];
    } else {
      return [
        {
          ...menuItem,
          parentId,
          // 路由组件替换
          element: (
            <React.Suspense fallback={<></>}>
              {RouteComponent ? <RouteComponent /> : null}
            </React.Suspense>
          ),
        },
      ];
    }
  });
};

export function patchClientRoutes({ routes }: {routes: typeUmiRoute}) {
  const routerIndex = routes.findIndex((item: RouteItem) => item.path === '/');
  const parentId = routes[routerIndex].id;
  
  routes[routerIndex]['routes'].push(
    ...handleApiMenus(menus, parentId),
  );
}

// 拦截路由渲染,比如做一些权限校验
export const render = async (oldRender: () => void) => {
 fetch('/api/auth').then(auth => {
    if (auth.isLogin) { oldRender() }
    else {
      location.href = '/login';
      oldRender()
    }
  });
}

三、layouts/index.tsx proLayout组件中使用

3.1 基本路由:config/routes.ts

js 复制代码
export default [
  {
    name: '欢迎页',
    path: '/welcome',
    component: './Welcome',
  },
  {
    path: '/',
    redirect: '/welcome',
  },
  {
    path: '/404',
    name: '迷路了',
    component: './Error/404',
    hideInMenu: true,
  },
  {
    path: '/403',
    name: '缺少授权',
    component: './Error/403',
    hideInMenu: true,
  },
  {
    // 404 必须这么写
    path: '/*',
    component: './Error/404',
  },
];

3.2 layouts/index.tsx

ts 复制代码
import routes from '@/../config/routes';
import { ProConfigProvider, ProLayout } from '@ant-design/pro-components';
import { history, Outlet, useLocation, useModel } from '@umijs/max';
import { ConfigProvider } from 'antd';
import { useMemo } from 'react';

export default function Layout() {
  const { initialState } = useModel('@@initialState');
  const location = useLocation();
    
    // 临时写死测试从接口获取的路由数据,实际上要从app.tsx中的getInitialState返回的全局数据获取:initialState.menus
    let testMenus = [
      {
        id: 1,
        key: 1,
        parentId: null,
        name: '动态页面-1',
        path: '/list-page?coode=123',
        component: 'Demo/index', // umi项目的src/pages/Demo/index.tsx需要存在
        icon: '#',
        visible: true,
        children: null,
      },
      {
        id: 2,
        key: 2,
        parentId: null,
        name: '动态页面-2',
        path: '/detail-page?code=456',
        component: 'Foo/index', // umi项目的src/pages/Foo/index.tsx需要存在
        icon: '#',
        visible: true,
        children: null,
      },
    ];


  // 动态获取路由菜单
  const routesList = useMemo(() => {
    return testMenus?.length
      ? routes.concat(testMenus)
      : routes;
  }, [routes, testMenus]);

  return (
    <div id="my-app-pro-layout">
      <ProConfigProvider hashed={false}>
        <ConfigProvider>
          <ProLayout
            prefixCls="my-app-prefix"
            location={location}
            route={{ path: '/', routes: routesList }}
            avatarProps={{
              src: 'https://aaa/bbb.jpg',
              size: 'small',
              title: initialState?.user?.nickname || '测试用户名',
            }}
            menuItemRender={(item, dom) => {
              // 菜单自定义渲染
              return (
                <div
                  onClick={() => {
                    history.push(item.path || '/');
                  }}
                >
                  {dom}
                </div>
              );
            }}
            footerRender={() => null}
            pure={!!window.__POWERED_BY_QIANKUN__ ? true : false}
            iconfontUrl={'//at.alicdn.com/font_test123.js'}
          >
            <Outlet />
          </ProLayout>
        </ConfigProvider>
      </ProConfigProvider>
    </div>
  );
}
相关推荐
张拭心2 小时前
Cursor 又偷偷更新,这个功能太实用:Visual Editor for Cursor Browser
前端·人工智能
I'm Jie2 小时前
深入了解 Vue 3 组件间通信机制
前端·javascript·vue.js
用户90443816324603 小时前
90%前端都踩过的JS内存黑洞:从《你不知道的JavaScript》解锁底层逻辑与避坑指南
前端·javascript·面试
CodeCraft Studio4 小时前
文档开发组件Aspose 25.12全新发布:多模块更新,继续强化文档、图像与演示处理能力
前端·.net·ppt·aspose·文档转换·word文档开发·文档开发api
PPPPickup4 小时前
easychat项目复盘---获取联系人列表,联系人详细,删除拉黑联系人
java·前端·javascript
老前端的功夫4 小时前
前端高可靠架构:医疗级Web应用的实时通信设计与实践
前端·javascript·vue.js·ubuntu·架构·前端框架
前端大卫5 小时前
【重磅福利】学生认证可免费领取 Gemini 3 Pro 一年
前端·人工智能
孜燃5 小时前
Flutter APP跳转Flutter APP 携带参数
前端·flutter
脾气有点小暴5 小时前
前端页面跳转的核心区别与实战指南
开发语言·前端·javascript
lxh01136 小时前
最长递增子序列
前端·数据结构·算法