共计 4880 个字符,预计需要花费 13 分钟才能阅读完成。
最近的面试中有一个面试官问我按钮级别的权限怎么管制,我说间接 v-if
啊,他说不够好,我说咱们我的项目中按钮级别的权限管制状况不多,所以 v-if
就够了,他说不够通用,最初他对我的评估是做过很多货色,然而都不够深刻,好吧,那明天咱们就来深刻深刻。
因为我本人没有相干实际,所以接下来就从这个有 16.2k
星星的后盾管理系统我的项目 Vue vben admin 中看看它是如何做的。
获取权限码
要做权限管制,必定须要一个 code
,无论是权限码还是角色码都能够,个别后端会一次性返回,而后全局存储起来就能够了,Vue vben admin
是在登录胜利当前获取并保留到全局的 store
中:
import {defineStore} from 'pinia'; | |
export const usePermissionStore = defineStore({state: () => ({ | |
// 权限代码列表 | |
permCodeList: [],}), | |
getters: { | |
// 获取 | |
getPermCodeList(){return this.permCodeList;}, | |
}, | |
actions: { | |
// 存储 | |
setPermCodeList(codeList) {this.permCodeList = codeList;}, | |
// 申请权限码 | |
async changePermissionCode() {const codeList = await getPermCode(); | |
this.setPermCodeList(codeList); | |
} | |
} | |
}) |
接下来它提供了三种按钮级别的权限管制形式,一一来看。
函数形式
应用示例如下:
<template> | |
<a-button v-if="hasPermission(['20000','2000010'])" color="error" class="mx-4"> | |
领有[20000,2000010]code 可见 | |
</a-button> | |
</template> | |
<script lang="ts"> | |
import {usePermission} from '/@/hooks/web/usePermission'; | |
export default defineComponent({setup() {const { hasPermission} = usePermission(); | |
return {hasPermission}; | |
}, | |
}); | |
</script> |
实质上就是通过v-if
,只不过是通过一个对立的权限判断办法hasPermission
:
export function usePermission() {function hasPermission(value, def = true) { | |
// 默认视为有权限 | |
if (!value) {return def;} | |
const allCodeList = permissionStore.getPermCodeList; | |
if (!isArray(value)) {return allCodeList.includes(value); | |
} | |
// intersection 是 lodash 提供的一个办法,用于返回一个所有给定数组都存在的元素组成的数组 | |
return (intersection(value, allCodeList)).length > 0; | |
return true; | |
} | |
} |
很简略,从全局 store
中获取以后用户的权限码列表,而后判断其中是否存在以后按钮须要的权限码,如果有多个权限码,只有满足其中一个就能够。
组件形式
除了通过函数形式应用,也能够应用组件形式,Vue vben admin
提供了一个 Authority
组件,应用示例如下:
<template> | |
<div> | |
<Authority :value="RoleEnum.ADMIN"> | |
<a-button type="primary" block> 只有 admin 角色可见 </a-button> | |
</Authority> | |
</div> | |
</template> | |
<script> | |
import {Authority} from '/@/components/Authority'; | |
import {defineComponent} from 'vue'; | |
export default defineComponent({components: { Authority}, | |
}); | |
</script> |
应用 Authority
包裹须要权限管制的按钮即可,该按钮须要的权限码通过 value
属性传入,接下来看看 Authority
组件的实现。
<script lang="ts"> | |
import {defineComponent} from 'vue'; | |
import {usePermission} from '/@/hooks/web/usePermission'; | |
import {getSlot} from '/@/utils/helper/tsxHelper'; | |
export default defineComponent({ | |
name: 'Authority', | |
props: { | |
value: {type: [Number, Array, String], | |
default: '', | |
}, | |
}, | |
setup(props, { slots}) {const { hasPermission} = usePermission(); | |
function renderAuth() {const { value} = props; | |
if (!value) {return getSlot(slots); | |
} | |
return hasPermission(value) ? getSlot(slots) : null; | |
} | |
return () => {return renderAuth(); | |
}; | |
}, | |
}); | |
</script> |
同样还是应用 hasPermission
办法,如果以后用户存在按钮须要的权限码时就一成不变渲染 Authority
包裹的内容,否则就啥也不渲染。
指令形式
最初一种就是指令形式,应用示例如下:
<a-button v-auth="'1000'" type="primary" class="mx-4"> 领有 code ['1000']权限可见 </a-button>
实现如下:
import {usePermission} from '/@/hooks/web/usePermission'; | |
function isAuth(el, binding) {const { hasPermission} = usePermission(); | |
const value = binding.value; | |
if (!value) return; | |
if (!hasPermission(value)) {el.parentNode?.removeChild(el); | |
} | |
} | |
const mounted = (el, binding) => {isAuth(el, binding); | |
}; | |
const authDirective = { | |
// 在绑定元素的父组件 | |
// 及他本人的所有子节点都挂载实现后调用 | |
mounted, | |
}; | |
// 注册全局指令 | |
export function setupPermissionDirective(app) {app.directive('auth', authDirective); | |
} |
只定义了一个 mounted
钩子,也就是在绑定元素挂载后调用,仍旧是应用 hasPermission
办法,判断以后用户是否存在通过指令插入的按钮须要的权限码,如果不存在,间接移除绑定的元素。
很显著,Vue vben admin
的实现有两个问题,一是不能动静更改按钮的权限,二是动静更改以后用户的权限也不会失效。
解决第一个问题很简略,因为上述只有删除元素的逻辑,没有加回来的逻辑,那么减少一个 updated
钩子:
app.directive("auth", {mounted: (el, binding) => { | |
const value = binding.value | |
if (!value) return | |
if (!hasPermission(value)) { | |
// 挂载的时候没有权限把元素删除 | |
removeEl(el) | |
} | |
}, | |
updated(el, binding) { | |
// 按钮权限码没有变动,不做解决 | |
if (binding.value === binding.oldValue) return | |
// 判断用户本次和上次权限状态是否一样,一样也不必做解决 | |
let oldHasPermission = hasPermission(binding.oldValue) | |
let newHasPermission = hasPermission(binding.value) | |
if (oldHasPermission === newHasPermission) return | |
// 如果变成有权限,那么把元素增加回来 | |
if (newHasPermission) {addEl(el) | |
} else { | |
// 如果变成没有权限,则把元素删除 | |
removeEl(el) | |
} | |
}, | |
}) | |
const hasPermission = (value) => {return [1, 2, 3].includes(value) | |
} | |
const removeEl = (el) => { | |
// 在绑定元素上存储父级元素 | |
el._parentNode = el.parentNode | |
// 在绑定元素上存储一个正文节点 | |
el._placeholderNode = document.createComment("auth") | |
// 应用正文节点来占位 | |
el.parentNode?.replaceChild(el._placeholderNode, el) | |
} | |
const addEl = (el) => { | |
// 替换掉给本人占位的正文节点 | |
el._parentNode?.replaceChild(el, el._placeholderNode) | |
} |
次要就是要把父节点保存起来,不然想再增加回去的时候获取不到原来的父节点,另外删除的时候创立一个正文节点给本人占位,这样下次想要回去能晓得本人原来在哪。
第二个问题的起因是批改了用户权限数据,然而不会触发按钮的从新渲染,那么咱们就须要想方法能让它触发,这个能够应用 watchEffect
办法,咱们能够在 updated
钩子里通过这个办法将用户权限数据和按钮的更新办法关联起来,这样当用户权限数据扭转了,能够主动触发按钮的从新渲染:
import {createApp, reactive, watchEffect} from "vue" | |
const codeList = reactive([1, 2, 3]) | |
const hasPermission = (value) => {return codeList.includes(value) | |
} | |
app.directive("auth", {updated(el, binding) {let update = () => { | |
let valueNotChange = binding.value === binding.oldValue | |
let oldHasPermission = hasPermission(binding.oldValue) | |
let newHasPermission = hasPermission(binding.value) | |
let permissionNotChange = oldHasPermission === newHasPermission | |
if (valueNotChange && permissionNotChange) return | |
if (newHasPermission) {addEl(el) | |
} else {removeEl(el) | |
} | |
}; | |
if (el._watchEffect) {update() | |
} else {el._watchEffect = watchEffect(() => {update() | |
}) | |
} | |
}, | |
}) |
将 updated
钩子里更新的逻辑提取成一个 update
办法,而后第一次更新在 watchEffect
中执行,这样用户权限的响应式数据就能够和 update
办法关联起来,后续用户权限数据扭转了,能够主动触发 update
办法的从新运行。
好了,深刻完了,看着仿佛也挺简略的,我不确定这些是不是面试官想要的,或者还有其余更高级更优雅的实现呢,晓得的敌人是否指导一二,在下感激不尽。