如何封裝一個可用js直接調(diào)用的彈窗組件
前言
在Vue開發(fā)中,正常的組件通常以模板方式調(diào)用,但對于彈窗這種呈現(xiàn)方式,使用模板的方式開發(fā)過于繁瑣,怎樣才能讓彈窗組件可以像原生alert()一樣通過函數(shù)調(diào)用觸發(fā)彈窗呢?本文將基于Vue2,詳細講解如何封裝一個可通過JavaScript函數(shù)直接調(diào)用的彈窗組件,實現(xiàn)高復(fù)用性和開發(fā)效率的提升。
一、開發(fā)思路
1. 先封裝一個正常的彈窗組件
2. 使用Vue.extend動態(tài)構(gòu)造組件實例,并掛載到新創(chuàng)建的DOM節(jié)點
3. 需要支持Promise的方式捕獲確定和取消操作,同時支持確定按鈕異步關(guān)閉。比如點確定后調(diào)個接口,接口返回處理后,再讓彈窗關(guān)閉。
二、開發(fā)過程
1.封裝彈窗組件
代碼如下:
<template>
<div class="my-dialog-container" v-if="visible" :style="{ 'z-index': zIndex }">
<div class="mask" v-if="showMask" :style="{ 'z-index': zIndex + 1 }"></div>
<div class="my-dialog-bg" @click="clickMask" :style="{ 'z-index': zIndex + 2 }">
<div
class="my-dialog"
:style="{ width: width ? width : '60%' }"
@click.stop="clickDialog"
>
<!-- 這里添加一個stop的事件,在點擊彈窗區(qū)域時,阻止向上冒泡觸發(fā)clickMask,把彈窗關(guān)掉了 -->
<div class="dialog-header">
<div class="dialog-title">{{ title }}</div>
<div class="close-btn" v-if="showClose" @click="doClose">
<i class="el-icon-close"></i>
</div>
</div>
<div class="dialog-content" v-if="content">
<div class="dialog-type-icon" v-if="type">
<i
class="el-icon-warning"
style="color: #e6a23c"
v-if="type == 'warning'"
></i>
<i class="el-icon-error" style="color: #f56c6c" v-if="type == 'danger'"></i>
<i class="el-icon-info" style="color: #909399" v-if="type == 'info'"></i>
<i
class="el-icon-success"
style="color: #67c23a"
v-if="type == 'success'"
></i>
</div>
<div class="dialog-context" v-html="content"></div>
</div>
<div class="dialoag-footer" v-if="showCancelButton || showConfirmButton">
<el-button v-if="showCancelButton" size="small" @click="doCancel">{{
cancelButtonName
}}</el-button>
<el-button
type="primary"
v-if="showConfirmButton"
size="small"
style="margin-right: 10px"
@click="doConfirm"
>{{ confirmButtonName }}</el-button
>
</div>
</div>
</div>
</div>
</template>
<script>
import Vue from "vue";
export default {
created() {
this.zIndex = Vue.dialogZIndex;
},
props: {
visible: {
type: Boolean,
isRequired: true,
},
title: {
type: String,
default: "提示",
},
content: {
type: String,
},
showMask: {
// 是否顯示遮罩
type: Boolean,
default: true,
},
clickMaskClose: {
// 點擊遮罩是否關(guān)閉彈窗
type: Boolean,
default: true,
},
showCancelButton: {
type: Boolean,
default: true,
},
cancelButtonName: {
type: String,
default: "取消",
},
showConfirmButton: {
type: Boolean,
default: true,
},
confirmButtonName: {
type: String,
default: "確定",
},
showClose: {
// 是否顯示右上角關(guān)閉按鈕
type: Boolean,
default: true,
},
width: {
// 彈窗占屏幕寬度,默認60%
type: String,
default: "60%",
},
type: {
// 提示類型,文案前的圖標會有所不同 success warning info danger
type: String,
},
beforeClose: {
type: Function,
default: (action, instance, done) => {},
},
},
data() {
return {
zIndex: 1000,
};
},
methods: {
clickMask() {
if (this.clickMaskClose && this.showMask) {
this.beforeClose("close", this, this.done("close"));
}
},
clickDialog() {},
doCancel() {
this.beforeClose("cancel", this, this.done("cancel"));
},
doConfirm() {
this.beforeClose("confirm", this, this.done("confirm"));
},
doCloseBtn() {
this.beforeClose("close", this, this.done("close"));
},
doClose() {
this.visible = false;
},
done(action) {
const fn = () => {
if (action == "confirm") {
this.$emit("confirm");
} else if (action == "cancel") {
this.$emit("cancel");
}
this.doClose();
};
return fn;
},
},
};
</script>
<style scoped lang="less">
.my-dialog-container {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
.mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #000;
opacity: 0.5;
}
.my-dialog-bg {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow-y: auto;
.my-dialog {
margin: 0 auto;
background: #fff;
max-height: 50vh;
overflow-y: auto;
margin-top: 30vh;
border-radius: 8px;
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
.dialog-title {
font-size: 18px;
font-weight: bold;
padding-left: 15px;
}
.close-btn {
padding-right: 15px;
}
}
.dialog-content {
display: flex;
text-align: left;
padding: 10px 15px;
font-size: 14px;
align-items: center;
.dialog-type-icon {
font-size: 24px;
padding-right: 10px;
}
.dialog-context {
}
}
.dialoag-footer {
display: flex;
justify-content: flex-end;
align-items: center;
height: 50px;
}
}
}
}
</style>該組件提供了幾個props
| 屬性 | 字段名 | 是否必填 | 默認值 | 備注 |
|---|---|---|---|---|
彈窗顯示狀態(tài) | visible | 是 | ||
| 彈窗標題 | title | "提示" | ||
| 內(nèi)容 | content | 支持html和文本 | ||
| 是否顯示遮罩 | showMask | true | ||
點擊遮罩是否關(guān)閉彈窗 | clickMaskClose | true | ||
| 是否顯示取消按鈕 | showCancelButton | true | ||
| 取消按鈕名稱 | cancelButtonName | “取消” | ||
| 是否顯示確定按鈕 | showConfirmButton | true | ||
| 確定按鈕名稱 | confirmButtonName | “確定” | ||
是否顯示右上角關(guān)閉按鈕 | showClose | true | ||
| 彈窗占屏幕寬度比例 | width | 60% | 支持字符串形式的值,會直接賦給width樣式 | |
| 彈窗類型 | type | 否 | success/warning/info/danger | |
| 關(guān)閉前回調(diào) | beforeClose | (action, instance, done) => {} |
注意事項:
1. 彈窗、遮罩都是以fixed絕對定位,為了讓新彈窗遮蓋就彈窗,不顯示到一個平面,所以z-index不能寫死。
設(shè)置Vue的全局變量Vue.prototype.dialogZIndex,默認是1000,當(dāng)創(chuàng)建新彈窗時,dialogZIndex + 3,新彈窗created生命周期函數(shù)中獲取最新的dialogZIndex,使用動態(tài)樣式的方式,設(shè)置給遮罩、彈窗體。
2. 由于有點擊遮罩關(guān)閉彈窗的功能,為了避免點擊彈窗事件冒泡到外層的遮罩,觸發(fā)關(guān)閉彈窗的動作,給彈窗dom綁定一個點擊事件,@click.stop="clickDialog",clickDialog是空方法,不處理。
3. 點擊確定、取消、關(guān)閉、遮罩觸發(fā)的關(guān)閉動作,都不直接修改visible屬性,而是調(diào)用了this.beforeClose(),該方法的第三個參數(shù)是this.done的執(zhí)行結(jié)果,可以看出,this.done返回了一個方法。當(dāng)該方法執(zhí)行時,會判斷當(dāng)前是何種方式觸發(fā)的關(guān)閉,并做相應(yīng)處理,然后關(guān)閉彈窗。這是實現(xiàn)異步關(guān)閉彈窗的關(guān)鍵。

2.封裝js調(diào)用
代碼如下:
// registerDialog.js
import Vue from "vue";
import MyDialog from "@/components/my-dialog.vue";
const DialogConstructor = Vue.extend(MyDialog);
const createModal = (options) => {
return new Promise((resolve, reject) => {
const instance = new DialogConstructor({
propsData: {
beforeClose: (action, ins, done) => {
done();
},
...options,
}
});
// 掛載到臨時DOM
const container = document.createElement('div')
document.body.appendChild(container)
instance.$mount(container);
// 手動打開彈窗
instance.visible = true
// 監(jiān)聽關(guān)閉事件
instance.$on('update:visible', (val) => {
if (!val) {
setTimeout(() => {
instance.$destroy();
document.body.removeChild(container)
}, 300)
}
});
instance.$on('confirm', resolve)
instance.$on('cancel', reject)
});
}
const registerDialog = () => {
Vue.prototype.$myConfirm = (options) => {
// 彈窗層級累加,保證先彈的框在下面
if (!Vue.dialogZIndex) {
Vue.dialogZIndex = 1000;
} else {
Vue.dialogZIndex = Vue.dialogZIndex + 3;
}
return createModal(options);
}
}
export default registerDialog;// main.js 添加這兩句 import registerDialog from "@/components/registerDialog.js"; registerDialog();
在main.js中調(diào)用registerDialog方法,將js調(diào)用彈窗的方法$myConfirm,掛到Vue的原型對象上。
$myConfirm方法接受一個options對象,里面就是彈窗組件要求的props。調(diào)用該方法,首先修改Vue原型對象上的全局變量dialogZIndex,維護彈窗絕對定位的層級高度。然后調(diào)用了createModal方法,該方法會使用Vue.extend動態(tài)構(gòu)造組件實例,并掛載到新創(chuàng)建的DOM節(jié)點,是整個實現(xiàn)的核心。
const DialogConstructor = Vue.extend(MyDialog);
registerDialog.js文件被main.js引用的時候,這句代碼被執(zhí)行了。

MyDialog是包含組件選項的對象,Vue.extend實際構(gòu)建了彈窗組件的子類,該子類繼承了彈窗組件所有的屬性和方法。
createModal返回一個Promise,這是為了方便使用then和catch捕獲到確定和取消的事件。
在該方法中,實例化了上面用Vue.extend創(chuàng)建的彈窗子類,存在變量instance中,通過propsData將用戶參數(shù)傳給組件,并提供beforeClose方法的默認值,不做異步處理,直接調(diào)用done方法往下。創(chuàng)建一個div,插入到body中,然后是用instance.$mount()方法將彈窗組件掛載到這個div中。并手動設(shè)置彈窗顯示狀態(tài)為true,此時彈窗已經(jīng)打開。
另外,監(jiān)聽visible的值變化,當(dāng)visible為false時,銷毀當(dāng)前彈窗實例,并從body中移除創(chuàng)建的div。
使用事件訂閱監(jiān)聽到confirm和cancel事件,分別觸發(fā)Promise的resolve和reject。
3.頁面使用
代碼示例
this.$myConfirm({
title: "梁家輝",
content: "<div>我話講完,<strong style='color: red;'>誰贊成,誰反對?</strong></div>",
type: "success",
beforeClose: (action, instance, done) => {
if (action == "confirm") {
console.log("2秒后關(guān)閉");
setTimeout(() => {
done();
}, 2000);
} else {
done();
}
},
}).then(() => {
console.log("確認完畢");
}).catch(() => {
console.log("取消完畢");
});點擊確定后,會先打印出“2秒后關(guān)閉”,等待兩秒后關(guān)閉,點擊取消或者關(guān)閉按鈕,會直接關(guān)閉。
需要注意的是,如果要先異步處理,再關(guān)閉彈窗,要寫在beforeClose中。如果是關(guān)閉彈窗后執(zhí)行的邏輯,可以寫在then或者catch中。
總結(jié)
到此這篇關(guān)于如何封裝一個可用js直接調(diào)用的彈窗組件的文章就介紹到這了,更多相關(guān)js直接調(diào)用彈窗組件封裝內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于微信小程序使用echarts/數(shù)據(jù)刷新重新渲染/圖層遮擋問題
這篇文章主要介紹了微信小程序使用echarts/數(shù)據(jù)刷新重新渲染/圖層遮擋問題,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
JavaScript實現(xiàn)飛機大戰(zhàn)游戲
這篇文章主要為大家詳細介紹了JavaScript實現(xiàn)飛機大戰(zhàn)游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09
cookie 最近瀏覽記錄(中文escape轉(zhuǎn)碼)具體實現(xiàn)
cookie 最近瀏覽記錄(中文escape轉(zhuǎn)碼)具體實現(xiàn),需要的朋友可以參考一下2013-06-06
JavaScript控制語句及搭建前端服務(wù)器的過程詳解
這篇文章主要介紹了JavaScript控制語句及搭建前端服務(wù)器,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04

