【前端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>
  );
}
相关推荐
brzhang4 小时前
我操,终于有人把 AI 大佬们 PUA 程序员的套路给讲明白了!
前端·后端·架构
止观止5 小时前
React虚拟DOM的进化之路
前端·react.js·前端框架·reactjs·react
goms5 小时前
前端项目集成lint-staged
前端·vue·lint-staged
谢尔登5 小时前
【React Natve】NetworkError 和 TouchableOpacity 组件
前端·react.js·前端框架
Lin Hsüeh-ch'in5 小时前
如何彻底禁用 Chrome 自动更新
前端·chrome
augenstern4167 小时前
HTML面试题
前端·html
张可7 小时前
一个KMP/CMP项目的组织结构和集成方式
android·前端·kotlin
G等你下课7 小时前
React 路由懒加载入门:提升首屏性能的第一步
前端·react.js·前端框架
蓝婷儿8 小时前
每天一个前端小知识 Day 27 - WebGL / WebGPU 数据可视化引擎设计与实践
前端·信息可视化·webgl
然我8 小时前
面试官:如何判断元素是否出现过?我:三种哈希方法任你选
前端·javascript·算法