vue权限按钮的实现

鉴权函数

由于下面几种方式都需要用到鉴权函数,所以将其放置在组件外面,供组件或其他文件调用。

javascript 复制代码
// src/utils/hasPermission.js

import { usePermissionStore } from '@/stores'
import array from 'lodash/array'
export const hasPermission = (value, def = true) => {
  // 不传值,默认视为有权限,不做鉴权
  if (!value) {
    return def
  }

  const allCodeList = usePermissionStore().getPermCodeList
  // 如果不是数组,直接判断pinia里的权限数组有没有相同的元素即可
  if (!Array.isArray(value)) {
    return allCodeList.includes(value)
  }
  // intersection是lodash提供的一个方法,用于返回一个所有给定数组都存在的元素组成的数组
  return array.intersection(value, allCodeList).length > 0
}

将可以操作按钮的权限码们以权限码数组的形式存放到了pinia中,所以要先获取pinia中的权限数组再进行判断。

  1. 情况一:没有传值,则无法做权限码的比对,所以可以认为不需要鉴权,返回true即可。
  2. 情况二:传入的值是权限码数值,不是数组,那么可以拿pinia中的权限码数组来检测是否包含该权限码,如果有则认为有权限返回true;否则返回false。
  3. 情况三:传入的值是权限码数组,此时需要拿传入的数组和pinia中的权限码数组进行比较,看看是否存在交集,如果有交集则说明有权限,返回true;否则返回false。

Pinia:

javascript 复制代码
// store/index.js

import { createPinia } from "pinia";

const pinia=createPinia()

export default pinia

export  * from './modules/permission.js'
javascript 复制代码
// store/modules/permission.js

import { defineStore } from 'pinia'
import { getPermissionList } from '@/api/permission'
export const usePermissionStore = defineStore('permission', {
  state: () => ({
    // 权限代码列表
    permCodeList: [101, 102, 778, 779]
  }),
  getters: {
    // 获取
    getPermCodeList() {
      return this.permCodeList
    }
  },
  actions: {
    // 存储
    setPermCodeList(codeList) {
      this.permCodeList = codeList
    },

    // 请求权限码
    async getPermissionCode() {
      const codeList = await getPermissionList()
      this.setPermCodeList(codeList)
    }
  }
})

方式一:v-if搭配鉴权函数实现权限按钮

引入鉴权函数,将该能够操作该按钮的权限码以实参形式传入鉴权函数,v-if根据返回值决定是否加载该按钮结构。

html 复制代码
<template>
  <div>
    <button class="fun-btn" v-if="hasPermission([101])">你能看到我?-v-if函数实现</button>
  </div>
</template>

<script setup>
import { hasPermission } from '@/utils/hasPermission'
</script>

<style scoped lang="scss"></style>

方式二:组件+插槽 实现权限按钮

将按钮以插槽形式放入组件中,给组件以props传递权限码数据,组件中使用鉴权函数来判断传入的权限码是否符合条件,符合则渲染插槽中的结构(按钮),否则不渲染。

html 复制代码
<!--components/PermisBtnComp.vue-->
<template></template>

<script>
// h函数  https://cn.vuejs.org/api/render-function.html#h
// defineComponent-函数签名 https://cn.vuejs.org/api/general.html#definecomponent
import { hasPermission } from '@/utils/hasPermission'
import { renderSlot, h, defineComponent } from 'vue'

export default defineComponent({
  props: {
    value: {
      type: [Number, String, Array],
      default: ''
    }
  },
  setup(props, { slots }) {
    return () => {
      // 根据权限判断结果来确定是否要渲染默认插槽的结构
      // 在 Vue 3 中,通过 renderSlot(父组件传入的插槽内容,插槽名) 函数进行插槽渲染时,需要将其导入并包含在组件的 setup 方法中。
      // 父组件引用子组件时,子组件标签内的默认内容对应的是:renderSlot(slots, 'default')
      // return hasPermission(props.value) ? h('div', renderSlot(slots, 'default')) : null

      // 免去renderSlot https://cn.vuejs.org/guide/extras/render-function.html#rendering-slots
      return hasPermission(props.value) ? h('div', slots.default()) : null
    }
  }
})
</script>

<style scoped lang="scss"></style>

方式三:自定义指令 实现权限按钮

自定义指令其实和第一种很像,就是我们自己出一个"v-if",只不过是专门用来做权限判断的指令。指令传入的数据即为权限码,指令内部根据指令绑定的数据来做鉴权判断。

注意点1:这里的mounted可能较容易实现,但需要注意updated的设计,考虑到有动态修改指令后面的权限码的可能,所以当涉及到指令后面的权限码修改后,要重新做鉴权判断。

注意点2:自定义指令在内部的鉴权判断后,如果不符合条件要移除指令所在的结构;如果符合条件的话,要把之前因为不符合条件被移除的结构要添加回来,也就是回添操作;回添操作为了避免不知道把原来的结构添加回来到哪个位置,这里使用了document.createComment() 即注释先做占位,等需要回添时使用replaceChild() 把注释替换为按钮结构。如果使用的是remove() 或**removeChild()**可能再放回来就不知道放到哪个位置了。

javascript 复制代码
// src/directs/judge-permission.js

import { hasPermission } from '@/utils/hasPermission'

// removeDom、addDom的存在是为了保证自定义指令绑定的权限码数据变化后可以不刷新页面动态添加/移除自定义指令所在的dom节点
// 移除dom节点
const removeDom = (el) => {
  // 把注释点绑在元素上,方便后面使用
  el._placeholderNode = document.createComment('permission-btn')
  // 和父节点关联上,方便后面使用
  el._parentNode = el.parentNode
  // 父节点做子元素替换
  el.parentNode.replaceChild(el._placeholderNode, el)
}

// 把移除的dom节点添加回来
const addDom = (el) => {
  el._parentNode?.replaceChild(el, el._placeholderNode)
}

const mounted = (el, binding) => {
  const permisList = binding.value
  if (!permisList) return
  if (!hasPermission(permisList)) {
    // 移除当前dom节点
    // el.parentNode?.removeChild(el) // 通过父节点删除子自己
    // el.remove() // 自杀
    /* 
        权限值会变,那就涉及到更新后要不要将移除的dom节点添加回来
        为了能够知道添加回来的dom节点放到哪个地方,可以创建注释节点来占位
        在添加时只对那个位置的子元素做替换操作即可
    */
    removeDom(el)
  }
}

const updated = (el, binding) => {
  // 比对前后变化的值,相同不需要后续操作
  let valDiff = binding.value === binding.oldValue
  if (valDiff) return
  // 重新判断改值前后的权限变化,相同不需要后续操作(如:修改前就没权限,修改后依旧没权限,无需操作)
  let oldPermisStatus = hasPermission(binding.oldValue)
  let nowPermisStatus = hasPermission(binding.value)

  if (oldPermisStatus == nowPermisStatus) return
  if (nowPermisStatus) {
    addDom(el)
  } else {
    removeDom(el)
  }
}

export const permisDirect = {
  mounted,
  updated
}

main.js中添加指令:

javascript 复制代码
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import pinia from './stores'
import {permisDirect} from '@/directs/judge-permission'

const app=createApp(App)
app.use(pinia)
app.directive('permis',permisDirect)
app.mount('#app')

根组件效果展示

html 复制代码
<script setup>
import PermisBtnFun from './components/PermisBtnFun.vue'
import PermisBtnComp from './components/PermisBtnComp.vue'
import { ref } from 'vue'

const permis = ref(102)
const permisToggle = () => {
  // 由于@/utils/hasPermission中有判断!value则返回true,表示没传值默认不需要鉴权,返回true放行
  // 所以这里在调整权限值的时候不能改成0,因为!0==true,如果变化前就有权限(true),变化后想没权限改为0,刚好!0是true,弄巧成拙,依旧显示。
  permis.value === 102 ? (permis.value = 100) : (permis.value = 102)
}
</script>

<template>
  <div class="btns-container">
    <!-- v-if搭配函数方式 -->
    <PermisBtnFun />
    <!-- 组件+插槽方式 -->
    <PermisBtnComp :value="101">
      <button class="comp-slot-btn">就你能耐?-组件插槽实现</button>
    </PermisBtnComp>
    <!-- 自定义指令方式 -->
    <button class="vpermis-btn" v-permis="permis">你依旧能看得到我?-自定义指令实现</button>
    <!-- 修改自定义指令绑定的权限值,测试自定义指令updated中的移除和回添的操作 -->
    <button @click="permisToggle">修改自定义指令绑定的权限值</button>
  </div>
</template>

<style scoped>
.btns-container {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  height: 400px;
}
/* ::v-deep usage as a combinator has been deprecated. Use :deep(<inner-selector>) instead. */
:deep(.fun-btn) {
  background-color: #acd384;
  color: #fff;
}
.vpermis-btn {
  background-color: #f38585;
  color: #fff;
}
.comp-slot-btn {
  background-color: #5aa3f8;
  color: #fff;
}
</style>
相关推荐
庸俗今天不摸鱼11 分钟前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX1873011 分钟前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下18 分钟前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox28 分钟前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞31 分钟前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行31 分钟前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_5937581032 分钟前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周35 分钟前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队1 小时前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei1 小时前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯