89 lines
1.6 KiB
Vue
89 lines
1.6 KiB
Vue
<template>
|
||
<div
|
||
v-if="visible"
|
||
class="context-menu"
|
||
:style="{ top: position.y + 'px', left: position.x + 'px' }"
|
||
>
|
||
<div
|
||
v-for="item in menuItems"
|
||
:key="item.key"
|
||
class="menu-item"
|
||
@click="handleClick(item)"
|
||
>
|
||
{{ item.label }}
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, watch, onMounted, onUnmounted } from 'vue';
|
||
|
||
const props = defineProps({
|
||
visible: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
position: {
|
||
type: Object,
|
||
default: () => ({ x: 0, y: 0 })
|
||
},
|
||
menuItems: {
|
||
type: Array,
|
||
default: () => []
|
||
}
|
||
});
|
||
|
||
const emit = defineEmits(['close', 'select']);
|
||
|
||
watch(() => props.visible, (newVal) => {
|
||
if (newVal) {
|
||
document.addEventListener('click', closeMenu);
|
||
} else {
|
||
document.removeEventListener('click', closeMenu);
|
||
}
|
||
});
|
||
|
||
const handleClick = (item) => {
|
||
emit('select', item);
|
||
closeMenu();
|
||
};
|
||
|
||
const closeMenu = () => {
|
||
emit('close');
|
||
};
|
||
|
||
onMounted(() => {
|
||
// 初始监听(如果visible初始为true)
|
||
if (props.visible) {
|
||
document.addEventListener('click', closeMenu);
|
||
}
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
document.removeEventListener('click', closeMenu);
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.context-menu {
|
||
position: fixed; /* 使用 fixed 定位,不受父容器 scroll 影响 */
|
||
z-index: 9999;
|
||
background-color: #fff;
|
||
border: 1px solid #eee;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||
border-radius: 4px;
|
||
padding: 5px 0;
|
||
}
|
||
|
||
.menu-item {
|
||
padding: 8px 15px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
color: #606266;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.menu-item:hover {
|
||
background-color: #f5f7fa;
|
||
}
|
||
</style> |