Ant Design Vue 之可定位对话框

前置篇 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>
相关推荐
Attacking-Coder1 分钟前
前端面试宝典---webpack面试题
前端·面试·webpack
极小狐26 分钟前
极狐GitLab 容器镜像仓库功能介绍
java·前端·数据库·npm·gitlab
程序猿阿伟38 分钟前
《Flutter社交应用暗黑奥秘:模式适配与色彩的艺术》
前端·flutter
rafael(一只小鱼)42 分钟前
黑马点评实战笔记
前端·firefox
weifont42 分钟前
React中的useSyncExternalStore使用
前端·javascript·react.js
初遇你时动了情1 小时前
js fetch流式请求 AI动态生成文本,实现逐字生成渲染效果
前端·javascript·react.js
影子信息1 小时前
css 点击后改变样式
前端·css
几何心凉1 小时前
如何使用 React Hooks 替代类组件的生命周期方法?
前端·javascript·react.js
小堃学编程1 小时前
前端学习(1)—— 使用HTML编写一个简单的个人简历展示页面
前端·javascript·html
hnlucky2 小时前
通俗易懂版知识点:Keepalived + LVS + Web + NFS 高可用集群到底是干什么的?
linux·前端·学习·github·web·可用性测试·lvs