Vue 中动态添加路由的核心需求通常是权限控制(根据用户角色/权限动态加载可访问的路由),实现方式在 Vue Router 3(Vue 2) 和 Vue Router 4(Vue 3) 中有较大差异。以下是分版本的完整实现方案:
项目汇总
code/all/cms-v3 vue3 vue-router4
一、Vue Router 4(Vue 3):主流方案
Vue Router 4 提供了更简洁的 router.addRoute() API,是动态路由的首选实现方式。
1. 核心 API
| API |
作用 |
router.addRoute(routeConfig) |
动态添加单个顶级路由 |
router.addRoute(parentName, routeConfig) |
动态添加子路由(挂载到指定父路由下) |
router.removeRoute(name) |
删除指定名称的路由 |
router.hasRoute(name) |
检查路由是否存在 |
2. 完整实现步骤(权限路由场景)
(1)基础路由配置
先定义无需权限的静态路由(如登录页、404页):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { createRouter, createWebHistory } from 'vue-router'
const staticRoutes = [ { path: '/login', component: () => import('@/views/Login.vue') }, ]
const router = createRouter({ history: createWebHistory(), routes: staticRoutes })
export default router
|
(2)在导航守卫中动态添加路由
结合 beforeEach 导航守卫,在用户登录后获取权限菜单,动态生成并添加路由:
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import { useUserStore } from '@/store/user'
let isDynamicRoutesAdded = false
router.beforeEach(async (to, from, next) => { const userStore = useUserStore() const token = userStore.token
if (!token && to.path !== '/login') { return next('/login') }
if (token && to.path === '/login') { return next('/') }
if (token && !isDynamicRoutesAdded) { try { const menuList = await userStore.fetchMenuList() const dynamicRoutes = menuList.map(menu => ({ path: menu.path, name: menu.name, component: () => import(`@/views${menu.component}.vue`), meta: { title: menu.title, icon: menu.icon }, children: menu.children?.map(child => ({ path: child.path, name: child.name, component: () => import(`@/views${child.component}.vue`), meta: { title: child.title } })) }))
dynamicRoutes.forEach(route => { router.addRoute(route) })
router.addRoute({ path: '/:pathMatch(.*)*', redirect: '/404' })
isDynamicRoutesAdded = true next({ ...to, replace: true }) } catch (error) { console.error('动态路由添加失败:', error) userStore.logout() next('/login') } } else { next() } })
|
(3)处理刷新页面路由丢失
刷新页面后,Vue Router 会重置,动态路由会丢失。解决方案:
- 将权限菜单数据存储在
localStorage 或 Pinia/Vuex 中(持久化);
- 在应用初始化(
main.js 或导航守卫)时,重新从存储中读取菜单数据并调用 addRoute。
二、Vue Router 3(Vue 2):兼容方案
Vue Router 3 没有 addRoute,需通过 router.addRoutes()(已废弃但可用)或直接修改 router.options.routes 实现。
1. 核心 API
| API |
作用 |
注意事项 |
router.addRoutes(routesArray) |
批量添加动态路由 |
Vue Router 3.5+ 已废弃,推荐用 router.matcher |
router.options.routes |
直接修改路由配置数组 |
修改后需重新创建 matcher 才能生效 |
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| import Vue from 'vue' import VueRouter from 'vue-router'
Vue.use(VueRouter)
const staticRoutes = [ { path: '/login', component: () => import('@/views/Login.vue') } ]
const router = new VueRouter({ routes: staticRoutes })
export function addDynamicRoutes(menuList) { const dynamicRoutes = menuList.map(menu => ({ path: menu.path, name: menu.name, component: () => import(`@/views${menu.component}.vue`), meta: { title: menu.title } }))
router.addRoutes(dynamicRoutes) router.addRoutes([{ path: '*', redirect: '/404' }])
}
router.beforeEach(async (to, from, next) => { const token = localStorage.getItem('token') const isDynamicAdded = localStorage.getItem('dynamicRoutesAdded')
if (!token && to.path !== '/login') return next('/login') if (token && to.path === '/login') return next('/')
if (token && !isDynamicAdded) { const menuList = await fetchMenuList() addDynamicRoutes(menuList) localStorage.setItem('dynamicRoutesAdded', 'true') next({ ...to, replace: true }) } else { next() } })
export default router
|
三、常见避坑指南
- 404 路由位置:必须在所有动态路由添加完成后再添加,否则刷新页面时动态路由会被 404 拦截。
- 组件动态导入:使用
() => import('@/views/xxx.vue') 时,路径需是静态字符串前缀(如 @/views/),不能全是变量(Webpack/Vite 需要静态分析)。
- 重复添加路由:务必通过标记位(如
isDynamicRoutesAdded)避免重复添加,否则会导致路由冲突。
- 刷新丢失路由:通过
localStorage、Pinia 或 Vuex 持久化权限菜单数据,应用初始化时重新添加。
四、最佳实践总结
| 场景 |
推荐方案 |
| Vue 3 + Vue Router 4 |
router.addRoute() + 导航守卫 + Pinia 持久化 |
| Vue 2 + Vue Router 3 |
router.matcher 重写(稳定)或 router.addRoutes()(简单) |
| 权限控制 |
后端返回菜单数据 → 前端转换为路由配置 → 动态添加 |