vue3 封装modal组件函数调用

组件支持以下特性:

  1. 自定义大小:支持通过 widthheight 设置弹框大小。

  2. 可移动:支持拖拽移动,适配桌面和移动端。

  3. 移动端适配:自动调整大小,避免超出屏幕。

  4. 控制按钮显示:通过 showConfirmButtonshowCancelButton 控制按钮的显示与隐藏。

  5. 直接调用显示:创建时直接显示,无需额外调用。

以下是完整的实现方案:


1. Modal 组件(Modal.vue)

这个组件支持自定义大小、可拖拽移动、移动端适配,并允许控制按钮的显示。

<template>
  <div v-if="visible" class="modal-overlay" @click="closeModal">
    <div
      class="modal-content"
      :style="modalStyle"
      @mousedown="startDrag"
      @touchstart="startDrag"
      @click.stop
    >
      <div class="modal-header">
        <h3>{
  
  { title }}</h3>
        <span class="close" @click="closeModal">&times;</span>
      </div>
      <div class="modal-body">
        <slot v-if="$slots.default"></slot>
        <div v-else v-html="content"></div>
      </div>
      <div class="modal-footer">
        <button v-if="showConfirmButton" @click="confirm">{
  
  { confirmText }}</button>
        <button v-if="showCancelButton" @click="closeModal">{
  
  { cancelText }}</button>
      </div>
    </div>
  </div>
</template>

<script>
import { ref, watch, defineComponent } from 'vue';

export default defineComponent({
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    title: {
      type: String,
      default: '',
    },
    content: {
      type: String,
      default: '',
    },
    width: {
      type: String,
      default: 'auto', // 默认自适应宽度
    },
    height: {
      type: String,
      default: 'auto', // 默认自适应高度
    },
    confirmText: {
      type: String,
      default: 'Confirm',
    },
    cancelText: {
      type: String,
      default: 'Cancel',
    },
    showConfirmButton: {
      type: Boolean,
      default: true, // 默认显示确认按钮
    },
    showCancelButton: {
      type: Boolean,
      default: true, // 默认显示取消按钮
    },
    onConfirm: {
      type: Function,
    },
    onClose: {
      type: Function,
    },
  },
  setup(props, { emit }) {
    const startX = ref(0);
    const startY = ref(0);
    const currentX = ref(0);
    const currentY = ref(0);

    const modalStyle = computed(() => ({
      width: props.width,
      height: props.height,
      left: `${currentX.value}px`,
      top: `${currentY.value}px`,
    }));

    const closeModal = () => {
      emit('update:visible', false);
      if (props.onClose) props.onClose();
    };

    const confirm = () => {
      if (props.onConfirm) {
        const result = props.onConfirm();
        if (result instanceof Promise) {
          result.then(closeModal);
        } else {
          closeModal();
        }
      }
    };

    const startDrag = (event) => {
      const { clientX, clientY } = event.touches ? event.touches[0] : event;
      startX.value = clientX - currentX.value;
      startY.value = clientY - currentY.value;

      document.addEventListener('mousemove', onDragging);
      document.addEventListener('touchmove', onDragging, { passive: false });
      document.addEventListener('mouseup', stopDrag);
      document.addEventListener('touchend', stopDrag);
    };

    const onDragging = (event) => {
      event.preventDefault();
      const { clientX, clientY } = event.touches ? event.touches[0] : event;
      currentX.value = clientX - startX.value;
      currentY.value = clientY - startY.value;
    };

    const stopDrag = () => {
      document.removeEventListener('mousemove', onDragging);
      document.removeEventListener('touchmove', onDragging);
      document.removeEventListener('mouseup', stopDrag);
      document.removeEventListener('touchend', stopDrag);
    };

    return {
      closeModal,
      confirm,
      startDrag,
      modalStyle,
    };
  },
});
</script>

<style scoped>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modal-content {
  position: absolute;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
  cursor: move;
  max-width: 90%;
  max-height: 90%;
  overflow: auto;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}

.close {
  cursor: pointer;
  font-size: 20px;
}

.modal-footer {
  margin-top: 10px;
  text-align: right;
}

@media (max-width: 768px) {
  .modal-content {
    width: 90%;
    height: auto;
    max-height: 80%;
  }
}
</style>


2. Modal 管理器(useModal.js)

管理器负责创建、显示、关闭和销毁弹框实例。

import { createVNode, render, h } from 'vue';
import Modal from './Modal.vue';

const ModalManager = {
  modals: [],
  container: null,

  initContainer() {
    if (!this.container) {
      this.container = document.createElement('div');
      this.container.id = 'modal-container';
      document.body.appendChild(this.container);
    }
  },

  createModal(options) {
    this.initContainer();

    const modalId = Date.now().toString();
    const modalInstance = {
      id: modalId,
      visible: true, // 直接显示
      ...options,
    };

    this.modals.push(modalInstance);

    const vnode = createVNode(Modal, modalInstance);
    render(vnode, this.container);

    return modalId;
  },

  closeModal(id) {
    const modal = this.modals.find((m) => m.id === id);
    if (modal) {
      modal.visible = false;
    }
  },

  destroyModal(id) {
    const index = this.modals.findIndex((m) => m.id === id);
    if (index !== -1) {
      this.modals.splice(index, 1);
      const vnode = createVNode(Comment);
      render(vnode, this.container.querySelector(`[data-id="${id}"]`));
    }
  },
};

export default {
  install(app) {
    app.config.globalProperties.$modal = {
      create: ModalManager.createModal,
      close: ModalManager.closeModal,
      destroy: ModalManager.destroyModal,
    };
  },
};

3. 主应用中安装插件(main.js)

在 Vue 应用中安装 Modal 插件。

import { createApp } from 'vue';
import App from './App.vue';
import ModalPlugin from './useModal';

const app = createApp(App);

app.use(ModalPlugin);

app.mount('#app');

4. 使用示例

示例:自定义按钮显示与隐藏
<template>
  <div>
    <button @click="openModal">Open Modal</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { $modal } from './useModal';

const openModal = () => {
  const modalId = $modal.create({
    title: 'Custom Modal',
    content: 'This is a draggable and responsive modal.',
    width: '80%', // 自适应宽度
    height: 'auto', // 自适应高度
    confirmText: 'OK',
    cancelText: 'Cancel',
    showConfirmButton: true, // 显示确认按钮
    showCancelButton: false, // 隐藏取消按钮
    onConfirm: () => {
      console.log('Modal confirmed');
      $modal.close(modalId); // 关闭 Modal
    },
    onClose: () => {
      console.log('Modal closed');
    },
  });
};
</script>

5. 功能说明

  1. 自定义大小

    • 通过 widthheight 设置弹框大小,默认自适应内容。

    • 在移动端,弹框会自动调整为最大宽度和高度的 90%,确保不会超出屏幕。

  2. 可移动

    • 支持拖拽移动,适配桌面和移动端。

  3. 移动端适配

    • 使用 max-widthmax-height 确保弹框在小屏幕上不会溢出。

    • 内容超出时支持滚动。

  4. 控制按钮显示

    • 使用 showConfirmButtonshowCancelButton 控制按钮的显示与隐藏。

  5. 直接调用显示

    • 创建弹框时直接设置 visible: true,无需额外调用打开方法。


通过这种方式,你可以创建一个功能强大、自适应移动端的弹框组件,同时支持自定义大小、拖拽功能,并灵活控制按钮的显示与隐藏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值