当前位置: 首页 > news >正文

Vue3 路由权限管理:基于角色的路由生成与访问控制--页面级的权限控制

Vue3 路由权限管理:基于角色的路由生成与访问控制–页面级的权限控制

一、核心概念

1.1 大致流程思路:

用户在登录完成的时候,后端给出一个此登录用户对应的角色名字,此时可以将这个用户的角色存起来(vuex/pinia)中,在设置路由时的meta字段中设置的roles属性如:roles: ['admin', 'accountA', 'accountB']----这就是每个页面能访问的角色名。至此为止,用户的角色名字我们拿到了,每个页面能访问的角色名字我们也知道了,接下来只需要去判断当前用户的角色名是否包含这些权限即可—这就需要交给导航守卫了:router.beforeEach((to, from, next) => {})

1.2 核心组件对比表

组件作用示例特点
静态路由基础公共路由登录页/404页无需权限,初始加载
动态路由权限控制路由管理后台各页面按需加载,角色过滤
路由守卫访问控制beforeEach钩子实时验证,安全屏障
状态管理角色存储Vuex/Pinia全局共享,持久化

基于角色的路由权限管理系统主要包含以下几个核心部分:

  1. 静态路由:无需权限即可访问的路由(如登录页、404页)

  2. 动态路由:需要根据角色的权限进行控制是否显示的路由(如后台管理页面)。

  3. 角色权限配置:定义哪些角色可以访问哪些路由,如:

在这里插入图片描述

  1. 路由守卫:在路由跳转时进行权限验证

二、代码实现解析

1. 路由配置

// 静态路由 - 无需权限
const staticRoutes = [
  {
    path: '/login',
    name: 'Login',
    component: LoginView
  },
  {
    path: '/404',
    name: 'NotFound',
    component: NotFoundView
  }
];

// 动态路由 - 要根据角色的权限进行显示
export const dynamicRoutes = [
  {
    path: '/admin',
    name: 'admin',
    component: HomeView,
    meta: {
      title: "首页",
      title_path: '/admin',
      sub_title: "后台管理",
      roles: ['admin', 'accountA', 'accountB'] // 修正为数组格式
    },
    children: [
      {
        path: 'user',
        name: 'user',
        component: UserView,
        meta: {
          title: "用户管理",
          title_path: '/admin/user',
          sub_title: "用户信息编辑",
          roles: ['admin', 'accountA', 'accountB'] // 修正为数组格式
        }
      },
      {
        path: 'role',
        name: 'role',
        component: RoleView,
        meta: {
          title: "角色管理",
          title_path: '/admin/role',
          sub_title: "角色信息编辑",
          roles: ['admin', 'accountA'] // 修正为数组格式
        }
      },
      {
        path: 'permis',
        name: 'permis',
        component: PermissView,
        meta: {
          title: "权限管理",
          title_path: '/admin/permis',
          sub_title: "权限信息编辑",
          roles: ['admin'] // 修正为数组格式
        }
      }
    ]
  }
]

2. 路由初始化

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes: staticRoutes // 初始只加载静态路由
})

3. 动态路由添加

注意:添加动态路由这一步需要在路由配置文件中调用,这样在刷新页面的时候,就可以重新调用一次。防止动态路由丢失,导致出现白屏的情况

// router/index.ts
// 根据角色筛选出路由
 function filterRoutesByRole(routes: any[], userRoles: string[]): any[] {
  return routes
    .map((route) => {
      // 复制路由对象避免修改原始数据
      const filteredRoute = { ...route };

      // 处理子路由(递归)
      if (route.children && route.children.length > 0) {
        filteredRoute.children = filterRoutesByRole(route.children, userRoles);
      }

      return filteredRoute;
    })
    .filter((route) => {
      // 过滤条件:无权限要求 或 有权限且用户有权限
      return (
        !route.meta?.roles ||
        route.meta.roles.some((role: string) => userRoles.includes(role))
      );
    });
}

// 根据路由动态创建菜单
export function generateDynamicRoutes() {
  const userRoles = localStorage.getItem("role_code")?[localStorage.getItem("role_code")]:[]
  console.log(userRoles);
  const accessibleRoutes = filterRoutesByRole(dynamicRoutes, userRoles);

  // 先移除可能已存在的动态路由
  router.getRoutes().forEach((r) => {
    if (dynamicRoutes.some((dr) => dr.name === r.name)) {
      router.removeRoute(r.name!);
    }
  });

  // 添加新路由
  accessibleRoutes.forEach((route) => {
    console.log("route>>>>>>>>>>", route);
    router.addRoute(route);
  });
}
// 每次加载调用一次,根据角色生成对应的路由
generateDynamicRoutes()

在登录成功的时候调用上面 添加动态路由 的方法:LoginView.vue

import { generateDynamicRoutes } from "@/router";
// 然后再登录成功中调用generateDynamicRoutes()

4. 路由守卫实现

router.beforeEach((to, from, next) => {
  const store = useStore()
  const userRoles = store.state.roles || []
  
  // 1. 检查是否需要权限
  if (to.meta.roles) {
    const requiredRoles = to.meta.roles
    const hasPermission = requiredRoles.some(role => 
      userRoles.includes(role)
    )
    
    if (!hasPermission) {
      return next('/404') // 无权限跳转404
    }
  }
  
  // 2. 检查是否已登录但访问登录页
  if (to.name === 'Login' && store.state.token) {
    return next('/admin') // 已登录用户跳转首页
  }
  
  next()
})

5. 动态菜单

import { useStore } from "vuex";
const store = useStore();

import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();

// 现生成一个动态菜单--需要从meta中拿到
const all_router = router.getRoutes();
interface menu_list_R {
  key: string;
  icon: string;
  label: string;
  children?: object;
}
const menu_list = ref<menu_list_R[]>([]);
const menu_list_fn = (all_meta_data: any) => {
  // 过滤出isMenu === true的meta
  const meta_data = all_meta_data.filter((item: any) => {
    return item.isMenu === true;
  });
  // 拿到此用户的角色组
  const userRoles = store.state.roles || [];
  // 过滤出此角色可以访问的路由meta
  const finish_meta = meta_data.filter((item: any) => {
    return item.roles.some((role: string) => userRoles.includes(role));
    // return item.roles.includes(store.state.st)
  });
  // 最后拿到可以给菜单使用的数组菜单
  const iconComponents = {
    AppstoreOutlined: AppstoreOutlined,
    InboxOutlined: InboxOutlined,
    DesktopOutlined: DesktopOutlined,
  };
  const use_meta = finish_meta.map((item: any) => {
    // 处理当前菜单项
    const menuItem: any = {
      key: item.title_path,
      icon: iconComponents[item.icon],
      label: item.title,
    };
    // 处理子菜单(递归)
    if (item.children && item.children.length > 0) {
      menuItem.children = menu_list_fn(item.children);

      // 如果子菜单处理后为空数组,则移除该菜单项
      if (menuItem.children.length === 0) {
        return null;
      }
    }
    return menuItem;
  });
  return use_meta;
  // menu_list.value = use_meta
  console.log("meta_data:::", finish_meta, use_meta);
};

// 调用函数生成菜单
const initMenu = () => {
  const allMetaData = all_router.map((route) => {
    return route.meta;
  });

  menu_list.value = menu_list_fn(allMetaData);
  console.log("生成的菜单数据:", menu_list.value);
};

三、检查用户角色操作的作用分析

您提供的代码中关于用户角色检查的部分实际上是非常必要的,它在整个权限控制流程中扮演着关键角色。让我详细解释其作用和必要性:

1、角色检查的核心作用

1.1. 权限过滤的核心机制
function filterRoutesByRole(routes: any[], userRoles: string[]) {
  return routes.filter((route) => {
    return (
      !route.meta?.roles || // 无权限限制的路由直接放行
      route.meta.roles.some((role: string) => userRoles.includes(role)) // 检查角色匹配
    );
  });
}

这段代码实现了:
路由筛选:根据用户角色过滤出可访问的路由
权限验证:确保用户只能看到自己有权限访问的路由
安全控制:防止越权访问敏感路由

1.2. 动态路由生成流程
登录模块 路由系统 状态管理 存储用户角色(如['admin']) 触发generateDynamicRoutes 清除旧路由 过滤出可访问路由 添加新路由 登录模块 路由系统 状态管理

2、为什么不能省略?

2.1. 必要性分析
检查环节作用省略后果
角色存储保留用户权限标识无法识别用户权限级别
路由过滤生成个性化路由表所有用户看到相同菜单
权限验证防止越权访问安全漏洞风险
2.2. 实际应用场景

假设:
• 用户角色:['editor']
• 路由配置:

[
  { path: '/admin', meta: { roles: ['admin'] }},
  { path: '/editor', meta: { roles: ['editor'] }}
]

无过滤时:用户可以看到/admin路由但无法正常使用,导致错误
有过滤时:用户只能看到/editor路由,体验更合理

3、代码优化建议

3.1. 类型安全改进
// 在类型声明文件中
declare module 'vue-router' {
  interface RouteMeta {
    roles?: string[];
    // 其他元字段...
  }
}

// 过滤函数改进
function filterAccessibleRoutes(routes: RouteRecordRaw[], userRoles: string[]): RouteRecordRaw[] {
  return routes.filter(route => {
    const requiredRoles = route.meta?.roles || []
    return requiredRoles.length === 0 || 
           requiredRoles.some(role => userRoles.includes(role))
  })
}
3.2. 性能优化
// 避免重复计算
const userRoles = computed(() => store.state.roles)

// 路由添加前检查
function needsRefresh(newRoutes: RouteRecordRaw[]) {
  const currentRoutes = router.getRoutes()
  return (
    newRoutes.length !== currentRoutes.length ||
    newRoutes.some(route => !currentRoutes.some(r => r.path === route.path))
  )
}
3.3. 错误处理增强
function generateDynamicRoutes() {
  try {
    const userRoles = store.state.roles || []
    if (!Array.isArray(userRoles)) {
      throw new Error('角色数据格式错误')
    }
    
    const accessibleRoutes = filterRoutesByRole(dynamicRoutes, userRoles)
    
    // 清除旧路由逻辑...
    // 添加新路由逻辑...
    
  } catch (err) {
    console.error('路由生成失败:', err)
    router.push('/error?code=route_init_failed')
  }
}

4、完整工作流程

  1. 用户登录
    • 获取角色信息 → 存储到Vuex

  2. 路由生成

    用户角色
    路由过滤
    清除旧路由
    添加新路由
    菜单渲染
  3. 访问控制
    • 路由守卫二次验证
    • 菜单动态生成

5、总结

您代码中的角色检查操作:
• ✅ 不是多余的:是权限系统的核心逻辑
• ✅ 必不可少:确保路由与权限的精确匹配
• ✅ 可以优化:类型、性能、错误处理等方面

建议保留这部分逻辑,同时参考上述优化建议进行改进,可以使您的权限控制系统更加健壮和安全。

四、最佳实践方案

4.1 路由配置规范

字段类型必填说明
pathstring路由路径
componentComponent组件引用
meta.rolesstring[]允许的角色
meta.titlestring推荐页面标题
meta.iconstring可选菜单图标
meta.cacheboolean是否缓存

4.2 权限检查方案对比

方案优点缺点适用场景
前端控制响应快,体验好安全性较低内部管理系统
前后端结合安全性高实现复杂高安全要求系统
动态接口灵活度高性能开销大权限频繁变更系统

4.3 性能优化建议

  1. 路由懒加载

    component: () => import('@/views/HeavyPage.vue')
    
  2. 路由分组打包

    // vite.config.js
    rollupOptions: {
      output: {
        manualChunks: {
          admin: ['@/views/Admin*.vue']
        }
      }
    }
    
  3. 持久化缓存

    // 使用pinia-plugin-persistedstate
    persist: {
      paths: ['user.roles']
    }
    

五、常见问题解决方案

5.1 问题排查表

现象可能原因解决方案
路由跳转循环守卫逻辑错误检查重定向条件
菜单不更新路由未正确重置确保先remove后add
404错误路由未加载检查addRoute调用
权限失效角色信息丢失检查状态持久化

5.2 典型错误处理

// 安全的路由添加方式
function safeAddRoute(route) {
  try {
    if (router.hasRoute(route.name)) {
      router.removeRoute(route.name)
    }
    router.addRoute(route)
    return true
  } catch (err) {
    console.error('路由添加失败:', err)
    return false
  }
}

六、完整示例项目结构

/src
├── router
│   ├── index.ts        # 路由入口
│   ├── static.ts       # 静态路由
│   └── dynamic.ts      # 动态路由配置
├── stores
│   └── auth.ts         # 权限状态管理
├── utils
│   └── permission.ts   # 权限验证工具
└── views
    ├── public          # 公共页面
    └── admin           # 权限页面

通过这套系统化方案,开发者可以快速实现:

  1. 基于角色的精细化权限控制
  2. 动态菜单的自动生成
  3. 安全的访问验证机制
  4. 良好的用户体验和性能表现

相关文章:

  • LeetCode刷题SQL笔记
  • vim/vi程序(1)
  • forms实现推箱子小游戏
  • C#语言的加密货币
  • PostgreSQL 事务
  • RISCV GCC 后端 -- 依赖(Dependence)简析
  • springboot项目中常用的工具类和api
  • 蓝桥云客 最大和
  • Docker Compose 常用命令 运行 docker-compose.yaml
  • Tomcat 安装与配置:超详细指南
  • 使用 `china-region` 实现 Vue3 + TS 的省市区三级联动选择器
  • 【Node】如何使用PM2高效部署nodejs前端应用
  • 5G N2信令
  • Excel计数、求和、统计、计算类函数
  • AutoGLM 沉思智能体:开启 AI 自主决策新时代
  • Python开源项目月排行 2025年3月
  • C#调用C++动态库时出现`System.DllNotFoundException`错误的解决思路
  • int 与 Integer 的区别详解
  • Redis原理:keys命令
  • 微信小程序开发前端培训课程
  • 上海明天短暂恢复晴热最高32℃,一大波雨水在候场
  • 读懂城市|成都高新区:打造“人尽其才”的“理想之城”
  • 2024年全国博物馆接待观众14.9亿人次
  • 专访|《内沙》导演杨弋枢:挽留终将失去的美好
  • 没有握手,采用翻译:俄乌三年来首次直接会谈成效如何?
  • 上海一保租房社区亮相,首批546套房源可拎包入住