Vue 3 路由守卫与权限控制完全指南
一、路由守卫架构设计
1. 路由守卫类型
// types/router.ts
export interface RouteMeta {
title?: string
auth?: boolean
roles?: string[]
permissions?: string[]
cache?: boolean
}
export interface UserState {
token?: string
roles: string[]
permissions: string[]
}
export type NavigationGuard = (to: RouteLocation, from: RouteLocation, next: NavigationGuardNext) => Promise<void> | void
2. 基础路由配置
// router/routes.ts
import { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: {
title: '登录',
auth: false
}
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: {
title: '控制台',
auth: true,
roles: ['admin', 'user'],
permissions: ['dashboard:view']
}
}
]
export default routes
二、全局路由守卫实现
1. 权限控制器
// utils/permission.ts
export class PermissionController {
private user: UserState
constructor(userState: UserState) {
this.user = userState
}
// 检查角色权限
hasRole(roles: string[]): boolean {
if (!roles || roles.length === 0) return true
return this.user.roles.some(role => roles.includes(role))
}
// 检查功能权限
hasPermission(permissions: string[]): boolean {
if (!permissions || permissions.length === 0) return true
return this.user.permissions.some(
permission => permissions.includes(permission)
)
}
// 检查路由访问权限
canAccess(routeMeta: RouteMeta): boolean {
if (!routeMeta.auth) return true
// 检查是否登录
if (!this.user.token) return false
// 检查角色和权限
return this.hasRole(routeMeta.roles || []) &&
this.hasPermission(routeMeta.permissions || [])
}
}
2. 全局守卫配置
// router/guards.ts
import { Router, RouteLocationNormalized } from 'vue-router'
import { PermissionController } from '@/utils/permission'
import { useUserStore } from '@/stores/user'
export function setupGuards(router: Router) {
// 标题守卫
const titleGuard = (to: RouteLocationNormalized) => {
const title = to.meta.title
if (title) {
document.title = `${title} - 系统名称`
}
}
// 权限守卫
const permissionGuard: NavigationGuard = async (to, from, next) => {
const userStore = useUserStore()
const permissionCtrl = new PermissionController(userStore.state)
// 检查路由是否需要认证
if (!to.meta.auth) {
next()
return
}
// 检查用户是否登录
if (!userStore.isLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
return
}
// 检查访问权限
if (!permissionCtrl.canAccess(to.meta)) {
next('/403')
return
}
next()
}
// 进度条守卫
const loadingGuard = () => {
NProgress.start()
return () => {
NProgress.done()
}
}
// 注册全局守卫
router.beforeEach(async (to, from, next) => {
const cleanup = loadingGuard()
try {
await permissionGuard(to, from, next)
titleGuard(to)
} finally {
cleanup()
}
})
}
三、动态路由控制
1. 动态路由生成
// router/dynamic.ts
export class DynamicRouteBuilder {
private router: Router
private permissionCtrl: PermissionController
constructor(router: Router, permissionCtrl: PermissionController) {
this.router = router
this.permissionCtrl = permissionCtrl
}
// 过滤路由配置
filterRoutes(routes: RouteRecordRaw[]): RouteRecordRaw[] {
return routes.filter(route => {
// 检查当前路由权限
if (!this.permissionCtrl.canAccess(route.meta || {})) {
return false
}
// 递归处理子路由
if (route.children) {
route.children = this.filterRoutes(route.children)
}
return true
})
}
// 添加动态路由
async addRoutes(routes: RouteRecordRaw[]) {
const accessibleRoutes = this.filterRoutes(routes)
for (const route of accessibleRoutes) {
this.router.addRoute(route)
}
return accessibleRoutes
}
}
2. 路由初始化
// router/index.ts
export async function initRouter() {
const router = createRouter({
history: createWebHistory(),
routes: constantRoutes // 基础路由
})
const userStore = useUserStore()
// 初始化权限控制器
const permissionCtrl = new PermissionController(userStore.state)
// 初始化动态路由构建器
const routeBuilder = new DynamicRouteBuilder(router, permissionCtrl)
// 设置路由守卫
setupGuards(router)
// 加载动态路由
if (userStore.isLoggedIn) {
// 可以从后端获取路由配置
const asyncRoutes = await fetchUserRoutes()
await routeBuilder.addRoutes(asyncRoutes)
}
return router
}
四、组件级权限控制
1. 权限指令
// directives/permission.ts
import { DirectiveBinding } from 'vue'
import { PermissionController } from '@/utils/permission'
export const permission = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding
const userStore = useUserStore()
const permissionCtrl = new PermissionController(userStore.state)
if (!permissionCtrl.hasPermission(value)) {
el.parentNode?.removeChild(el)
}
}
}
// 使用示例
<button v-permission="['user:add']">添加用户</button>
2. 权限组件
<!-- components/Permission.vue -->
<template>
<template v-if="hasPermission">
<slot />
</template>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useUserStore } from '@/stores/user'
const props = defineProps<{
roles?: string[]
permissions?: string[]
}>()
const userStore = useUserStore()
const permissionCtrl = new PermissionController(userStore.state)
const hasPermission = computed(() => {
return permissionCtrl.hasRole(props.roles || []) &&
permissionCtrl.hasPermission(props.permissions || [])
})
</script>
<!-- 使用示例 -->
<Permission :roles="['admin']" :permissions="['user:edit']">
<button>编辑用户</button>
</Permission>
五、菜单权限控制
1. 菜单配置
// config/menu.ts
export interface MenuItem {
key: string
title: string
icon?: string
path?: string
children?: MenuItem[]
roles?: string[]
permissions?: string[]
}
export const menuConfig: MenuItem[] = [
{
key: 'dashboard',
title: '控制台',
icon: 'dashboard',
path: '/dashboard',
permissions: ['dashboard:view']
},
{
key: 'user',
title: '用户管理',
icon: 'user',
roles: ['admin'],
children: [
{
key: 'user-list',
title: '用户列表',
path: '/user/list',
permissions: ['user:list']
}
]
}
]
2. 菜单生成器
// components/Menu/generator.ts
export class MenuGenerator {
private permissionCtrl: PermissionController
constructor(permissionCtrl: PermissionController) {
this.permissionCtrl = permissionCtrl
}
// 过滤菜单项
filterMenuItems(items: MenuItem[]): MenuItem[] {
return items.filter(item => {
// 检查权限
if (!this.permissionCtrl.hasRole(item.roles || []) ||
!this.permissionCtrl.hasPermission(item.permissions || [])) {
return false
}
// 处理子菜单
if (item.children) {
item.children = this.filterMenuItems(item.children)
return item.children.length > 0
}
return true
})
}
// 生成菜单树
generateMenu(config: MenuItem[]): MenuItem[] {
return this.filterMenuItems(config)
}
}
六、缓存控制
1. 路由缓存
// router/cache.ts
export class RouteCache {
private cache = new Set<string>()
// 添加缓存路由
add(name: string) {
this.cache.add(name)
}
// 移除缓存路由
remove(name: string) {
this.cache.delete(name)
}
// 获取所有需要缓存的路由
getList(): string[] {
return Array.from(this.cache)
}
// 清空缓存
clear() {
this.cache.clear()
}
}
// 在路由守卫中使用
const cacheGuard: NavigationGuard = (to, from, next) => {
const routeCache = new RouteCache()
if (to.meta.cache) {
routeCache.add(to.name as string)
}
next()
}
2. 组件缓存控制
<!-- layouts/MainLayout.vue -->
<template>
<div class="layout">
<keep-alive :include="cachedRoutes">
<router-view />
</keep-alive>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const routeCache = new RouteCache()
const cachedRoutes = computed(() => routeCache.getList())
</script>
七、权限更新处理
1. 权限刷新
// stores/permission.ts
export const usePermissionStore = defineStore('permission', {
state: () => ({
routes: [] as RouteRecordRaw[],
menus: [] as MenuItem[]
}),
actions: {
async refreshPermissions() {
// 获取最新权限
const newPermissions = await fetchUserPermissions()
// 更新用户状态
const userStore = useUserStore()
userStore.updatePermissions(newPermissions)
// 重新生成路由
const router = useRouter()
const permissionCtrl = new PermissionController(userStore.state)
const routeBuilder = new DynamicRouteBuilder(router, permissionCtrl)
// 清空现有路由
this.routes.forEach(route => {
router.removeRoute(route.name as string)
})
// 添加新路由
const asyncRoutes = await fetchUserRoutes()
this.routes = await routeBuilder.addRoutes(asyncRoutes)
// 重新生成菜单
const menuGenerator = new MenuGenerator(permissionCtrl)
this.menus = menuGenerator.generateMenu(menuConfig)
}
}
})
八、最佳实践建议
-
性能优化
- 路由组件懒加载
- 合理使用路由缓存
- 避免不必要的权限检查
-
安全考虑
- 前后端权限双重验证
- 定期刷新权限状态
- Token 安全存储
-
用户体验
- 权限不足时的友好提示
- 路由切换进度条
- 保持导航状态
-
代码质量
- 完善的类型定义
- 统一的错误处理
- 模块化设计
总结
本指南涵盖了:
- 全局路由守卫实现
- 动态路由控制
- 组件级权限控制
- 菜单权限管理
- 缓存控制策略
核心要点:
- 职责分离
- 类型安全
- 灵活配置
- 性能优化