实现可拖拽功能的Vue模态框组件 基于antd Vue3 加ts

实现可拖拽功能的Vue模态框组件


在Vue开发中,我们经常需要使用模态框组件来展示提示、对话框等信息。今天我们将介绍如何实现一个可拖拽的Vue模态框组件,使用户可以通过拖拽模态框的标题栏来改变其位置,使用场景后代管理系统。

首先,我们需要使用Vue的单文件组件来构建我们的模态框组件。在单文件组件中,我们可以定义模态框的HTML模板和相关的JavaScript逻辑

<template>
  <a-modal ref="modalRef" :visible="props.visible" :wrap-style="{ overflow: 'hidden' }" @ok="handleOk" :mask-closable="maskClosable" 
  :centered="props.centered" :footer="props.footer" @cancel="handleCancel" :confirm-loading="props.confirmLoading" :destroyOnClose="props.destroyOnClose">
    <slot></slot>

    <template #title>
      <div ref="modalTitleRef" style="width: 100%; cursor: move">{{ props.title }}</div>
    </template>

    <template #modalRender="{ originVNode }">
      <div :style="transformStyle">
        <component :is="originVNode"/>
      </div>
    </template>
  </a-modal>
</template>
<script lang="ts" setup>
import { computed, CSSProperties, ref, watch, watchEffect } from "vue";
import { useDraggable } from "@vueuse/core";

// 定义Props接口,描述组件的属性
interface Props {
  title?: string, // 对话框的标题,默认为"提示"
  footer?: null, // 对话框的页脚,默认为null
  visible: boolean, // 对话框是否可见
  confirmLoading?: boolean, // 确认按钮是否处于加载状态,默认为false
  destroyOnClose?: boolean, // 对话框关闭时是否销毁组件,默认为false
  maskClosable?: boolean, // 点击遮罩层是否可关闭对话框,默认为false
  centered?: boolean, // 对话框是否居中显示,默认为false
}

// 设置props的默认值
const props = withDefaults(defineProps<Props>(), { title: '提示', visible: false, destroyOnClose: false, maskClosable: false })

// 创建ref引用modalTitleRef,用于引用组件中的标题元素
const modalTitleRef = ref<HTMLElement | null | any>(null);

// 使用useDraggable钩子,获取拖拽相关属性
const { x, y, isDragging } = useDraggable(modalTitleRef);

// 创建emit函数,用于触发自定义事件
const emit = defineEmits(['ok', 'update:visible', 'cancel'])

// 处理ok事件的函数
const handleOk = (e: MouseEvent) => {
  emit('ok')
};

// 处理cancel事件的函数
const handleCancel = () => {
  emit('update:visible', false)
  emit('cancel')
}

// 创建各种响应式数据
const startX = ref<number>(0); // 记录起始点的 x 坐标
const startY = ref<number>(0); // 记录起始点的 y 坐标
const startedDrag = ref(false); // 标志位,表示是否开始拖拽,默认为 false
const transformX = ref(0); // x 偏移量
const transformY = ref(0); // y 偏移量
const preTransformX = ref(0); // 拖拽前的 x 偏移量
const preTransformY = ref(0); // 拖拽前的 y 偏移量
const dragRect = ref({ left: 0, right: 0, top: 0, bottom: 0 }); // 可拖拽的边界

// 监听x和y的变化
watch([x, y], () => { // 监听鼠标移动事件的函数
  if (!startedDrag.value) { // 如果尚未开始拖拽
    startX.value = x.value; // 记录起始点的 x 坐标
    startY.value = y.value; // 记录起始点的 y 坐标
    const bodyRect = document.body.getBoundingClientRect(); // 获取页面 body 元素的边界信息
    const titleRect = modalTitleRef.value.getBoundingClientRect(); // 获取 modalTitle 元素的边界信息
    dragRect.value.right = bodyRect.width - titleRect.width; // 计算可拖拽的最大右边界
    dragRect.value.bottom = bodyRect.height - titleRect.height; // 计算可拖拽的最大下边界
    preTransformX.value = transformX.value; // 记录拖拽前的 x 偏移量
    preTransformY.value = transformY.value; // 记录拖拽前的 y 偏移量
  }
  startedDrag.value = true; // 设置已开始拖拽标志
});

// 监听isDragging的变化
watch(isDragging, () => { // 监听 isDragging 变量的改变
  if (!isDragging) { // 如果 isDragging 变为 false
    startedDrag.value = false; // 将 startedDrag.value 设置为 false,表示拖拽操作结束
  }
});

// 使用watchEffect监听响应式数据的变化
watchEffect(() => { // 响应 startedDrag.value 的变化
  if (startedDrag.value) { // 如果 startedDrag.value 为 true,表示正在进行拖拽操作
    transformX.value = // 计算 x 偏移量
      preTransformX.value +
      Math.min(Math.max(dragRect.value.left, x.value), dragRect.value.right) -
      startX.value;
    transformY.value = // 计算 y 偏移量
      preTransformY.value +
      Math.min(Math.max(dragRect.value.top, y.value), dragRect.value.bottom) -
      startY.value;
  }
});

// 计算transformStyle,用于动态设置对话框的位置
const transformStyle = computed<CSSProperties>(() => {
  return {
    transform: `translate(${transformX.value}px, ${transformY.value}px)`,
  };
});

</script>

使用组件

<template>
  <div>
    <!-- 其他内容 -->
    <Modal :visible="modalVisible" @ok="handleOk" @cancel="handleCancel">
      <!-- 这里是modal的内容 -->
    </Modal>
  </div>
</template>

<script setup>
import Modal from "@/components/Modal.vue";

// 对modalVisible的处理
const modalVisible = ref(false);
const handleOk = () => {
  // 处理确认事件
  modalVisible.value = false;
};
const handleCancel = () => {
  // 处理取消事件
  modalVisible.value = false;
};
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值