Vue3自定义指令 html 元素级别的鉴权

文章介绍了在Vue3中如何改造生命周期钩子,并利用这些钩子进行权限管理。通过在前端获取并存储操作权限数据,结合vue-router的路由守卫,实现了在进入指定路由前的权限校验。此外,还详细展示了如何创建一个自定义指令,用于动态控制按钮、链接等元素的展示和禁用状态,以及提供权限提示功能。
摘要由CSDN通过智能技术生成

Vue3 改造后的生命周期钩子变成了七个,而且名称变得比较好记了。

在这里插入图片描述

前端获取到操作权限数据之后,进行转换并存储在sessionStorage中,转换后的权限数据大概如下

{
  "UserMgr": ["add", "edit"],
  "RoleMgr": ["add", "edit"]
}

通过 vue-router 的路由守卫,在进入指定的路由前先获取to路由的路由名,然后存储在localStorage中
自定义指令先从localStorage中获取to路由名,然后通过这个to路由名,找到对应的功能权限标识
自定义指令将第 2 步获取到操作权限标识数组和传入的操作权限标识比较,有相同的值,则有权限,否则表示没有权限

<script lang="ts">
export default {
  name: 'PermissionDirective01',
}
</script>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { Directive, nextTick, ref } from 'vue'
import MyButton from './MyButton.vue'

let hasPermissions = ref<string[]>([])
interface CustomHTMLElement extends HTMLElement {
  _clickCall?: (evt: MouseEvent) => boolean | undefined
  // 存储原始的display方式
  originalDisplay?: string
}
function doPermissionValidate(el: CustomHTMLElement, permissionFlag: string, arg: string) {
  let clickCall = el._clickCall as (evt: MouseEvent) => boolean | undefined
  if (!clickCall) {
    /**
     * 自定义的点击事件必须在一开始的时候就添加上去,保证鉴权事件在所有事件之前
     *
     */
    clickCall = (evt: MouseEvent) => {
      if (hasPermissions.value.indexOf(permissionFlag) < 0) {
        /*
        阻止事件冒泡且同时阻止同类事件的触发(如:原本可能绑定了多个click事件,
        stopImmediatePropagation执行之后能确保当前click事件之后的其他click事件都不会被触发)
         */
        evt.stopImmediatePropagation()
        evt.stopPropagation()
        evt.preventDefault()
        if (arg === 'tips') {
          ElMessage({
            showClose: true,
            message: '没权限执行该操作',
            type: 'warning',
          })
        }
        return false
      }
    }
    el.addEventListener('click', clickCall, false)
    el._clickCall = clickCall
  }
  if (hasPermissions.value.indexOf(permissionFlag) < 0) {
    // 之所以在nextTick中执行,是为了解决el-button这种UI库组件,样式设置不上去的问题
    nextTick(() => {
      if (!el.classList.contains('is-disabled')) {
        el.classList.add('is-disabled')
      }
    })
    switch (arg) {
      case 'hidden': {
        el.originalDisplay = el.style.display
        el.style.display = 'none'
        break
      }
      case 'disabled': {
        // 之所以在nextTick中执行,是为了解决el-button这种UI库组件,属性设置不上去的问题
        nextTick(() => {
          el.setAttribute('disabled', 'disabled')
        })
      }
    }
  } else {
    if (el.style.display === 'none') {
      el.style.display = 'block'
    }
    if (el.getAttribute('disabled')) {
      el.removeAttribute('disabled')
    }
    el.classList.remove('is-disabled')
  }
}
const vPermission: Directive<CustomHTMLElement, string> = {
  created(el, binding, vnode, prevVnode) {
    const { value: permissionFlag, arg = 'disabled' } = binding
    doPermissionValidate(el, permissionFlag, arg)
  },
  updated(el, binding, vnode, prevVnode) {
    const { value: permissionFlag, arg = 'disabled' } = binding
    doPermissionValidate(el, permissionFlag, arg)
  },
  beforeUnmount(el, binding, vnode, prevVnode) {
    el._clickCall && el.removeEventListener('click', el._clickCall, false)
  },
}
function sayHello() {
  alert('你好')
}
function addOrDelPermission(type: 'add' | 'del') {
  hasPermissions.value = []
  if (type === 'add') {
    hasPermissions.value.push('permissionFlag')
  }
}
function containerClick() {
  alert('背景点击')
}
</script>

<template>
  <div>
    <button @click="addOrDelPermission('add')">添加权限</button>
    <button @click="addOrDelPermission('del')">去除权限</button>
    <div>hasPermissions: {{ hasPermissions }}</div>
    <div>原生按钮button(事件通过vue的@click添加)</div>
    <div @click="containerClick" style="background-color: lightpink">
      <div>
        <button v-permission="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则禁用)</button>
      </div>
      <div>
        <button v-permission:hidden="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则隐藏)</button>
      </div>
      <div>
        <button v-permission:tips="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限给出tips提示)</button>
      </div>
    </div>
    <div>原生超链接a(事件通过vue的@click添加)</div>
    <div @click="containerClick" style="background-color: lightpink">
      <div>
        <a v-permission="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则禁用)</a>
      </div>
      <div>
        <a v-permission:hidden="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则隐藏)</a>
      </div>
      <div>
        <a v-permission:tips="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限给出tips提示)</a>
      </div>
    </div>
    <div>原生span(事件通过vue的@click添加)</div>
    <div @click="containerClick" style="background-color: lightpink">
      <div>
        <span v-permission="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则禁用)</span>
      </div>
      <div>
        <span v-permission:hidden="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则隐藏)</span>
      </div>
      <div>
        <span v-permission:tips="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限给出tips提示)</span>
      </div>
    </div>
    <div>el-button</div>
    <div @click="containerClick" style="background-color: lightpink">
      <div>
        <el-button v-permission="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限则禁用)</el-button>
      </div>
      <div>
        <el-button v-permission:hidden="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限则隐藏)</el-button>
      </div>
      <div>
        <el-button v-permission:tips="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限给出tips提示)</el-button>
      </div>
    </div>
    <div>自封装组件</div>
    <div @click="containerClick" style="background-color: lightpink">
      <div>
        <MyButton v-permission="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限则禁用)</MyButton>
      </div>
      <div>
        <MyButton v-permission:hidden="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限则隐藏)</MyButton>
      </div>
      <div>
        <MyButton v-permission:tips="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限给出tips提示)</MyButton>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
button {
  background-color: lightseagreen;
  padding: 10px;
  border: 1px solid #333;
  border-radius: 5px;
}
button[disabled],
a[disabled],
span[disabled] {
  background-color: #666;
}
</style>

var ownPermission = ['user', 'order'];
function toolPermission(el, permission) {
  if (permission && !ownPermission.includes(permission)) {
    el.parentNode && el.parentNode.removeChild(el); // 关键代码, 没有权限则删除元素
  }
}

app.directive('permission', {
  mounted(el, binding) {
    toolPermission(el, binding.value)
  },
  updated(el, binding) {
    toolPermission(el, binding.value)
  }
})

具体使用

<div>
  <button v-permission="'user'">用户模块</button>
  <button v-permission="'order'">订单模块</button>
  <button v-permission="'goods'">商品模块</button>
</div>

批量注册指令

新建 directives/directive.js 文件
// 导入指令定义文件
import debounce from './debounce'
import throttle from './throttle'
// 集成一起
const directives = {
  debounce,
  throttle,
}
//批量注册
export default {
  install(Vue) {
    Object.keys(directives).forEach((key) => {
      Vue.directive(key, directives[key])
    })
  },
}
在 main.js 引入,并Vue.use() 调用完成批量注册。
import Vue from 'vue'
import Directives from './directives/directive.js'
Vue.use(Directives)

vue3 main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import lazyPlugin from 'vue3-lazy'
import loadingDirective from '@/components/base/loading/directive'
import noResultDirective from '@/components/base/no-result/directive'
import { load, saveAll } from '@/assets/js/array-store'
import { FAVORITE_KEY, PLAY_KEY } from '@/assets/js/constant'
import { processSongs } from '@/service/song'


createApp(App).use(store).use(router).use(lazyPlugin, {
  loading: require('@/assets/images/default.png')      //   lazyPlugin  配置默认图片 基于当前的懒加载插件
}).directive('loading', loadingDirective).directive('no-result', noResultDirective).mount('#app') // 使用directive API 做全局注册 //
// 相当于上面有两个指令的注册 //

// 这里分别注册了 load 和 404页面的 指令 //

v-clickoutside

<script lang="ts">
export default {
  name: 'ClickoutsideDemo',
}
</script>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { Directive, ref } from 'vue'
interface ClickoutsideHTMLElement extends HTMLElement {
  _clickEvt: (e: MouseEvent) => void
}
const vClickoutside: Directive<ClickoutsideHTMLElement> = {
  created(el, binding) {
    function clickEvt(e: MouseEvent) {
      if (!el.contains(e.target as Node)) {
        el.style.display = 'none'
        binding.value && binding.value()
      }
    }
    el._clickEvt = clickEvt
    document.addEventListener('click', clickEvt, false)
  },
  beforeUnmount(el) {
    el._clickEvt && document.removeEventListener('click', el._clickEvt)
  },
}
const testRef = ref<HTMLDivElement>()
function showAlert() {
  ElMessage('点在了ClickoutsideDemo之外的地方,ClickoutsideDemo将隐藏,3秒后会重新显示')

  setTimeout(() => {
    if (testRef.value) {
      testRef.value.style.display = 'inline-block'
    }
  }, 3000)
}
</script>

<template>
  <div v-clickoutside="showAlert" class="test" ref="testRef">ClickoutsideDemo</div>
</template>

<style lang="scss" scoped>
.test {
  display: inline-block;
  border: 1px solid red;
  padding: 10px;
  border-radius: 5px;
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值