需求:封装一个表单弹框组件,弹框和表单是两个组件,表单组件以插槽的形式动态传入弹框组件中。
外部组件使用的方式如下:

直接上代码:
MyDialog.vue 弹框组件
javascript
<template>
<el-dialog
:title=title
:visible.sync="dialogVisible"
:close-on-click-modal="false"
width="40%">
<slot name="content"></slot>
<span slot="footer" class="dialog-footer">
<el-button size="mini" @click="handleCancelClick">取 消</el-button>
<el-button size="mini" type="primary" @click="handleOkClick">确 定</el-button>
</span>
</el-dialog>
</template>
<script>
export default {
name: "MyDialog",
props: {
title: {
type: String
},
message: {
type: String
},
icon: {
type: String,
default: "info"
},
handleCancel: {
type: Function
},
handleOk: {
type: Function
}
},
data() {
return {
dialogVisible: true,
}
},
methods: {
handleCancelClick() {
this.dialogVisible = false;
this.handleCancel();
},
handleOkClick() {
this.dialogVisible = false;
this.handleOk();
},
handleTestClick() {
this.$emit('test-click')
},
}
}
</script>
<style scoped>
/deep/.el-dialog__body {
padding: 15px 20px;
}
/deep/ .el-dialog__header {
padding: 2px 10px 2px;
background-color: #1E2C3D;
color: white;
}
/deep/ .el-dialog__title {
color: white;
font-size: 13px;
font-family: 微软雅黑,serif;
}
/deep/ .el-dialog__headerbtn {
top: 6px;
}
/deep/ .el-dialog__headerbtn .el-dialog__close {
color: #fff;
}
</style>
MyDialog.js
javascript
import Vue from 'vue';
import MyDialog from "@/components/dialog4/MyDialog.vue";
import EventBus from "@/lib/event-bus";
/**
* 弹框组件的构造器
* @param ctxCpm
* @param dlgProps
* @param onOkClick
* @param onCancelClick
* @returns {ExtendedVue<Vue, unknown, unknown, unknown, Record<never, any>, {}, ComponentOptionsMixin, ComponentOptionsMixin>|VNode}
*/
function getDialogConstructor(ctxCpm, dlgProps, onOkClick, onCancelClick) {
return Vue.extend({
render(h) {
return h(
MyDialog, {
props: {
...dlgProps,
handleOk: onOkClick,
handleCancel: onCancelClick
}
},
[h(ctxCpm, {
slot: 'content',
ref: 'myform',
},)
]
)
}
})
}
// 暴露此函数供外部组件调用
/**
*
* @param ctxCpm 表单组件
* @param dlgProps 弹框组件的配置项props
* @param onOkClick 确认按钮点击事件回调函数
* @param onCancelClick 取消按钮点击事件回调函数
* @returns {(function(): void)|*} 弹窗关闭后的回调函数
*/
export const useDialog = (ctxCpm, dlgProps, onOkClick, onCancelClick) => {
let DialogConstructor = getDialogConstructor(ctxCpm, dlgProps, () => {
EventBus.$emit('form-submit', {callback: (formData) => {
onOkClick(formData);
}});
}, onCancelClick);
const dlg = new DialogConstructor();
const dlgInstance = dlg.$mount();
document.body.appendChild(dlgInstance.$el);
return () => {
dlgInstance.$el.remove();
dlgInstance.$destroy();
EventBus.$off("form-submit"); // 移除表单提交事件监听
}
}
UserForm.vue 表单组件
javascript
<template>
<el-form>
<el-form-item label="用户名">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="年龄">
<el-input v-model="form.age"></el-input>
</el-form-item>
<el-form-item label="住址">
<el-input v-model="form.address"></el-input>
</el-form-item>
</el-form>
</template>
<script>
import EventBus from "@/lib/event-bus";
export default {
name: "UserForm",
props: {
dlgProps: Object
},
data() {
return {
form: {
name: '',
age: 0,
address: ''
}
}
},
methods: {
takeFormData() {
return {...this.form}
}
},
created() {
// 监听表单提交(确认按钮点击)
EventBus.$on('form-submit', (p) => {
p.callback(this.takeFormData());
});
}
}
</script>
<style scoped>
</style>
MyDialogTest.vue 组件中调用
javascript
<template>
<div>
<el-button @click="handleClick">点我弹出用户组件弹框</el-button>
</div>
</template>
<script>
import {useDialog} from "@/components/dialog4/MyDialog";
import UserForm from "@/components/dialog4/UserForm";
export default {
name: "MyDialogTest",
methods: {
handleClick() {
const close = useDialog(UserForm, {title: "新增用户表单", message: "是否确定?", icon: "warn"}, (params) => {
console.log("Test.....", params);
close();
}, () => {
console.log("取消按钮被点击");
close();
})
}
}
}
</script>
<style scoped>
</style>
效果如下:

优化1:
以上的写法中,是采用EventBus 事件总线的方式来获取表单提交的数据,也就是点击确认后提交表单,在onOkClick 处理函数中获取表单数据。后面想了想,可以再精简一点。
MyDialog.js
import Vue from 'vue';
import MyDialog from "@/components/dialog5/MyDialog.vue";
function getDialogConstructor(ctxCpm, dlgProps, onOkClick, onCancelClick) {
return Vue.extend({
render(h) {
return h(
MyDialog, {
props: {
...dlgProps,
handleOk: onOkClick,
handleCancel: onCancelClick
}
},
[h(ctxCpm, {
slot: 'content',
ref: 'myform',
props: {
fdata: dlgProps.fdata
}
},)
]
)
}
})
}
export const useDialog = (ctxCpm, dlgProps, onOkClick, onCancelClick) => {
let DialogConstructor = getDialogConstructor(ctxCpm, dlgProps, onOkClick, onCancelClick);
const dlg = new DialogConstructor();
const dlgInstance = dlg.$mount();
document.body.appendChild(dlgInstance.$el);
return () => {
dlgInstance.$el.remove();
dlgInstance.$destroy();
}
}
这个文件的调整是,在h函数渲染表单组件ctxCpm时,通过props传入一个fdata。把事件总线的代码删了。
那么在表单组件中,就顶一个props fdata来接收。修改如下:
TestForm.vue
<template>
<el-form>
<el-form-item label="姓名">
<el-input v-model="fdata.name"></el-input>
</el-form-item>
<el-form-item label="年龄">
<el-input v-model="fdata.age"></el-input>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: "TestForm",
props: ["fdata"],
}
</script>
<style scoped>
</style>
在TestForm.vue 组件中只需要定义一个props来接收即可。
然而,这样修改后,在外部组件中要调用 useDIalog 这个就需要传参数了。
MyDialogTest.vue
<template>
<div>
<el-button @click="handleClick">点我弹出弹框</el-button>
</div>
</template>
<script>
import TestForm from "@/components/dialog5/TestForm";
import {useDialog} from "@/components/dialog5/MyDialog"
export default {
name: "MyDialogTest5",
data() {
return {
fdata: {}
}
},
methods: {
handleClick() {
const close = useDialog(TestForm, {fdata: this.fdata}, () => {
console.log({...this.fdata})
close();
}, () => {});
}
}
}
</script>
<style scoped>
</style>
----------------------------------------------------------分隔线----------------------------------------------------------