组件支持以下特性:
-
自定义大小:支持通过
width
和height
设置弹框大小。 -
可移动:支持拖拽移动,适配桌面和移动端。
-
移动端适配:自动调整大小,避免超出屏幕。
-
控制按钮显示:通过
showConfirmButton
和showCancelButton
控制按钮的显示与隐藏。 -
直接调用显示:创建时直接显示,无需额外调用。
以下是完整的实现方案:
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">×</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. 功能说明
-
自定义大小:
-
通过
width
和height
设置弹框大小,默认自适应内容。 -
在移动端,弹框会自动调整为最大宽度和高度的 90%,确保不会超出屏幕。
-
-
可移动:
-
支持拖拽移动,适配桌面和移动端。
-
-
移动端适配:
-
使用
max-width
和max-height
确保弹框在小屏幕上不会溢出。 -
内容超出时支持滚动。
-
-
控制按钮显示:
-
使用
showConfirmButton
和showCancelButton
控制按钮的显示与隐藏。
-
-
直接调用显示:
-
创建弹框时直接设置
visible: true
,无需额外调用打开方法。
-
通过这种方式,你可以创建一个功能强大、自适应移动端的弹框组件,同时支持自定义大小、拖拽功能,并灵活控制按钮的显示与隐藏。