vue3常用自定义指令

1,132 阅读3分钟

基于github.com/PanJiaChen/…

复制

src/directives/clipboard/clipboard.ts

// <el-button type="primary" v-clipboard:copy="123" v-clipboard:success="setLog" v-clipboard:error="setLog">主要按钮</el-button>
import Clipboard from 'clipboard'
import { DirectiveBinding } from 'vue'
if (!Clipboard) {
  throw new Error('you should npm install `clipboard` --save at first ')
}
let successCallback: Function | null
let errorCallback: Function | null
let clipboardInstance: Clipboard | null

export default {
  name: 'clipboard',
  mounted(el: Element, binding: DirectiveBinding) {
    if (binding.arg === 'success') {
      successCallback = binding.value
    } else if (binding.arg === 'error') {
      errorCallback = binding.value
    } else {
      clipboardInstance = new Clipboard(el, {
        text() {
          return binding.value
        },
        action() {
          return binding.arg === 'cut' ? 'cut' : 'copy'
        }
      })
      clipboardInstance.on('success', (e: Clipboard.Event) => {
        const callback = successCallback
        callback && callback(e)
      })
      clipboardInstance.on('error', (e: Clipboard.Event) => {
        const callback = errorCallback
        callback && callback(e)
      })
    }
  },
  update(el: Element, binding: DirectiveBinding) {
    if (binding.arg === 'success') {
      successCallback = binding.value
    } else if (binding.arg === 'error') {
      errorCallback = binding.value
    } else {
      clipboardInstance = new Clipboard(el, {
        text() {
          return binding.value
        },
        action() {
          return binding.arg === 'cut' ? 'cut' : 'copy'
        }
      })
    }
  },
  unmounted(_: Element, binding: DirectiveBinding) {
    if (binding.arg === 'success') {
      successCallback = null
    } else if (binding.arg === 'error') {
      errorCallback = null
    } else {
      if (clipboardInstance) {
        clipboardInstance.destroy()
      }
      clipboardInstance = null
    }
  }
}

弹窗拖拽

src/directives/el-drag/el-drag.ts

// 需要套一层div 不然会报错
// <div v-el-drag>
//   <el-dialog title="提示" v-model="dialogVisible" width="30%">
//   <span>这是一段信息</span>
//   <template #footer>
// <span class="dialog-footer">
//   <el-button @click="dialogVisible = false">取 消</el-button>
// <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
// </span>
// </template>
// </el-dialog>
// </div>
import { DirectiveBinding, VNode } from 'vue'

export default {
  name: 'el-drag',
  mounted(el: HTMLElement, binding: DirectiveBinding, vnode: VNode) {
    el.style.height = '100vh'
    el.style.width = '100vw'
    const dragDom = el.querySelector('.el-dialog') as HTMLElement
    const dialogHeaderEl = el.querySelector('.el-dialog__header') as HTMLElement
    dragDom.style.cssText += ';top:0px;'
    dialogHeaderEl.style.cssText += ';cursor:move;'

    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

      const styleLeftStr = getComputedStyle(dragDom).left
      const styleTopStr = getComputedStyle(dragDom).top
      if (!styleLeftStr || !styleTopStr) return
      let styleLeft: number
      let styleTop: number

      // Format may be "##%" or "##px"
      if (styleLeftStr.includes('%')) {
        styleLeft = +document.body.clientWidth * (+styleLeftStr.replace(/%/g, '') / 100)
        styleTop = +document.body.clientHeight * (+styleTopStr.replace(/%/g, '') / 100)
      } else {
        styleLeft = +styleLeftStr.replace(/px/g, '')
        styleTop = +styleTopStr.replace(/px/g, '')
      }

      document.onmousemove = (e) => {
        let left = e.clientX - disX
        let top = e.clientY - disY

        // Handle edge cases
        if (-left > minDragDomLeft) {
          left = -minDragDomLeft
        } else if (left > maxDragDomLeft) {
          left = maxDragDomLeft
        }
        if (-top > minDragDomTop) {
          top = -minDragDomTop
        } else if (top > maxDragDomTop) {
          top = maxDragDomTop
        }

        // Move current element
        dragDom.style.cssText += `;left:${left + styleLeft}px;top:${top + styleTop}px;`
      }

      document.onmouseup = () => {
        document.onmousemove = null
        document.onmouseup = null
      }
    }
  }
}

权限

src/directives/permission/permission.ts

// <el-button type="primary" v-permission="{ action: 'create', effect: 'disabled' }" @click="setLog">主要按钮</el-button>
import { DirectiveBinding } from 'vue'

export default {
  name: 'permission',
  mounted(el: HTMLButtonElement, binding: DirectiveBinding) {
    if (binding.value == undefined) return
    const { action, effect } = binding.value
    // 如果action不传,则认为不需要授权认证
    if (action == undefined) {
      return
    }
    if (!hasPermission(action)) {
      if (effect == 'disabled') {
        el.disabled = true
        el.setAttribute('title', '没有权限')
      } else {
        el.remove()
      }
    }
  }
}

function hasPermission(action: string) {
  // 判断是否拥有权限
  return 1 !== 1
}

波纹

src/directives/waves/waves.ts

import './waves.css'
import { DirectiveBinding } from 'vue'

export default {
  name: 'waves',
  mounted(el: HTMLButtonElement, binding: DirectiveBinding) {
    el.addEventListener(
      'click',
      (e) => {
        const customOpts = Object.assign({}, binding.value)
        const opts = Object.assign(
          {
            ele: el, // 波纹作用元素
            type: 'hit', // hit 点击位置扩散 center中心点扩展
            color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
          },
          customOpts
        )
        const target: HTMLElement = opts.ele
        if (target) {
          target.style.position = 'relative'
          target.style.overflow = 'hidden'
          const rect = target.getBoundingClientRect()
          let ripple = target.querySelector('.waves-ripple') as HTMLElement
          if (!ripple) {
            ripple = document.createElement('span')
            ripple.className = 'waves-ripple'
            ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
            target.appendChild(ripple)
          } else {
            ripple.className = 'waves-ripple'
          }
          switch (opts.type) {
            case 'center':
              ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
              ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
              break
            default:
              ripple.style.top =
                (e.pageY -
                  rect.top -
                  ripple.offsetHeight / 2 -
                  document.documentElement.scrollTop || document.body.scrollTop) + 'px'
              ripple.style.left =
                (e.pageX -
                  rect.left -
                  ripple.offsetWidth / 2 -
                  document.documentElement.scrollLeft || document.body.scrollLeft) + 'px'
          }
          ripple.style.backgroundColor = opts.color
          ripple.className = 'waves-ripple z-active'
          return false
        }
      },
      false
    )
  }
}

src/directives/waves/waves.css

.waves-ripple {
    position: absolute;
    border-radius: 100%;
    background-color: rgba(0, 0, 0, 0.15);
    background-clip: padding-box;
    pointer-events: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    -webkit-transform: scale(0);
    -ms-transform: scale(0);
    transform: scale(0);
    opacity: 1;
}

.waves-ripple.z-active {
    opacity: 0;
    -webkit-transform: scale(2);
    -ms-transform: scale(2);
    transform: scale(2);
    -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
    transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
    transition: opacity 1.2s ease-out, transform 0.6s ease-out;
    transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
}

批量注册

src/directives/index

import clipboard from '@/directives/clipboard/clipboard'
import permission from '@/directives/permission/permission'
import elDrag from '@/directives/el-drag/el-drag'
import waves from '@/directives/waves/waves'
// 注册全局自定义指令
const directive = [clipboard, permission, elDrag, waves]

export default (app: any) => {
  directive.forEach((directive) => {
    app.directive(directive.name, directive)
  })
}

main.ts

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import directives from './directives/index'
// 创建App实例
const app = createApp(App)

// 注册全局自定义指令
directives(app)

app.use(store).use(router).mount('#app')

看完的朋友可以动动手点个赞再走哦,你们的支持是对我最大的鼓励!!!