Vue3动态路由核心实现

1. 动态导入组件

使用 import.meta.glob 批量导入组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// utils/loadComponent.js
const modules = import.meta.glob('../views/modules/**/index.vue')

export function loadComponent(componentPath) {
// Layout组件
if (componentPath === 'Layout') {
return () => import('@/views/layout/index.vue')
}

// 动态组件
const component = modules[`../views/modules${componentPath}/index.vue`]
if (component) {
return component
}

// 404组件
return () => import('@/views/404/index.vue')
}

2. 路由数据转换

将后端返回的菜单数据转换为路由配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// utils/routeHelper.js
import { loadComponent } from './loadComponent'

export function transformRoutes(menuList) {
const routes = []

menuList.forEach(menu => {
const route = {
path: menu.path,
name: menu.name,
component: loadComponent(menu.component),
meta: {
title: menu.title,
icon: menu.icon,
hidden: menu.hidden,
keepAlive: menu.keepAlive
}
}

// 处理子路由
if (menu.children && menu.children.length) {
route.children = transformRoutes(menu.children)
}

routes.push(route)
})

return routes
}

3. 权限Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// store/permission.js
import { defineStore } from 'pinia'
import { getMenuList } from '@/api/menu'
import { transformRoutes } from '@/utils/routeHelper'

export const usePermissionStore = defineStore('permission', {
state: () => ({
routes: [],
menuList: []
}),

actions: {
async generateRoutes() {
try {
// 获取菜单数据
const { data } = await getMenuList()
this.menuList = data

// 转换为路由配置
const dynamicRoutes = transformRoutes(data)
this.routes = dynamicRoutes

return dynamicRoutes
} catch (error) {
console.error('生成路由失败:', error)
return []
}
}
}
})

4. 动态添加路由

在路由守卫中添加动态路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/store/user'
import { usePermissionStore } from '@/store/permission'

const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
component: () => import('@/views/login/index.vue')
}
]
})

router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
const permissionStore = usePermissionStore()

if (userStore.token) {
if (permissionStore.routes.length === 0) {
try {
// 生成动态路由
const dynamicRoutes = await permissionStore.generateRoutes()

// 添加到router实例
dynamicRoutes.forEach(route => {
router.addRoute(route)
})

// 重新跳转,确保路由已加载
next({ ...to, replace: true })
} catch (error) {
userStore.logout()
next('/login')
}
} else {
next()
}
} else {
next('/login')
}
})

export default router

5. 后端数据格式

后端返回的菜单数据格式示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[
{
"id": 1,
"path": "/system",
"name": "System",
"component": "Layout",
"title": "系统管理",
"icon": "system",
"hidden": false,
"children": [
{
"id": 11,
"path": "user",
"name": "SystemUser",
"component": "/system/user",
"title": "用户管理",
"icon": "user",
"hidden": false
}
]
}
]

6. 文件目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
src/
├── views/
│ ├── layout/
│ │ └── index.vue
│ └── modules/
│ ├── system/
│ │ ├── user/
│ │ │ └── index.vue
│ │ └── role/
│ │ └── index.vue
│ └── content/
│ └── article/
│ └── index.vue

核心要点

  1. import.meta.glob: 批量导入组件,避免手动维护组件映射
  2. router.addRoute(): 动态添加路由到router实例
  3. next({ ...to, replace: true }): 确保动态路由添加后重新导航
  4. 组件路径映射: 后端返回的component字段对应views目录结构