了解更多

一个自定义指令由一个包含类似组件生命周期钩子的对象来定义.

自定义指令: https://vuejs.org/guide/reusability/custom-directives.html

下面是在 vue 中可能用到的一些自定义指令实现:

容器遮罩

部分组件场景需要给元素添加遮罩层,禁止用户点击。

import Vue from 'vue';

Vue.directive('mask', {
  bind(el) {
    const maskDiv = document.createElement('div');
    const position = getComputedStyle(el).position;

    if (!['relative, absolute'].includes(position)) {
      el.style.position = 'relative'; // 给父元素设置定位
    }

    Object.assign(maskDiv.style, {
      height: '100%',
      width: '100%',
      position: 'absolute',
      top: '0',
      left: '0',
      background: 'transparent',
      zIndex: 1,
    });
    el.appendChild(maskDiv);
  },
});

按钮权限

在实际项目中,有很多根据用户权限进行展示的操作按钮,比如某些按钮只有管理员才能操作,那么我们可以通过自定义指令来实现。

在这个指令中,接受的参数就是当前按钮的所需权限和用户已有权限(也可直接从 store 中获取用户已有权限),如果用户权限中包含按钮所需权限,则显示按钮,否则不显示。

import Vue from 'vue';

Vue.directive('permissions', {
  bind(el, banding) {
    const { permission, userPermission } = banding.value as {
      permission: string;
      userPermission: string[];
    };

    // 实际需要根据 permission 的结构和类型进行判断处理,我这里示例给出的 string
    const isPermission = userPermission.includes(permission);

    if (!isPermission) {
      await Vue.nextTick();
      el.parentNode?.removeChild(el);
    }
  },
});

空状态文字显示

部分组件容器在没有获取到值,或者没有子组件的时候,需要向用户展示一个空状态的文字,那么我们可以通过自定义指令来实现。

import Vue from 'vue';
import type { CSSProperties } from 'vue/types/jsx';

interface Empty {
  content: string;
  visible: boolean;
}

function createEmptySpanCtx(content: string, className: string) {
  const emptyEl = document.createElement('span');

  emptyEl.innerText = content;
  emptyEl.className = className;

  Object.assign(emptyEl.style, {
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',
    fontSize: '14px',
    color: '#909399',
    background: 'transparent',
  } as CSSProperties);

  return emptyEl;
}

function triggerEmptyEl(containerEl: HTMLElement, bindValue: Empty) {
  const emptyClassName = 'empty-span';
  const emptyEls = containerEl.querySelectorAll(`.${emptyClassName}`);

  if (bindValue.visible && !emptyEls.length) {
    const emptyEl = createEmptySpanCtx(bindValue.content, emptyClassName);

    containerEl.appendChild(emptyEl);
  } else if (!bindValue.visible && emptyEls.length) {
    emptyEls.forEach((el) => {
      containerEl.removeChild(el);
    });
  }
}

Vue.directive('empty', {
  bind(el: HTMLElement, binding: { value: Empty }) {
    const position = getComputedStyle(el).position;

    if (!['relative, absolute'].includes(position)) {
      el.style.position = 'relative';
    }

    triggerEmptyEl(el, binding.value);
  },
  update(el: HTMLElement, binding: { value: Empty }) {
    triggerEmptyEl(el, binding.value);
  },
});