Here I will share with you some of the knowledge I have summarized on the Internet, I hope it will be helpful to everyone
In a recent interview, an interviewer asked me how to control the button-level permissions. I said straightforward v-if
, but he said it was not good enough. I said that there are not many button-level permission controls in our project, so it v-if
is enough. He said it is not general enough. , In the end, his evaluation of me was that I had done a lot of things, but none of them were deep enough. Well, let’s go deep today.
Because I don't have relevant practice myself, so let's see how it works from this 16.2k
star background management system project Vue vben admin .
Get permission code
To do permission control, one is definitely needed code
, whether it is a permission code or a role code. Generally, the backend will return it at one time, and then store it globally. It is obtained Vue vben admin
and saved to the global after successful login 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);
}
}
})
Next, it provides three button-level permission control methods, let’s look at them one by one.
Functional way
An example of use is as follows:
<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>
In essence, it is passed v-if
, but only through a unified permission judgment method 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;
}
}
It's very simple. store
Get the current user's permission code list from the global, and then judge whether there is a permission code required by the current button. If there are multiple permission codes, you only need to satisfy one of them.
Components
In addition to using the function method, you can also use the component method. Vue vben admin
A component is provided Authority
. The usage example is as follows:
<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>
Just use Authority
the button that needs to be controlled by the package. The permission code required by the button value
is passed in through the attribute. Next, let's look at Authority
the implementation of the component.
<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>
The same is still used hasPermission
. If the current user has the permission code required by the button, Authority
the content of the package will be rendered intact, otherwise nothing will be rendered.
command mode
The last one is the instruction method, the usage example is as follows:
<a-button v-auth="'1000'" type="primary" class="mx-4"> 拥有code ['1000']权限可见 </a-button>
The implementation is as follows:
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);
}
Only one hook is defined mounted
, which is called after the bound element is mounted. It is still used to hasPermission
determine whether the current user has the permission code required by the button inserted through the command. If it does not exist, the bound element is directly removed.
Obviously, Vue vben admin
there are two problems in the implementation of , one is that the permissions of the button cannot be dynamically changed, and the other is that dynamically changing the permissions of the current user will not take effect.
Solving the first problem is very simple, because the above logic only deletes elements, but does not add back logic, so add a updated
hook:
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)
}
The main thing is to save the parent node, otherwise you won’t be able to get the original parent node when you want to add it back, and create a comment node for yourself when you delete it, so that you can know where you were when you want to go back next time.
The reason for the second problem is that the user permission data has been modified, but the re-rendering of the button will not be triggered, so we need to find a way to make it trigger. This method can be used, and we can pass the user permission data in the watchEffect
hook updated
. Associated with the update method of the button, so that when the user permission data changes, the re-rendering of the button can be automatically triggered:
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
The update logic in the hook is extracted into a method update
, and then the first update watchEffect
is executed in it, so that the responsive data of user permissions can be update
associated with the method. Subsequent changes in user permission data can automatically update
trigger the rerun of the method.