2022-01-27 ·前端·Vue自定义指令权限判断框架

全局动态权限判断(Vue指令)

目标

在Vue组件中,使用v-has指令即可控制某一元素的权限。

后端提示权限变化的时候,前端去判断这些元素还是否有权限,并作出相应的操作。

实现

创建一个js文件,定义权限判断方法,以及权限判断自定义指令:

/**
 * name: v-has 动态变化
 * desc: 禁止嵌套使用v-has,不然你就自己加一下子节点的判断或者换其他方案吧
 */
import Vue from 'vue'

// 项目检查权限检查的方法,定义在Vue原型链上,方便全局调用
Vue.prototype.$_has = function (id) {
  const permission = JSON.parse(localStorage.getItem('APP_PERMISSION'))
  if (!Array.isArray(permission))
    return false

  return !!permission.filter(per => per.id === id).length
}

// 收集所有需要权限判断的DOM节点
const permissionNodeConf = new Map()

// 自定义Vue指令
Vue.directive('has', {
  bind(el, binding) {
    const permissionKey = binding.value
    const hasPermission = Vue.prototype.$_has(permissionKey)

    // 将Dom节点作为key,value为相应的配置
    // 为每一个Dom节点创建一个对应的注释节点
    permissionNodeConf.set(el, {
      permissionKey,
      hasPermission,
      comment: document.createComment('permission')
    })

    if (!hasPermission) {
      // 这里使用setTimeout是因为第一次绑定的时候还没有parentNode
      // 如果没有权限就将Dom节点换成注释节点
      setTimeout(() => {
        el.parentNode.replaceChild(permissionNodeConf.get(el).comment, el)
      }, 0)
    }
  },
  unbind(el) {
    // unbind 的时候从Map中移除相应的Dom
    permissionNodeConf.delete(el)
  }
})

export {
  permissionNodeConf
}
/**
 * name: v-has 动态变化
 * desc: 禁止嵌套使用v-has,不然你就自己加一下子节点的判断或者换其他方案吧
 */
import Vue from 'vue'

// 项目检查权限检查的方法,定义在Vue原型链上,方便全局调用
Vue.prototype.$_has = function (id) {
  const permission = JSON.parse(localStorage.getItem('APP_PERMISSION'))
  if (!Array.isArray(permission))
    return false

  return !!permission.filter(per => per.id === id).length
}

// 收集所有需要权限判断的DOM节点
const permissionNodeConf = new Map()

// 自定义Vue指令
Vue.directive('has', {
  bind(el, binding) {
    const permissionKey = binding.value
    const hasPermission = Vue.prototype.$_has(permissionKey)

    // 将Dom节点作为key,value为相应的配置
    // 为每一个Dom节点创建一个对应的注释节点
    permissionNodeConf.set(el, {
      permissionKey,
      hasPermission,
      comment: document.createComment('permission')
    })

    if (!hasPermission) {
      // 这里使用setTimeout是因为第一次绑定的时候还没有parentNode
      // 如果没有权限就将Dom节点换成注释节点
      setTimeout(() => {
        el.parentNode.replaceChild(permissionNodeConf.get(el).comment, el)
      }, 0)
    }
  },
  unbind(el) {
    // unbind 的时候从Map中移除相应的Dom
    permissionNodeConf.delete(el)
  }
})

export {
  permissionNodeConf
}

接下来是main.js中引用

import { permissionNodeConf } from './directive/v-has/index'

// new Vue 实例
vm = new Vue({
  el: '#app',
  // router, store, ...
})

// 初始化权限监听
Global.initPermissionWatcher(vm, permissionNodeConf)
import { permissionNodeConf } from './directive/v-has/index'

// new Vue 实例
vm = new Vue({
  el: '#app',
  // router, store, ...
})

// 初始化权限监听
Global.initPermissionWatcher(vm, permissionNodeConf)

Global.js:

Global.initPermissionWatcher = function (vm, nodeMap) {
  // 监听Vuex中权限变化
  vm.$watch('$store.state.user.app_permission_change', () => {
    // 遍历收集来的Dom节点,通过对应的配置上的key判断是否有权限
    let visiblePermissionChanged = false
    nodeMap.forEach((conf, el) => {
      const hasPermission = vm.$_has(conf.permissionKey)
      if (hasPermission !== conf.hasPermission) {
        // 权限发生变化
        // keepAlive中的也会触发,也可以使用vNode组件实例的_inactive判断判断是否在当前页面
        visiblePermissionChanged = document.contains(el) || document.contains(conf.comment)
        if (hasPermission) {
          // 有权限,把注释节点替换回来
          conf.comment.parentNode.replaceChild(el, conf.comment)
        }
        else {
          // 无权限,将Dom节点替换成注释节点
          el.parentNode.replaceChild(conf.comment, el)
        }
        conf.hasPermission = hasPermission
      }
    })
    // 如果发现有变化的,提示页面权限变化了
    if (visiblePermissionChanged)
      vm.$message.info('页面权限发生变化!')
  })
}
Global.initPermissionWatcher = function (vm, nodeMap) {
  // 监听Vuex中权限变化
  vm.$watch('$store.state.user.app_permission_change', () => {
    // 遍历收集来的Dom节点,通过对应的配置上的key判断是否有权限
    let visiblePermissionChanged = false
    nodeMap.forEach((conf, el) => {
      const hasPermission = vm.$_has(conf.permissionKey)
      if (hasPermission !== conf.hasPermission) {
        // 权限发生变化
        // keepAlive中的也会触发,也可以使用vNode组件实例的_inactive判断判断是否在当前页面
        visiblePermissionChanged = document.contains(el) || document.contains(conf.comment)
        if (hasPermission) {
          // 有权限,把注释节点替换回来
          conf.comment.parentNode.replaceChild(el, conf.comment)
        }
        else {
          // 无权限,将Dom节点替换成注释节点
          el.parentNode.replaceChild(conf.comment, el)
        }
        conf.hasPermission = hasPermission
      }
    })
    // 如果发现有变化的,提示页面权限变化了
    if (visiblePermissionChanged)
      vm.$message.info('页面权限发生变化!')
  })
}

接下来,你只要改变vuex中的app_permission_change的值就可以实现页面动态权限控制了。

比如后端通过websocket通知用户权限变化,这时候,重新去请求到用户权限,得到响应后,改变app_permission_change的值,即可。

总结

一开始我通过判断页面上是否存在Dom去删除数组中的Dom元素,但是后来我想到自定义指令还有unbind回调,就在unbind删除了元素,这样也解决了嵌套的指令使用无法使用的问题(嵌套后的子节点,不论是comment和原来的Dom节点,都不会在document上,所以会被我删除)。

还有一点,Vue页面很多会有keepAlive,这些被缓存的页面当然不会触发unbind,所以,提示用户权限变更的判断逻辑我改成了:只有document中存在的Dom并且这些Dom发生了权限变化,才会提示用户。

后来我想,能不能再binding的时候,获取到组件实例,在组件实例上监听Vuex 状态呢,然后,每个el,都会创建一个watcher?应该也是一种方法吧


返回