vue 比較實用的directive封裝 contextMenu

  • 其中用到vue directive extend 的用法,不理解可以看官網(wǎng)介紹
  • 實用方式<chid-comp v-contextMenu.contextmenu="{ menu }"/>
  <div v-contextMenu="{ menu: handleMenu(data), id: data, node }" class="icon-boxes">
   <span class="icon"></span>
 </div>
<script>
import contextMenu from '@/components/contextMenu/directive'
export default {
  directives: {
    contextMenu: contextMenu()
  },
data() {
return {
  menu: [
        {
          label: '新建主題域',
          onAction: this.handleFolderAction,
          type: 'addFolder',
        },
        {
          label: '新建主題',
          type: 'addTheme',
          onAction: this.handleThemeAction,

        },
        {
          label: '重命名',
          type: 'rename',
          border: true,
          onAction: this.hanleRename
        },
        {
          label: '刪除',
          type: 'delete',
          onAction: this.handleDelete
        },
        {
          label: '刷新',
          onAction: () => {
            location.reload()
          }
        }
      ],
}
},
methods: {
  handleFolderAction(data, node, callback) {
    callback()
  }
}
}
</script>
// directive.js
import Vue from 'vue'
import index from './index.vue'
import { isParentNode, on, off, preventDefault } from '@/utils/dom'
function createInstance(options) {
  return new (Vue.extend(index))({
    el: document.createElement('div'),
    propsData: options
  })
}

function handlerContextMenu(el, binding, vnode) {
  const trigger = binding.modifiers.contextmenu? 'contextmenu': undefined
  const instance = createInstance({ list: binding.value.menu, trigger, targetDom: el, id: binding.value.id, node: binding.value.node })
   el.__contextMenuInstance__ = instance  
  append(instance.$el)
}
function ContextMenu() {
  return {
    bind(el, binding, vnode) {
      handlerContextMenu(el, binding, vnode)
    },
    unbind(el, binding, vnode) {
      remove(el.__contextMenuInstance__.$el)
    }
  }
}

function append(el, parent = document.body) {
  const style = getComputedStyle(parent)
  if (['absolute', 'fixed', 'relative'].indexOf(style.position) === -1) {
    parent.style.position = 'relative'
  }
  parent.appendChild(el)
}

function remove(el) {
  var parent = el.parentNode;
  parent.removeChild(el)
}

export default ContextMenu

  // index.vue
<template>
  <div v-if="show" class="context-menu-boxes" :style="style" ref="contextMenu">
    <div class="menu-pane" :class="{ border: item.border }" v-for="(item, i) in list" :key="i" @click.stop="clickItem(item)">
      {{ item.label }}
    </div>
  </div>
</template>

<script>
import { isParentNode, on, off } from '@/utils/dom'
export default {
  props: {
    trigger: {
      type: String,
      default: 'click'
    },
    targetDom: null,
    onClose: {// 想實現(xiàn)在外部的異步關(guān)閉,覺得沒必要
      type: [Function, null],
      default: null
    },
    node: {}, // 
    id: { // 自定義參數(shù)
      type: [String, Number, Object],
      default: ''
    },
    list: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      show: false,
      touchTarget: null,
      left: 0,
      top: 0
    }
  },
  computed: {
    style() {
      return {
        left: `${ this.left }px`,
        top: `${ this.top }px`
      }
    }
  },
  methods: {
    clickItem(item) {
      item.onAction && item.onAction(this.id, this.node, (s) => {// 實現(xiàn)異步關(guān)閉
        if(s === false) { return }
        this.show = false
      })
    },
    handler(e) {
      e.preventDefault();
      window.event? window.event.cancelBubble = true : e.stopPropagation();
      this.touchTarget = e
      this.show = true
      this.left = this.touchTarget.pageX
      this.top = this.touchTarget.pageY
    },
    offHandler(e) {
      if(isParentNode(e.target, this.$refs.contextMenu)) return
      this.show = false
    }
  },
  mounted() {
    if(this.targetDom) {
      on(this.targetDom, this.trigger, this.handler)
      on(document, 'click', this.offHandler, true)
    }
  },
  destroyed() {
    off(document, 'click', this.offHandler)
    off(this.targetDom, this.trigger, this.handler)
  }
}
</script>

<style lang="scss" scoped>
.context-menu-boxes {
  position: absolute;
  // top: 50%;
  border: 1px solid #e6e6e6;
  box-shadow: 0 0 8px rgb(0 0 0 / 10%);
  z-index: 99;
  color: #333;
  padding: 1px;
  background-color: #fff;
  height: auto;
  .menu-pane {
    white-space: nowrap;
    height: 32px;
    line-height: 32px;
    width: auto;
    min-width: 150px;
    font-size: 14px;
    padding-left: 20px;
    &.border {
      border-bottom: 1px solid #e6e6e6;
    }
    &:last-child {
      border: none;
    }
    &:hover {
      background-color: rgba(63, 153, 231, .3);
      cursor: pointer;
      user-select: none;
    }
  }
}
</style>
// dom.js
export const on = (() => {
  if(document.addEventListener) {
    return function(element, event, handler, useCapture = false) {
      if (element && event && handler) {
        element.addEventListener(event, handler, useCapture);
      }
    }
  } else {
    return function(element, event, handler) {
      if(element && event && handler) {
        element.attachEvent('on' + event, handler)
      }
    }
  }
})();

export function off(target, event, handler) {
  target.removeEventListener(event, handler);
}


export function isParentNode(element, rootParent = window) {
  let node = element;
  
  while( node &&  node.tagName !== 'HTML' && node.nodeType === 1) {
    // console.log(node)
    if(node == rootParent) {
      return true
    }
    node = node.parentNode;
  }
  return null
}


export function preventDefault(event) {
  if (typeof event.cancelable !== 'boolean' || event.cancelable) {
    event.preventDefault();
  } else {
    event.stopPropagation();
  }
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容