最近在工作中需要实现个功能,希望通过右键菜单的方式打开对话框的功能,且需要支持拖拽功能。因为使用的前端组件是Ant Design Vue,所以在其官网上找了半天都没找到示例,终于在网上又找了半天给我找到了个实现方式(不好意思忘记是哪个博主的文章了),虽然可能有点绕,但总归是能实现需求了。现记录实现方式,希望对各位有所帮助。
总体思路为,通过包装a-modal组件的方式定义出一个虚拟弹出框组件(支持拖动),虚拟组件监听pinia中属性的状态变化来实现打开与关闭。此组件在全局页面上注册,需要打开时改变pinia中的状态值即可。虚拟弹出框组件详细步骤如下:
-
template
html<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>
其中modalTitleRef,被定义为可拖拽,modalRef用于响应拖拽变从而改变弹出框的位置。
-
script
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); // 1. 会自动响应pinia中popoverInfo.visible的变化,从而控制打开 const modalTitleRef = ref(); const modalRef = ref(); const { x, y, isDragging } = useDraggable(modalTitleRef); // 2.使用vueues 将头部做成可拖拽。 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; } }); // 3.根据拖拽的坐标变化更新弹出框的位置(transformX) 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; } }); // 4.根据变化后的transformX真正更改弹出框位置 const transformStyle = computed<CSSProperties>(() => { return { transform: `translate(${transformX.value}px, ${transformY.value}px)`, }; }); onMounted(() => {}); </script>