我本没有想着说要封装一个弹窗组件,但有同行的朋友在问我,而且弹窗组件也确实在项目开发中用的比较多。思前想后,又本着样式统一且修改起来方便的原则,还是再为大家分享一个我所封装的弹窗组件吧。
其实,并不是所有封装组件的方式都是一成不变的,你可以采用函数式组件这种能提高性能的方式,也可以使用带有状态和生命周期的普通组件的封装方式。但像dialog这种包含很多点击事件如确定或提交事件、取消或重置事件、右上角那个小叉叉的关闭事件等,又有可能包含嵌套其他组件如表格组件、表单组件、树形组件、穿梭框组件等的公共组件,其成分略微复杂,功能不太单一,你若要采用函数式组件的方式来封装也不是不可以,只是可能xue微要麻烦一些,我自己建议是不采用这种封装方式,就采用普通的封装方式就好。
至于普通组件的封装方式,我想大家平时在开发的过程中对所接触的普通组件即带有状态和生命周期,也能快乐地使用this关键词的组件已经是非常熟悉了,所以这种封装方式我就不会再做过多的介绍了。以下是具体的实现过程。
照例还是先来张效果图:
1、所封装的弹窗组件dialog.vue
<template>
<el-dialog
top="20vh"
class="el-dialog-cus"
v-bind="attributes"
:visible="visible"
:before-close="beClose"
append-to-body
:close-on-click-modal="false"
v-on="on"
>
<slot v-if="visible"></slot>
<div slot="footer">
<el-button @click="cancel" plain>{{btnTxt[0]}}</el-button>
<el-button @click="confirm" type="primary" v-if="btnTxt[1]">{{btnTxt[1]}}</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
inheritAttrs: false,
props: {
config: {
type: Object,
default: () => ({
'420px',
title: '提示',
center: true,
btnTxt: ["取消", "确定"],
})
},
autoClose: {
type: Boolean,
default: true,
},
beforeClose: {
type: Function,
default: () => {},
},
},
data() {
const { config } = this, { btnTxt } = config;
return {
visible: false,
attributes: config,
btnTxt,
on: this.getDialogEvents(),
};
},
methods: {
open(ok) {
this.ok = ok;
this.visible = true;
},
cancel() {
this.visible = false;
},
confirm() {
let cancel = () => this.cancel();
this.ok(cancel);
this.autoClose && cancel();
},
beClose(done) {
done();
this.beforeClose();
this.cancel();
},
getDialogEvents(){
let { close } = this.config || {}, events = {};
if(close && typeof close == 'function'){
Object.assign(events, {
close,
})
}
return events
},
},
};
</script>
<style lang="scss">
.el-dialog-cus {
.el-dialog {
padding: 8px;
}
.el-dialog__title {
font-weight: bold;
}
.el-dialog__header {
padding: 20px 0 12px;
}
.el-dialog__headerbtn {
top: 8px;
right: 8px;
}
.el-dialog--center .el-dialog__body {
padding: 0 24px;
text-align: center;
}
.el-dialog__footer {
padding: 20px;
.el-button {
padding: 8px 20px;
& + .el-button {
margin-left: 40px;
}
}
}
}
</style>
对于以上的一些代码,我需要做一些特别的说明:
<slot v-if="visible"></slot>
这段代码是弹窗的插槽,给弹窗中加入的主体内容都会出现在这里。为什么要在slot上加一个v-if的判断呢?你猜... 哈哈哈哈哈哈,其实是为了防止在弹窗中嵌套一些其他组件时,那些组件的生命周期只会执行一次的问题出现。
open(ok) {
this.ok = ok;
this.visible = true;
}
这段代码是弹出弹窗的方法,为的是在使用弹窗组件时,我们只需点击一个按钮并使用ref来获取弹窗组件的这个方法即可打开弹窗,剩下的关闭弹窗的操作就交给弹窗的确定或取消按钮来完成即可。我们不用再额外的写关闭弹窗的方法并将关闭弹窗的props参数传给弹窗组件。另外,在打开弹窗的方法中我还保存了一个ok事件,这个ok事件是用于在点击了弹窗组件的确定或提交按钮后所触发的回调函数。比如我们点击了弹窗的提交按钮,我们需要调一个接口来完成数据的存储或修改,那么这个ok事件就是为它实现的,毕竟弹窗组件充当的只是一个我们用于处理业务逻辑的中间桥梁。
confirm() {
let cancel = () => this.cancel();
this.ok(cancel);
this.autoClose && cancel();
}
这段代码是在点击弹窗的确定或提交按钮时触发的,但为什么要给一个之前保存的ok回调函数传一个关闭的方法作为参数呢,这是因为有时我们在点击了确定或提交的按钮后并不想立即关闭这个弹窗,而是想在几秒钟的倒计时后再关闭这个弹窗并跳转到其他页面,亦或是在A弹窗的基础上又弹出另外一个B弹窗,在B弹窗的基础上又弹出一个C弹窗。关闭C弹窗时,还能看到B弹窗,而不用在A弹窗的基础上通过点击事件再弹出B弹窗。这个时候就需要把关闭的方法当作参数传递给ok回调函数,让调用弹窗组件的人自行控制在什么时候关闭弹窗,这难道不香吗?只不过这个时候可能需要多给弹窗组件传一个参数autoClose来通知它是不是需要前端自行控制什么时候来关闭弹窗,毕竟弹窗组件在大多数情况下都是点击了确定或提交按钮后就直接被关闭了。
beClose(done) {
done();
this.beforeClose();
this.cancel();
}
这段代码是弹窗组件的关闭前before-close
方法,element的官方解释是“关闭前的回调,会暂停Dialog的关闭”,官方还给了一个特别的说明:
before-close
仅当用户通过点击关闭图标或遮罩关闭Dialog
时起效。如果你在footer
具名slot
里添加了用于关闭Dialog
的按钮,那么可以在按钮的点击回调函数里加入before-close
的相关逻辑。
它接收一个参数done,用于关闭Dialog。而this.beforeClose()
是用来自定义关闭前所要做的一些事情的方法。
还有一点需要注意的是:普通组件所有未声明的属性都会被解析到$attrs里面,并自动挂载到组件根元素上面。因为本次封装的弹窗组件的外面已经没有根元素了,也就是标签el-dialog的外面没有再包裹一层父标签,所以前边这句话的意义已经不大了。如果标签el-dialog的外面又包裹了一层div,那么那句话就有意义了,也就是说这些未声明的属性也会出现在最外层的div上,如果不想让这些未声明的属性也出现在最外层的div上,那么就可以用inheritAttrs:false
来禁止。但本次封装的弹窗组件的外面没有根元素,所以加不加这个inheritAttrs:false
都无所谓了。
2、弹窗组件的使用:
<template>
<div>
<el-button @click="open">点我打开</el-button>
<Dialog ref="dialog" :config="config" :beforeClose="beforeClose" @close="resetForm"><span>this is a dialog</span></Dialog>
</div>
</template>
<script>
import Dialog from "@/components/dialog";
export default {
components: {
Dialog,
},
data() {
return {
config: {
'500px',
title: '温馨提示',
center: true,
btnTxt: ['取消', '提交'],
},
};
},
methods: {
open() {
this.$refs.dialog.open(cancel => {
// cancel();
console.log('点击提交按钮了')
});
},
beforeClose(){
console.log('关闭前');
},
resetForm(){
// 这里可以写重置表单的实现
},
}
};
</script>
以上具体的使用方法中:
open() {
this.$refs.dialog.open(cancel => {
// cancel();
console.log('点击提交按钮了')
});
}
这段代码就是用来打开或弹出弹窗组件,采用的是ref获取弹窗组件的open方法,并向弹窗组件的open方法传一个回调函数,而这个回调函数的参数就是组件中ok事件触发时所返回的函数参数cancel,如果不需要前端来自行控制弹窗的关闭,则不接收这个cancel参数即可。