基于vue-router的cms后台权限控制

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
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const staticRoutes = [
{ path: '/login', component: () => import('@/views/Login.vue') },
// 注意:404路由必须放在最后,先留空,动态路由添加完再补
]

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
// router/index.js(补充导航守卫)
import { useUserStore } from '@/store/user' // 假设用 Pinia 存储用户信息

// 标记是否已添加动态路由(避免重复添加)
let isDynamicRoutesAdded = false

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

// 1. 无 token 且访问非登录页 → 跳登录
if (!token && to.path !== '/login') {
return next('/login')
}

// 2. 有 token 且访问登录页 → 跳首页
if (token && to.path === '/login') {
return next('/')
}

// 3. 有 token 且未添加动态路由 → 动态添加
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) // 添加顶级路由
// 如果是嵌套路由,可指定父路由 name:router.addRoute('Layout', route)
})

// 最后添加 404 路由(必须放在动态路由之后!)
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 会重置,动态路由会丢失。解决方案:

  • 将权限菜单数据存储在 localStoragePinia/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
// router/index.js(Vue 2 + Vue Router 3)
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 }
}))

// 方法1:使用 addRoutes(简单但已废弃)
router.addRoutes(dynamicRoutes)
// 补充 404 路由
router.addRoutes([{ path: '*', redirect: '/404' }])

// 方法2:直接修改 matcher(推荐,更稳定)
// const newRoutes = [...staticRoutes, ...dynamicRoutes, { path: '*', redirect: '/404' }]
// router.matcher = new VueRouter({ routes: newRoutes }).matcher
}

// 导航守卫(类似 Vue 3 逻辑)
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

三、常见避坑指南

  1. 404 路由位置:必须在所有动态路由添加完成后再添加,否则刷新页面时动态路由会被 404 拦截。
  2. 组件动态导入:使用 () => import('@/views/xxx.vue') 时,路径需是静态字符串前缀(如 @/views/),不能全是变量(Webpack/Vite 需要静态分析)。
  3. 重复添加路由:务必通过标记位(如 isDynamicRoutesAdded)避免重复添加,否则会导致路由冲突。
  4. 刷新丢失路由:通过 localStoragePiniaVuex 持久化权限菜单数据,应用初始化时重新添加。

四、最佳实践总结

场景 推荐方案
Vue 3 + Vue Router 4 router.addRoute() + 导航守卫 + Pinia 持久化
Vue 2 + Vue Router 3 router.matcher 重写(稳定)或 router.addRoutes()(简单)
权限控制 后端返回菜单数据 → 前端转换为路由配置 → 动态添加

基于vue-router的cms后台权限控制
https://cszy.top/20251029-基于vue-router的cms后台权限控制/
作者
csorz
发布于
2025年10月29日
许可协议