前言
上一篇 讲了使用 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.jsloadRoutes 中的
// 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 函数,通过它本地基础路由的配置方便很多,现在看来这个异步动态路由方案还是挺不错的,点赞。