公司项目需要配置动态菜单,即从后端获取权限菜单,然后显示在左边菜单栏里。而我们现在的菜单是从静态配置文件config/routes.js中获得的。我一开始试过用umi封装的方法patchRoutes和render这两个方法结合来改变routes.js中的routes:

routes.js中留下必要的几个路由:

export default [
  {
    path: '/user',
    component: '@/layouts/UserLayout',
    routes: [
      {
        name: 'login',
        path: '/user/login',
        component: '@/pages/user/login',
      },
    ],
  },
  {
    path: '/',
    component: '@/layouts/SecurityLayout',
    routes: [
      {
        path: '/',
        component: '@/layouts/BasicLayout',
        routes: [
            // 动态菜单
        ],
      },
    ],
  },
  {
    component: './404',
  },
]

  1. 在src目录下新建一个app.js文件

app.js

import { getRouterData } from '@/services/permission'

let routerData = []

export function patchRoutes({ routes }) {
  console.log("patchRoutes", routes)
  routerData.forEach(item => routes[1].routes[0].routes.push(item))
}

export function render(oldRoutes) {
  console.log("render")
  getRouterData().then(res => {
    if (res.code === 0) {
      routerData = res.data[0].children
    }
    // 必要操作,否则界面一直处于loading状态
    oldRoutes()
  })
}

但是这个方法有个问题,就是点击路由,界面不会显示相应的组件(有知道的解决方法的大神可以给大家参考下)。为此我也是找了好多文章,终于找到一个解决办法。就是在BasicLayout.jsx中直接修改route.这个方法我就没用到umi的patchRoutes方法了。

  • 首先routes.js中也是保留以上内容
  • 在BasicLayout.jsx中获取后端的菜单数据
import ProLayout, { DefaultFooter } from '@ant-design/pro-layout'
import React, { useEffect, useState } from 'react'
import RightContent from '@/components/GlobalHeader/RightContent'
import { Link, connect, useDispatch, useSelector, history } from 'umi'
import { Result, Button, Icon } from 'antd'

const BasicLayout = props => {
  const {
    children,
    settings,
    location = {
      pathname: '/',
    },
    routerData // 动态菜单
  } = props
  const dispatch = useDispatch()
 
  useEffect(() => {
    // 获取动态菜单
    if (dispatch) {
      dispatch({
        type: 'permission/queryRouterData'
      })
    }
  }, [])
  /**
   * init variables
   */
  const handleMenuCollapse = payload => {
    if (dispatch) {
      dispatch({
        type: 'global/changeLayoutCollapsed',
        payload,
      })
    }
  }

  return (
    <ProLayout
      logo={logo}
      {...props}
      {...settings}
      onCollapse={handleMenuCollapse}
      menuItemRender={(menuItemProps, defaultDom) => {
        if (menuItemProps.isUrl || !menuItemProps.path) {
          return defaultDom
        }
        return <Link to={menuItemProps.path}>{defaultDom}</Link>
      }}
      breadcrumbRender={(routers = []) => [
        {
          path: '/',
          breadcrumbName: '首页',
        },
        ...routers,
      ]}
      route={{ routes: getRouterMenu(asyncRouters) }}
      rightContentRender={() => <RightContent />}
    >
      {children}
    </ProLayout>
  )
}

export default connect(({ global, settings, permission }) => ({
  collapsed: global.collapsed,
  settings,
  routerData: permission.routerData
}))(BasicLayout)
  • 得到后端的菜单数据routerData后需要对数据格式处理一下
// 后端给的格式
[{
    component: "RouteView",
    hidden: false,
    hideChildrenInMenu: true,
    icon: "dashboard",
    id: "3502511398862003",
    name: "dashboard",
    parentId: "0",
    path: "/dashboard",
    redirect: "/dashboard/workplace",
    title: "驾驶舱",
    children: []
}]

所以我们可以封装一个方法来对菜单进行格式化,我是专门在config下新建了一个router.js文件,来存放这几个方法了

router.js

// 存放组件
const constantRouterComponents = {
  WorkbenchDd: require('@/pages/index.jsx').default
}

import { SmileOutlined, HeartOutlined } from '@ant-design/icons'
// 菜单的icon
const IconMap = {
  dashboard: <SmileOutlined />,
  heart: <HeartOutlined />,
}

export const generateMenu = (item) => {
  const menu = {
    // 路由地址 动态拼接生成如 /dashboard/workplace
    path: item.path || '',
    // 路由名称,建议唯一
    name: item.title || '',
    // 控制路由是否显示在 sidebar
    hideInMenu: item.hidden,
    // 该路由对应页面的 组件
    component: constantRouterComponents[item.component] || null,
    // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
    icon: item.icon && IconMap[item.icon],
    
  }
  // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
  if (menu.path.indexOf('http') === -1) {
    menu.path = menu.path.replace('//', '/')
  }
  // 重定向
  item.redirect && (menu.redirect = item.redirect)
  // 隐藏子菜单
  item.hideChildrenInMenu && (menu.hideChildrenInMenu = item.hideChildrenInMenu)
  // 菜单跳转到外部地址时使用
  item.target && (menu.meta.target = item.target)
  // 嵌套外部链接url
  item.url && (menu.meta.url = item.url)
  return menu
}

export const generateRouter = (item) => {
  const router = {
    path: item.path,
    name: item.name,
    exact: true,
  };
  if (item.redirect) {
    router.redirect = item.redirect
  }
  router.component = constantRouterComponents[item.component]
  return router
}
export const getAsyncRouter = (asyncRouterMap) => {
  let Routers = []
  if (asyncRouterMap && asyncRouterMap.length > 0) {
    asyncRouterMap.forEach((item) => {
      if (item.children) {
        Routers.push(generateRouter(item))
        Routers = Routers.concat(getAsyncRouter(item.children))
      } else {
        Routers.push(generateRouter(item))
      }
    })
  }
  return Routers
}

export const getRouterMenu = (asyncRouterMap) => {
  let RouterMenus = []
  if (asyncRouterMap && asyncRouterMap.length > 0) {
    asyncRouterMap.forEach((item) => {
      const parent = generateMenu(item)
      let children = []
      if (item.children) {
        children = getRouterMenu(item.children)
      }
      if (children.length > 0) {
        parent.children = children
      }
      RouterMenus.push(parent)
    })
  }
  return RouterMenus
}
  • 然后在BasicLayout.jsx中引入这里的方法,对菜单处理后直接修改props.route.routes的值即可
import { getRouterMenu, getAsyncRouter } from '../../config/router'

let asyncRouters = []
if (routerData.length > 0) {
   asyncRouters = routerData[0].children // 取菜单的值
}

const reactRouters = getAsyncRouter(asyncRouters)
  useEffect(() => {
    reactRouters.forEach((item) => {
      props.route.routes.push(item) // 直接修改props.route.routes值
    })
    history.push(window.location.pathname) // 避免界面不刷新到相应的组件
  }, [reactRouters.length])

这里还需要注意一个小问题:就是在ProLayout组件中props和route的顺序:

<ProLayout
      logo={logo}
      {...props} // 旧值放在前面
      {...settings}
      onCollapse={handleMenuCollapse}
      menuItemRender={(menuItemProps, defaultDom) => {
        if (menuItemProps.isUrl || !menuItemProps.path) {
          return defaultDom
        }
        return <Link to={menuItemProps.path}>{defaultDom}</Link>
      }}
      breadcrumbRender={(routers = []) => [
        {
          path: '/',
          breadcrumbName: '首页',
        },
        ...routers,
      ]}
      footerRender={() => defaultFooterDom}
      route={{ routes: getRouterMenu(asyncRouters) }} // 新值放后面
      rightContentRender={() => <RightContent />}
    >
      {children}
</ProLayout>

至此,我们的动态菜单就能在左边菜单栏中显示出来,并且没有组件不显示的问题。

本文的方法参考:https://github.com/ant-design/ant-design-pro/issues/8121

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐