从零开始Vue3+Element Plus后台管理系统(18)——权限路由实现

news/2025/2/21 13:10:01/

一开始打算做两种模式的路由权限,最后还是分成了3种,分别是:

  1. 前端固定路由,所有路由是固定的,通过权限过滤菜单和显示
  2. 前端动态路由,通过权限过滤路由表和菜单
  3. 后端动态路由,获取接口返回数据,挂载路由表和菜单
VITE_PERMISSION_MODE = 'CONSTANT'
# VITE_PERMISSION_MODE = 'FRONT'
# VITE_PERMISSION_MODE = 'BACK'

因为权限配置灵活,实际工作中一般采用第3种。在个人demo或者权限简单的项目中,第一种也够用。

简要流程

下图是一个简单流程说明,主要在路由的全局前置守卫router.beforeEach体现。

image.png

各个模块的路由对应views,分文件放在router/modules,方便后面统一使用
image.png

路由入口文件 router/index.ts

  1. 存放系统固定的几个路由(登录、注册、403、404…)
const constantRoutes: RouteRecordRaw[] = [{path: '/',name: 'Home',redirect: '/dashboard',hidden: true,meta: {title: 'home'}},{path: '/login',name: 'Login',hidden: true,meta: {title: 'signIn'},component: () => import(/* webpackChunkName: "login" */ '../views/login/login.vue')},{path: '/403',name: '403',hidden: true,meta: {title: '没有权限'},component: () => import(/* webpackChunkName: "400" */ '../views/403.vue')}
]
const lastRoutes = [{path: '/:pathMatch(.*)*',name: '404',hidden: true,meta: {title: '404'},component: () => import(/* webpackChunkName: "400" */ '../views/404.vue')}
]
  1. 获取router/modules文件夹中注册个模块路由
const modules = import.meta.glob('./modules/**/*.ts', { eager: true })let routeModuleList: RouteRecordRaw[] = []// 获取路由并排序
Object.values(modules).forEach((key: any) => {const mod = key.default || []const modList = Array.isArray(mod) ? [...mod] : [mod]routeModuleList.push(...modList)
})
  1. 导出挂载在app上的router。

前端固定路由模式会在此把所有路由挂载上。动态路由只需要挂载constanctRoutes,动态路由在前置守卫中挂载。

let routes = constantRoutes// 前端固定路由模式
if (import.meta.env.VITE_PERMISSION_MODE === 'CONSTANT') {routes = [...routeModuleList, ...constantRoutes]
}const router = createRouter({history: createWebHashHistory(),routes
})export default router
export { constantRoutes, routeModuleList, lastRoutes }

除了router文件夹中的文件,我们还需要:

  • useUserStore 管理用户状态,判断路由权限时会用到它,侧边栏菜单的路由表也暂时放在这里。
  • 两个API:获取用户信息、获取路由信息

通用判断

当一个导航触发时,我们通过前置守卫来处理各种情况下的导航——判断是否放行,或是跳转其他页面。

代码放在router/permission.ts,如果你习惯放在src下也没问题。

  • 如果用户未登录,
    当前路由在白名单则直接放行,否则跳转到登录页面/login

  • 如果用户已登录,导航到/login,则直接进入系统首页/

// 用户已登录if (useUser.userid) {if (to.path === '/login') {return '/'}...} else {// 白名单,直接放行if (whiteList.indexOf(to.path) > -1) return true// 非白名单,去登录else return '/login'}

继续往下走,需要用到开始说的3种路由模式分别处理。

1. 前端固定路由

我们扩展了_RouteRecordBase接口,增加roles用于保存可访问路由的角色

declare module 'vue-router' {interface _RouteRecordBase {hidden?: boolean}interface RouteMeta {order?: numberroles?: array}
}

router/modules/system.ts

    ...{path: 'account',name: 'account',meta: {title: 'accountManagement',roles: [RoleEnum.USER]   // 用于判断角色权限},component: () => import('~/views/system/account/index.vue')},...

在登录状态下,只需要判断用户角色是否能够访问这个路由即可,如果没有权限则跳转去403页面

// 前端固定路由模式,如果没有权限,进入403页面if (import.meta.env.VITE_PERMISSIOIN_MODE === 'CONSTANT' &&to.meta.roles &&!to.meta.roles.includes(role)) {return '/403'}

前端动态路由模式

无论是前端还是后端动态路由模式,都需要动态挂载。

    // 前端固定路由模式,如果没有权限,进入403页面if (...}// 前端动态路由和后端动态路由,动态挂载路由else {if (!to.redirectedFrom) {await addAsyncRoutes(router, useUser)return { ...to, replace: true }} else return true}

前端模式中,通过路由角色权限和当前用户角色比对,筛选出有权限的路由,挂载到router上

后端模式中,通过接口返回数据,组装成前端可用的路由数据,挂载到router上

动态挂载路由: router.addRoute(route)

在此,因为addRoute不会改变router.optioins.routes(创建路由器时的原始routes),所以挂载后的路由数据,我们需要保存起来,以便侧边栏菜单使用。

async function addAsyncRoutes(router, userStore) {const permissionMode = import.meta.env.VITE_PERMISSIOIN_MODElet filteredRoutes = <RouteRecordRaw[]>[]// 前端动态路由模式if (permissionMode === 'FRONT') {filteredRoutes = filterFunc(routeModuleList)}// 后端动态路由模式if (permissionMode === 'BACK') {let routes = await getBackAsyncRoutes(userStore.userid)filteredRoutes = formatAsyncRoutes(routes)}console.log(filteredRoutes)filteredRoutes.forEach((val) => router.addRoute(val))userStore.setAsyncRoutes(filteredRoutes) // 保存最新的路由信息
}

所以前端动态路由,只需过滤出如何角色权限的路由即可,在router/index导出的模块路由数据中比对role即可。

后端动态路由

它和前端模式的区别就是,路由数据通过接口获取,拿到数据后,最重要的操作就是重新拼装成前端可用的形式。

async function getBackAsyncRoutes(userid: number) {return await systemApi.getRoutes({ userid })
}const modules = import.meta.glob('~/views/**/**.vue')// 把接口路由组装成前端可用结构
function formatAsyncRoutes(routes: any[]) {routes.forEach((r) => {if (r.component === 'layout' || !r.component) {r.component = Layout} else {r.component = modules[`/src/views${r.component}`]}if (r.children && r.children.length > 0) {r.children = formatAsyncRoutes(r.children)}})return routes
}

产品需求千千万,万变不离其宗 😄

写在最后

路由权限这块,这也是项目最初立下的Flag,在我✍️写到系统第18篇文章的时候终于写了,刚开始的时候还是碰到一些问题,但是当我细细梳理,画出思维导图,写完文章后,觉得一切都清晰起来。

如果一味前行不作停留,不去回顾和总结,很多东西带着一知半解就过去了忘记了。

在忙碌的项目中,我觉得自己变成了一个螺丝钉,没有丝毫成长。停下来做一些输出和总结,整装待发反而更好。

项目地址

本项目GIT地址:https://github.com/lucidity99/mocha-vue3-system

如果有帮助,给个star ✨ 点个赞👍


http://www.ppmy.cn/news/167054.html

相关文章

提高团队协作效率的妙招:分享团队协作方法和工具推荐

团队管理是任何领导者或管理者的基本技能。拥有一支由积极进取的员工组成的团队对任何组织来说都是无价的资产。有效的团队管理对于创建一个高效成功的企业是至关重要的。如何提高团队协作的效率&#xff1f;这里有一些成功团队管理的基本技巧。 1、建立明确的目标 任何成功的团…

QDateTime类型加减计算

在Qt框架中&#xff0c;QDateTime类提供了一系列可以进行日期和时间的加减计算的方法&#xff0c;可用于处理日期和时间相关的问题。一些常用的方法如下&#xff1a; QDateTime::addDays(int days)&#xff1a;在当前时间的基础上增加指定天数后的日期和时间。 QDateTime cur…

面试官:深拷贝与浅拷贝有啥区别?

文章目录 1.前言2.基本类型的拷贝3.引用类型的拷贝3.1 关于引用类型的浅拷贝3.2 关于引用类型的深拷贝 1.前言 首先&#xff0c;明确一点深拷贝和浅拷贝是针对对象属性为对象的&#xff0c;因为基本数据类型在进行赋值操作时&#xff08;也就是拷贝&#xff09;是直接将值赋给…

macbook pro window蓝牙驱动 下载

macbook pro的蓝牙驱动一直找不到&#xff0c;使用DRIVERTOOLKIT 更新成功&#xff0c;将蓝牙驱动备份下来。放在百度网盘下&#xff0c;供使用 链接: https://pan.baidu.com/s/1vfuvyFdegB3kn2PPiqsn9w 提取码: jktz 接小米鼠标会有断的现象&#xff0c;这个驱动&#xff0c;…

惠普电脑使用蓝牙连接蓝牙设备

电脑原装的驱动是windows自带的旧驱动&#xff0c;需到惠普官网下载新的驱动 下载地址&#xff1a;https://support.hp.com/cn-zh/drivers/laptops 安装驱动 在设备管理中卸载蓝牙设备&#xff0c;重新添加蓝牙设备 这样操作之后在右侧托盘中会有蓝牙标记 右键点击蓝牙标记–&…

笔记本蓝牙突然消失

蓝牙突然消失有可能是 因为电脑产生静电&#xff0c;关机后&#xff0c;切开电源长按开机键20秒释放静电。 之后打开设备管理器&#xff0c;看是否会出现有未知设备描述符请求失败 打开隐藏设备 看蓝牙选项中&#xff0c;蓝牙是灰色的就是不可用&#xff0c; 把未知设备卸掉或…

Windows10蓝牙驱动丢失,100%解决方案

1.打开控制面板 2.设备和打印机 3.找到如下设备(应该会有个!警告&#xff09; 4.右键->疑难解答 5.一步步修复&#xff0c;最后关机重启即可。

华为matebook重装win11系统后,蓝牙失效无法使用,重新安装蓝牙驱动

华为电脑重新安装win11系统后&#xff0c;蓝牙无法使用&#xff0c;华为电脑管家无法更新驱动 浏览器搜索 华为电脑驱动 进入华为官方驱动程序下载网站&#xff0c;下载对应产品型号的蓝牙驱动&#xff0c;安装即可。