vue的自定义指令directive总结

572 阅读1分钟

自定义指令的一些实用例子

使用时记得先引入Vue, import Vue from 'vue'

1. el-dialog可拖拽

// directive代码
Vue.directive('el-drag-dialog', {
    bind(el, binding, vnode) {
        const dialogHeaderEl = el.querySelector('.el-dialog__header')
        const dragDom = el.querySelector('.el-dialog')
        dialogHeaderEl.style.cssText += ';cursor:move;'
        dragDom.style.cssText += ';top:0px;'
    
        // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
        const getStyle = (function() {
          if (window.document.currentStyle) {
            return (dom, attr) => dom.currentStyle[attr]
          } else {
            return (dom, attr) => getComputedStyle(dom, false)[attr]
          }
        })()
    
        dialogHeaderEl.onmousedown = (e) => {
          // 鼠标按下,计算当前元素距离可视区的距离
          const disX = e.clientX - dialogHeaderEl.offsetLeft
          const disY = e.clientY - dialogHeaderEl.offsetTop
    
          const dragDomWidth = dragDom.offsetWidth
          const dragDomHeight = dragDom.offsetHeight
    
          const screenWidth = document.body.clientWidth
          const screenHeight = document.body.clientHeight
    
          const minDragDomLeft = dragDom.offsetLeft
          const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
    
          const minDragDomTop = dragDom.offsetTop
          const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
    
          // 获取到的值带px 正则匹配替换
          let styL = getStyle(dragDom, 'left')
          let styT = getStyle(dragDom, 'top')
    
          if (styL.includes('%')) {
            styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
            styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
          } else {
            styL = +styL.replace(/\px/g, '')
            styT = +styT.replace(/\px/g, '')
          }
    
          document.onmousemove = function(e) {
            // 通过事件委托,计算移动的距离
            let left = e.clientX - disX
            let top = e.clientY - disY
    
            // 边界处理
            if (-(left) > minDragDomLeft) {
              left = -minDragDomLeft
            } else if (left > maxDragDomLeft) {
              left = maxDragDomLeft
            }
    
            if (-(top) > minDragDomTop) {
              top = -minDragDomTop
            } else if (top > maxDragDomTop) {
              top = maxDragDomTop
            }
    
            // 移动当前元素
            dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
    
            // emit onDrag event
            vnode.child.$emit('dragDialog')
          }
    
          document.onmouseup = function(e) {
            document.onmousemove = null
            document.onmouseup = null
          }
        }
      }
})

在vue文件中使用

<el-dialog
  v-el-drag-dialog
  :visible.sync="dialogTableVisible"
  title="Shipping address"
  @dragDialog="handleDrag"
>
  <h1>可拖拽的dialog</h1>
</el-dialog>

2. v-clipboard复制文本内容

需要使用npm插件clipboard。 首先安装插件npm i clipboard -S。 然后注册全局directive,记得引入clipboard。最后在vue文件中使用。复制成功的回调可写可不写。

// 全局注册clipboard
const Clipboard = require('clipboard');
Vue.directive('clipboard', {
  bind(el, binding) {
    if (binding.arg === 'success') {
      el._v_clipboard_success = binding.value
    } else if (binding.arg === 'error') {
      el._v_clipboard_error = binding.value
    } else {
      const clipboard = new Clipboard(el, {
        text() { return binding.value },
        action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
      })
      clipboard.on('success', e => {
        const callback = el._v_clipboard_success
        callback && callback(e) // eslint-disable-line
      })
      clipboard.on('error', e => {
        const callback = el._v_clipboard_error
        callback && callback(e) // eslint-disable-line
      })
      el._v_clipboard = clipboard
    }
  },
  update(el, binding) {
    if (binding.arg === 'success') {
      el._v_clipboard_success = binding.value
    } else if (binding.arg === 'error') {
      el._v_clipboard_error = binding.value
    } else {
      el._v_clipboard.text = function() { return binding.value }
      el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
    }
  },
  unbind(el, binding) {
    if (binding.arg === 'success') {
      delete el._v_clipboard_success
    } else if (binding.arg === 'error') {
      delete el._v_clipboard_error
    } else {
      el._v_clipboard.destroy()
      delete el._v_clipboard
    }
  }
})

在.vue文件中使用

<template>
  <div class="app-container">
    <el-button
      v-clipboard="testVal"
      v-clipboard:success="clipboardSuccess"
      type="primary"
      icon="el-icon-document"
    >
      复制内容
    </el-button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      testVal: 'O(∩_∩)O哈哈~'
    }
  },
  methods: {
    clipboardSuccess(val) {
      this.$message({
        message: `内容(${val.text})复制成功`,
        type: 'success'
      })
    }
  }
}
</script>

3. el-input关于价格的注册

需求: 价格相关的input。默认居右显示,默认有placeholder请输入。聚焦后在左侧修改。 只允许输入最多2位小数。 输入完后失焦居右显示千分位数字。 难点: 失焦后居右显示千分位数字。 v-model不支持filter, 所以本例子的思路是,新建一个input, 隐藏之前的input。 update钩子里也要写逻辑, 否则报错。

Vue.directive('price', {
  bind: function(el, { value = 2 }) {
    el = el.nodeName === "INPUT" ? el : el.children[0];
    el.placeholder = "请输入";
    el.style.textAlign = "right";
    var RegStr = value === 0 ? `^[\\+\\-]?\\d+\\d{0,0}` : `^[\\+\\-]?\\d+\\.?\\d{0,${value}}`;
    el.addEventListener('keyup', function() {
      el.value = el.value.match(new RegExp(RegStr, 'g'));
      el.dispatchEvent(new Event('input'))
    })
    el.addEventListener('focus', function() {
      const newAddIpt = el.parentNode.querySelector('.thousand-ipt');
      if (newAddIpt) {
        el.parentNode.removeChild(newAddIpt);
      }
      el.style.textAlign = "left";
      el.style.opacity = 1;
      el.style.position = '';
    })
    el.addEventListener('blur', function() {
      if (el.value === '') {
        el.value = '0.00';
      } else {
        el.value = Number(el.value).toFixed(2);
      }
      el.style.opacity = 0;
      el.style.position = 'absolute';
      el.dispatchEvent(new Event('input'))
      const newIpt = document.createElement("input"); // 新建input节点
      newIpt.value = parseFloat(el.value).toFixed(2).replace(/\d(?=(?:\d{3})+\b)/g, `$&,`)
      newIpt.className = 'el-input__inner thousand-ipt';
      newIpt.style.textAlign = "right";
      el.parentNode.appendChild(newIpt);
    })
  },
  update: function(el) {
    if (el.children[1] && el.children[1].value === undefined) {
      const newAddIpt = el.parentNode.querySelector('.thousand-ipt');
      if (newAddIpt) {
        el.removeChild(newAddIpt);
        el.children[0].style.textAlign = "left";
        el.children[0].style.opacity = 1;
        el.children[0].style.position = '';
      }
    }
  }
})

4. el-input自动聚焦

/**
 * 自动聚焦
 */
Vue.directive("focus", {
  // 获取光标在inserted中操作,此时元素已经插入到父节点了
  inserted(el) {
    el = el.nodeName === "INPUT" ? el : el.children[0];
    el.focus();
  },
  bind(el) {
    el = el.nodeName === "INPUT" ? el : el.children[0];
    el.focus();
  }
});