Files
sunProduct/src/components/ContextMenu/index.vue
2026-04-06 14:45:50 +08:00

110 lines
2.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<Teleport to="body">
<div v-show="show" ref="menuRef" class="context-menu" :style="{ left: `${x}px`, top: `${y}px`, zIndex: 999999 }">
<div class="item" v-for="item in menu" :key="item.label" @click="handleClick(item)">
<div class="item-list">
{{ item.label }}
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const props = defineProps<{
menu: Array<{ label: string; value: any }>;
}>();
const emit = defineEmits<{
select: [value: any];
}>();
const show = ref(false);
const x = ref(0);
const y = ref(0);
const menuRef = ref();
// 打开菜单
const open = async (e: MouseEvent, value: any) => {
e.preventDefault();
x.value = e.clientX;
y.value = e.clientY;
// 先显示一次,才能获取 DOM 尺寸
show.value = true;
await nextTick();
// 🔥 这里用 ref 拿 DOM,100% 拿到
if (!menuRef.value) return;
const rect = menuRef.value.getBoundingClientRect();
let left = e.clientX;
let top = e.clientY;
// 右边界碰撞
if (left + rect.width > window.innerWidth) {
left = window.innerWidth - rect.width - 8;
}
console.log('window', window.innerWidth);
// 下边界碰撞
if (top + rect.height > window.innerHeight) {
top = window.innerHeight - rect.height - 8;
}
// 上边界不能小于0
if (top < 8) top = 8;
if (left < 8) left = 8;
x.value = left;
y.value = top;
setTimeout(() => {
show.value = true;
document.addEventListener('click', close);
document.addEventListener('contextmenu', close);
}, 10);
};
// 关闭
const close = () => {
show.value = false;
console.log('close');
document.removeEventListener('click', close);
document.removeEventListener('contextmenu', close);
};
// 点击选项
const handleClick = (item) => {
emit('select', item.value);
close();
};
// 暴露方法给父组件调用
defineExpose({ open });
</script>
<style scoped>
.context-menu {
position: fixed;
background: #fff;
border: 1px solid #eee;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
min-width: 100px;
}
.item {
cursor: pointer;
font-size: 14px;
color: rgba(26, 117, 255, 1);
.item-list {
width: 100%;
padding: 15px;
}
}
.item:hover {
background: #f5f7fa;
color: #409eff;
}
</style>