In the previous project process, when encountering multi-level routing, it was always necessary to nest a redundant parent component in the outer layer. Only a single router-view was placed in the parent component. I didn’t know how to deal with it before. I found a better solution in vben. When vben handles multi-level routing with more than two layers, it will expand it and convert it into a second-level route, so that unnecessary parent routing templates can be avoided. The following is the process I reproduced with reference to vben.
1. Transform the RouteRecordRaw type to meet our requirements
import { RouteMeta, RouteRecordRaw } from 'vue-router'
export interface AppRouteMeta extends RouteMeta {
title: string
icon?: string
sn?: number
hideMenu?: boolean
permission?: string[]
}
export interface AppRouteRecordRaw
extends Omit<RouteRecordRaw, 'meta' | 'children' | 'name'> {
meta: AppRouteMeta
children?: AppRouteRecordRaw[]
name: string
}
2. Define the basic route
import { AppRouteRecordRaw } from './type'
import { LAYOUT, PAGE_NOT_FOUND_NAME } from './constants'
export const PAGE_ROOT_ROUTE: AppRouteRecordRaw = {
path: '/',
name: 'Root',
redirect: '/auth',
component: LAYOUT,
meta: {
title: 'Root'
}
}
export const PAGE_LOGIN_ROUTE: AppRouteRecordRaw = {
path: '/login',
name: 'Login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录'
}
}
export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
path: '/:path(.*)*',
name: PAGE_NOT_FOUND_NAME,
component: LAYOUT,
meta: {
title: 'ErrorPage',
hideMenu: true
},
children: [
{
path: '/:path(.*)*',
name: PAGE_NOT_FOUND_NAME,
component: () => import('@/views/404/index.vue'),
meta: {
title: 'ErrorPage',
hideMenu: true
}
}
]
}
export default [PAGE_ROOT_ROUTE, PAGE_LOGIN_ROUTE, PAGE_NOT_FOUND_ROUTE]
3. Define the dynamic routing module
Here I take a four-layer routing module as an example. When I looked at the source code, I found that vben would set the parent component of multi-level routing as a Promise, as follows. I don’t see what the purpose of this is. Try to remove this line and find that it has no effect on routing, so I commented it out.
export const PARENT_LAYOUT_NAME = 'ParentLayout'
export const getParentLayout = (_name?: string) => {
return () =>
new Promise((resolve) => {
resolve({
name: PARENT_LAYOUT_NAME
})
})
}
import { AppRouteRecordRaw } from '../type'
import { getParentLayout, LAYOUT } from '../constants'
const dict: AppRouteRecordRaw = {
path: '/dict',
name: 'Dict',
redirect: '/dict/dict-type/test1',
component: LAYOUT,
meta: {
title: '字典管理',
icon: 'classify'
},
children: [
{
path: '/dict/dict-type',
name: 'DictType',
redirect: '/dict/dict-type/test1',
// component: getParentLayout('DictType'),
meta: {
title: '字典类型'
},
children: [
{
path: '/dict/dict-type/test1',
name: 'Test1',
// component: getParentLayout('Test1'),
redirect: '/dict/dict-type/test1/test11',
meta: {
title: '测试1'
},
children: [
{
path: '/dict/dict-type/test1/test11',
name: 'Test11',
component: () => import('@/views/dict/dictType/index.vue'),
meta: {
title: '测试11'
}
},
{
path: '/dict/dict-type/test1/test12',
name: 'Test12',
component: () => import('@/views/dict/dictType/index1.vue'),
meta: {
title: '测试12'
}
}
]
},
{
path: '/dict/dict-type/test2',
name: 'Test2',
component: () => import('@/views/dict/dictType/index1.vue'),
meta: {
title: '测试2'
}
}
]
},
{
path: '/dict/dict-item',
name: 'DictItem',
component: () => import('@/views/dict/dictItem/index.vue'),
meta: {
title: '字典元素'
}
}
]
}
export default dict
4. Some initialization routing operations
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import setupRouterGuard from './guard'
import { AppRouteRecordRaw } from './type'
import basicRoutes from './basic'
/**
* 导入所有私有路由模块
*/
const routeModuleList: AppRouteRecordRaw[] = []
const modules = require.context('./modules', true, /\.ts$/)
modules.keys().forEach((key) => {
const mod = modules(key).default
routeModuleList.push(mod)
})
const asyncRoutes = [...routeModuleList]
/**
* 获取初始基础路由名
*/
const WHITE_NAME_LIST: string[] = []
const getRouteNames = (basicRoutes: AppRouteRecordRaw[]) => {
basicRoutes.forEach((item) => {
WHITE_NAME_LIST.push(item.name as string)
getRouteNames(item.children || [])
})
}
getRouteNames(basicRoutes)
/**
* 路由初始化
*/
const router = createRouter({
history: createWebHashHistory(process.env.BASE_URL),
routes: [...basicRoutes] as RouteRecordRaw[]
})
/**
* 添加路由守卫
*/
setupRouterGuard(router)
/**
* 重置路由
*/
const resetRouter = () => {
// 扁平化的路由
const routes = router.getRoutes() as unknown as AppRouteRecordRaw[]
routes.forEach((route) => {
const { name } = route
if (name && !WHITE_NAME_LIST.includes(name as string)) {
router.hasRoute(name) && router.removeRoute(name)
}
})
}
export { WHITE_NAME_LIST, asyncRoutes, resetRouter }
export default router
5. Process private routes as secondary routes
import { cloneDeep } from 'lodash'
import {
createRouter,
createWebHashHistory,
Router,
RouteRecordRaw
} from 'vue-router'
import { AppRouteRecordRaw } from '../type'
/**
*
* @param route 判断是否是多级路由
* @returns
*/
const isMultipleRoute = (route: AppRouteRecordRaw) => {
if (
!route ||
!Reflect.has(route, 'children') ||
route.children!.length <= 0
) {
return false
}
const children = route.children!
let flag = false
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (child.children && child.children.length > 0) {
flag = true
break
}
}
return flag
}
/**
*
* @param route 需要处理的多级路由
*/
const promoteRouteLevel = (route: AppRouteRecordRaw) => {
let router: Nullable<Router> = createRouter({
routes: [route as RouteRecordRaw],
history: createWebHashHistory()
})
const routes = router.getRoutes() as unknown as AppRouteRecordRaw[]
addToChildren(routes, route.children || [], route)
router = null
// 子路由的children不需要了
route.children = (route.children || []).map((item) => {
item.children = undefined
return item
})
}
/**
*
* @param routes router.getRoutes()得到的扁平路由
* @param children 需要处理的多级路由的children
* @param routeModule 待处理的多级路由的引用
*/
const addToChildren = (
routes: AppRouteRecordRaw[],
children: AppRouteRecordRaw[],
routeModule: AppRouteRecordRaw
) => {
for (let i = 0; i < children.length; i++) {
const child = children[i]
const route = routes.find((item) => item.name === child.name)
if (!route) continue
routeModule.children = routeModule.children || []
if (!routeModule.children.find((item) => item.name === route.name)) {
routeModule.children.push(route)
}
if (child.children && child.children.length > 0) {
addToChildren(routes, child.children, routeModule)
}
}
}
/**
* 将多级路由转为二级路由
* @param routes 需要展开的路由组
* @returns
*/
export const flatMultiLevelRoutes = (routes: AppRouteRecordRaw[]) => {
const modules = cloneDeep(routes)
for (let i = 0; i < modules.length; i++) {
const mod = modules[i]
if (!isMultipleRoute(mod)) {
continue
}
promoteRouteLevel(mod)
}
return modules
}
6. Result test
import { asyncRoutes } from '@/router/index'
let result = asyncRoutes
result = flatMultiLevelRoutes(result)
console.log(result)
The obtained routing results are as follows, and all sub-routes have been expanded into secondary routes.