element-plus Dialog 二次封装~

1,322 阅读1分钟

痛点

项目总总是有很多的弹窗场景, 每次都需要定义visible去管理弹窗的状态, 但在一个vue文件中出现多个弹窗时就会觉得很不舒服

<el-dialog :visible="visible1"></el-dialog>
<el-dialog :visible="visible2"></el-dialog>
<el-dialog :visible="visible3"></el-dialog>

想法

能不能像$confirm那样直接函数调用弹窗呢?网上查了好久,看到大佬(捏泥巴的小人)的更优雅的方式使用el-dialog(juejin.cn/post/698091… 借鉴之后决定用 renderh + Promise来实现。

开干

  • 新建modal.ts
import { createApp, h } from 'vue'
import { ElDialog, ElButton } from 'element-plus'
import 'element-plus/dist/index.css'
/**
 * @param component 弹窗显示内容
 * @param props ElDialog props
 * @param componentsProps 子组件 props
 */
const modal = ({ component, props, componentsProps }) => {
    return new Promise((resolve) => {
        const dom = document.createElement('div')
        dom.setAttribute('id', 'cus-dialog')
        document.body.appendChild(dom)
        let childDom: any
        return createApp({
            el: dom,
            data() { return { showModal: true, loading: false }},
            render() {
                childDom = this.showModal ? h(component, { ...componentProps }) : null
                const formButtons = [{ label: '取消', text: true, type: 'primary', onClick: () => this.close() }, { label: '保存', type: 'primary', loading: this.loading, onClick: () => this.save() }]
                const footer = h('div', {}, formButtons.map(e => h(ElButton, { ...e }, { default: () => e.label }))
                return h(ElDialog, { modelValue: this.showModal, ...props }, { default: () => childDom, footer: () => footer })
            },
            methods: {
                close() {
                    const dom = document.querySelector('#cus-dialog')
                    document.body.removeChild(dom)
                    this.showModal = false
                },
                save() {
                    if (!childDom.component.devtoolsRawSetupState.save) {
                        return console.warn('请在子组件中定义save方法')
                    }
                    new Promise((res, rej) => {
                        this.loading = true
                        childDom.component.devtoolsRawSetupState.save(res, rej)
                    }).then(res => {
                        this.showModal = false
                        resolve(res)
                    }).catch(() => { this.loading = false })
                }
            }
        }).mount('#cus-dialog')
    })
}

expot default modal

interface ModalCraete {
    component: any,
    props?: object,
    componentProps?: object
}

调用

<!-- home.vue -->
<template>
    <el-button type="primary" @click="openDialog">打开弹窗</el-button>
</template>
<script lang="ts" setup>
    import HelloWorld from '@/compomemts/HelloWorld.vue'
    // 这里可以在mian.js中挂载到原型链上, 我懒得写了
    import modal from './modal'
    const openDialog = async() => {
        const formData = await modal({ props: { title: 'hello world!' }, component: HelloWorld })
        console.log(formData)
    }
</script>
<!-- HelloWorld.vue -->
<template>
    <el-form ref="formRef" :model="form" label-width="120px">
        <el-form-item prop="name" label="名称">
            <el-input v-model="form.name" />
        </el-form-item>
    </el-form>
</template>
<script lang="ts" setup>
    import { reactive, ref } from 'vue'
    let form = reactive({ name: '' })
    const formRef = ref()
    const save = (req, rej) => {
        formRef.validate(valid => {
            req(form)
        })
    }
</script>

最后

刚开始学vue3很多地方还不怎么清楚, 只好摸索着写, 过程中遇到的最难搞的问题就是怎么像ref那样获取子组件的dom然后调用其中的save方法。 最后log出h函数渲染组件后的对象翻找出在component.devtoolsRawSetupState中, 最后虽然实现了想要的效果, 凡是不确定是不是最佳的方案。

还有什么遗留的问题以后在思索吧。