美文网首页
vue 比较实用的directive封装 contextMenu

vue 比较实用的directive封装 contextMenu

作者: 刘彪lastbee | 来源:发表于2021-12-28 09:43 被阅读0次
  • 其中用到vue directive extend 的用法,不理解可以看官网介绍
  • 实用方式<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: {// 想实现在外部的异步关闭,觉得没必要
      type: [Function, null],
      default: null
    },
    node: {}, // 
    id: { // 自定义参数
      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) => {// 实现异步关闭
        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();
  }
}


相关文章

网友评论

      本文标题:vue 比较实用的directive封装 contextMenu

      本文链接:https://www.haomeiwen.com/subject/jatpqrtx.html