1 动态菜单
技术思路:配置路由,用户登录后根据用户信息获取后台菜单。
2 动态路由+动态菜单
技术思路: 使用umijs的运行时修改路由 patchRoutes({ routes }) UMIJS 参考文档 ,react umi 没有守护路由的功能 直接在 app.tsx 的 layout 下的 childrenRender 添加守护路由 实现登录后的菜单路由增加。登录后的菜单由登录接口 加个menu参数获取。 默认路由+动态登录路由+动态菜单
具体操作
1. 动态菜单:
文件:/src/app.tsx
找到 layout 插入 menu
menu: { locale: false, params: { userId: initialState?.currentUser?.userid,//引起菜单请求的参数 }, request: async (params, defaultMenuData) => { const { data } = await getAuthRoutes(); return loopMenuItem(data); }, },
完全版:
/** * 映射菜单对应的图标 * */const loopMenuItem = (menus: MenuDataItem[]): MenuDataItem[] => menus.map(({ icon, routes, ...item }) => ({ ...item, icon: icon && <Icon component={icons[icon]} />, routes: routes && loopMenuItem(routes), }));// ProLayout 支持的api https://procomponents.ant.design/components/layoutexport const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => { const onCollapse = (collapsed: boolean): void => { setInitialState({ ...initialState, collapsed }).then(); }; const onSettings = (settings: any): void => { setInitialState({ ...initialState, settings }).then(); }; return { // 自定义头内容的方法 我把 自定义侧边栏收缩按钮位置 方在这里 headerContentRender: () => ( <HeaderContent collapse={initialState?.collapsed} onCollapse={onCollapse} /> ), rightContentRender: () => <RightContent onSettings={onSettings} />, disableContentMargin: false, waterMarkProps: { content: initialState?.currentUser?.name, }, // 去掉系统自带 collapsedButtonRender: false, // 指定配置collapsed collapsed: initialState?.collapsed, footerRender: () => <Footer />, onPageChange: () => { const { location } = history; // 如果没有登录,重定向到 login if (!initialState?.currentUser && location.pathname !== loginPath) { history.push(loginPath); } }, menu: { locale: false, params: { userId: initialState?.currentUser?.userid,//引起菜单请求的参数 }, request: async (params, defaultMenuData) => { const { data } = await getAuthRoutes(); return loopMenuItem(data); }, }, links: isDev ? [ <Link key="openapi" to="/umi/plugin/openapi" target="_blank"> <LinkOutlined /> <span>OpenAPI 文档</span> </Link>, <Link to="/~docs" key="docs"> <BookOutlined /> <span>业务组件文档</span> </Link>, ] : [], menuHeaderRender: undefined, // 自定义 403 页面 // unAccessible: <div>unAccessible</div>, // 增加一个 loading 的状态 childrenRender: (children, props) => { // if (initialState?.loading) return <PageLoading />; const { location, route } = props; return ( <> {children} {/* {!props.location?.pathname?.includes('/login') && ( <SettingDrawer disableUrlParams enableDarkTheme settings={initialState?.settings} onSettingChange={(settings) => { setInitialState((preInitialState) => ({ ...preInitialState, settings, })); }} /> )} */} </> ); }, ...initialState?.settings, };};
2. 动态路由+动态菜单技术思路:
使用umijs的运行时修改路由 patchRoutes({ routes }) UMIJS 参考文档 ,react umi 没有守护路由的功能 直接在 app.tsx 的 layout 下的 childrenRender 添加守护路由 实现登录后的菜单路由增加。登录后的菜单由登录接口 加个menu参数获取。 默认路由+动态登录路由
文件:config/config.ts
// https://umijs.org/config/import { defineConfig } from 'umi';import { join } from 'path';import defaultSettings from './defaultSettings';import proxy from './proxy';import routes from './routes';const { REACT_APP_ENV } = process.env;export default defineConfig({ hash: true, antd: {}, dva: { hmr: true, }, layout: { // https://umijs.org/zh-CN/plugins/plugin-layout locale: false, siderWidth: 208, ...defaultSettings, }, // https://umijs.org/zh-CN/plugins/plugin-locale locale: { // default zh-CN default: 'zh-CN', antd: true, // default true, when it is true, will use `navigator.language` overwrite default baseNavigator: true, }, dynamicImport: { loading: '@ant-design/pro-layout/es/PageLoading', }, targets: { ie: 11, }, // umi routes: https://umijs.org/docs/routing routes, access: {}, // Theme for antd: https://ant.design/docs/react/customize-theme-cn theme: { // 如果不想要 configProvide 动态设置主题需要把这个设置为 default // 只有设置为 variable, 才能使用 configProvide 动态设置主色调 // https://ant.design/docs/react/customize-theme-variable-cn 'root-entry-name': 'variable', }, // esbuild is father build tools // https://umijs.org/plugins/plugin-esbuild esbuild: {}, title: false, ignoreMomentLocale: true, proxy: proxy[REACT_APP_ENV || 'dev'], manifest: { basePath: '/', }, // Fast Refresh 热更新 fastRefresh: {}, openAPI: [ { requestLibPath: "import { request } from 'umi'", // 或者使用在线的版本 // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json" schemaPath: join(__dirname, 'oneapi.json'), mock: false, }, { requestLibPath: "import { request } from 'umi'", schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json', projectName: 'swagger', }, ], nodeModulesTransform: { type: 'none', }, mfsu: {}, webpack5: {}, exportStatic: {},});
文件:config/routes.ts
import type { MenuDataItem } from '@ant-design/pro-layout';export default [ { path: '/user', layout: false, routes: [ { path: '/user/login', layout: false, name: 'login', component: '@/pages/modules/user/Login', }, { path: '/user/openlogin', layout: false, name: 'login', component: '@/pages/modules/user/openlogin', }, { path: '/user', redirect: '@/pages/modules/user/login', }, { name: 'register-result', icon: 'smile', path: '/user/register-result', component: '@/pages/modules/user/register-result', }, { name: 'register', icon: 'smile', path: '/user/register', component: '@/pages/modules/user/register', } ] }, { path: '/', component: './layouts/commonLayout', flatMenu: true, routes: [ // { // path: '/welcome', // name: '工作台', // component: '@/pages/modules/welcome', // } ] }, { component: '404', },];
app.tsx
let extraRoutes;export function patchRoutes({ routes }) { if (extraRoutes) { // extraRoutes.forEach((element: any) => { routes.forEach((route: any) => { if (route.path == "/") { route.routes = mergeRoutes(extraRoutes); } }); // }); } console.log("--------------------路由-------------------------", routes);}// export function render(oldRender) {// getMenu().then((res: any) => {// if (res.code == 200) {// extraRoutes = res.result;// oldRender();// } else {// history.push('/login');// oldRender()// }// });// }// /**// * 映射菜单对应的图标// * */// const loopMenuItem = (menus: MenuDataItem[]): MenuDataItem[] =>// menus.map(({ icon, routes, ...item }) => ({// ...item,// icon: icon && <Icon component={icons[icon]} />,// routes: routes && loopMenuItem(routes),// })// );/** * @see https://umijs.org/zh-CN/plugins/plugin-initial-state * */export async function getInitialState(): Promise<{ settings?: Partial<LayoutSettings>; currentUser?: API.CurrentUser; loading?: boolean; collapsed?: boolean; // menuData?: MenuDataItem[] | undefined; fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;}> { const fetchUserInfo = async () => { try { const msg = await queryCurrentUser(); return msg.data; } catch (error) { // 跳转到指定路由 history.push(loginPath); } return undefined; }; // 如果不是登录页面,执行 if (history.location.pathname !== loginPath) { const currentUser = await fetchUserInfo(); return { fetchUserInfo, currentUser, settings: defaultSettings, }; } return { fetchUserInfo, settings: defaultSettings, };}// ProLayout 支持的api https://procomponents.ant.design/components/layoutexport const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => { const onCollapse = (collapsed: boolean): void => { setInitialState({ ...initialState, collapsed }).then(); }; const onSettings = (settings: any): void => { setInitialState({ ...initialState, settings }).then(); }; return { // 自定义头内容的方法 我把 自定义侧边栏收缩按钮位置 方在这里 headerContentRender: () => ( <HeaderContent collapse={initialState?.collapsed} onCollapse={onCollapse} /> ), rightContentRender: () => <RightContent onSettings={onSettings} />, disableContentMargin: false, waterMarkProps: { content: initialState?.currentUser?.name, }, // 去掉系统自带 collapsedButtonRender: false, // 指定配置collapsed collapsed: initialState?.collapsed, footerRender: () => <Footer />, onPageChange: () => { const { location } = history; // 如果没有登录,重定向到 login if (!initialState?.currentUser && location.pathname !== loginPath) { history.push(loginPath); } }, // menuDataRender: () => { return fixMenuItemIcon(initialState.menuData) }, menu: { locale: false, params: { userId: initialState?.currentUser?.userid,//引起菜单请求的参数 }, request: async (params, defaultMenuData) => { // const msg = await getMenu(); return fixMenuItemIcon(initialState?.currentUser?.menu); }, }, links: isDev ? [ <Link key="openapi" to="/umi/plugin/openapi" target="_blank"> <LinkOutlined /> <span>OpenAPI 文档</span> </Link>, <Link to="/~docs" key="docs"> <BookOutlined /> <span>业务组件文档</span> </Link>, ] : [], menuHeaderRender: undefined, // 自定义 403 页面 // unAccessible: <div>unAccessible</div>, // 增加一个 loading 的状态 childrenRender: (children, props) => { // if (initialState?.loading) return <PageLoading />; const { location, route } = props; if (history.location.pathname !== loginPath && initialState?.currentUser?.menu) { // 路由守卫 // const msg = await getMenu(); extraRoutes = initialState?.currentUser?.menu; console.log("--------------------路由01-------------------------", extraRoutes); patchRoutes(route); } return ( <> {children} {/* {!props.location?.pathname?.includes('/login') && ( <SettingDrawer disableUrlParams enableDarkTheme settings={initialState?.settings} onSettingChange={(settings) => { setInitialState((preInitialState) => ({ ...preInitialState, settings, })); }} /> )} */} </> ); }, ...initialState?.settings, };};
src/utils/fixMenuItemIcon.tsx
import React from 'react';import type { MenuDataItem } from '@ant-design/pro-layout';import * as allIcons from '@ant-design/icons';const fixMenuItemIcon = (menus: MenuDataItem[], iconType = 'Outlined'): MenuDataItem[] => { menus.forEach((item) => { const { icon, routes } = item if (typeof icon === 'string' && icon != null) { const fixIconName = icon.slice(0, 1).toLocaleUpperCase() + icon.slice(1) + iconType // eslint-disable-next-line no-param-reassign item.icon = React.createElement(allIcons[fixIconName] || allIcons[icon]) } // eslint-disable-next-line no-param-reassign,@typescript-eslint/no-unused-expressions routes && routes.length > 0 ? item.routes = fixMenuItemIcon(routes) : null }); return menus};export default fixMenuItemIcon;
src/utils/roles.tsx
// import { getUserPerm } from '@/services/menu';// import router from 'umi/router';import React from 'react';import Icon from '@ant-design/icons';import * as icons from '@ant-design/icons';import { dynamic, RunTimeLayoutConfig } from 'umi';import { SmileOutlined, HeartOutlined } from '@ant-design/icons'export function mergeRoutes(routes) { if (!Array.isArray(routes)) return []; return routes.map(route => { let module = route.component; if (route.component) { route.component = (component => { if (typeof component === 'object') { return component; } // 封装一个异步组件 return dynamic({ loader: () => import(`../pages/${module}/index.tsx`) }); })(route.component); } if (route.routes) { route.routes = mergeRoutes(route.routes); } return route; });}
注意: 登陆子模块要 符合 /src/pages/ 模块名称 /index.tsx 的组合