前置篇 antdv的对话框在前面已经改成可以全局打开,如此就可以使用右键菜单的方式打开对话框,但是打开的对话框默认都是居中或在头部位置的,我希望的是在右键鼠标点击的位置,所以又研究了下增加了个定位的功能,且不挡住当前元素,参照的也是前篇的实现方式,具体如下:
typescript
<script setup lang="ts">
// 此处为前篇的代码,此处不重复贴出。
watch(popoverInfo.value, () => {
if (popoverInfo.value.visible) {
setTimeout(() => {
const { x, y } = calculateElementPosition(
heightW.value,
widthW.value,
heightM.value,
widthM.value,
popoverInfo.value.position.x,
popoverInfo.value.position.y,
popoverInfo.value.position.width,
popoverInfo.value.position.height,
);
// 计算出希望出现的位置修改transformX、transformY 即可重新定位对话框的位置.
transformY.value = y - popoverInfo.value.position.y;
transformX.value = x - popoverInfo.value.position.x;
}, 50);
}
});
// 计算位置: 按照 右、左、下、右、居中的顺序计算位置,若前面一个放不下则考虑下一个位置。
// 参数说明:heightW、widthW 当前窗口的大小 heightM、widthM 对话框的大小 x、y 当前鼠标操作的位置 refW、refH 当前元素的大小(对话框会尽量不遮挡此区域)
const calculateElementPosition = (heightW: number, widthW: number, heightM: number, widthM: number, x: number, y: number, refW: number, refH: number) => {
let finalX = 0;
let finalY = 0;
const rightBlance = widthW - x - refW - widthM;
const leftBlance = x - widthM;
const topBlance = y - heightM;
const topCenterBlance = y - heightM / 2;
const bottomBlance = heightW - y - refH - heightM;
const canPlaceOnRight = rightBlance >= 0;
const canPlaceOnLeft = leftBlance >= 0;
if (canPlaceOnRight) {
finalX = x + refW + Math.min(rightBlance, 10);
} else if (canPlaceOnLeft) {
finalX = x - widthM - Math.min(leftBlance, 10);
} else {
finalX = Math.floor((widthW - widthM) / 2);
}
if (canPlaceOnRight || canPlaceOnLeft) {
// 左右可以放下,则计算y坐标位置
finalY = topCenterBlance >= 0 ? topCenterBlance : 10;
const bottomBlanceTmp = heightW - finalY - heightM;
if (bottomBlanceTmp < 0) {
finalY = finalY + bottomBlanceTmp;
if (finalY > 20) {
finalY = finalY - 10;
} else {
finalY = finalY / 2;
}
}
} else if (bottomBlance >= 0) {
// 左右放不下,则先考虑放下方,左右居中
finalY = y + refH + Math.min(bottomBlance, 10);
} else if (topBlance >= 0) {
// 左右放不下,则先考虑放下方,左右居中
finalY = y - Math.min(topBlance, 10);
} else {
//上下都放不下,则左右居中,上下居中
finalY = Math.floor((heightW - heightM) / 2);
}
// 返回最终的坐标位置
return { x: finalX, y: finalY };
};
onMounted(() => {});
</script>
全量代码:
html
<template>
<a-modal v-model:open="popoverInfo.visible" :wrap-style="{ overflow: 'hidden' }" :style="modalStyle" :mask="false" width="800px" :destroyOnClose="true">
<slot></slot>
<template #title>
<div ref="modalTitleRef" style="width: 100%; cursor: move">{{ props.title }}</div>
</template>
<template #modalRender="{ originVNode }">
<div :style="transformStyle" ref="modalRef">
<component :is="originVNode" />
</div>
</template>
</a-modal>
</template>
typescript
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useMxGraphStore } from "@/stores/mxGraphStore.ts";
import { useDraggable, useElementSize, useWindowSize } from "@vueuse/core";
import { onMounted, ref, watch, watchEffect, computed, CSSProperties, onUpdated } from "vue";
const props = defineProps<{ title: string }>();
const mxGraphStore = useMxGraphStore();
const { popoverInfo } = storeToRefs(mxGraphStore);
const modalTitleRef = ref();
const modalRef = ref();
const { x, y, isDragging } = useDraggable(modalTitleRef);
const { width: widthM, height: heightM } = useElementSize(modalRef);
const modalStyle = computed(() => `top: ${popoverInfo.value.position.y}px;left: ${popoverInfo.value.position.x}px;margin:0px`);
const { width: widthW, height: heightW } = useWindowSize();
const startX = ref<number>(0);
const startY = ref<number>(0);
const startedDrag = ref(false);
const transformX = ref(0);
const transformY = ref(0);
const preTransformX = ref(0);
const preTransformY = ref(0);
const dragRect = ref({ left: 0, right: 0, top: 0, bottom: 0 });
watch([x, y], () => {
if (!startedDrag.value) {
startX.value = x.value;
startY.value = y.value;
const bodyRect = document.body.getBoundingClientRect();
const titleRect = modalTitleRef.value.getBoundingClientRect();
dragRect.value.right = bodyRect.width - titleRect.width;
dragRect.value.bottom = bodyRect.height - titleRect.height;
preTransformX.value = transformX.value;
preTransformY.value = transformY.value;
}
startedDrag.value = true;
});
watch(isDragging, () => {
if (!isDragging) {
startedDrag.value = false;
}
});
watchEffect(() => {
if (startedDrag.value) {
transformX.value = preTransformX.value + Math.min(Math.max(dragRect.value.left, x.value), dragRect.value.right) - startX.value;
transformY.value = preTransformY.value + Math.min(Math.max(dragRect.value.top, y.value), dragRect.value.bottom) - startY.value;
}
});
const transformStyle = computed<CSSProperties>(() => {
return {
transform: `translate(${transformX.value}px, ${transformY.value}px)`,
};
});
watch(popoverInfo.value, () => {
if (popoverInfo.value.visible) {
setTimeout(() => {
const { x, y } = calculateElementPosition(
heightW.value,
widthW.value,
heightM.value,
widthM.value,
popoverInfo.value.position.x,
popoverInfo.value.position.y,
popoverInfo.value.position.width,
popoverInfo.value.position.height,
);
transformY.value = y - popoverInfo.value.position.y;
transformX.value = x - popoverInfo.value.position.x;
}, 50);
}
});
const calculateElementPosition = (heightW: number, widthW: number, heightM: number, widthM: number, x: number, y: number, refW: number, refH: number) => {
let finalX = 0;
let finalY = 0;
const rightBlance = widthW - x - refW - widthM;
const leftBlance = x - widthM;
const topBlance = y - heightM;
const topCenterBlance = y - heightM / 2;
const bottomBlance = heightW - y - refH - heightM;
const canPlaceOnRight = rightBlance >= 0;
const canPlaceOnLeft = leftBlance >= 0;
if (canPlaceOnRight) {
finalX = x + refW + Math.min(rightBlance, 10);
} else if (canPlaceOnLeft) {
finalX = x - widthM - Math.min(leftBlance, 10);
} else {
finalX = Math.floor((widthW - widthM) / 2);
}
if (canPlaceOnRight || canPlaceOnLeft) {
// 左右可以放下,则计算y坐标位置
finalY = topCenterBlance >= 0 ? topCenterBlance : 10;
const bottomBlanceTmp = heightW - finalY - heightM;
if (bottomBlanceTmp < 0) {
finalY = finalY + bottomBlanceTmp;
if (finalY > 20) {
finalY = finalY - 10;
} else {
finalY = finalY / 2;
}
}
} else if (bottomBlance >= 0) {
// 左右放不下,则先考虑放下方,左右居中
finalY = y + refH + Math.min(bottomBlance, 10);
} else if (topBlance >= 0) {
// 左右放不下,则先考虑放下方,左右居中
finalY = y - Math.min(topBlance, 10);
} else {
//上下都放不下,则左右居中,上下居中
finalY = Math.floor((heightW - heightM) / 2);
}
// 返回最终的坐标位置
return { x: finalX, y: finalY };
};
onMounted(() => {});
</script>