【前端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>
  );
}
相关推荐
旧识君20 分钟前
移动端1px终极解决方案:Sass混合宏工程化实践
开发语言·前端·javascript·前端框架·less·sass·scss
吃没吃1 小时前
vue2.6-源码学习-Vue 核心入口文件分析
前端
Carlos_sam1 小时前
Openlayers:海量图形渲染之图片渲染
前端·javascript
XH2761 小时前
Android Retrofit用法详解
前端
鸭梨大大大1 小时前
Spring Web MVC入门
前端·spring·mvc
吃没吃1 小时前
vue2.6-源码学习-Vue 初始化流程分析 (src/core/instance/init.js)
前端
XH2761 小时前
Android Room用法详解
前端
木木黄木木2 小时前
css炫酷的3D水波纹文字效果实现详解
前端·css·3d
郁大锤2 小时前
Flask与 FastAPI 对比:哪个更适合你的 Web 开发?
前端·flask·fastapi
HelloRevit3 小时前
React DndKit 实现类似slack 类别、频道拖动调整位置功能
前端·javascript·react.js