house
This commit is contained in:
109
src/components/ContextMenu/index.vue
Normal file
109
src/components/ContextMenu/index.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user