鉴权函数
由于下面几种方式都需要用到鉴权函数,所以将其放置在组件外面,供组件或其他文件调用。
// src/utils/hasPermission.jsimport { 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中的权限数组再进行判断。
- 情况一:没有传值,则无法做权限码的比对,所以可以认为不需要鉴权,返回true即可。
- 情况二:传入的值是权限码数值,不是数组,那么可以拿pinia中的权限码数组来检测是否包含该权限码,如果有则认为有权限返回true;否则返回false。
- 情况三:传入的值是权限码数组,此时需要拿传入的数组和pinia中的权限码数组进行比较,看看是否存在交集,如果有交集则说明有权限,返回true;否则返回false。
Pinia:
// store/index.jsimport { createPinia } from "pinia";const pinia=createPinia()export default piniaexport * from './modules/permission.js'
// store/modules/permission.jsimport { 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根据返回值决定是否加载该按钮结构。
<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传递权限码数据,组件中使用鉴权函数来判断传入的权限码是否符合条件,符合则渲染插槽中的结构(按钮),否则不渲染。
<!--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-slotsreturn 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()可能再放回来就不知道放到哪个位置了。
// src/directs/judge-permission.jsimport { 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.valueif (!permisList) returnif (!hasPermission(permisList)) {// 移除当前dom节点// el.parentNode?.removeChild(el) // 通过父节点删除子自己// el.remove() // 自杀/* 权限值会变,那就涉及到更新后要不要将移除的dom节点添加回来为了能够知道添加回来的dom节点放到哪个地方,可以创建注释节点来占位在添加时只对那个位置的子元素做替换操作即可*/removeDom(el)}
}const updated = (el, binding) => {// 比对前后变化的值,相同不需要后续操作let valDiff = binding.value === binding.oldValueif (valDiff) return// 重新判断改值前后的权限变化,相同不需要后续操作(如:修改前就没权限,修改后依旧没权限,无需操作)let oldPermisStatus = hasPermission(binding.oldValue)let nowPermisStatus = hasPermission(binding.value)if (oldPermisStatus == nowPermisStatus) returnif (nowPermisStatus) {addDom(el)} else {removeDom(el)}
}export const permisDirect = {mounted,updated
}
main.js中添加指令:
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')
根组件效果展示
<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>