vue-自定义指令合集

v-copy(一键复制)

/*
* 需求:实现一键复制文本内容,用于鼠标右键粘贴。
* 思路:
* 1、动态创建 textarea 标签,并设置 readOnly 属性及移出可视区域
* 2、将要复制的值赋给 textarea 标签的 value 属性,并插入到 body
* 3、选中值 textarea 并复制
* 4、将 body 中插入的 textarea 移除
* 5、在第一次调用时绑定事件,在解绑时移除事件
*/
const copy={
  bind (el, binding) {
    // 双击触发复制
    if (binding.modifiers.dblclick) {
      el.addEventListener('dblclick', () => handleClick(el.innerText))
      el.style.cursor = 'copy'
    }
    // 点击icon触发复制
    else if (binding.modifiers.icon) {
      if (el.hasIcon) return
      const iconElement = document.createElement('i')
      iconElement.setAttribute('class', 'el-icon-document-copy')
      iconElement.setAttribute('style', 'margin-left:5px')
      el.appendChild(iconElement)
      el.hasIcon = true
      iconElement.addEventListener('click', () => handleClick(el.innerText))
      iconElement.style.cursor = 'copy'
    }
    // 单击触发复制
    else {
      el.addEventListener('click', () => handleClick(el.innerText))
      el.style.cursor = 'copy'
    }
  }
}
 
function handleClick (text) {
  // 创建元素
  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')
  input.value = text
  input.select()
  document.execCommand('copy')
  // alert('复制成功')
}
export  default  copy



<div v-copy> 单击复制 </div>
<div v-copy.dblclick> 双击复制 </div>
<div v-copy.icon> icon复制 </div>

v-debounce(按键防抖)

/*
* 背景:在开发中,有时遇到要给input或者滚动条添加监听事件,需要做防抖处理。
* 需求:防止input或scroll事件在短时间内被多次触发,使用防抖函数限制一定时间后触发。
* 思路:

* 定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。
* 将事件绑定在传入的方法上。
*/
const debounce = {
    inserted: function (el, {value:{fn, event, time}}) {
      //没绑定函数直接返回
      if (typeof fn !== 'function') return
      el._timer = null
      //监听点击事件,限定事件内如果再次点击则清空定时器并重新定时
      el.addEventListener(event, () => {
        if (el._timer !== null) {
          clearTimeout(el._timer)
          el._timer = null
        }
        el._timer = setTimeout(() => {
          fn()
        }, time)
      })
    },
  }
  
  export default debounce

使用:给 Dom 加上 v-debounce 及回调函数即可
 <template>
  <input v-debounce="{fn: debounce, event: 'input', time: 5000}" />
      <div v-debounce="{fn: debounce, event: 'scroll', time: 5000}">
          <p>文字文字文字文字...</p>
      </div>
</template>

<script>
export default {
  methods: {
    debounce(){
      console.log('debounce 防抖')
    },
  }
}
</script>

v-longPress(实现长按)

/*
* 需求:当用户按下鼠标左键或移动端单指触碰,并按住按钮几秒钟时,视为一次长按,触发对应的函数。

* 思路:

* 定义一个计时器, n 秒后执行函数,n作为参数。
* 当用户按下按钮时触发 mousedown 或touchstart 事件,启动计时器。
* 如果 click 、 mouseup 、touchend 或 touchcancel 事件在 n 秒内被触发,则清除计时器,视为普通点击事件。
* 如果计时器没有在 n秒内清除,则视为一次长按,触发对应的函数。
*/

const longpress = {
  bind: function (el, {value:{fn,time}}) {
    //没绑定函数直接返回
    if (typeof fn !== 'function') return
    // 定义定时器变量
    el._timer = null
    // 创建计时器( n秒后执行函数 )
    el._start = (e) => {
      //e.type表示触发的事件类型如mousedown,touchstart等
      //pc端: e.button表示是哪个键按下0为鼠标左键,1为中键,2为右键
      //移动端: e.touches表示同时按下的键为个数
      if (  (e.type === 'mousedown' && e.button && e.button !== 0) || 
            (e.type === 'touchstart' && e.touches && e.touches.length > 1)
      ) return;
      //定时长按n秒后执行事件
      if (el._timer === null) {
        el._timer = setTimeout(() => {
          fn()
        }, time)
        //取消浏览器默认事件,如右键弹窗
        el.addEventListener('contextmenu', function(e) {
          e.preventDefault();
        })
      }
    }
    // 如果两秒内松手,则取消计时器
    el._cancel = (e) => {
      if (el._timer !== null) {
        clearTimeout(el._timer)
        el._timer = null
      }
    }
    // 添加计时监听
    el.addEventListener('mousedown', el._start)
    el.addEventListener('touchstart', el._start)
    // 添加取消监听
    el.addEventListener('click', el._cancel)
    el.addEventListener('mouseout', el._cancel)
    el.addEventListener('touchend', el._cancel)
    el.addEventListener('touchcancel', el._cancel)
  },
  // 指令与元素解绑时,移除事件绑定
  unbind(el) {
    // 移除计时监听
    el.removeEventListener('mousedown', el._start)
    el.removeEventListener('touchstart', el._start)
    // 移除取消监听
    el.removeEventListener('click', el._cancel)
    el.removeEventListener('mouseout', el._cancel)
    el.removeEventListener('touchend', el._cancel)
    el.removeEventListener('touchcancel', el._cancel)
  },
}

export default longpress

使用:给 Dom 加上 v-longpress 及参数即可

<template>
  <button v-longpress="{fn: longpress,time:2000}">长按</button>
</template>

<script>
export default {
  methods: {
    longpress () {
      console.log('长按指令生效')
    }
  }
}
</script>

v-inputNumber(正整数)

/*
* 需求:根据正则表达式,设计自定义处理表单输入规则的指令,这里只能输入正整数
* 使用:将需要校验的输入框加上 v-inputNumber 即可
*/
let findEle = (parent, type) => {
  return parent.tagName.toLowerCase() === type ? parent : parent.querySelector(type)
}

const trigger = (el, type) => {
  const e = document.createEvent('HTMLEvents')
  e.initEvent(type, true, true)
  el.dispatchEvent(e)
}
const inputNumber ={
  bind: function (el, binding, vnode) {
    // 正则规则可根据需求自定义
    var regRule = /[^0-9]/g
    let $inp = findEle(el, 'input')
    el.$inp = $inp
    $inp.handle = function () {
      let val = $inp.value
      $inp.value = val.replace(regRule, '')

      trigger($inp, 'input')
    }
    $inp.addEventListener('keyup', $inp.handle)
  },
  unbind: function (el) {
    el.$inp.removeEventListener('keyup', el.$inp.handle)
  }
}
export  default  inputNumber 



<input type="text" v-model="note" v-inputNumber />

v-waterMarker(添加背景水印)

/*
* 需求:给整个页面添加背景水印。
* 思路:
* 1、使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
* 2、将其设置为背景图片,从而实现页面或组件水印效果
*/
function addWaterMarker(str, parentNode, font, textColor) {
  // 水印文字,父元素,字体,文字颜色
  var can = document.createElement('canvas')
  parentNode.appendChild(can)
  can.width = 200
  can.height = 150
  can.style.display = 'none'
  var cans = can.getContext('2d')
  cans.rotate((-20 * Math.PI) / 180)
  cans.font = font || '16px Microsoft JhengHei'
  cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.3)'
  cans.textAlign = 'left'
  cans.textBaseline = 'Middle'
  cans.fillText(str, can.width / 10, can.height / 2)
  parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')'
}

export default {
  bind: function (el, binding) {
    addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor)
  }
}



<div v-waterMarker="{text:'版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>

v-draggable(拖拽)

/*
  需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。

  思路:
    1、设置需要拖拽的元素为absolute,其父元素为relative。
    2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
    3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
    4、鼠标松开(onmouseup)时完成一次拖拽
  
  使用:在 Dom 上加上 v-draggable 即可
  <div class="dialog-model" v-draggable></div>
*/
export default {
  inserted: function (el) {
    el.style.cursor = 'move';
    el.style.position="absolute";
    el.onmousedown = function (e) {
      let disx = e.pageX - el.offsetLeft
      let disy = e.pageY - el.offsetTop
      document.onmousemove = function (e) {
        let x = e.pageX - disx
        let y = e.pageY - disy
        let maxX = parseInt(window.getComputedStyle(el.parentNode).width) - parseInt(window.getComputedStyle(el).width)
        let maxY = parseInt(window.getComputedStyle(el.parentNode).height) - parseInt(window.getComputedStyle(el).height)
        if (x < 0) {
          x = 0
        } else if (x > maxX) {
          x = maxX
        }

        if (y < 0) {
          y = 0
        } else if (y > maxY) {
          y = maxY
        }
        el.style.left = x + 'px'
        el.style.top = y + 'px'
      }
      document.onmouseup = function () {
        document.onmousemove = document.onmouseup = null
      }
    }
  }
}

v-permission(权限)

/*
* 背景:在一些后台管理系统,我们可能需要根据用户角色进行一些操作权限的判断,很多时候我们都是粗暴地给一个元素添加 v-if / v-show 来进行显示隐藏,但如果判断条件繁琐且多个地方需要判断,这种方式的代码不仅不优雅而且冗余。针对这种情况,我们可以通过全局自定义指令来处理。
* 需求:自定义一个权限指令,对需要权限判断的 Dom 进行显示隐藏。
* 思路:
* 自定义一个权限数组
* 判断用户的权限是否在这个数组内,如果是则显示,否则则移除 Dom
*/

const removeNode = el => {
    let parentNode = el.parentNode;
    if (
        el.children.length > 0 &&
        el.children[0].className.includes("el-switch")
    ) {
        el.parentNode.hasDeletedNode = true;
        let firstChild = el.children[0];
        el.removeChild(firstChild);
    } else {
        parentNode && !parentNode.hasDeletedNode && parentNode.removeChild(el);
    }
};

const nodeOperator = (el, binding) => {
    const menuIds = window.sessionStorage.getItem('menuIds').split(",").map(Number)
    const { value } = binding
    if (!value) {
        removeNode(el)
        return el
    }
    if (!menuIds.includes(value)) {
        removeNode(el)
    }

}

export default {
    name: "auth",
    option: {
        inserted: function (el, binding) {
            nodeOperator(el, binding)
        }
    }
}

<div class="btns">
  <!-- 显示 -->
  <button v-permission="'1'">权限按钮1</button>
  <!-- 不显示 -->
  <button v-permission="'10'">权限按钮2</button>
</div>

v-clickOut(点击弹窗外部关闭弹窗)

/*
* 背景:在我们的项目里,经常会出现一个弹窗,需要点击弹窗外部关闭该弹窗。
* 需求:实现一个指令,点击目标区域外部,触发指定函数。
* 思路:
* 判断点击的元素是否为目标元素,是则不作为,否则触发指定函数。
*/
const clickOut = {
    bind(el,{value}){
        function clickHandler(e) {
            //先判断点击的元素是否是本身,如果是本身,则返回
            if (el.contains(e.target)) return;
            //判断指令中是否绑定了函数
            if (typeof value === 'function') {
                //如果绑定了函数,则调用函数,此处value就是clickImgOut方法
                value()
            }
        }
        // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
        el.handler = clickHandler;
        //添加事件监听
        setTimeout(() => {
            document.addEventListener('click',el.handler);
        }, 0);
    },
    unbind(el){
        //解除事件监听
        document.removeEventListener('click',el.handler);
    }
}

export default clickOut

// 使用,将需要用到该指令的元素添加v-click-out

<template>
  <div>
 		<button @click="isImgShow = true">展示弹窗</button>
    <div v-click-out="clickImgOut" v-if="isImgShow" class="pop">
          <img src="https://xxx.jpg" alt="">
          <p>文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字</p>
    </div>
  </div>
</template>

<script>
  export default {
		data(){
      return {
        isImgShow : false
      }
    },
    methods:{
      clickImgOut(){
        this.isImgShow = false;
        console.log('点击弹窗外部')
      }
    }
	}
</script>

v-throttle(防止多次请求)

/*
* 背景:在开发中,有些提交保存按钮有时候会在短时间内被点击多次,这样就会多次重复请求后端接口,造成数据的混乱,比如立即购买按钮,多次点击就会多次调用创建订单接口。
* 需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。
* 思路:
* 定义一个由开关(默认为开)控制是否执行的方法,第一次执行函数时将开关关闭,在规定时间内再调用该方法,则不会再次执行,直至规定时间过后开关打开。
* 将事件绑定在 click 方法上。
*/

 const throttle = {
    bind:function (el,{value:{fn,time}}) {
        if (typeof fn !== 'function') return
        el._flag = true;//开关默认为开
        el._timer = null
        el.handler = function () {
            if (!el._flag) return;
            //执行之后开关关闭
            el._flag && fn()
            el._flag = false
            if (el._timer !== null) {
                clearTimeout(el._timer)
                el._timer = null
            }
            el._timer = setTimeout(() => {
                el._flag = true;//三秒后开关开启
            }, time);
        }
        el.addEventListener('click',el.handler)
    },
    unbind:function (el,binding) {
        el.removeEventListener('click',el.handler)
    }
}

export default throttle



<template>
 <button v-throttle="{fn: throttle,time:3000}">throttle节流</button>
</template>

<script>
export default {
  methods: {
    throttle () {
      console.log('throttle 节流 只触发一次')
    }
  }
}
</script>

v-scrollPop(弹窗内部内容可以滚动,外部无法滚动)

/*
* 背景:在我们的项目中,经常使用弹窗展示活动规则,活动规则过长需要滚动时,时长会导致外部滚动。这时针对这种情况,我们可以通过全局自定义指令来处理。
* 需求:自定义一个指令,使得弹窗内部内容可以滚动,外部无法滚动。
* 思路:
* 当弹窗展示时,记录滚动条滚动距离,然后给body和html设置固定定位,高度100%,top值为滚动距离。
* 当弹窗解除时,恢复原先样式,并把滚动距离设置成原来的值。
*/

const scrollPop = {
    bind(el) {
        //定义此时到元素的内容垂直滚动的距离
        el.st = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
        let cssStr = `overflow: hidden;width: 100%; height: 100%; position: fixed; top: ${- el.st}px;`
        document.querySelector('html').cssText = cssStr
        document.body.style.cssText = cssStr
    },
    unbind(el,{value}) {
        let cssStr = 'overflow: auto; height: 100%; position: relative; top: 0px;scroll-behavior: auto'
        document.querySelector('html').cssText = cssStr
        document.body.style.cssText = cssStr
        document.querySelector('html').style.scrollBehavior = 'auto'
        //手动设置滚动距离
        document.documentElement.scrollTop = el.st
        document.body.scrollTop = el.st
        if (value !== 'smooth')return;
        //如果传了滚动方式为smooth平稳滚动即有感滚动,当滚动完毕后,把auto改回smooth
        let timer = setTimeout(() => {
            cssStr = `overflow: auto; height: 100%; position: relative; top: 0px; scroll-behavior: ${value||'smooth'}`
            document.querySelector('html').cssText = cssStr
            document.querySelector('html').style.scrollBehavior = value || 'smooth'
            document.body.style.cssText = cssStr
        }, 1);
    }
}

export default scrollPop

<div class="scroll-pop" v-if="isScrollPopShow" v-scroll-pop="'smooth'">
	<div class="content">
    <p>这是很长一段文字,请耐心读完,然后你会发现这段文字并没有什么意义。</p>
    ...
  </div>
</div>

v-sensor(埋点)

/*
* 背景:目前前端埋点代码大量入侵业务,埋点代码量大且难以区分和维护,现做出优化方案以减少其代码量。
* 埋点类型:
* ElementShow:页面元素显示
* PopupTrack:弹窗显示
* $WebClick:点击页面按钮
* PopupBtnClick:点击弹窗中按钮
* 自定义事件
* 优化方案:
* 1.自定义指令 v-sensor=" {el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'} "
*/

const sensor = {
    // 当被绑定的元素插入到 DOM 中时
    inserted: function (el,{value: sensorObj}) {
        let showObj={} ,clickObj={}//showObj代表展示类埋点,clickObj代表点击类埋点
        //如果传入参数格式不为对象,则不向下执行
        if (!Object.prototype.toString.call(sensorObj) === '[object Object]'|| JSON.stringify(sensorObj) == "{}") return
        //遍历传入对象参数,根据key值确定埋点类型
        for (const key in sensorObj) {
            if (Object.hasOwnProperty.call(sensorObj, key)) {
                switch (key) {
                    case 'el':
                        showObj= {
                            name:'ElementShow',
                            value: sensorObj[key]
                        };
                        break;
                    case 'pop':
                        showObj= {
                            name:'PopupTrack',
                            value: sensorObj[key]
                        };
                        break;
                    case 'elClick':
                        clickObj= {
                            name:'$WebClick',
                            value: sensorObj[key]
                        };
                        break;
                    case 'popClick':
                        clickObj= {
                            name:'PopupBtnClick',
                            value: sensorObj[key]
                        };
                        break;  
                    default:
                        break;
                }
            }
        }
        // 展示类埋点执行
        showObj.value && sensors.track(showObj.name, {
            FileName: showObj.value
        });
        //点击类埋点执行
        if (clickObj.value) {
            el.handler = function () {
                clickObj.name === '$WebClick' && sensors.track(clickObj.name, {
                    $element_name: clickObj.value
                });
                clickObj.name === 'PopupBtnClick' && sensors.track(clickObj.name, {
                    FileName: clickObj.value
                });
            }
            el.addEventListener('click',el.handler)
        }
    },
    // 指令与元素解绑的时候,移除事件绑定
    unbind(el) {
        el.handler && el.removeEventListener('click', el.handler)
    }
}
  
  export default sensor


/*
对于除自定义事件以外的埋点事件,较好的优化办法就是使用自定义指令。使用 v-sensor=" {el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'} " 。v-sensor接收一个对象作为参数,对象的key为事件标识,对象的value为事件属性,key值具体对应关系如下。

el:ElementShow
pop:PopupTrack
elClick:$WebClick
popClick:PopupBtnClick
*/

//单独使用ElementShow或$WebClick
<div v-sensor="{el :'Btn_XXX_Tag_CXXXon'}">我是一个么得感情的标签</div>
<div v-sensor="{elClick:'Btn_XXX_Tag_Common'}">俺也一样</div>
//ElementShow和$WebClick组合使用方法
<div v-sensor="{el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'}">俺也一样</div>
//单独使用PopupTrack和PopupBtnClick
<div v-sensor="{pop :'Pop_XXX_Tag_Common'}">俺也一样</div>
<div v-sensor="{popClick:'Pop_XXX_Tag_Common'}">俺也一样</div>
//PopupTrack和PopupBtnClick组合使用方法
<div v-sensor="{pop :'Pop_XXX_Tag_Common',popClick:'Pop_XXX_Tag_Common'}">俺也一样</div>
//变量使用方法
<div v-sensor="{pop :`${sensorVal}`}">俺也一样</div>

/*
提示:
​ 由于该自定义指令是在元素插入页面DOM中时执行的,所以如果事件属性值使用变量的话,请在created生命周期内操作完毕,或给该元素绑定v-if为对应变量。
*/

v-emoji(输入框禁止表情)

/*
背景:开发中遇到的表单输入,往往会有对输入内容的限制,比如不能输入表情和特殊字符,只能输入数字或字母等。
*/
let findEle = (parent, type) => {
    return parent.tagName.toLowerCase() === type ? parent : parent.querySelector(type)
}

const trigger = (el, type) => {
    const e = document.createEvent('HTMLEvents')
    e.initEvent(type, true, true)
    el.dispatchEvent(e)
}

const emoji= {
    bind: function (el, binding, vnode) {
        // 正则规则可根据需求自定义
        var regRule = /[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()-+/*{}[]]|s/g
        let $inp = findEle(el, 'input')
        el.$inp = $inp
        $inp.handle = function () {
            let val = $inp.value
            $inp.value = val.replace(regRule, '')

            trigger($inp, 'input')
        }
        $inp.addEventListener('keyup', $inp.handle)
    },
    unbind: function (el) {
        el.$inp.removeEventListener('keyup', el.$inp.handle)
    },
}

export default emoji


<template>
  <input type="text" v-model="note" v-emoji />
</template>

v-LazyLoad(图片懒加载)

/*
背景:在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以进行图片懒加载优化势在必行。

需求:实现一个图片懒加载指令,只加载浏览器可见区域的图片。

思路:

图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的
拿到所有的图片 Dom ,遍历每个图片判断当前图片是否到了可视区范围内
如果到了就设置图片的 src 属性,否则显示默认图片
图片懒加载有两种方式可以实现,一是绑定 srcoll 事件进行监听,二是使用 IntersectionObserver 判断图片是否到了可视区域,但是有浏览器兼容性问题。

下面封装一个懒加载指令兼容两种方法,判断浏览器是否支持 IntersectionObserver API,如果支持就使用 IntersectionObserver 实现懒加载,否则则使用 srcoll 事件监听 + 节流的方法实现。
*/
const LazyLoad = {
  // install方法
  install(Vue, options) {
    const defaultSrc = options.default
    Vue.directive('lazy', {
      bind(el, binding) {
        LazyLoad.init(el, binding.value, defaultSrc)
      },
      inserted(el) {
        if (IntersectionObserver) {
          LazyLoad.observe(el)
        } else {
          LazyLoad.listenerScroll(el)
        }
      },
    })
  },
  // 初始化
  init(el, val, def) {
    el.setAttribute('data-src', val)
    el.setAttribute('src', def)
  },
  // 利用IntersectionObserver监听el
  observe(el) {
    var io = new IntersectionObserver((entries) => {
      const realSrc = el.dataset.src
      if (entries[0].isIntersecting) {
        if (realSrc) {
          el.src = realSrc
          el.removeAttribute('data-src')
        }
      }
    })
    io.observe(el)
  },
  // 监听scroll事件
  listenerScroll(el) {
    const handler = LazyLoad.throttle(LazyLoad.load, 300)
    LazyLoad.load(el)
    window.addEventListener('scroll', () => {
      handler(el)
    })
  },
  // 加载真实图片
  load(el) {
    const windowHeight = document.documentElement.clientHeight
    const elTop = el.getBoundingClientRect().top
    const elBtm = el.getBoundingClientRect().bottom
    const realSrc = el.dataset.src
    if (elTop - windowHeight < 0 && elBtm > 0) {
      if (realSrc) {
        el.src = realSrc
        el.removeAttribute('data-src')
      }
    }
  },
  // 节流
  throttle(fn, delay) {
    let timer
    let prevTime
    return function (...args) {
      const currTime = Date.now()
      const context = this
      if (!prevTime) prevTime = currTime
      clearTimeout(timer)

      if (currTime - prevTime > delay) {
        prevTime = currTime
        fn.apply(context, args)
        clearTimeout(timer)
        return
      }

      timer = setTimeout(function () {
        prevTime = Date.now()
        timer = null
        fn.apply(context, args)
      }, delay)
    }
  },
}

export default LazyLoad



<img v-LazyLoad="xxx.jpg" />

v-expandClick(元素点击范围扩展)

/*
使用该指令可以隐式的扩展元素的点击范围,由于借用伪元素实现,故不会影响元素在页面上的排列布局。

可传入的参数为:上右下左扩展的范围,单位 px,默认向外扩展 10px。指令的代码如下:
*/
export default function (el, binding) {
    const s = document.styleSheets[document.styleSheets.length - 1]
    const DEFAULT = -10 // 默认向外扩展10px
    const ruleStr = `content:"";position:absolute;top:-${top || DEFAULT}px;bottom:-${bottom || DEFAULT}px;right:-${right || DEFAULT}px;left:-${left || DEFAULT}px;`
    const [top, right, bottom, left] = binding.expression && binding.expression.split(',') || []
    const classNameList = el.className.split(' ')
    el.className = classNameList.includes('expand_click_range') ? classNameList.join(' ') : [...classNameList, 'expand_click_range'].join(' ')
    el.style.position = el.style.position || "relative"
    if (s.insertRule) {
        s.insertRule('.expand_click_range::before' + '{' + ruleStr + '}', s.cssRules.length)
    } else { /* IE */
        s.addRule('.expand_click_range::before', ruleStr, -1)
    }
}

// 参数 Attributes:
![](https://img2023.cnblogs.com/blog/2131762/202303/2131762-20230308172116870-1219264398.png)

<div v-expandClick="20,30,40,50" @click="glabClickoutside"> 点击范围扩大</div>

参数 Attributes:

参数 说明 默认值 类型 可选
top, right, bottom, left 上右下左扩展宽度(逗号分割),
单位px 10,10,10,10 String 可填

v-tooltip(元素说明指令)

/*
元素添加说明,如同 element-ui 的 el-tooltip(问号 icon 在鼠标覆盖后,展示说明文字)。
*/
import Vue from 'vue'
export default function (el, binding) {
    if (el.hasIcon) return
    const iconElement = structureIcon(binding.arg, binding.value)
    el.appendChild(iconElement)
    el.hasIcon = true
}
 
function structureIcon (content, attrs) {
    // 拼接绑定属性
    let attrStr = ''
    for (let key in attrs) {
        attrStr += `${key}=${attrs[key]} `
    }
    const a = `<el-tooltip content=${content} ${attrStr}><i class="el-icon-question" style="margin:0 10px"></i></el-tooltip>`
    // 创建构造器
    const tooltip = Vue.extend({
        template: a
    })
    // 创建一个 tooltip 实例并返回 dom 节点
    const component = new tooltip().$mount()
    return component.$el
}

// 然后你可以在模板中任何元素上使用新的 v-tooltipproperty,如下:
<div v-tooltip:content='tootipParams'> 提示 </div>

// 举例

<div v-tooltip:提示内容为XXX1> 提示1</div>
<div v-tooltip:提示内容为XXX='tootipParams'> 提示2 </div>

// 为指令传入 element-ui 支持的参数:

data() {
    return {
        tootipParams: {
            placement: 'top',
            effect: 'light',
        }
    }
}

参数 Attributes:

参数 说明 默认值 类型 可选
content 传给指令的参数。例如 v-tooltip:content 中,参数为 "content" ,tooltip中展示的内容为:"content" / String 可选
tootipParams element-ui 支持的 tooltip 属性 / Object 可选

v-screenfull(全屏)

// 全屏指令,点击元素进行全屏/退出全屏的操作。支持元素后面是否插入 element-ui 的全屏图标 el-icon-full-screen。

import screenfull from 'screenfull'
 
export default {
  bind (el, binding) {
    if (binding.modifiers.icon) {
      if (el.hasIcon) return
      // 创建全屏图标
      const iconElement = document.createElement('i')
      iconElement.setAttribute('class', 'el-icon-full-screen')
      iconElement.setAttribute('style', 'margin-left:5px')
      el.appendChild(iconElement)
      el.hasIcon = true
  }
    el.style.cursor = el.style.cursor || 'pointer'
    // 监听点击全屏事件
    el.addEventListener('click', () => handleClick())
  }
}
 
function handleClick () {
  if (!screenfull.isEnabled) {
    alert('浏览器不支持全屏')
    return
  }
  screenfull.toggle()
}


<div v-screenfull.icon> 全屏 </div>

参数 Attributes:

参数 说明 默认值 类型 可选
icon 是否添加 icon / String 可选

v-ellipsis(文字省略)

// 使用该指令当文字内容超出宽度(默认 100 px)时自动变为省略形式。等同于使用 css:

width: 100px;
whiteSpace: nowrap
overflow: hidden;
textOverflow: ellipsis;


export default function (el, binding) {
    el.style.width = binding.arg || 100 + 'px'
    el.style.whiteSpace = 'nowrap'
    el.style.overflow = 'hidden';
    el.style.textOverflow = 'ellipsis';
}

<div v-ellipsis:100> 需要省略的文字是阿萨的副本阿萨的副本阿萨的副本阿萨的副本</div>

参数 Attributes:

参数 说明 默认值 类型 可选
width 元素宽度 100 Number 必填

v-empty(空状态)

// 使用该指令可以显示缺省的空状态。可以传入默认图片(可选,默认无图片)、默认文字内容(可选,默认为暂无数据)、以及标示是否显示空状态(必选)。

import Vue from "vue";
export default {
  update (el, binding, vnode) {
    el.style.position = el.style.position || 'relative'
    const { offsetHeight, offsetWidth } = el
    const { visible, content, img } = binding.value
    const image = img ? `<img src="${img}" height="30%" width="30%"></img>` : ''
    const defaultStyle = "position:absolute;top:0;left:0;z-index:9999;background:#fff;display:flex;justify-content: center;align-items: center;"
    const empty = Vue.extend({
    template: `<div style="height:${offsetHeight}px;width:${offsetWidth}px;${defaultStyle}">
      <div style="text-align:center">
        <div>${image}</div>
        <div>${content || '暂无数据'}</div>
      </div>
    </div>`
    })
    const component = new empty().$mount().$el
    if (visible) {
      el.appendChild(component)
    } else {
      el.removeChild(el.lastChild)
    }
  },
}



<div style="height:500px;width:500px" v-empty="emptyValue">  // 原本内容

// 需要传入一个参数对象,例如显示文字为:暂无列表,图片路径为 ../../assets/images/blue_big.png,控制标示 visible:
emptyValue = {
  content: '暂无列表',
  img: require('../../assets/images/blue_big.png'),
  visible: true,
},

参数 Attributes:

参数 说明 默认值 类型 可选
emptyValue 包含文字内容 content、图片 img、是否显示 visible,仅 visible 必传 / Object 必须

v-backtop(回到顶部)

// 使用该指令可以让页面或指定元素回到顶部。
// 可选指定元素,如果不指定则全局页面回到顶部。可选在元素偏移多少 px 后显示 backtop 元素,例如在滚动 400px 后显示回到顶部按钮。



export default {
  bind (el, binding, vnode) {
    // 响应点击后滚动到元素顶部
    el.addEventListener('click', () => {
    const target = binding.arg ? document.getElementById(binding.arg) : window
    target.scrollTo({
      top: 0,
      behavior: 'smooth'
      })
    })
  },
  update (el, binding, vnode) {
    // 滚动到达参数值才出现绑定指令的元素
    const target = binding.arg ? document.getElementById(binding.arg) : window
    if (binding.value) {
      target.addEventListener('scroll', (e) => {
        if (e.srcElement.scrollTop > binding.value) {
          el.style.visibility = 'unset'
        } else {
          el.style.visibility = 'hidden'
        }
      })
    }
    // 判断初始化状态
    if (target.scrollTop < binding.value) {
      el.style.visibility = 'hidden'
    }
  },
  unbind (el) {
    const target = binding.arg ? document.getElementById(binding.arg) : window
    target.removeEventListener('scroll')
    el.removeEventListener('click')
  }
}

<div  v-backtop:app="400"> 回到顶部 </div>

// 也可以这样使用,表示为一直显示绑定指令的元素,并且是全局页面回到顶部:
<div  v-backtop> 回到顶部 </div>

参数 Attributes:

参数 说明 默认值 类型 可选
id 给需要回到顶部的元素添加的id / String 可选
offset 偏移距离为 height 时显示指令绑定的元素 / Number 可选

v-resize(响应缩放)

// 使用该指令可以响应元素宽高改变时执行的方法。
export default {
  bind(el, binding) {
    let width = '', height = '';
    function isReize() {
      const style = document.defaultView.getComputedStyle(el);
      if (width !== style.width || height !== style.height) {
        binding.value();  // 执行传入的方法
      }
      width = style.width;
      height = style.height;
     }
     el.__timer__ = setInterval(isReize, 300); // 周期性监听元素是否改变
  },
  unbind(el) {
    clearInterval(el.__timer__);
  }
}


// 传入 resize() 方法
<div v-resize="resize"></div>

参数 Attributes:

参数 说明 默认值 类型 可选
resize() 传入元素改变 size 后执行的方法 / Function 必选

v-format(字符串整型)

/*
* 使用该指令可以修改字符串,如使用 v-format.toFixed 保留两位小数、 v-format.price 将内容变成金额(每三位逗号分隔),可以同时使用,如 v-format.toFixed.price。
* 例如将数字 243112.331 变成 243112.33,或 243,112.33。
*/
export default {
  update (el, binding, vnode) {
    const { value, modifiers } = binding
    if (!value) return
    let formatValue = value
    if (modifiers.toFixed) {
      formatValue = value.toFixed(2)
    }
    console.log(formatValue)
    if (modifiers.price) {
      formatValue = formatNumber(formatValue)
    }
    el.innerText = formatValue
  },
}
 
 
 
function formatNumber (num) {
  num += '';
  let strs = num.split('.');
  let x1 = strs[0];
  let x2 = strs.length > 1 ? '.' + strs[1] : '';
  var rgx = /(\d+)(\d{3})/;
  while (rgx.test(x1)) {
    x1 = x1.replace(rgx, '$1' + ',' + '$2');
  }
  return x1 + x2
}



<div v-format.toFixed.price="123333"> 123 </div>

参数 Attributes:

参数 说明 默认值 类型 可选
toFixed 保留两位小数 / String 可选
price 整形成金额(三位逗号分隔) / String 可选

v-badge(徽标)


/*
使用该指令在元素右上角显示徽标。
支持配置徽标的背景颜色、徽标形状;支持传入徽标上显示的数字。
*/
import Vue from 'vue'
 
const SUCCESS = '#72c140'
const ERROR = '#ed5b56'
const WARNING = '#f0af41'
const INFO = '#4091f7'
const HEIGHT = 20
let flag = false
export default {
  update (el, binding, vnode) {
    const { modifiers, value } = binding
    const modifiersKey = Object.keys(modifiers)
    let isDot = modifiersKey.includes('dot')
    let backgroundColor = ''
    if (modifiersKey.includes('success')) {
      backgroundColor = SUCCESS
    } else if (modifiersKey.includes('warning')) {
      backgroundColor = WARNING
    } else if (modifiersKey.includes('info')) {
      backgroundColor = INFO
    } else {
      backgroundColor = ERROR
    }
 
    const targetTemplate = isDot 
        ? `<div style="position:absolute;top:-5px;right:-5px;height:10px;width:10px;border-radius:50%;background:${backgroundColor}"></div>` 
        : `<div style="background:${backgroundColor};position:absolute;top:-${HEIGHT / 2}px;right:-${HEIGHT / 2}px;height:${HEIGHT}px;min-width:${HEIGHT}px;border-radius:${HEIGHT / 2}px;text-align:center;line-height:${HEIGHT}px;color:#fff;padding:0 5px;">${value}</div>`
        
    el.style.position = el.style.position || 'relative'
    const badge = Vue.extend({
      template: targetTemplate
    })
    const component = new badge().$mount().$el
    if (flag) {
      el.removeChild(el.lastChild)
    }
    el.appendChild(component)
    flag = true
  }
}

<div v-badge.dot.info="badgeCount" style="height:50px;width:50px;background:#999"> </div>
参数 说明 默认值 类型 可选
normal、dot 徽标形状normal为正常徽标;dot 仅为一个点 normal String 可选
success、error、info、warning 徽标颜色 error String 可选
number 徽标上显示的数字 / Number 可选

directives/index.js文件

// import Vue from "vue"
// 全局自定义指令
import draggable from './modules/draggable'
// 自定义指令
const directives = {
    draggable,
}
 
export default {
  install(Vue) {
    Object.keys(directives).forEach((key) => {
      Vue.directive(key, directives[key])
    })
  },
}

main.js文件

import Vue from 'vue'
import Directives from '@/directives'
Vue.use(Directives)
posted @ 2023-03-08 17:45  seekHelp  阅读(172)  评论(0编辑  收藏  举报