- 其中用到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();
}
}
网友评论