在前端开发中,弹窗组件是必不可少的交互元素。虽然 ElementPlus 提供了优秀的 Dialog 组件,但有时我们需要更灵活、更自定义的调用方式。本文将介绍如何实现一个类似 ElementPlus 的函数式弹窗调用方案,让你的弹窗使用更加优雅便捷。
核心实现
1. 弹窗容器管理 Hook
首先我们创建一个管理弹窗容器和动画的 Hook:
javascript
// useDialog.js
import { render, h } from 'vue'
export function useDialog() {
const div = document.createElement('div')
div.style.display = 'none'
document.body.appendChild(div)
// 进场动画
setTimeout(() => {
div.style.opacity = '0'
div.style.position = 'fixed'
div.style.zIndex = '2001'
div.style.display = 'initial'
div.style.transition = 'opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
requestAnimationFrame(() => {
div.style.opacity = '1'
})
}, 10)
// 退场动画
const close = () => {
const bgElement = div.querySelector('.mark-bg')
if (bgElement) {
bgElement.classList.add('closing')
}
setTimeout(() => {
render(null, div)
document.body.removeChild(div)
}, 300)
}
return { div, close }
}
2. 函数式调用封装
javascript
// dialogManager.js
import { useDialog } from './useDialog'
export function createDialog(component, props = {}) {
return new Promise((resolve) => {
const { div, close } = useDialog()
const handleConfirm = (data) => {
close()
resolve({ action: 'confirm', data })
}
const handleCancel = () => {
close()
resolve({ action: 'cancel' })
}
const vNode = h(component, {
...props,
onConfirm: handleConfirm,
onCancel: handleCancel
})
render(vNode, div)
})
}
3. 基础弹窗组件样式
css
/* dialog.css */
.mark-bg {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2000;
display: flex;
justify-content: center;
align-items: center;
transition: opacity 0.3s ease;
}
.mark-bg.closing {
opacity: 0;
}
.base-popup {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
max-width: 80%;
max-height: 80%;
overflow: auto;
}
使用示例
1. 创建自定义弹窗组件
vue
<!-- CustomDialog.vue -->
<template>
<div class="mark-bg" @click.self="$emit('cancel')">
<div class="base-popup">
<h3>自定义弹窗标题</h3>
<div class="content">
<!-- 你的自定义内容 -->
<p>这是一个自定义弹窗的内容</p>
</div>
<div class="footer">
<button @click="$emit('confirm', { data: '示例数据' })">确认</button>
<button @click="$emit('cancel')">取消</button>
</div>
</div>
</div>
</template>
<script setup>
defineEmits(['confirm', 'cancel'])
</script>
2. 函数式调用
javascript
import { createDialog } from './dialogManager'
import CustomDialog from './CustomDialog.vue'
// 在任何地方调用
const openCustomDialog = async () => {
const result = await createDialog(CustomDialog, {
title: '自定义标题',
content: '自定义内容'
})
if (result.action === 'confirm') {
console.log('用户确认', result.data)
} else {
console.log('用户取消')
}
}
// 在Vue组件中使用
const handleClick = () => {
openCustomDialog()
}
高级功能扩展
1. 支持传参和返回值
javascript
export function createDialog(component, props = {}) {
return new Promise((resolve) => {
// ...同上
const vNode = h(component, {
...props,
onConfirm: (data) => {
close()
resolve({ action: 'confirm', data })
},
onCancel: (reason) => {
close()
resolve({ action: 'cancel', reason })
}
})
render(vNode, div)
})
}
2. 多个弹窗队列管理
javascript
class DialogManager {
constructor() {
this.queue = []
this.currentDialog = null
}
async open(component, props) {
return new Promise((resolve) => {
this.queue.push({ component, props, resolve })
this.processQueue()
})
}
processQueue() {
if (this.currentDialog || this.queue.length === 0) return
const { component, props, resolve } = this.queue.shift()
this.currentDialog = { component, props, resolve }
const { div, close } = useDialog()
const vNode = h(component, {
...props,
onConfirm: (data) => {
close()
this.currentDialog = null
resolve({ action: 'confirm', data })
this.processQueue()
},
onCancel: (reason) => {
close()
this.currentDialog = null
resolve({ action: 'cancel', reason })
this.processQueue()
}
})
render(vNode, div)
}
}
export const dialogManager = new DialogManager()
优势总结
- 使用简单:一行代码即可调用弹窗
- 解耦性强:弹窗逻辑与业务逻辑完全分离
- 灵活性高:支持任意自定义弹窗内容
- 用户体验好:内置动画效果,交互流畅
- 易于维护:统一的弹窗管理机制
总结
通过这种函数式弹窗调用方案,我们实现了类似 ElementPlus 的便捷调用方式,同时保持了高度的自定义灵活性。这种方法特别适合需要频繁使用弹窗交互的复杂应用,能够显著提升开发效率和用户体验,希望这个方案能为你带来启发!