Record - how to control button level permissions

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-ifis 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.2kstar 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 adminand 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. storeGet 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 adminA 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 Authoritythe button that needs to be controlled by the package. The permission code required by the button valueis passed in through the attribute. Next, let's look at Authoritythe 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, Authoritythe 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 hasPermissiondetermine 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 adminthere 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 updatedhook:

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 watchEffecthook 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()
            })
        }
    },
})

updatedThe update logic in the hook is extracted into a method update, and then the first update watchEffectis executed in it, so that the responsive data of user permissions can be updateassociated with the method. Subsequent changes in user permission data can automatically updatetrigger the rerun of the method.

This article is reproduced in:

https://juejin.cn/post/7209648356530896953

If it is helpful to you, you are welcome to pay attention. I will update the technical documents regularly, and we will discuss and learn together and make progress together.

 

Guess you like

Origin blog.csdn.net/qq_40716795/article/details/130859041