更好一点的:Vue 利用指令实现禁止反复发送请求

理论上可以用于任何元素,生效时会在元素上出现一个同大小的灰色蒙层(button元素会该表原生的disabled属性)。
/**
  * 当元素触发发起请求后,当发起的请求中最后一个请求的结果返回(不关心返回顺序和结果),解锁元素禁用。
  * 优化:用一个pending记录所有请求,逐个判定是否返回结果。
  * 指令的方式使用轮询去校验接口是否返回结果,也可以在axios拦截器中,改变store中的数据,
  * 然后在页面的computed中处理,不过页面内代码不如一个指令来的方便。
  * 也可以用Bus来代替轮询
  */
const forbiddenInterval = 200;

/** 
 * 配合ElementUI逐次查找父节点,目前仅考虑 body 和 dialogue 2个场景
 * 将目标节点设为root,将mask(非button元素,el-button实际上就是个原生button元素,直接用原生属性disabled即可)挂在root下
 * 并且相对root定位
 */
function findRootTag(el) {
  let parent = el.parentNode;
  let type = null;
  let root = null;
  while (parent) {
    if (parent.tagName === 'BODY') {
      type = 'body';
      root = parent;
      break;
    } else if (parent.getAttribute('role') === 'dialog') {
      type = 'dialog';
      root = parent;
      break;
    }
    parent = parent.parentNode;
  }
  return { type, root };
}

export default {
  inserted(el) {
    el.timer = null;
    if (el.tagName === 'BUTTON') {
      el.forbiddenClick = () => {
        el.disabled = true;
        el.classList.add('is-disabled');
        el.timer = setInterval(() => {
          if (window.currentResq.done) {
            clearInterval(el.timer);
            el.disabled = false;
            el.classList.remove('is-disabled');
          }
        }, forbiddenInterval);
      };
      el.addEventListener('click', el.forbiddenClick);
    } else {
      const { type, root} = findRootTag(el);
      let mask = document.createElement('div');
      if (type === 'dialog') {
        /* dialog上的mask z-index 设置的较大 */
        mask.setAttribute('style',
          `
          position: absolute;
          diplay: none;
          background-color: #e4e7ed;
          cursor: not-allowed;
          opacity: .4;
          z-index: 9999;
          `
        );
      } else {
        mask.setAttribute('style',
          `
          position: absolute;
          diplay: none;
          background-color: #e4e7ed;
          cursor: not-allowed;
          opacity: .4;
          z-index: 1000;
          `
        );
      }
      
      mask.classList.add('mask');
      root.appendChild(mask);
      el.instance = mask;
      el.root = root;
      el.type = type;
    }
  },
  update(el, binding) {
    if (el.tagName !== 'BUTTON' && binding.value !== binding.oldValue) {
      const root = el.root;
      const type = el.type;
      const scrollTop = root.scrollTop || document.documentElement.scrollTop;
      const scrollLeft = root.scrollLeft || document.documentElement.scrollLeft;
      const mask = el.instance;
      const elRect = el.getBoundingClientRect();
      mask.style.width = `${elRect.width}px`;
      mask.style.height = `${elRect.height}px`;
      if (type === 'body') {
        mask.style.top = `${elRect.top + scrollTop}px`;
        mask.style.left = `${elRect.left + scrollLeft}px`;
      } else if (type === 'dialog') {
        const rootRect = root.getBoundingClientRect();
        mask.style.top = `${elRect.top - rootRect.top}px`;
        mask.style.left = `${elRect.left - rootRect.left}px`;
      }
      mask.style.display = 'block';

      el.timer = setInterval(() => {
        if (window.currentResq.done) {
          clearInterval(el.timer);
          mask.style.display = 'none';
        }
      }, forbiddenInterval);
    }
  },
  unbind(el) {
    if (el.instance) {
      el.root.removeChild(el.instance);
    }
    if (el.forbiddenClick) {
      document.removeEventListener('click', el.forbiddenClick);
    }
  },
};

还有很多可以改进的地方,比如应该是有办法去兼容dialog这种会禁用body滚动并且本身也会有动条的元素,等下一个项目去实践一下脑子里的想法。
原文地址:https://www.cnblogs.com/youyouluo/p/11971431.html