VUE自定义指令-权限校验

使用场景:

web后台项目都会有权限控制,可以分为两类,分别为页面权限操作权限

  • 页面权限:是指当前账号是否可以看到此页面;
  • 操作权限:是指当前账号在此页面可以做哪些操作

很显然,账号必须先要能看到此页面,才能说在此页面能做的操作。如果连页面都看不到,具有的操作毫无意义,一般查看操作就是页面的权限,只能分配了查看操作的权限,才能在此页面进行其它操作。

本文章只探讨操作权限的解决方案,页面权限后面再新建文章

账号权限控制分类:

账号权限控制主要分为账号具有的角色账号具有的权限标识

  • 角色 : 一个账号可以绑定多个角色,例如useradmintest等,不同的角色拥有不同的权限标识
  • 权限标识: 一个为有权限的操作,例如sys:user:selectsys:user:add分别表示可以对用户管理这个页面进行查看及新增操作。

前端可以根据这两个权限信息来校验用户具有的页面权限操作权限

例如(权限标识控制)

一个用户管理页面,分别有查询、新增、修改、删除四个操作,且新增、修改、删除分别对应一个操作按钮,此4个操作分别对应的权限标识为sys:user:selectsys:user:createsys:user:updatesys:user:delete。当登录的账号具有sys:user:select权限标识时,才可以看到此页面,否则无法进入此页面。

  • 当用户具有sys:user:create权限标识时,才能看到(或者启用)新增按钮,实现新增操作。否则此按钮隐藏(或者禁用)

  • 当用户具有sys:user:update权限标识时,才能看到(或者启用)更新按钮,实现更新操作。否则此按钮隐藏(或者禁用)

  • 当用户具有sys:user:delete权限标识时,才能看到(或者启用)删除按钮,实现删除操作。否则此按钮隐藏(或者禁用)

这样同一个管理后台,不同权限的账号登录进来看到的效果及其操作都是不一样的

角色控制也是相同的原理。比如user角色具有查看更新的权限,admin具有全部的操作权限

复杂场景

一个操作,需要多个接口才能完成。还是以用户管理举例,例如搜索条件有部门的下拉框,而下拉框中的数据是通过调用接口来得到的。那么查询这个操作,除了sys:user:select权限标识外,还需要sys:org:list权限标识。新增操作也是如此,新增用户的时候也需要选择部门信息,那么除了sys:user:create权限标识外还需要其它权限标识才能完整的实现操作。

总结:

  1. 权限标识一般和后台接口绑定,一个权限标识对应一个接口
  2. 一个操作可能需要多个接口才能完成功能

前端操作权限的本质

对于前端而言,操作权限的本质就是校验账号的具有的角色或者权限标识是否具有此按钮(或者dom)需要的角色或者权限标识,通过这个校验的结果来控制删除/禁用按钮(或者dom)或者显示/启用按钮(或者dom)

例子

例如有一个新增的按钮,需要有sys:user:create权限标识才能看到此按钮,伪代码如下

  if(isPerms('sys:user:create')){
     <!-- 有权限,显示按钮  -->
  	 <button>新增用户</button>
  }else{
       <!-- 没有权限不显示按钮  -->
  }

从上面的结果来看,需要我们收到的操作dom元素,实现对dom的显示/隐藏或者启用/禁用。

解决方案

用vuex存储用户的所有角色及权限标识,并封装成判断的方法,VUE实例上挂载全局属性方法,方便所有组件调用,同时封装自定义指令,指令中的判断逻辑也是调用此判断方法。

权限校验工具方法

permissionUtils.js

/**
 * 权限校验工具
 */
import store from "@/store";

/**
 * 权限校验的类型
 * @type {{ROLE: string, PERMS: string}}
 */
export const PermTypeEnum = {
    PERMS: 'perms',
    ROLE: 'role'
}

/**
 * 权限校验的操作
 * @type {{OR: string, AND: string}}
 */
export const CheckOperateEnum = {
    AND: 'and',
    OR: 'or'
}

/**
 *  检查是否具有全部资源权限
 * @param checkPerms {Array<String>}  检查的资源
 * @param allPerms {Array<String>}   具有的资源
 * @returns {Boolean}  true 具有权限  false 没有权限
 */
export const checkPermsAll = (checkPerms, allPerms) => (checkPerms || []).every((perm) => allPerms.includes(perm))

/**
 *  检查是否具有任意资源权限
 * @param checkPerms {Array<String>}  检查的资源
 * @param allPerms {Array<String>}   具有的资源
 * @returns {Boolean}  true 具有权限  false 没有权限
 */
export const checkPermsAny = (checkPerms, allPerms) => (checkPerms || []).some((perm) => allPerms.includes(perm))

/**
 * 权限校验
 * @param checkPerms {String|Array} 需要检查的权限
 * @param permType  权限的类型,角色或者权限标识
 * @param checkOperate  操作类型,且或者 是 或
 * @returns {Boolean}   是否具有权限, true 有权限, false 无权限
 */
export const checkPermissions = (checkPerms,
                                 permType = PermTypeEnum.PERMS,
                                 checkOperate = CheckOperateEnum.AND) => {
    //得到用户具有的权限集合
    const perms = store.getters.perms
    // 得到用户具有的角色集合
    const roles = store.getters.roles.map(role => role.roleId)
    //权限判断结果
    const allPerms = []
    //开始判断是 角色权限校验 还是 权限校验 , 再判断是 或 操作 还是 与 操作
    if (permType === PermTypeEnum.ROLE) {
        allPerms.push(...roles)
    } else {
        allPerms.push(...perms)
    }
    //这里需要对校验的权限标记或者角色进行转换为数组
    const needCheckPerms = []
    if (checkPerms) {
        if ((checkPerms instanceof Array) && (checkPerms.length > 0)) {
            //如果输入数据为数组,则直接加入
            needCheckPerms.push(...checkPerms)
        } else if ((typeof checkPerms) === 'string') {
            needCheckPerms.push(checkPerms)
        }
    }
    let hasPerm = false;
    // 如果包含特殊标识,则视为管理员,全部放行
    if (allPerms.includes('*')) {
        hasPerm = true;
    } else {
        // 判断是 或 操作 还是 与 操作 , 如果给了or的修饰符,则表示为任意权限
        if (checkOperate === CheckOperateEnum.OR) {
            hasPerm = checkPermsAny(needCheckPerms, allPerms)
        } else {
            hasPerm = checkPermsAll(needCheckPerms, allPerms)
        }
    }
    return hasPerm;
}

export default {
    PermTypeEnum,
    CheckOperateEnum,
    checkPermissions
}

挂载到全局属性

这里可以想到vue提供的v-if指令,再结合全局方法属性配合实现,在全局方法属性中提供权限校验,用v-if指令来实现操作dom。
例子如下:

import {checkPermissions} from "@/utils/permissionUtils";
/**
 * 挂载全局 权限判断函数
 */
Vue.prototype.isPerms = checkPermissions

使用

 <button v-if="isPerms(['sys:user:create','sys:org:list'])">新增用户</button>

此方案可以实现,但过于单一,不过灵活。如果需要扩展角色权限校验或者采用任意权限无法很好的扩展。

自定义权限指令 (推荐

注册vue的自定义指令,在自定义指令的钩子函数中提供绑定的参数及其dom,这样就可以进行dom操作。在自定义指令中dom元素挂载在父dom后,进行权限校验,如果权限不满足,则删除此dom
自定义指令代码如下:

import {CheckOperateEnum, checkPermissions, PermTypeEnum} from '@/utils/permissionUtils'

/**
 * 权限校验命令,支持:
 * * 角色权限校验
 * * 权限标识校验
 * * 输入为字符串或者数组
 * * 任意权限或者全部权限校验
 * 使用例子 :
 *  权限标识校验:
 *    全部存在  v-permission="['sys:user:create','sys:user:list']" 或者 v-permission="'sys:user:create'"
 *    任意存在  v-permission.or="['sys:user:create','sys:user:list']"
 *
 *  角色权限校验:
 *    全部存在  v-permission:role="['user1','user2']" 或者 v-permission="'user'"
 *    任意任意  v-permission:role.or="['user1','user2']"
 *
 */
export default {

    //被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
    /*
    el:指令所绑定的元素,可以用来直接操作 DOM。
    binding:一个对象,包含以下 property:
        name:指令名,不包括 v- 前缀。
        value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
        oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
        expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
        arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
        modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
    vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
    */
    inserted(el, binding, vnode) {
        // 得到指令的绑定值,此值为js计算完成后的值,当前为需要的权限
        const {value, arg, modifiers} = binding
        if (value) {
            //权限判断结果 , 这里会处理字符串及数组的情况
            const hasPermission = checkPermissions(value,
                (arg === 'role') ? PermTypeEnum.ROLE : PermTypeEnum.PERMS,
                modifiers.or ? CheckOperateEnum.OR : CheckOperateEnum.AND
            )
            if (!hasPermission) {
                //如果没有权限则直接删除此节点
                el.parentNode && el.parentNode.removeChild(el)
            }
        } else {
            throw new Error(`缺少权限标识 例如 v-permission="['sys:user:create','sys:user:list']" 或者 v-permission:role="['user']" `)
        }
    }
}

挂载指令

  Vue.directive('permission', permission)

使用例子

  <div v-permission:role.or="['admin','role']">
      <span>admin和role角色都能看到我</span>
    </div>

    <div v-permission:role="['admin','user']">
      <span>必须同时有admin和user角色才能看到</span>
    </div>

    <div v-permission="['sys:user:select','sys:org:list']">
      <span>必须同时有sys:user:select和sys:org:list权限标识才能看到</span>
    </div>

    <div v-permission.or="['sys:user:select','sys:org:list']">
      <span>sys:user:select和sys:org:list权限标识都能看到我</span>
    </div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值