解决问题是,在点击色块区域的时候,显示不同的右键菜单。同时,希望右键菜单的高度,以动画的形式,慢慢变高。
组件封装
ContextMenu.vue
<template>
<div ref="containRef">
<slot></slot>
<Teleport to="body">
<Transition @beforeEnter="handleBeforEnter" @enter="handleEnter" @afterEnter="handleAfterEnter">
<div class="context-menu" v-if="showMenu" :style="{
left: x + 'px',
top: y + 'px'
}">
<div class="menu-list">
<div class="menu-item" @click="handleClcik(item)" v-for="(item, index) in menu" :key="item.label">
{{ item.label }}
</div>
</div>
</div>
</Transition>
</Teleport>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import useContextMenu from './useContextMenu'
defineProps({
menu: {
type: Array<any>,
default: () => []
}
})
const emits = defineEmits(['select'])
const containRef = ref(null)
const { x, y, showMenu } = useContextMenu(containRef)
const handleClcik = (item: any) => {
emits('select', item)
showMenu.value = false
}
const handleBeforEnter = (el: any) => {
el.style.height = 0;
}
const handleEnter = (el: any) => {
el.style.height = 'auto';
const h = el.clientHeight;
el.style.height = 0;
requestAnimationFrame(() => {
el.style.height = h + 'px';
el.style.transition = '0.5s';
})
}
const handleAfterEnter = (el: any) => {
el.style.transition = 'none';
}
</script>
<style>
.context-menu {
position: fixed;
background: #eee;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
min-width: 100px;
overflow: hidden;
border-radius: 5px;
}
.menu-item {
padding: 5px;
color: #1d1d1f;
}
.menu-item:hover {
background: #3477d9;
color: #fff;
}
.v-enter-from {
opacity: 0;
}
.v-enter-to {
transition: 0.5s;
opacity: 1;
}
</style>
useContextMenu.ts文件 这个钩子的作用就是 获取鼠标距离视口的x y坐标,然后显示右键菜单
import { onUnmounted } from "vue"
import { ref } from "vue"
import { onMounted } from "vue"
const useContextMenu = (containerRef: any) => {
const showMenu = ref(false)
const x = ref(0)
const y = ref(0)
const handleContextMenu = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
showMenu.value = true
x.value = e.clientX
y.value = e.clientY
}
const closeMenu = () => {
showMenu.value = false
}
onMounted(() => {
const div = containerRef.value as HTMLElement;
div.addEventListener("contextmenu", handleContextMenu);
window.addEventListener("contextmenu", closeMenu, true);
window.addEventListener('click', closeMenu, true)
})
onUnmounted(() => {
const div = containerRef.value as HTMLElement;
div && div.removeEventListener('contextmenu', handleContextMenu)
window.removeEventListener("contextmenu", closeMenu);
window.removeEventListener('click', closeMenu)
})
return {
x,
y,
showMenu
}
}
export default useContextMenu
使用
App.vue文件
<template>
<div class="container" @click.stop="">
<ContextMenu class="block1" :menu="[
{ label: '添加' },
{ label: '删除' },
{ label: '编辑' },
{ label: '查看' },
{ label: '复制' },
]" @select="choose1">
<h2>{{ choose1Text }}</h2>
</ContextMenu>
<ContextMenu class="block2" :menu="[
{ label: '员工2' },
{ label: '部门2' },
{ label: '角色2' },
]" @select="choose2">
<h2>{{ choose2Text }}</h2>
<ContextMenu class="block3" :menu="[
{ label: '员工3' },
{ label: '部门3' },
{ label: '角色3' },
]" @select="choose3">
<h2>{{ choose3Text }}</h2>
</ContextMenu>
</ContextMenu>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ContextMenu from '../ContextMenu/index.vue'
const choose1Text = ref('')
const choose2Text = ref('')
const choose3Text = ref('')
const choose1 = (event: any) => {
choose1Text.value = event.label
}
const choose2 = (event: any) => {
choose2Text.value = event.label
}
const choose3 = (event: any) => {
choose3Text.value = event.label
}
</script>
<style>
.container {
background: #ffffff;
display: flex;
justify-content: space-between;
}
.block1 {
width: 30%;
height: 300px;
margin-right: 30px;
background-color: aqua;
}
.block2 {
flex: 1;
height: 300px;
padding: 20px;
box-sizing: border-box;
background-color: red;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.block3 {
height: 100px;
background-color: skyblue;
}
</style>
总结 :
1、之所使用Teleport组件,是因为fixed的定位一般情况下是相对于视口的,但是如果父组件的样式是transform时,这是fixed的定位就相对于父组件了
2、老板可能要求菜单高度需要动画,组件里无法获取菜单项的具体数量,只能使用js来动态的获取高度值了,如何获取请看 handleEnter 事件里的代码
3、如何在点击页面别的区域时,取消右键菜单,需要在windows上添加关闭弹出框,注意的是。可能用户在处于某些需要,给组件的父元素添加了阻止冒泡事件,需要在捕获阶段关闭弹出框