element-plus 手把手带你用vue建后台 系列二(权限路由篇)
完整项目地址:xhwy-v3-ts-elementplus
系列文章:
前言
hello 让大家久等了,相隔五天才来更新系列二,为了能够更加严谨的说明白权限这个篇章,做了很多准备工作,上一章对我们的后台项目做了一个整体的介绍,一个完整的后台管理系统,必不可少的就是权限管理,权限和路由是不可分割的,放到一个篇章讲一下,由于大家使用模板所用的路由不尽相同,没有放到模板当中,在这里统一讲一下
rbac 权限机制和动态路由

什么是 rbac 权限管理机制
“RBAC 是一套成熟的权限模型。在传统权限模型中,我们直接把权限赋予用户。而在 RBAC 中,增加了“角色”的概念,我们首先把权限赋予角色,再把角色赋予用户。这样,由于增加了角色,授权会更加灵活方便。”
1.系统用户
2.角色管理
3.权限管理
后台管理系统的权限管理有有哪些?
- 菜单权限
- 路由权限
- 按钮权限
- 后台权限
什么是动态路由?
路由需要分成两类,静态路由和动态路由。静态路由是任何菜单权限下都能查看的界面路由;动态路由是根据菜单权限动态生成的路由集合。我们可能有一个 User 组件,它应该对所有用户进行渲染,但用户 ID 不同,我们分配不同的菜单权限
为什么使用匹配动态路由
能模块的访问,本质上是由路由实现的,点击菜单只是途径之一,所以权限控制放在路由上是合理的,计算出来的路由的访问权限。这个计算出来的路由,并不是可预知的。就没法在全量里面先定义了,这种场景是必须要用动态路由的。
vue-router4 动态路由 API 有哪些?
- router.addRoute(route: RouteRecord) :动态添加路由
- router.removeRoute(name: string | symbol) :动态删除路由
- router.hasRoute(name: string | symbol) : 判断路由是否存在
- router.getRoutes() : 获取路由列表
了解下 vue-router4 动态路由
getRoutes()就可以获取当前已配置的路由的数组
在路由守卫中添加 console.log(router.getRoutes())打印看一下

hasRoute 方法,它接受一个参数,路由的名称,来判断在已配置的路由里有没有这个路由,返回的是个布尔值
console.log(router.hasRoute('Index'))
removeRoute 移除当前配置的单个路由
router.removeRoute("Index")
console.log(router.getRoutes())

这里能看到 index 被移除掉了
值得注意的是 router.addRoute 是 vue-router4 的改变之前添加的是数组,现在变成只能添加单条路由了
router.addRoute({
path: '/qingning',
name: 'qingning',
component: () => import('@/views/login/Login.vue'),
})
console.log(router.getRoutes())
来填一下系列一的坑
如何使用动态路由
之前我们使用静态路由的时候是将路由信息全部写在 router 文件夹下的 index 文件中,当我们利用权限赋予不同的角色不同的权限的时候,就要用到动态路由,每个账号登录对应的动态路由和动态菜单都不一样,那么路由信息就不能够存在一个文件里,因此,我们在 router 文件夹下新建 modules 文件夹,存放不同菜单的路由信息

还记得在系列一中提到的 store 文件夹嘛?在里面我们封装好了用于登录的模块和 tab 的状态,现在我们要新建一个 menu 的模块
import { Module } from 'vuex'
import { RootState } from '../index'
import { asyncRoutes } from '@/router/modules'
import { RouteRecordRaw } from 'vue-router'
export interface MenuState {
menuList: RouteRecordRaw[]
}
export const menuStore: Module<MenuState, RootState> = {
namespaced: true,
state: (): MenuState => ({
menuList: [],
}),
getters: {
getMenus: (state) => state.menuList,
},
mutations: {
setMenus(state, systemMenu) {
state.menuList = systemMenu
},
},
actions: {
generateSystemMenus({ commit, state }, perm: string[]) {},
},
}
别忘了在 store/index.ts 中注册
路由赋值 menu 菜单
首先 permissions 权限对比路由 meta.permission,找到相同的保留并且 push
function hasPermission(permissions: string[], needPermission: string) {
for (let i = 0; i < permissions.length; i++) if (permissions[i].startsWith(needPermission)) return true
return false
}
function filterRouter(routes: RouteRecordRaw[], perms: string[]) {
const res: RouteRecordRaw[] = []
routes.forEach((route) => {
if (route.children) {
route.children = filterRouter(route.children, perms) // 递归处理孩子节点
if (route.children && route.children.length > 0) res.push(route)
} else {
if (route.meta!.permission) {
// 需要权限
if (hasPermission(perms, route.meta!.permission)) res.push(route)
} else {
res.push(route)
}
}
})
return res
}
在 actions 中引入对比函数
generateSystemMenus({ commit ,state},perm:string[]) {
let routers = filterRouter(asyncRoutes, perm);
routers.forEach(route=>{
if(route.redirect == null&&route.children?.length==1) {
route.redirect = route.path+ '/' +route.children[0].path
route.meta = route.children[0].meta
}else {
router.addRoute(route)
}
})
console.log(routers)
commit('setMenus',routers)
}
在 menuBar/index.vue 中引入 store
并且给响应式数据 menus 赋值为 store.getters['menuStore/getMenus'], 切记,使用 getters 计算属性的时候要用 [中括号]
import { useStore } from '@/store'
const store = useStore()
const menus = store.getters['menuStore/getMenus']
解决登录和刷新时 state 中 menuList 添加数据 在 auth.ts 中给账号密码登录 login 和 loginBytToken 登录添加 menuStore/generateSystemMenus
store.dispatch('menuStore/generateSystemMenus', result.data.permissions)
这样登录就会自动加载动态菜单,不过,如果刷新 vuex 中的 menuList 数据就会消失,所以最后在前置路由守卫刷新中也添加 menuStore/generateSystemMenus,不过这里要注意:在配置路由的时候 typescript 需要给路由的 meta 配置属性,注意,提示 meta 类型为 RouteMeta||undefine
需要补充的类型
declare module 'vue-router' {
interface RouteMeta {
// 是可选的标题
title: string
// 每个路由都必须声明
icon?: string
permission: string
}
}
其次,路由守卫获取动态路由需要重定向
if (to.matched.length == 0) {
router.push(to.path)
}
最后如果有二级菜单,当二级菜单中只剩下一条菜单数据,我们让二级菜单变成一级菜单
// 添加到动态路由
routers.forEach((route) => {
// 二级menu跳成一级menu
if (route.redirect == null && route.children?.length == 1) {
route.redirect = route.path + '/' + route.children[0].path
route.meta = route.children[0].meta
}
router.addRoute(route)
})
// 添加动态菜单
commit('setMenus', routers)