Vue自定义指令功能整合

当前案例全部是vue3+TS,vue2的可以根据自己情况修改

1.v-copy指令

文本内容复制指令

// copy.ts
import { App, Directive } from "vue";

const copy: Directive<HTMLElement> =  {
  beforeMount(el, binding){
    if(binding.modifiers.ref && binding.value){// 如果是要复制指定ref元素的数据
      // 交给mounted里处理
    }else {// 复制当前元素下的文字
      if(binding.modifiers.dblclick){ //如果是双击复制
        el.addEventListener('dblclick', () => handlerClick(el.innerText));
      }else{//单击复制
        el.addEventListener('click', () => handlerClick(el.innerText));
        el.style.cursor = 'copy';
      }
    }
  },
  mounted(el, binding){
    const refElem = binding.instance?.$refs[binding.value] as HTMLElement | undefined;
    if(!refElem){// 如果不存在这个元素
      return;
    }
    if(binding.modifiers.dblclick){// 双击复制,注意这里的双击复制跟上面的不是同一个元素
      el.addEventListener('dblclick', () => handlerClick(refElem.innerText));
    }else{//单击复制
      el.addEventListener('click', () => handlerClick(refElem.innerText));
    }
  }
}

const handlerClick = (txt: string) => {
  //创建元素
  if(!document.getElementById('copyTarget')){// 如果不存在该元素
    const copyTarget = document.createElement('input');
    copyTarget.setAttribute('style', 'position:fixed;top:0;left:0;opacity:0;z-index:-1000;');
    copyTarget.setAttribute('id', 'copyTarget');
    document.body.appendChild(copyTarget);
  }
  //复制内容
  const input = document.getElementById('copyTarget') as HTMLInputElement;
  input.value = txt;
  input.select();
  document.execCommand('copy');
  input.remove();
  //execCommand已被弃用,可考虑navigator.clipboard.writeText(txt).then(() => {console.log('复制成功')})
}

export default {
  install(app: App<Element>){
    app.directive('copy', copy);
  }
}

/*
main.ts使用 
app.use(copy);
*/
指令说明实例
v-copy单击复制<button v-copy>单击我,文本会被复制</button>
v-copy.dblclick双击复制当前节点下的文本数据<button v-copy>双击我,文本会被复制</button>
v-copy.ref="'ref'"单击之后复制节点名为ref的文本数据

<p ref="ref">我将会被复制</p>

<button v-copy.ref="'ref'">单击我,复制ref节点文本</button>

v-.copy.ref.dblclick="'ref'"双击之后复制节点名为ref的文本数据

<p ref="ref">我将会被复制</p>

<button v-copy.ref="'ref'">双击我,复制ref节点文本</button>

2.v-screenfull指令

全屏指令,需提前引用screenfull包(screenfull - npm

import { App, Directive } from "vue";
import screenfull, { Screenfull } from "screenfull";

const screen: Directive<HTMLElement> =  {
  beforeMount(el, binding){
    let iconName = binding.value || 'el-icon-full-screen'; // 默认使用element-plus的图标
    if(binding.modifiers.icon) { // 添加icon
      const i = document.createElement('i');
      i.setAttribute('style', 'margin-left: 5px;');
      i.setAttribute('class', iconName);
      el.appendChild(i);
    }
    el.style.cursor = el.style.cursor || 'pointer';
    el.addEventListener('click', handleClick);
  }
}

const handleClick = () => {
  if(!screenfull.isEnabled) {
    alert('您的浏览器不支持全屏');
    return;
  }
  (screenfull as Screenfull).toggle();
}

export default {
  install(app: App<Element>){
    app.directive('screenfull', screen);
  }
}
指令说明实例

v-screenfull

不使用图标<button v-screenfull>切换全屏</button>
v-screenfull.icon使用默认图标<div v-screenfull.icon>切换全屏</div>
v-screenfull.icon="'xxx'"使用xxx的图标<div v-screenfull.icon="'xxx'">切换全屏</div>

3.v-ellipsis指令

文字超出显示省略号

import { App, Directive } from "vue";

const ellipsis: Directive<HTMLElement> =  {
  mounted(el, binding){
    el.style.whiteSpace = 'nowrap'
    el.style.overflow = 'hidden';
    el.style.textOverflow = 'ellipsis';
    let width = '0';
    if(binding.arg){ //如果存在自定义的宽度
      width = binding.arg;
    }else{// 否则拿取父级的宽度
      const parent = el.parentElement;
      if(parent){// 存在父元素
        if(binding.modifiers.flex){// 如果是flex布局
          parent.style.overflow = 'hidden';
        }
        width = parent.offsetWidth.toString();
      }else{//不存在父元素,默认100,一般不会进入这里
        width = '100';
      }
    }
    el.style.width = width + 'px';
  }
}

export default {
  install(app: App<Element>){
    app.directive('ellipsis', ellipsis);
  }
}
指令说明实例

v-ellipsis

以父元素为宽度<p v-ellipsis>超出文本隐藏</p>
v-ellipsis:200超出200px隐藏<div v-ellipsis:200>超出文本隐藏</div>
v-ellipsis.flex如果父级使用了flex用这个<div v-ellipsis.flex>超出文本隐藏</div>

4.v-drag指令

使用后可拖拽当前元素,注意父元素必须使用了绝对定位,当前元素使用了相对定位

import { App, Directive } from "vue";

const drag: Directive<HTMLElement> = {
  mounted(el) {
    const parent = el.parentElement; // 获取父级元素
    if(!parent){
      return;
    }
    const minX = 0;// 最小x方向距离
    const minY = 0;// 最小y方向距离
    const maxX = parent.offsetWidth;// 最大x方向距离
    const maxY = parent.offsetHeight;// 最大y方向距离
    document.onselectstart = () => {
      return false;
    }
    el.style.cursor = 'move';
    el.onmousedown = (e) => {
      const left = e.clientX - el.offsetLeft;
      const top = e.clientY - el.offsetTop;
      document.onmousemove = (de) => {
        let l = de.clientX - left;
        let t = de.clientY - top;
        // 限制不能超出父级区域
        if(l <= minX) {
          l = minX;
        }
        if(t <= minY) {
          t = minY;
        }
        if(l >= maxX - el.offsetWidth) {
          l = maxX - el.offsetWidth;
        }
        if(t >= maxY - el.offsetHeight) {
          t = maxY - el.offsetHeight;
        }
        el.style.left = l + 'px';
        el.style.top = t + 'px';
      }
      document.onmouseup = () => {
        document.onmousemove = document.onmouseup = null;
      }
      return false;
    }
  }
}

export default {
  install(app: App<Element>){
    app.directive('drag', drag);
  }
}
指令说明实例

v-drag

<div v-drag>拖拽我</div>

 5.v-format指令

格式化数字,可保留指定小数位,转成千分制

import { App, Directive, DirectiveBinding, VNode } from "vue";

const format: Directive<HTMLElement> = {
  beforeMount(el, binding, vnode) {
    formatCallBack(el, binding, vnode);
  },
  updated(el, binding, vnode) {
    formatCallBack(el, binding, vnode);
  }
}

const formatCallBack = (el: HTMLElement, binding: DirectiveBinding<any>, vnode: VNode<any, HTMLElement>) => {
  let oldVal = vnode.children;
  let val = Number(oldVal);
  if(isNaN(val)) {
    throw '值必须为数字类型';
  }
  const value = Number(binding.value);
  const fixedNum = isNaN(value) ? 2 : value;
  el.innerText = val.toFixed(fixedNum);
  if(binding.modifiers.price) {
    el.innerText = formatNumber(el.innerText);
  }
}

const  formatNumber = (num: string) => {
  let str = num.split('.');
  let x1 = str[0];
  let x2 = str.length > 1 ? '.' + str[1] : '';
  var rgx = /(\d+)(\d{3})/;
  while (rgx.test(x1)) {
    x1 = x1.replace(rgx, '$1' + ',' + '$2');
  }
  return x1 + x2;
}

export default {
  install(app: App<Element>) {
    app.directive('format', format);
  }
}
指令说明实例

v-format

默认保留两位小数<div v-format>25.123456</div>
v-format="4"保留指定小数<div v-format="4">25.123456</div>
v-format.price保留两位小数,并格式化为千分制<div v-format.price>2525525.123456</div>
v-format.price="1"保留一位小数,并格式化为千分制<div v-format.price="1">225255.123456</div>

 6.v-debounce指令

防抖指令,避免重复点击

import { App, Directive } from "vue";

const debounce: Directive<HTMLElement> = {
  mounted(el, binding) {
    if(typeof binding.value !== 'function') {
      throw '回调必须是函数';
    }
    const arg = Number(binding.arg);
    let timer: NodeJS.Timeout;
    el.addEventListener('click', () => {
      if(timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(() => {
        binding.value()
      }, isNaN(arg) ? 500 : arg);
    })
  }
}

export default {
  install(app: App<Element>) {
    app.directive('debounce', debounce);
  }
}
指令说明实例

v-debounce="callBack"

默认延迟500ms<button v-debounce="callBack">防抖</button>
v-debounce:200="xxx"设置延迟为200ms<button v-debounce:200="xxx">防抖</button>

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Agwenbi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值