仿 ElementPlus 的函数式弹窗调用

在前端开发中,弹窗组件是必不可少的交互元素。虽然 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()

优势总结

  1. 使用简单:一行代码即可调用弹窗
  2. 解耦性强:弹窗逻辑与业务逻辑完全分离
  3. 灵活性高:支持任意自定义弹窗内容
  4. 用户体验好:内置动画效果,交互流畅
  5. 易于维护:统一的弹窗管理机制

总结

通过这种函数式弹窗调用方案,我们实现了类似 ElementPlus 的便捷调用方式,同时保持了高度的自定义灵活性。这种方法特别适合需要频繁使用弹窗交互的复杂应用,能够显著提升开发效率和用户体验,希望这个方案能为你带来启发!

相关推荐
用户21411832636025 小时前
AI 当 “牛马”!免费云服务器 + 开源插件,7×24 小时写小说,一晚交出 70 章长篇
前端
IT_陈寒7 小时前
React 18新特性全解析:这5个隐藏API让你的性能飙升200%!
前端·人工智能·后端
朦胧之8 小时前
【NestJS】项目调试
前端·node.js
!win !8 小时前
不定高元素动画实现方案(中)
前端·动画
xw58 小时前
不定高元素动画实现方案(中)
前端·css
shuangshuangda9 小时前
8. Hooks 的设计动机和规则?为什么不能条件调用?
前端
是晓晓吖9 小时前
说说 page.on('response',fn)
前端·puppeteer
李重楼9 小时前
一次性帮你搞定 Promise 的四个静态方法
前端·promise
卓伊凡9 小时前
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Br
前端·后端