vue3 右键多级菜单展示
vue3 右键多级菜单展示,一共两个文件,子菜单循环展示,只需要将菜单数据传入即可展示,里面有的样式是用element,自行修改。
<!--
@文件名: contextmenuCom.vue
@来源:YongPing.Wang|2023/11/24 15:10
@描述:右键
-->
<template>
<transition enter-active-class="animate__animated animate__fadeIn animate__faster">
<div v-show="isShow" ref="contextmenuComRef" class="contextmenuCom">
<contextmenu-item-com :menuList="menuList" @contextmenuHandle="handleContextmenu" />
</div>
</transition>
</template>
<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
import { getUuidUnit } from 'lg-js-tools'
import ContextmenuItemCom from '@/components/engineerDraw/components/contextmenuItemCom.vue'
const emits = defineEmits(['contextmenuClose', 'contextmenuHandle'])
const props = defineProps({
domRef: Object,
})
const menuList = ref<any>([])
const isShow = ref(false)
const contextmenuComRef = ref()
const init = (event: any, data: any) => {
menuList.value = data.map((item: any) => {
item.key = getUuidUnit()
if (item.children) {
item.children.forEach((child: any) => {
child.key = getUuidUnit()
})
}
return item
})
isShow.value = true
nextTick(() => {
const menu: HTMLDivElement | null = contextmenuComRef.value
if (menu) {
const menuWidth = menu.offsetWidth + 5
const menuHeight = menu.offsetHeight + 5
const chaY = document.body.clientHeight - event.clientY
const chaX = document.body.clientWidth - event.clientX
// 防止菜单太靠底,根据可视高度调整菜单出现位置
if (chaY < menuHeight) {
menu.style.top = event.clientY - menuHeight + 'px'
} else {
menu.style.top = event.clientY + 5 + 'px'
}
if (chaX < menuWidth) {
menu.style.left = event.clientX - menuWidth + 'px'
} else {
props.domRef?.setIsShow(event.clientX)
menu.style.left = event.clientX + 5 + 'px'
}
}
})
}
// 关闭
const close = () => {
menuList.value = []
isShow.value = false
emits('contextmenuClose')
}
// 获取状态
const getIsShow = () => {
return isShow.value
}
// 点击菜单
const handleContextmenu = (data: any) => {
emits('contextmenuHandle', data)
close()
}
onMounted(() => {
document.addEventListener('mousedown', close)
})
onUnmounted(() => {
document.removeEventListener('mousedown', close)
})
defineExpose({
init,
getIsShow,
})
</script>
<style lang="scss" scoped>
.contextmenuCom {
font-family: custom-harmony-sans-regular;
position: fixed;
padding: 0 6px;
font-size: 13px;
border-radius: 4px;
box-sizing: border-box;
white-space: nowrap;
z-index: 1000;
background-color: var(--el-color-white);
box-shadow: var(--el-box-shadow);
}
</style>
<!--
@文件名: contextmenuItemCom.vue
@来源:YongPing.Wang|2024/11/06 09:51
@描述:
-->
<template>
<template v-for="item in props.menuList" :key="item.key">
<div class="contextmenu__item" @mouseleave="mouseoutHandle(item)">
<div :class="[item.value === 'children' ? '' : 'link']" class="name LG-btn" @click="handleContextmenu(item)" @mouseover="mouseoverHandle(item)" @mouseup.stop="" @mousedown.stop="">
{{ item.name }}
<span v-if="item.value === 'children'" class="value">
<el-icon><arrow-right /></el-icon>
</span>
</div>
<div v-if="item.children && item.children.length" :id="item.key" class="childrenBox">
<div class="children">
<contextmenu-item-com :menuList="item.children" @contextmenuHandle="handleContextmenu" />
</div>
</div>
</div>
</template>
</template>
<script lang="ts" setup>
import { ArrowRight } from '@element-plus/icons-vue'
const props = defineProps({
menuList: Array,
})
const emits = defineEmits(['contextmenuHandle'])
// 二级菜单
const mouseoverHandle = (item: any) => {
let dom = document.getElementById(`${item.key}`)
if (dom) {
dom.style.display = 'block'
}
}
const mouseoutHandle = (item: any) => {
let dom = document.getElementById(`${item.key}`)
if (dom) {
dom.style.display = 'none'
}
}
// 点击菜单
const handleContextmenu = (data: any) => {
if ((data.children && data.children.length) || data.value === 'children') {
return
}
emits('contextmenuHandle', data)
}
</script>
<style lang="scss" scoped>
.contextmenu__item {
position: relative;
display: flex;
align-items: center;
padding: 6px 8px 6px 4px;
.name {
display: flex;
align-items: center;
position: relative;
width: 100%;
.value {
width: 15px;
font-size: 13px;
margin-bottom: -5px;
position: absolute;
right: -15px;
}
}
.childrenBox {
background-color: transparent;
display: none;
position: absolute;
left: calc(100% + 5px);
top: 0;
z-index: 1;
padding-left: 5px;
.children {
border-radius: 4px;
background-color: var(--el-color-white);
box-shadow: var(--el-box-shadow);
padding: 0 4px;
}
}
}
.contextmenu__item:not(:last-child) {
border-bottom: 1px solid var(--el-border-color-light);
}
.contextmenu__item:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.contextmenu__item:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.link:hover {
color: var(--el-color-primary);
}
</style>