One big business scenario for front-end development is the management back end. In these management background systems, it is inevitable that a very important requirement, is authority control.

Problems you might encounter

Permissions can be roughly divided into page level and page element level. Change adult words is: this menu only administrator can see, this page so-and-so can not access, this button has no authority can not click, this area do not let ordinary users see!!

Now we’re going to turn product manager jargon into technology development jargon.

Suppose a user has a list of permissionCodes:

  • If a menu needs itcode=100To access, and the current user does not have the code, the menu is not rendered
  • If a menu needs itcode=100If the current user does not have the code, a message is displayed indicating that the user has no permission to access the page corresponding to the menu
  • If a button needs tocode=200Can be accessed, and the current user does not have the code, then the buttondisabledProperty set totrue
  • If a region needs itcode=200Can be accessed, and the current user does not have the code, then thedisplaySet tonone

The general process is as follows:

The specific implementation

Gets the user permission list

We need to get the user permission list before rendering. We can request the current user information from the server before Vue instantiation.

$fetch.getPermissions(token)
    .then(user= > {
        Vue.prototype.$user = user;

        new Vue({
            router,
            store,
            render: h= > h(App),
        }).$mount('#app');
    });
Copy the code

Suppose we need to render a landing page? We just need to separate the login service from the main application, adding an additional entry, or Vue instance, can solve this problem.

Control menu rendering

Control menu rendering is simple, because menus are usually a configured array and simply filter menus that the current user does not have access to.

// sidebar menus
const asideMenus = [
    {
        code: '100'.name: 'home'.path: '/home'}, {code: '500'.name: 'System Settings'.path: '/manage'.children: [{code: '510'.name: 'User Settings'.path: 'user'}, {code: '520'.name: 'Access Settings'.path: 'visit',},],},];// Vue component options
{
    computed: {
         asideMenus() {
            const { $user } = this;

            return (function filter(arr) {
                return arr.filter(menu= > {
                    if (Array.isArray(menu.children)) {
                        menu.children = filter(menu.children);
                    }

                    if (menu.children && menu.children.length) {
                        return true
                    } else if (menu.code && $user && $user.codes) {
                        return ~$user.codes.indexOf(menu.code);
                    } else {
                        return true; } }) })(asideMenus); }},}Copy the code

Restricting route access

Vue routing provides a generic route interception hook to facilitate permission control.

const noPermissionPage = ' ';

router.beforeEach((to, from, next) = > {
    const code = to.meta.code;
    const user = router.app.$user;
    
    if(code && user && user.codes && ! ~user.codes.indexOf(code)) { next(noPermissionPage); }else{ next(); }});Copy the code

Disable button operation/hide some areas

Next, we come to the core of this article: how to disable button operations and hide parts of the area based on permissions?

In vUe-type projects, the elements on the page are almost all Vue components, so the question can be equated to: How do I set the component’s disabled or visible property to false based on the permissions?

Let’s break it down first:

  1. How do I determine whether a user has permission for a component?
  2. How do I set the disabled or visible property of a component to false?

So let’s solve these two problems.

We can easily think of the following implementation ideas:

// Code snippet<el-button :disabled="$user && $user.codes && ! ~$user.codes.indexOf('200')"></el-button>

<div v-show=! "" $user || ! $user.codes || ~$user.codes.indexOf('300')">some content</div>
Copy the code

This solution can solve the problem, but it is more complicated to write and difficult to maintain. In addition, if the user has permission, the disabled attribute or V-show may also be affected by other conditions, making written expressions more complex and difficult to maintain.

Here’s another idea:

import Vue from 'vue';

Vue.directive('p', {
    bind: handler,
    update: handler,
});

function handler(el, binding, vnode) {
    const user = vnode.context.$user;
    const code = binding.arg || binding.value;
    const prop = binding.modifiers.visible ? 'visible': 'disabled';
    constvalue = prop ! = ='visible';

    if(code && user && user.codes && ! ~user.codes.indexOf(code)) {const vm = vnode.componentInstance;
        if (vm && vm.hasOwnProperty(prop)) {
            const silent = Vue.config.silent;
            Vue.config.silent = true; // Ignore warnings by force
            vm[prop] = value;
            Vue.config.silent = silent;
        } else {
            if (prop === 'visible') {
                el.classList.add('display-none');
            } else if (prop === 'disabled') {
                el.setAttribute('disabled'.true);
                el.classList.add('is-disabled'); }}}}Copy the code

Usage:

<sa-button type="primary" v-p:200>operation</sa-button>
<p v-p:300.visible>Operating hints</p>
Copy the code

How about that? Is that a lot more comfortable to write?

Next, how do we modify component properties directly in directives?

It is explicitly mentioned in the official Vue documentation that modifying prop within components is anti-pattern (not recommended).

So, in development mode, we get a warning when we modify prop directly within a component.

Unfortunately, this doesn’t match most of what the official documentation says, so we chose to force the warnings to be ignored.

The problem with this approach is that if the Disabled attribute is affected by other conditions, it will not work as expected. Such as:

<! -- permission priority over coby's conditions -->
<sa-button type="primary" :disabled="submitting" v-p:200>operation</sa-button>
<p v-p:300.visible>Operating hints</p>
Copy the code

In general, the user’s permissions are fixed, so you can bind the value of the attribute by the permissions in the bind hook function. Here is the final implementation:

import Vue from 'vue';

Vue.directive('p', {
    bind: function (el, binding, vnode) {
        const user = vnode.context.$user;
        const code = binding.arg || binding.value;
        const prop = binding.modifiers.visible ? 'visible': 'disabled';
        constvalue = prop ! = ='visible';

        if(code && user && user.codes && ! ~user.codes.indexOf(code)) {const vm = vnode.componentInstance;
            if (vm && vm._props.hasOwnProperty(prop)) {
                const silent = Vue.config.silent;
                const property = Object.getOwnPropertyDescriptor(vm._props, prop);
                Object.defineProperty(vm._props, prop, { ... property, get() {return value } });
                Vue.config.silent = true; // Ignore warnings by force
                property.set(value); // Triggers computed dependency updates
                Vue.config.silent = silent;
            } else {
                if (prop === 'visible') {
                    el.classList.add('display-none');
                } else if (prop === 'disabled') {
                    el.setAttribute('disabled'.true);
                    el.classList.add('is-disabled'); }}}}});Copy the code

If you don’t understand why would be so, can go to see the Vue initProps method of source (located in the SRC/core/instance/state js# L64 – L110).

What is the

If the system is large and involves many and complex permissions, it may not be a good idea to pull all of the user’s permissions locally in the first place. At this time, it is necessary to combine the actual system architecture, and then deal with it flexibly according to the basic methods provided above.