指令示例

这里收集了一些实用的自定义指令示例,可以直接在项目中使用。实用自定义指令示例,包括自动聚焦、权限控制、懒加载、防抖等常用指令。

自动聚焦 v-focus

Vue.directive('focus', {
  inserted(el) {
    el.focus()
  }
})

使用:

<input v-focus placeholder="自动聚焦">

权限控制 v-permission

Vue.directive('permission', {
  bind(el, binding) {
    const { value } = binding
    const permissions = ['edit', 'delete', 'admin']
    
    if (value && !permissions.includes(value)) {
      el.parentNode && el.parentNode.removeChild(el)
    }
  }
})

使用:

<button v-permission="'edit'">编辑</button>
<button v-permission="'admin'">管理员操作</button>

图片懒加载 v-lazy

Vue.directive('lazy', {
  bind(el, binding) {
    el.setAttribute('data-src', binding.value)
    el.src = 'placeholder.jpg'
  },
  inserted(el) {
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const src = el.getAttribute('data-src')
          if (src) {
            el.src = src
            el.removeAttribute('data-src')
          }
          observer.unobserve(el)
        }
      })
    })
    observer.observe(el)
    el._observer = observer
  },
  unbind(el) {
    if (el._observer) {
      el._observer.disconnect()
    }
  }
})

使用:

<img v-lazy="imageUrl" alt="懒加载图片">

防抖 v-debounce

Vue.directive('debounce', {
  bind(el, binding) {
    const delay = parseInt(binding.arg) || 300
    let timer = null
    
    el._debounce = function(event) {
      if (timer) clearTimeout(timer)
      timer = setTimeout(() => {
        binding.value(event)
      }, delay)
    }
    
    el.addEventListener('input', el._debounce)
  },
  unbind(el) {
    el.removeEventListener('input', el._debounce)
  }
})

使用:

<input v-debounce:500="search" placeholder="搜索...">

节流 v-throttle

Vue.directive('throttle', {
  bind(el, binding) {
    const delay = parseInt(binding.arg) || 300
    let lastTime = 0
    
    el._throttle = function(event) {
      const now = Date.now()
      if (now - lastTime >= delay) {
        binding.value(event)
        lastTime = now
      }
    }
    
    el.addEventListener('click', el._throttle)
  },
  unbind(el) {
    el.removeEventListener('click', el._throttle)
  }
})

使用:

<button v-throttle:1000="submit">提交</button>

点击外部 v-click-outside

Vue.directive('click-outside', {
  bind(el, binding) {
    el._clickOutside = function(event) {
      if (!(el === event.target || el.contains(event.target))) {
        binding.value(event)
      }
    }
    document.addEventListener('click', el._clickOutside)
  },
  unbind(el) {
    document.removeEventListener('click', el._clickOutside)
  }
})

使用:

<template>
  <div v-click-outside="closeDropdown" class="dropdown">
    <button @click="isOpen = !isOpen">菜单</button>
    <ul v-show="isOpen">
      <li>选项一</li>
      <li>选项二</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isOpen: false
    }
  },
  methods: {
    closeDropdown() {
      this.isOpen = false
    }
  }
}
</script>

复制到剪贴板 v-copy

Vue.directive('copy', {
  bind(el, binding) {
    el._copyValue = binding.value
    
    el._copy = function() {
      const text = el._copyValue || el.innerText
      
      if (navigator.clipboard) {
        navigator.clipboard.writeText(text).then(() => {
          console.log('复制成功')
        })
      } else {
        const textarea = document.createElement('textarea')
        textarea.value = text
        document.body.appendChild(textarea)
        textarea.select()
        document.execCommand('copy')
        document.body.removeChild(textarea)
        console.log('复制成功')
      }
    }
    
    el.addEventListener('click', el._copy)
  },
  update(el, binding) {
    el._copyValue = binding.value
  },
  unbind(el) {
    el.removeEventListener('click', el._copy)
  }
})

使用:

<button v-copy="textToCopy">复制文本</button>
<button v-copy>复制按钮内的文字</button>

长按 v-longpress

Vue.directive('longpress', {
  bind(el, binding) {
    const delay = parseInt(binding.arg) || 500
    let timer = null
    
    el._start = function() {
      timer = setTimeout(() => {
        binding.value()
      }, delay)
    }
    
    el._cancel = function() {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
    }
    
    el.addEventListener('mousedown', el._start)
    el.addEventListener('mouseup', el._cancel)
    el.addEventListener('mouseleave', el._cancel)
    el.addEventListener('touchstart', el._start)
    el.addEventListener('touchend', el._cancel)
  },
  unbind(el) {
    el.removeEventListener('mousedown', el._start)
    el.removeEventListener('mouseup', el._cancel)
    el.removeEventListener('mouseleave', el._cancel)
    el.removeEventListener('touchstart', el._start)
    el.removeEventListener('touchend', el._cancel)
  }
})

使用:

<button v-longpress="showMenu">长按显示菜单</button>
<button v-longpress:1000="delete">长按1秒删除</button>

拖拽 v-draggable

Vue.directive('draggable', {
  bind(el) {
    el.style.cursor = 'move'
    el.style.position = 'absolute'
    
    let isDragging = false
    let startX, startY, initialX, initialY
    
    el._mousedown = function(e) {
      isDragging = true
      startX = e.clientX
      startY = e.clientY
      initialX = el.offsetLeft
      initialY = el.offsetTop
      
      document.addEventListener('mousemove', el._mousemove)
      document.addEventListener('mouseup', el._mouseup)
    }
    
    el._mousemove = function(e) {
      if (!isDragging) return
      
      const dx = e.clientX - startX
      const dy = e.clientY - startY
      
      el.style.left = initialX + dx + 'px'
      el.style.top = initialY + dy + 'px'
    }
    
    el._mouseup = function() {
      isDragging = false
      document.removeEventListener('mousemove', el._mousemove)
      document.removeEventListener('mouseup', el._mouseup)
    }
    
    el.addEventListener('mousedown', el._mousedown)
  },
  unbind(el) {
    el.removeEventListener('mousedown', el._mousedown)
  }
})

使用:

<div v-draggable class="draggable-box">拖拽我</div>

输入限制 v-input

Vue.directive('input', {
  bind(el, binding) {
    const type = binding.arg || 'text'
    
    el._input = function() {
      let value = el.value
      
      switch (type) {
        case 'number':
          value = value.replace(/[^\d]/g, '')
          break
        case 'decimal':
          value = value.replace(/[^\d.]/g, '')
          break
        case 'phone':
          value = value.replace(/[^\d]/g, '').slice(0, 11)
          break
      }
      
      el.value = value
      el.dispatchEvent(new Event('input'))
    }
    
    el.addEventListener('input', el._input)
  },
  unbind(el) {
    el.removeEventListener('input', el._input)
  }
})

使用:

<input v-input:number placeholder="只能输入数字">
<input v-input:decimal placeholder="只能输入数字和小数点">
<input v-input:phone placeholder="手机号">

工具提示 v-tooltip

Vue.directive('tooltip', {
  bind(el, binding) {
    const tooltip = document.createElement('div')
    tooltip.className = 'tooltip'
    tooltip.textContent = binding.value
    tooltip.style.cssText = `
      position: absolute;
      background: #333;
      color: white;
      padding: 5px 10px;
      border-radius: 4px;
      font-size: 12px;
      white-space: nowrap;
      z-index: 1000;
      display: none;
    `
    
    document.body.appendChild(tooltip)
    el._tooltip = tooltip
    
    el._showTooltip = function(e) {
      tooltip.style.display = 'block'
      tooltip.style.left = e.pageX + 10 + 'px'
      tooltip.style.top = e.pageY + 10 + 'px'
    }
    
    el._hideTooltip = function() {
      tooltip.style.display = 'none'
    }
    
    el.addEventListener('mouseenter', el._showTooltip)
    el.addEventListener('mousemove', el._showTooltip)
    el.addEventListener('mouseleave', el._hideTooltip)
  },
  unbind(el) {
    el.removeEventListener('mouseenter', el._showTooltip)
    el.removeEventListener('mousemove', el._showTooltip)
    el.removeEventListener('mouseleave', el._hideTooltip)
    if (el._tooltip) {
      el._tooltip.parentNode.removeChild(el._tooltip)
    }
  }
})

使用:

<button v-tooltip="提示文字">悬停查看提示</button>

指令使用建议

  1. 命名清晰:使用语义化的指令名称
  2. 参数灵活:支持 arg 和 modifiers 增强灵活性
  3. 清理资源:在 unbind 中清理事件监听和定时器
  4. 错误处理:添加必要的边界检查
  5. 文档注释:为复杂指令添加使用说明

这些指令示例涵盖了常见的使用场景,可以根据项目需求进行调整和扩展。