解析路由表,生成结构化数据
如果我们需要创建后台管理系统的动态菜单(根据权限显示对应的选项),需要获取路由表的数据信息
想要获取路由表数据,那么有两种方式:
- router.options.routes:初始路由列表(无法获取到新增的路由)
- router.getRoutes():获取所有 路由记录 的完整列表
因为方式1的特性有缺陷,因此我们使用方式2获取到路由表
使用router.getRoutes()获取到的路由表信息如下:
[ { path:/user/info/:id, name:userInfo, meta:{ title:userInfo }, children:[ ] }, { path:/user/manage, meta:{ title:userManage, icon:personnel-manage }, children:[ ] }, { path:/user/role, meta:{ title:roleList, icon:role }, children:[ ] }, { path:/user/permission, meta:{ title:permissionList, icon:permission }, children:[ ] }, { path:/user/import, name:import, meta:{ title:excelImport }, children:[ ] }, { path:/login, meta:{ }, children:[ ] }, { path:/profile, name:profile, meta:{ title:profile, icon:el-icon-user }, children:[ ] }, { path:/404, name:404, meta:{ }, children:[ ] }, { path:/401, name:401, meta:{ }, children:[ ] }, { path:/user, redirect:/user/manage, meta:{ title:user, icon:personnel }, children:[ { path:/user/manage, meta:{ title:userManage, icon:personnel-manage } }, { path:/user/role, meta:{ title:roleList, icon:role } }, { path:/user/permission, meta:{ title:permissionList, icon:permission } }, { path:/user/info/:id, name:userInfo, meta:{ title:userInfo } }, { path:/user/import, name:import, meta:{ title:excelImport } } ] } ] 由上,可以看出,它是一个一级路由与二级路由位于数组同一层级的数据结构,这不符合我们的需求,我们需要的是一个一级路由内包括二级路由的数据结构,类似于树结构的数据结构,它需要符合如下特点:
- 满足该条件 meta && meta.title && meta.icon
- 一级路由二级路由应该是树形嵌套
[ { path:/profile, name:profile, meta:{ title:profile, icon:el-icon-user }, children:[ ] }, { path:/user, redirect:/user/manage, meta:{ title:user, icon:personnel }, props:{ default:false }, children:[ { path:/user/manage, meta:{ title:userManage, icon:personnel-manage }, children:[ ] }, { path:/user/role, meta:{ title:roleList, icon:role }, children:[ ] }, { path:/user/permission, meta:{ title:permissionList, icon:permission }, children:[ ] } ], instances:{ }, leaveGuards:{ }, updateGuards:{ }, enterCallbacks:{ }, components:{ default:{ __scopeId:data-v-13877386, __hmrId:13877386, __file:src/layout/index.vue } } }, { path:/article, redirect:/article/ranking, meta:{ title:article, icon:article }, props:{ default:false }, children:[ { path:/article/ranking, meta:{ title:articleRanking, icon:article-ranking }, children:[ ] }, { path:/article/create, meta:{ title:articleCreate, icon:article-create }, children:[ ] } ], instances:{ }, leaveGuards:{ }, updateGuards:{ }, enterCallbacks:{ }, components:{ default:{ __scopeId:data-v-13877386, __hmrId:13877386, __file:src/layout/index.vue } } } ] 因此,我们需要对它进行解析
utils/route.js
import path from 'path' /** * 返回所有子路由 */ const getChildrenRoutes = routes => { const result = [] routes.forEach(route => { if (route.children && route.children.length > 0) { result.push(...route.children) } }) return result } /** * 处理脱离层级的路由:某个一级路由为其他子路由,则剔除该一级路由,保留路由层级 * @param {*} routes router.getRoutes() */ export const filterRouters = routes => { const childrenRoutes = getChildrenRoutes(routes) return routes.filter(route => { return !childrenRoutes.find(childrenRoute => { return childrenRoute.path === route.path }) }) } /** * 判断数据是否为空值 */ function isNull(data) { if (!data) return true if (JSON.stringify(data) === '{}') return true if (JSON.stringify(data) === '[]') return true return false } /** * 根据 routes 数据,返回对应 menu 规则数组 */ export function generateMenus(routes, basePath = '') { const result = [] // 遍历路由表 routes.forEach(item => { // 不存在 children && 不存在 meta 直接 return if (isNull(item.meta) && isNull(item.children)) return // 存在 children 不存在 meta,进入迭代 if (isNull(item.meta) && !isNull(item.children)) { result.push(...generateMenus(item.children)) return } // 合并 path 作为跳转路径 const routePath = path.resolve(basePath, item.path) // 路由分离之后,存在同名父路由的情况,需要单独处理 let route = result.find(item => item.path === routePath) if (!route) { route = { ...item, path: routePath, children: [] } // icon 与 title 必须全部存在 if (route.meta.icon && route.meta.title) { // meta 存在生成 route 对象,放入 arr result.push(route) } } // 存在 children 进入迭代到children if (item.children) { route.children.push(...generateMenus(item.children, route.path)) } }) return result } 生成符合动态menu要求的结构化数据:
import { computed } from 'vue' import { useRouter } from 'vue-router' import { filterRouters, generateMenus } from '@/utils/route' const router = useRouter() // 获取符合动态menu菜单需要的结构化路由表数据 const routes = computed(() => { const filterRoutes = filterRouters(router.getRoutes()) return generateMenus(filterRoutes) })