痛点
项目总总是有很多的弹窗场景, 每次都需要定义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… 借鉴之后决定用 render 和 h + 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中, 最后虽然实现了想要的效果, 凡是不确定是不是最佳的方案。
还有什么遗留的问题以后在思索吧。