<template>
<div class="my-contextmenu"
v-show="visible"
:class="classes"
:style="styles">
<Menu v-if="visible" ref="menu" :items="data"></Menu>
</div>
</template>
<script>
/**
* 右键菜单组件
* @module $ui/components/my-contextmenu
*/
import {on, off} from 'element-ui/lib/utils/dom'
import Menu from './Menu'
/**
* 点击菜单项时触发
* @event click
* @param {Object} item 菜单项数据
* @param {Object} vm 菜单项实例
*/
export default {
name: 'MyContextmenu',
components: {
Menu
},
provide() {
return {
wrapper: this
}
},
/**
* 属性参数
* @member props
* @property {string} [theme=light] 主题配色,可选 'light', 'dark'
* @property {Array} [data] 菜单项数据,[{icon, label, info, disabled, divider, children}]
* @property {string|object} [data.icon] 图标
* @property {string} [data.label] 标题文本
* @property {string} [data.info] 附加信息文本
* @property {boolean} [data.disabled] 禁用
* @property {boolean} [data.divider] 分割线
* @property {Array} [data.children] 子菜单项
* @property {boolean} [disabled] 禁用菜单
* @property {number} [zIndex=1000] 显示层级
* @property {String|HTMLElement|Function} 触发菜单容器,支持选择器和函数,默认 document.body
* @property {boolean} [manual] 手动模式,需要自行调用show 、hide 方法
*/
props: {
// 主题风格
theme: {
type: String,
default: 'light',
validator(val) {
return ['light', 'dark'].includes(val)
}
},
// 数据 [{icon, label, info, disabled, divider, children}]
data: {
type: Array,
default() {
return []
}
},
disabled: Boolean,
zIndex: {
type: Number,
default: 1000
},
target: {
type: [String, HTMLElement, Function],
default() {
return document.body
}
},
// 手动控制菜单显示
manual: Boolean
},
data() {
return {
visible: false,
x: 0,
y: 0,
rect: null
}
},
computed: {
styles() {
return {
left: `${this.x}px`,
top: `${this.y}px`,
zIndex: this.zIndex
}
},
classes() {
return {
[`is-${this.theme}`]: !!this.theme
}
}
},
methods: {
getTarget() {
let el = document.body
if (typeof this.target === 'string') {
el = document.querySelector(this.target)
}
if (typeof this.target === 'function') {
el = this.target()
}
return el
},
handleContextMenu(e) {
if (this.disabled) return
e.preventDefault()
if(!this.manual) {
this.show({x: e.pageX, y: e.pageY})
}
return false
},
getPlacement(rect, x, y) {
const targetRect = this.triggerTarget.getBoundingClientRect()
if (rect.height + y - Math.abs(targetRect.top) >= targetRect.height) {
y -= rect.height
}
if (rect.width + x - Math.abs(targetRect.left) >= targetRect.width) {
x -= rect.width
}
return {
x, y
}
},
show({x, y}) {
this.visible = true
this.$nextTick(() => {
const rect = this.$refs.menu.rect
const placement = this.getPlacement(rect, x, y)
this.x = placement.x
this.y = placement.y
})
},
hide() {
this.visible = false
}
},
mounted() {
this.triggerTarget = this.getTarget()
on(this.triggerTarget, 'contextmenu', this.handleContextMenu)
on(document.body, 'click', this.hide)
document.body.appendChild(this.$el)
},
beforeDestroy() {
off(this.triggerTarget, 'contextmenu', this.handleContextMenu)
off(document.body, 'click', this.hide)
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el)
}
this.triggerTarget = null
}
}
</script>