基于 vue-antd-admin 开发项目后的几点感想(二):异步路由和菜单
前言
上一篇 讲了使用 vue-antd-admin 开发中台项目后的一些感想,并没有提到其动态路由的配置,主要是因为当时觉得其文档写的比较详细了,而且当时并没有遇到什么坑,没想到今天接了一个需求,需要开发一套消息系统,其中有一个显示全部消息的页面,我按照官方文档中的方法配置了页面路由后,发现和我想要的效果不同,其直接全屏显示了,而我需要的是显示在侧边栏菜单的右侧内容部分,于是仔细看了文档和源码后,花了亿点点时间解决了这个需求,并且产出了本篇文章。
路由与菜单
我长话短说,基本的使用一定要看官方文档,概述起来就是说,vue-antd-admin 项目也是完全依赖 vue-router 并使用其配置规则,其提供了同步、异步两种路由方案:
同步的非常简单,就是我们熟悉的vue-router 的配置, 示例代码可以看这个 src/router/config.js就不讲了;
关键是异步,首先需要本地配置路由的map 文件,也就是把所有的完整路由拆分成单个的路由配置进行注册, 文件在这里 /router/async/router.map.js
然后根据接口返回的路由配置与map 文件结合生成最终的路由,接口返回的路由格式如下
[{
router: 'root', //匹配 router.map.js 中注册名 registerName = root 的路由
children: [ //root 路由的子路由配置
{
router: 'dashboard', //匹配 router.map.js 中注册名 registerName = dashboard 的路由
children: ['workplace', 'analysis'], //dashboard 路由的子路由配置,依次匹配 registerName 为 workplace 和 analysis 的路由
},
{
router: 'form', //匹配 router.map.js 中注册名 registerName = form 的路由
children: [ //form 路由的子路由配置
'basicForm', //匹配 router.map.js 中注册名 registerName = basicForm 的路由
'stepForm', //匹配 router.map.js 中注册名 registerName = stepForm 的路由
{
router: 'advanceForm', //匹配 router.map.js 中注册名 registerName = advanceForm 的路由
path: 'advance' //重写 advanceForm 路由的 path 属性
}
]
},
{
router: 'basicForm', //匹配 router.map.js 中注册名 registerName = basicForm 的路由
name: '验权表单', //重写 basicForm 路由的 name 属性
icon: 'file-excel', //重写 basicForm 路由的 icon 属性
authority: 'form' //重写 basicForm 路由的 authority 属性
}
]
}]
重点来了,异步路由是不能覆盖所有的路由的,比如404、login 之类的页面是不需要配置到接口权限里的,所有我们需要一个基础路由配置文件,里面注册的路由都可以merge 到最终的路由配置中。
文件地址在这里 /router/async/config.async.js
官方文档中基础路由的配置使用方法如下:
const routesConfig = [
'login', //匹配 router.map.js 中注册的 registerName = login 的路由
'root', //匹配 router.map.js 中注册的 registerName = root 的路由
{
router: 'exp404', //匹配 router.map.js 中注册的 registerName = exp404 的路由
path: '*', //重写 exp404 路由的 path 属性
name: '404' //重写 exp404 路由的 name 属性
},
{
router: 'exp403', //匹配 router.map.js 中注册的 registerName = exp403 的路由
path: '/403', //重写 exp403 路由的 path 属性
name: '403' //重写 exp403 路由的 name 属性
}
]
如果按照此方法配置,最终的路由文件大概是这样子
可以看到 消息中心是和跟路由平级的,此时的效果是这样的
看来我需要把 notification 路由给添加到首页下的children 属性里,其才会在侧边栏生成新的菜单,我修改一下配置方法试试
然后发现,/首页下的children 中并没有这个路由,也就是说其并没有别merge 到一起;问题出在 路由merge 函数里,这个函数在这里 src/utils/routerUtil.js loadRoutes 中的
// loadRoutes 函数中的这行是关键
const finalRoutes = mergeRoutes(basicOptions.routes, routes)
那么看下它是怎么定义的
/**
* 合并路由
* @param target {Route[]} 本地基础路由
* @param source {Route[]} 接口异步路由
* @returns {Route[]}
*/
function mergeRoutes(target, source) {
const routesMap = {}
target.forEach(item => routesMap[item.path] = item)
source.forEach(item => {
routesMap[item.path] = item
})
return Object.values(routesMap)
}
可以看到,其是一个浅拷贝函数,同名属性会被覆盖掉,额看来我需要改造一下写一个深度拷贝函数,突然我发现 mergeRoutes 函数下紧接着就有一个定义好的 深度合并路由函数!
/**
* 深度合并路由
* @param target {Route[]}
* @param source {Route[]}
* @returns {Route[]}
*/
function deepMergeRoutes(target, source) {
// 映射路由数组
const mapRoutes = routes => {
const routesMap = {}
routes.forEach(item => {
routesMap[item.path] = {
...item,
children: item.children ? mapRoutes(item.children) : undefined
}
})
return routesMap
}
console.log(target, source)
const tarMap = mapRoutes(target)
const srcMap = mapRoutes(source)
// 合并路由
const merge = _merge(srcMap, tarMap)
// 转换为 routes 数组
const parseRoutesMap = routesMap => {
return Object.values(routesMap).map(item => {
if (item.children) {
item.children = parseRoutesMap(item.children)
} else {
delete item.children
}
return item
})
}
return parseRoutesMap(merge)
}
可以看到其是用深度优先的递归方式来进行merge 操作的; 现在我把 mergeRoutes 替换为 deepMergeRoutes 尝试下发现真的成功了!
虽然在官方文档中并没有看到这个函数的身影,但是作者显然考虑到了这种需求,这样,异步路由的方案使用起来就更方便了。
对了,官方还在 routes 的元数据属性 meta 中注入了三个属性 icon、invisible 和 page,其中 invisible 将控制其是否显示在侧边栏菜单中,这个很有用。
总结
今天通过一个具体的需求探究,介绍了 vue-antd-admin 的动态路由方案,本来我还觉得它不太好用,今天无意中发现了 deepMergeRoutes 函数,通过它本地基础路由的配置方便很多,现在看来这个异步动态路由方案还是挺不错的,点赞。
转载自:https://juejin.cn/post/7067104311301373989