【V3 Admin Vite 4.x】教程五:前端权限(涉及角色、动态路由、权限函数、权限指令)

20,823 阅读5分钟

前言

本系列文章是为了帮助没有直接上手(或上手比较困难)做项目能力的初级前端开发工程师采用 V3 Admin Vite 开源模板来编写业务代码。

如果你是一个有经验的朋友,那建议你直接阅读文档即可:V3 Admin Vite 中文文档,因为本系列教程节奏偏慢。

本系列文章的同步视频教程版本地址:B 站(群友好心录制)

文章目的

本文将带你学习该项目是如何通过用户的 “角色” 字段来进行页面级别的权限控制(动态路由的挂载);以及通过角色字段进行内容级别的权限控制(权限函数、权限指令)

Begin

页面权限

动态路由

@/router/index.ts 就是用来存放常驻路由和动态路由的文件,如图所示(图是旧图,但是不影响阅读就没更新):

router/index.ts

不管什么情况下,都需要挂载的路由,我们就存放在 constantRoutes 数组下,比如登录页、首页;需要用户登录并根据角色字段来判断是否有权限的路由,我们就放在 asyncRoutes 数组下,并且要为该路由配置好 rolesname 属性

下面源码就是项目中写好的一个动态路由示例,注意看它是有 rolesname 属性的:

{
  path: "/permission",
  component: Layout,
  redirect: "/permission/page",
  name: "Permission", // 不要忘了写
  meta: {
    title: "权限管理",
    svgIcon: "lock",
    roles: ["admin", "editor"], // 可以在根路由中设置角色
    alwaysShow: true // 将始终显示根菜单
  },
  children: [
    {
      path: "page",
      component: () => import("@/views/permission/page.vue"),
      name: "PagePermission", // 不要忘了写
      meta: {
        title: "页面权限",
        roles: ["admin"] // 或者在子导航中设置角色
      }
    },
    {
      path: "directive",
      component: () => import("@/views/permission/directive.vue"),
      name: "DirectivePermission", // 不要忘了写
      meta: {
        title: "指令权限" // 如果未设置角色,则表示:该页面不需要权限,但会继承根路由的角色
      }
    }
  ]
}

演示

根据上文的动态路由示例代码,反映到页面上就是这样的:

登录 admin 账号时,可以看见这两个页面

admin

但是在登录 editor 账号时,只能看见一个了

editor

开启动态路由功能

项目默认是开启状态

写好了动态路由后,我们在 @/config/route.ts 文件中可以找到是否开启动态路由的开关,源码如下,只需要将下面代码中的 routeSettings.dynamic 设置为 true 就可以开启动态路由功能:

/** 路由配置 */
interface RouteSettings {
  /**
   * 是否开启动态路由功能?
   * 1. 开启后需要后端配合,在查询用户详情接口返回当前用户可以用来判断并加载动态路由的字段(该项目用的是角色 roles 字段)
   * 2. 假如项目不需要根据不同的用户来显示不同的页面,则应该将 dynamic: false
   */
  dynamic: boolean
  /** 当动态路由功能关闭时:
   * 1. 应该将所有路由都写到常驻路由里面(表明所有登录的用户能访问的页面都是一样的)
   * 2. 系统自动给当前登录用户赋值一个没有任何作用的默认角色
   */
  defaultRoles: Array<string>
  /**
   * 是否开启三级及其以上路由缓存功能?
   * 1. 开启后会进行路由降级(把三级及其以上的路由转化为二级路由)
   * 2. 由于都会转成二级路由,所以二级及其以上路由有内嵌子路由将会失效
   */
  thirdLevelRouteCache: boolean
}

const routeSettings: RouteSettings = {
  dynamic: true,
  defaultRoles: ["DEFAULT_ROLE"],
  thirdLevelRouteCache: false
}

export default routeSettings

开启以后,主要是作用于路由守卫 @/router/permission.ts 中的这样一段代码:

await userStore.getInfo()
// 注意:角色必须是一个数组! 例如: ["admin"] 或 ["developer", "editor"]
const roles = userStore.roles
// 生成可访问的 Routes
routeSettings.dynamic ? permissionStore.setRoutes(roles) : permissionStore.setAllRoutes()
// 将 "有访问权限的动态路由" 添加到 Router 中
permissionStore.addRoutes.forEach((route) => router.addRoute(route))

简单来说就是:如果开启该功能,那么通过用户详情接口拿到用户角色数组后,调用 setRoutes 根据角色去过滤动态路由,否则调用 setAllRoutes 拿到所有路由,然后再通过 router.addRoute() 挂载过滤之后的动态路由

关闭动态路由功能

假如,你选择关闭动态路由功能,那么你需要记得将所有路由都写在常驻路由数组里面(虽然写在动态路由数组里也行,因为程序兼容了这种偷懒)

这样的话,所有登陆的用户能访问的页面都是一模一样的了

内容权限

权限函数

@/utils/permission.ts 文件里,有一个 checkPermission 权限判断函数:

import { useUserStoreHook } from "@/store/modules/user"

/** 权限判断函数 */
export const checkPermission = (permissionRoles: string[]): boolean => {
  if (Array.isArray(permissionRoles) && permissionRoles.length > 0) {
    const { roles } = useUserStoreHook()
    return roles.some((role) => permissionRoles.includes(role))
  } else {
    console.error("need roles! Like checkPermission(['admin','editor'])")
    return false
  }
}

向该函数传递一个权限数组,然后它会去对比当前登录用户的角色数组,如果能匹配上,就返回 true

使用方法非常简单,checkPermission 函数配合 v-if 即可:

// 引入
import { checkPermission } from "@/utils/permission"

// 使用
<el-button v-if="checkPermission(['admin'])">按钮</el-button>

更多详细的使用案例,可见 @/views/permission/directive.vue 页面

权限指令

@/directives/permission/index.ts 文件里,写好了权限判断指令 v-permission

import { type Directive } from "vue"
import { useUserStoreHook } from "@/store/modules/user"

/** 权限指令 */
export const permission: Directive = {
  mounted(el, binding) {
    const { value: permissionRoles } = binding
    const { roles } = useUserStoreHook()
    if (Array.isArray(permissionRoles) && permissionRoles.length > 0) {
      const hasPermission = roles.some((role) => permissionRoles.includes(role))
      // hasPermission || (el.style.display = "none") // 隐藏
      hasPermission || el.parentNode?.removeChild(el) // 销毁
    } else {
      throw new Error(`need roles! Like v-permission="['admin','editor']"`)
    }
  }
}

向该指令传递一个权限数组,然后它会去对比当前登录用户的角色数组,如果不能匹配上,就通过 CSS style.display = "none" 将其隐藏或直接销毁

v-permission 已经通过 app.directive() 挂载完成,可以直接在 template 中直接使用:

<el-button v-permission="['admin']">按钮</el-button>

更多详细的使用案例,可见 @/views/permission/directive.vue 页面

后端返回动态路由?

虽然前端控制动态路由是我个人比较喜欢的一种方式,但是也同样有人偏爱后端返回动态路由,这项功能的话是已经在该项目的后续维护计划中,到时候会同步放上配套的用户管理、角色管理、权限管理三个页面

敬请期待~

End

本系列所有手摸手教程

V3 Admin Vite 相关链接