按钮权限控制

按钮权限控制的交互方式无非两种:"不可见""可见不可点"

不可见

不可见的交互方式相对简单,控制DOM的显示隐藏即可。

Vue中可以通过下述方式实现:

  • v-if 控制其是否显示
  • v-show控制其是否显示,但不够保险,毕竟 v-show 只是把样式改成 display: none,在真实的 DOM 渲染还是存在

React中可以通过下述方式实现:

  • JSX中条件判断
const Demo = () => {
  return <div>
    {boolean && <Button>submit</Button>}
  </div>
}
  • 使用高阶组件HOC,抽离判断逻辑
const Demo = () => {
  return <div>code...</div>
}

/** 高阶组件 */
const HOC = <T>(Com: Com: React.ComponentType<T>) => {
  if (whiteList.includes(code)) return <Com {...this.props} />
  return null;
}

可见不可点

页面中有很多功能点,如果有些功能点用户不能操作而不显示DOM,可能会导致页面布局错乱,或者影响页面的美观,有些产品在控制功能点权限时期望元素“可见不可点”。

Vue中可以通过下述方式实现:

  • 自定义指令Vue.directive。使用addEventListener给元素增加一个捕获事件,捕获事件会优先于 @click 触发,然后在捕获事件方法体中使用 stopImmediatePropagation 阻止事件冒泡和其它相同事件的触发,因此达到控制元素不可点击的目的
// 注册自定义指令脚本
/** 权限拦截 */
const interception = (event) => {
    event && event.stopImmediatePropagation();
}

/** 白名单 */
const whiteList = [];

/** 注册指令 */
Vue.directive('permission', {
    bind(el, binding) {
        /** 不在白名单中 */
        if (!whiteList.includes(binding.value)) {
            el.style.pointerEvents = 'none';
            el.setAttribute('disabled', 'disabled');
            el.addEventListener('click', interception, true);
        }
    },
    unbind(el) {
        el.removeEventListener('click', interception);
    }
});

这里使用 pointer-events 只是一个辅助功能,并不一定意味着元素上的事件监听器永远不会触发,如果后代元素有指定 pointer-events 并允许成为事件目标的话,是可以触发父元素事件,而且单纯依靠 CSS 属性来控制不点击,还是有风险,因此这里仅作辅助作用。

React中可以通过下述方式实现:

import { Button } from 'antd';
import axios from 'axios';
import React, { Component } from 'react';

/** 按钮权限控制高阶组件 */
const RbacHOC = (Com: any) => {
  return class WrappedComponent extends Component {
    divRef = React.createRef<HTMLDivElement>();

    componentDidMount() {
      this.fetchWilteList();
    }

    componentWillUnmount() {
      this.divRef.current?.removeEventListener('click', this.intercept);
    }

    /** 获取黑白名单 */
    fetchWilteList = async () => {
      try {
        const { code, data } = await axios.get(`${url}`).then((res: any) => res.data);
        if (code === 200 && !data) {
          this.register();
        }
      } catch (error) {
        this.register();
      }
    };

    /** 注册捕获事件,拦截事件 */
    register = () => {
      this.divRef.current?.addEventListener('click', this.intercept, true);
    };

    /** 拦截函数 */
    intercept = (event: Event) => {
      event.stopImmediatePropagation();
      message.info('你没有权限使用该功能!!!');
    };

    render() {
      const { props } = this;
      return (
        <div style={{ display: 'inline-block' }} ref={this.divRef}>
          <Com {...props} />
        </div>
      );
    }
  };
};

export default RbacHOC;

页面组件中使用:

import React from 'react';

const Demo = () => {
  return <>coding...</>
}

export default RbacHOC(inject('store')(observer(Demo)));
// export default inject('store')(observer(RbacHOC(Demo));
// 不能这么写,因为store改变后WrappedComponent组件不会重新渲染,继而传给Demo组件的props不变,Demo也不会重新渲染

如果不想每次点击触发时都去调用接口拿黑白名单,可以将存放黑白名单数据的store传到高阶组件中

import React from 'react';

const Demo = () => {
  return <>coding...</>
}

export default inject('xxxstore')(RbacHOC(inject('store')(observer(Demo))));

参考

基于 Element 按钮权限实现方案

03-05 15:36