Vue3路由权限动态管理

Vue3路由权限动态管理方案

一、方案概述

我的项目采用 基于角色的访问控制(RBAC) 实现路由权限动态管理,核心思路是:根据用户角色动态加载可访问的路由表,实现细粒度的权限控制。


二、核心架构

2.1 整体架构图

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        前端路由权限系统                          │
├─────────────────────────────────────────────────────────────────┤
│  用户登录 → 获取用户信息 → 动态加载路由 → 路由守卫校验 → 页面渲染   │
│                                                                 │
│  ┌──────────┐    ┌──────────┐    ┌──────────────┐    ┌──────────┐│
│  │ 登录页面 │ → │ UserStore │ → │ Permission   │ → │ Router   ││
│  │          │    │ 获取角色  │    │ 动态路由生成  │    │ 路由守卫 ││
│  └──────────┘    └──────────┘    └──────────────┘    └──────────┘│
│         ↓                                           ↓           │
│  ┌───────────────────────────────────────────────────────────────┐│
│  │                    asyncRoutes.ts                             ││
│  │  定义所有动态路由,通过 meta.roles 指定可访问角色              ││
│  └───────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘

2.2 关键文件职责

文件路径 职责
src/router/index.ts 路由配置入口,集成路由守卫
src/router/asyncRoutes.ts 动态路由定义,包含角色权限配置
src/stores/permission.ts 权限状态管理,负责路由生成与重置
src/utils/permission.ts 权限工具函数,路由守卫逻辑

三、实现步骤

3.1 步骤一:定义动态路由

asyncRoutes.ts 中定义所有需要权限控制的路由:

typescript 复制代码
// src/router/asyncRoutes.ts
export const asyncRoutes: RouteRecordRaw[] = [
  {
    path: '/admin',
    name: 'admin',
    component: () => import('@/layout/admin/AdminLayout.vue'),
    meta: {
      title: '管理后台',
      roles: ['管理员']  // 指定可访问角色
    },
    children: [
      {
        path: 'shop',
        name: 'shopManage',
        component: () => import('@/views/admin/components/shop/shopProduct.vue'),
        meta: {
          title: '商铺管理',
          roles: ['管理员']
        }
      }
    ]
  },
  {
    path: '/foster-manage',
    name: 'fosterManage',
    component: () => import('@/views/fosterManage/foster-manage.vue'),
    meta: {
      title: '寄养管理',
      roles: ['商家', '管理员']  // 多个角色可访问
    }
  }
]

3.2 步骤二:权限状态管理

permission.ts store 中管理路由加载状态:

typescript 复制代码
// src/stores/permission.ts
export const usePermissionStore = defineStore('permission', () => {
  const isRoutesLoaded = ref(false)
  const routes = ref<RouteRecordRaw[]>([])
  
  /**
   * 动态生成路由
   * @param roles 用户角色列表
   */
  async function generateRoutes(roles: string[]) {
    // 根据角色筛选可访问路由
    const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
    routes.value = accessedRoutes
    
    // 动态添加到路由表
    accessedRoutes.forEach(route => {
      router.addRoute(route)
    })
    
    isRoutesLoaded.value = true
    return accessedRoutes
  }
  
  /**
   * 重置路由
   */
  function resetRouter() {
    // 移除所有动态添加的路由
    routes.value.forEach(route => {
      const { name } = route
      if (name) {
        router.removeRoute(name)
      }
    })
    routes.value = []
    isRoutesLoaded.value = false
  }
  
  return {
    isRoutesLoaded,
    routes,
    generateRoutes,
    resetRouter
  }
})

3.3 步骤三:路由守卫校验

permission.ts 工具函数中实现路由守卫逻辑:

typescript 复制代码
// src/utils/permission.ts
export function setupPermissionGuard(router: Router) {
  router.beforeEach(async (to, from, next) => {
    // 白名单路由直接放行
    if (whiteList.includes(to.path)) {
      return next()
    }
    
    // 获取用户角色
    const userStore = useUserStore(store)
    const permissionStore = usePermissionStore(store)
    
    if (!userStore.isLoggedIn()) {
      // 未登录,跳转到登录页
      return next(`/login?redirect=${to.path}`)
    }
    
    // 路由已加载,检查权限
    if (permissionStore.isRoutesLoaded) {
      // 检查当前路由是否需要权限
      if (to.meta?.roles && !to.meta.roles.some(role => userStore.userInfo.roles?.includes(role))) {
        // 无权限,跳转到403页面
        return next('/403')
      }
      return next()
    }
    
    // 动态加载路由
    try {
      await permissionStore.generateRoutes(userStore.userInfo.roles || [])
      // 重新导航,确保新路由生效
      next({ ...to, replace: true })
    } catch (error) {
      console.error('路由加载失败:', error)
      next('/login')
    }
  })
}

3.4 步骤四:登录时触发路由加载

在登录成功后,调用路由生成方法:

typescript 复制代码
// src/views/enterPet/login-pet.vue
const handleLogin = async (formData: LoginForm) => {
  const response = await loginByAccount({ ... })
  
  if (response.code === 0) {
    const { token, userInfo } = response.data
    
    // 更新用户状态
    userStore.setToken(token)
    userStore.setUserInfo(userInfo)
    
    // 动态生成路由
    await permissionStore.generateRoutes(userInfo.roles)
    
    // 跳转到目标页面
    router.push('/foster-care')
  }
}

四、核心难点

4.1 路由重复加载问题

问题描述:多次触发路由守卫时,动态路由可能被重复添加。

解决方案

typescript 复制代码
// 通过 isRoutesLoaded 标志位避免重复加载
if (permissionStore.isRoutesLoaded) {
  // 路由已加载,直接检查权限
  return checkPermissionAndNext(to, next)
}

4.2 路由导航状态问题

问题描述:动态路由添加后,当前导航可能已处于失败状态。

解决方案

typescript 复制代码
// 使用 replace: true 重新导航,避免历史记录重复
next({ ...to, replace: true })

4.3 角色权限匹配逻辑

问题描述:如何判断用户是否有权限访问某个路由。

解决方案

typescript 复制代码
// 路由角色列表与用户角色列表的交集判断
function hasPermission(routeRoles: string[], userRoles: string[]) {
  if (!routeRoles || routeRoles.length === 0) {
    return true  // 无角色限制的路由默认可访问
  }
  return userRoles.some(role => routeRoles.includes(role))
}

4.4 路由重置问题

问题描述:用户退出登录后,动态添加的路由需要清理。

解决方案

typescript 复制代码
function resetRouter() {
  routes.value.forEach(route => {
    const { name } = route
    if (name) {
      router.removeRoute(name)  // 移除路由
    }
  })
  isRoutesLoaded.value = false
}

五、关键代码解析

5.1 路由筛选函数

typescript 复制代码
// src/stores/permission.ts
function filterAsyncRoutes(routes: RouteRecordRaw[], roles: string[]): RouteRecordRaw[] {
  const res: RouteRecordRaw[] = []
  
  routes.forEach(route => {
    const temp = { ...route }
    
    // 检查当前路由是否有权限
    if (hasPermission(temp.meta?.roles, roles)) {
      // 递归处理子路由
      if (temp.children) {
        temp.children = filterAsyncRoutes(temp.children, roles)
      }
      res.push(temp)
    }
  })
  
  return res
}

解析:递归遍历路由树,根据用户角色筛选可访问的路由,确保子路由也遵循相同的权限规则。

5.2 路由守卫完整逻辑

typescript 复制代码
// src/utils/permission.ts
router.beforeEach(async (to, from, next) => {
  // 1. 白名单校验
  if (whiteList.includes(to.path)) {
    return next()
  }
  
  // 2. 登录状态校验
  if (!userStore.isLoggedIn()) {
    return next(`/login?redirect=${to.path}`)
  }
  
  // 3. 路由加载状态校验
  if (!permissionStore.isRoutesLoaded) {
    await permissionStore.generateRoutes(userStore.userInfo.roles || [])
    return next({ ...to, replace: true })
  }
  
  // 4. 权限校验
  if (to.meta?.roles) {
    if (!hasPermission(to.meta.roles, userStore.userInfo.roles || [])) {
      return next('/403')
    }
  }
  
  next()
})

解析:路由守卫按照"白名单 → 登录状态 → 路由加载 → 权限校验"的顺序进行层层校验,确保安全性。


六、权限控制流程总结

阶段 操作 文件
1. 定义路由 在 asyncRoutes.ts 中定义动态路由及角色权限 router/asyncRoutes.ts
2. 登录认证 获取用户角色信息,保存到 Pinia store stores/user.ts
3. 动态加载 根据角色筛选路由,添加到路由表 stores/permission.ts
4. 路由守卫 校验登录状态、权限,控制导航 utils/permission.ts
5. 页面渲染 根据路由配置渲染对应页面 views/**/*.vue
6. 退出清理 重置路由表,清除权限状态 stores/permission.ts

七、安全性保障

  1. 前端校验:路由守卫在前端层面控制页面访问,但不能完全依赖
  2. 后端校验:所有接口必须独立进行权限校验,防止绕过前端直接访问
  3. Token 验证:每次请求携带 Token,后端验证有效性
  4. 路由重置:退出登录时清理动态路由,防止信息泄露
相关推荐
RANxy1 小时前
零基础全栈 React 入门(四):React Router 路由配置
前端·react.js
KaMeidebaby1 小时前
卡梅德生物技术快报|peg 修饰调控 MXene/WS2 异质结,氨气传感器制备与机理研究
大数据·前端·人工智能·架构·spark·新浪微博
lichenyang4531 小时前
鸿蒙实战:安全高度 · 输入框贴键盘弹起 · Tab 底部导航全解
前端
前端毕业班1 小时前
uni-app 小程序样式隔离实践指南和原理分析
前端·javascript·vue.js
JarvanMo1 小时前
Flutter 鸿蒙化迎来"大搬家"
前端
龙佚1 小时前
抖动缓冲与播放控制:平滑播放的艺术
前端·架构
仿生狮子2 小时前
🎼 从文本到交互界面——GenUI 的中庸之道
前端·vue.js·markdown
wuhen_n2 小时前
LangChain 核心:Chain 链式调用实现复杂 AI 任务
前端·langchain·ai编程
往上跑山2 小时前
【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读
前端