113 lines
2.3 KiB
Vue
113 lines
2.3 KiB
Vue
<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 openHouseId = ref(null);
|
||
// 打开菜单
|
||
const open = async (e: MouseEvent, value: string) => {
|
||
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;
|
||
}
|
||
|
||
// 下边界碰撞
|
||
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);
|
||
openHouseId.value = value;
|
||
}, 10);
|
||
};
|
||
|
||
// 关闭
|
||
const close = () => {
|
||
show.value = false;
|
||
document.removeEventListener('click', close);
|
||
document.removeEventListener('contextmenu', close);
|
||
};
|
||
|
||
// 点击选项
|
||
const handleClick = (item) => {
|
||
emit('select', {
|
||
type: item.value,
|
||
value: openHouseId.value
|
||
});
|
||
close();
|
||
};
|
||
|
||
// 暴露方法给父组件调用
|
||
defineExpose({ open, close });
|
||
</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>
|