Элегантная реализация контроля разрешений в проектах Vue

Vue.js

Для front-end разработки существует большой бизнес-сценарий, который заключается в управлении back-end. В этих фоновых системах управления неизбежным и важным требованием является контроль разрешений.

возможные проблемы

Управление разрешениями можно условно разделить на页面级а также页面元素级. Изменить на мужские слова: Это меню можно только увидеть, на эту страницу нельзя зайти, у этой кнопки нет разрешений, эта область не позволяет видеть обычным пользователям! !

Далее мы преобразуем слова менеджера по продуктам в ерунду технического развития.

Предположим, у пользователя есть набор кодов разрешений (permissionCodes):

  • Если меню требуетcode=100можно только получить доступ, и текущий пользователь не владеет кодом, меню не будет отображаться
  • Если меню требуетcode=100можно получить только доступ, и текущий пользователь не владеет кодом, когда пользователь напрямую обращается к странице, соответствующей меню, он не будет запрашивать разрешение
  • Если нужна кнопкаcode=200можно получить только доступ, и текущий пользователь не владеет кодом, то кнопкаdisabledсвойство установлено наtrue
  • Если область требуетcode=200доступ, а текущий пользователь не владеет кодом, тоdisplayУстановить какnone

Общий процесс выглядит следующим образом:

image

Реализация

Получить список разрешений пользователя

Перед рендерингом нам нужно получить список разрешений пользователя.Мы можем сначала запросить текущую информацию о пользователе с сервера, а затем перейти кVueсоздать экземпляр.

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

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

Предположим, нам нужно отобразить страницу входа? Нам просто нужно отделить службу входа в систему от основного приложения и добавить дополнительную запись, илиVueПримеры могут решить эту проблему.

Рендеринг меню управления

Управлять отрисовкой меню легко, потому что меню обычно представляют собой настраиваемый массив, и вам нужно фильтровать только те меню, на которые у текущего пользователя нет разрешения.

// sidebar menus
const asideMenus = [
    {
        code: '100',
        name: '首页',
        path: '/home',
    },
    {
        code: '500',
        name: '系统设置',
        path: '/manage',
        children: [
            {
                code: '510',
                name: '用户设置',
                path: 'user',
            },
            {
                code: '520',
                name: '访问设置',
                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);
        },
    },
}

Ограничить доступ к маршруту

VueМаршрутизация предоставляет общий хук перехвата маршрутизации, который нам удобен для контроля разрешений.

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();
    }
});

Отключить действие кнопки/скрыть часть области

Далее мы подходим к основной части этой статьи: как отключить действия кнопок и скрыть некоторые области на основе разрешений?

существуетVueтип проекта, элементы на странице почти всеVue component, так что этот вопрос может быть эквивалентен: как присвоить компонентуdisabledсобственность илиvisibleсвойство установлено наfalse?

Сначала разберем проблему:

  1. Как определить, есть ли у пользователя права доступа к определенному компоненту?
  2. Как установить для отключенного свойства или видимого свойства компонента значение false?

Далее мы решим эти две задачи.

Мы можем легко придумать следующие идеи реализации:

// 代码片段
<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>

Это решение может решить проблему, но его сложнее написать и сложнее поддерживать. Кроме того, если у пользователя есть разрешение,disabledсобственность илиv-showНа него также могут влиять другие условия, и выражения, написанные в это время, будут более сложными и сложными в обслуживании.

Вот еще один способ мышления:

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';
    const value = 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; // 强行忽略警告
            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');
            }
        }
    }
}

Как использовать:

<sa-button type="primary" v-p:200>操作</sa-button>
<p v-p:300.visible>操作提示</p>

Так что, не намного ли удобнее писать так?

Далее объясните, как мы можем напрямую изменить свойства компонента в директиве?

существуетVueофициальная документацияявно упоминается в модификации компонентаpropявляется анти-шаблоном (не рекомендуется).

image

Итак, в режиме разработки, когда мы напрямую модифицируем компонентprop, вы получите предупреждение.

image

К сожалению, большая часть того, что говорится в официальной документации, здесь не соответствует действительности, поэтому мы выбрали强行忽略警告.

Проблема с этим подходом заключается в том, что еслиdisabledКогда на атрибуты могут влиять и другие условия, ожидаемый эффект не будет достигнут. Например:

<!-- 权限的优先级大于submitting条件 -->
<sa-button type="primary" :disabled="submitting" v-p:200>操作</sa-button>
<p v-p:300.visible>操作提示</p>

Обычно разрешения, которые есть у пользователя, являются постоянными, поэтому мы можемbindВ функции ловушки значение атрибута напрямую закрепляется в соответствии с разрешением. Вот окончательная реализация:

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';
        const value = 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; // 强行忽略警告
                property.set(value); // 触发computed依赖更新
                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');
                }
            }
        }
    }
});

Если вы не понимаете, почему это происходит, вы можете пойти и посмотретьVueв исходном кодеinitPropsМетоды (расположенныеsrc/core/instance/state.js#L64-L110).

что еще

Если система относительно велика и включает много сложных разрешений, может быть не очень подходящим подходом вытягивать все разрешения пользователя локально в начале. В это время необходимо объединить актуальную системную архитектуру, а затем гибко обращаться с ней в соответствии с основными методами, представленными выше.