美文网首页
elementui解决select下拉框 和日期选择器点击页面其

elementui解决select下拉框 和日期选择器点击页面其

作者: 5cc9c8608284 | 来源:发表于2022-10-31 20:14 被阅读0次

    思路:定义一个自定义指令,判断当用户点击了非select或者日期选择器的某处时,就关闭他们
    element源码:
    clickoutside.js

    import Vue from 'vue';
    import { on } from './dom';
    
    const nodeList = [];
    const ctx = '@@clickoutsideContext';
    
    let startClick;
    let seed = 0;
    
    !Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e));
    
    !Vue.prototype.$isServer && on(document, 'mouseup', e => {
      nodeList.forEach(node => node[ctx].documentHandler(e, startClick));
    });
    
    function createDocumentHandler(el, binding, vnode) {
      return function(mouseup = {}, mousedown = {}) {
        if (!vnode ||
          !vnode.context ||
          !mouseup.target ||
          !mousedown.target ||
          el.contains(mouseup.target) ||
          el.contains(mousedown.target) ||
          el === mouseup.target ||
          (vnode.context.popperElm &&
          (vnode.context.popperElm.contains(mouseup.target) ||
          vnode.context.popperElm.contains(mousedown.target)))) return;
    
        if (binding.expression &&
          el[ctx].methodName &&
          vnode.context[el[ctx].methodName]) {
          vnode.context[el[ctx].methodName]();
        } else {
          el[ctx].bindingFn && el[ctx].bindingFn();
        }
      };
    }
    
    /**
     * v-clickoutside
     * @desc 点击元素外面才会触发的事件
     * @example
     * ```vue
     * <div v-element-clickoutside="handleClose">
     * ```
     */
    export default {
      bind(el, binding, vnode) {
        nodeList.push(el);
        const id = seed++;
        el[ctx] = {
          id,
          documentHandler: createDocumentHandler(el, binding, vnode),
          methodName: binding.expression,
          bindingFn: binding.value
        };
      },
    
      update(el, binding, vnode) {
        el[ctx].documentHandler = createDocumentHandler(el, binding, vnode);
        el[ctx].methodName = binding.expression;
        el[ctx].bindingFn = binding.value;
      },
    
      unbind(el) {
        let len = nodeList.length;
    
        for (let i = 0; i < len; i++) {
          if (nodeList[i][ctx].id === el[ctx].id) {
            nodeList.splice(i, 1);
            break;
          }
        }
        delete el[ctx];
      }
    };
    

    dom.js

    /* istanbul ignore next */
    
    import Vue from 'vue';
    
    const isServer = Vue.prototype.$isServer;
    const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
    const MOZ_HACK_REGEXP = /^moz([A-Z])/;
    const ieVersion = isServer ? 0 : Number(document.documentMode);
    
    /* istanbul ignore next */
    const trim = function(string) {
      return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
    };
    /* istanbul ignore next */
    const camelCase = function(name) {
      return name.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
        return offset ? letter.toUpperCase() : letter;
      }).replace(MOZ_HACK_REGEXP, 'Moz$1');
    };
    
    /* istanbul ignore next */
    export const on = (function() {
      if (!isServer && document.addEventListener) {
        return function(element, event, handler) {
          if (element && event && handler) {
            element.addEventListener(event, handler, false);
          }
        };
      } else {
        return function(element, event, handler) {
          if (element && event && handler) {
            element.attachEvent('on' + event, handler);
          }
        };
      }
    })();
    
    /* istanbul ignore next */
    export const off = (function() {
      if (!isServer && document.removeEventListener) {
        return function(element, event, handler) {
          if (element && event) {
            element.removeEventListener(event, handler, false);
          }
        };
      } else {
        return function(element, event, handler) {
          if (element && event) {
            element.detachEvent('on' + event, handler);
          }
        };
      }
    })();
    
    /* istanbul ignore next */
    export const once = function(el, event, fn) {
      var listener = function() {
        if (fn) {
          fn.apply(this, arguments);
        }
        off(el, event, listener);
      };
      on(el, event, listener);
    };
    
    /* istanbul ignore next */
    export function hasClass(el, cls) {
      if (!el || !cls) return false;
      if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
      if (el.classList) {
        return el.classList.contains(cls);
      } else {
        return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
      }
    };
    
    /* istanbul ignore next */
    export function addClass(el, cls) {
      if (!el) return;
      var curClass = el.className;
      var classes = (cls || '').split(' ');
    
      for (var i = 0, j = classes.length; i < j; i++) {
        var clsName = classes[i];
        if (!clsName) continue;
    
        if (el.classList) {
          el.classList.add(clsName);
        } else if (!hasClass(el, clsName)) {
          curClass += ' ' + clsName;
        }
      }
      if (!el.classList) {
        el.className = curClass;
      }
    };
    
    /* istanbul ignore next */
    export function removeClass(el, cls) {
      if (!el || !cls) return;
      var classes = cls.split(' ');
      var curClass = ' ' + el.className + ' ';
    
      for (var i = 0, j = classes.length; i < j; i++) {
        var clsName = classes[i];
        if (!clsName) continue;
    
        if (el.classList) {
          el.classList.remove(clsName);
        } else if (hasClass(el, clsName)) {
          curClass = curClass.replace(' ' + clsName + ' ', ' ');
        }
      }
      if (!el.classList) {
        el.className = trim(curClass);
      }
    };
    
    /* istanbul ignore next */
    export const getStyle = ieVersion < 9 ? function(element, styleName) {
      if (isServer) return;
      if (!element || !styleName) return null;
      styleName = camelCase(styleName);
      if (styleName === 'float') {
        styleName = 'styleFloat';
      }
      try {
        switch (styleName) {
          case 'opacity':
            try {
              return element.filters.item('alpha').opacity / 100;
            } catch (e) {
              return 1.0;
            }
          default:
            return (element.style[styleName] || element.currentStyle ? element.currentStyle[styleName] : null);
        }
      } catch (e) {
        return element.style[styleName];
      }
    } : function(element, styleName) {
      if (isServer) return;
      if (!element || !styleName) return null;
      styleName = camelCase(styleName);
      if (styleName === 'float') {
        styleName = 'cssFloat';
      }
      try {
        var computed = document.defaultView.getComputedStyle(element, '');
        return element.style[styleName] || computed ? computed[styleName] : null;
      } catch (e) {
        return element.style[styleName];
      }
    };
    
    /* istanbul ignore next */
    export function setStyle(element, styleName, value) {
      if (!element || !styleName) return;
    
      if (typeof styleName === 'object') {
        for (var prop in styleName) {
          if (styleName.hasOwnProperty(prop)) {
            setStyle(element, prop, styleName[prop]);
          }
        }
      } else {
        styleName = camelCase(styleName);
        if (styleName === 'opacity' && ieVersion < 9) {
          element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')';
        } else {
          element.style[styleName] = value;
        }
      }
    };
    
    export const isScroll = (el, vertical) => {
      if (isServer) return;
    
      const determinedDirection = vertical !== null || vertical !== undefined;
      const overflow = determinedDirection
        ? vertical
          ? getStyle(el, 'overflow-y')
          : getStyle(el, 'overflow-x')
        : getStyle(el, 'overflow');
    
      return overflow.match(/(scroll|auto)/);
    };
    
    export const getScrollContainer = (el, vertical) => {
      if (isServer) return;
    
      let parent = el;
      while (parent) {
        if ([window, document, document.documentElement].includes(parent)) {
          return window;
        }
        if (isScroll(parent, vertical)) {
          return parent;
        }
        parent = parent.parentNode;
      }
    
      return parent;
    };
    
    export const isInContainer = (el, container) => {
      if (isServer || !el || !container) return false;
    
      const elRect = el.getBoundingClientRect();
      let containerRect;
    
      if ([window, document, document.documentElement, null, undefined].includes(container)) {
        containerRect = {
          top: 0,
          right: window.innerWidth,
          bottom: window.innerHeight,
          left: 0
        };
      } else {
        containerRect = container.getBoundingClientRect();
      }
    
      return elRect.top < containerRect.bottom &&
        elRect.bottom > containerRect.top &&
        elRect.right > containerRect.left &&
        elRect.left < containerRect.right;
    };
    

    在组件中使用:

    1.导入
    import Clickoutside from "src/utils/clickoutside";
    2.注册为局部指令:
    export default{
        directives: { Clickoutside }
    }
    3.在需要的元素上使用
      <el-date-picker
      v-Clickoutside
        v-else-if="column.el === 'datePicker'"
        v-model="row[column.prop]"
        class="nvr-my-item"
        :size="column.size || size"
        :type="column.type"
        unlink-panels
        :placeholder="
          column.placeholder != undefined
            ? $t(column.placeholder)
            : $t(column.label)
        "
        :is-show-select="column.isShowSelect"
        :time-picker-options="column.timePickerOptions || {}"
        :picker-options="
          column.pickerOptions || getpickerOptions(column.type, column.isNeedFast)
        "
        :disabled="column.disabled"
        :readonly="column.readonly"
        :editable="column.editable || false"
        :clearable="column.clearable"
        :format="column.format"
        :align="column.align"
        @change="dateChange($event, column.prop)"
        @focus="dateFocus($event, column.prop)"
        :range-separator="$t(column.rangeSeparator)"
        :value-format="column.valueFormat || 'yyyy-MM-dd HH:mm:ss'"
        :default-time="column.defaultTime"
        :append-to-body="appendToBody"
        :start-placeholder="
          column.startPlaceholder != undefined
            ? $t(column.startPlaceholder)
            : $t(column.label)
        "
        :end-placeholder="
          column.endPlaceholder != undefined
            ? $t(column.endPlaceholder)
            : $t(column.label)
        "
      >
      </el-date-picker>
    

    源码写的让人有点看不懂,我们可以试着自己封装一个不那么晦涩的指令,能实现需要就好,代码如下所示:

    Vue.directive("click-outside", {
      // el就是绑定指令的元素,bindings.expression就是动态参数这里是hide,vnode是绑定指令的元素的虚拟节点,vnode.context就是节点所在的vm实例
      bind(el, bindings, vnode) {
        el.handle = e => {
          // 点击的是 绑定指令元素么 不是就触发 参数传进来的函数
          if (!el.contains(e.target)) {
            let method = bindings.expression;
            vnode.context[method]();
          }
        };
        document.addEventListener("click", el.handle);
      },
      unbind(el) {
        // 相关事件移除
        document.removeEventListener("click", el.handle);
      }
    });
    

    补充:注册为全局指令
    main.js

    import Clickoutside from "shuai-element-ui/src/utils/clickoutside";
    Vue.directive('Clickoutside',Clickoutside)
    

    用法同上

    相关文章

      网友评论

          本文标题:elementui解决select下拉框 和日期选择器点击页面其

          本文链接:https://www.haomeiwen.com/subject/sfkdtdtx.html