如何封裝一個可用js直接調(diào)用的彈窗組件
前言
在Vue開發(fā)中,正常的組件通常以模板方式調(diào)用,但對于彈窗這種呈現(xiàn)方式,使用模板的方式開發(fā)過于繁瑣,怎樣才能讓彈窗組件可以像原生alert()一樣通過函數(shù)調(diào)用觸發(fā)彈窗呢?本文將基于Vue2,詳細講解如何封裝一個可通過JavaScript函數(shù)直接調(diào)用的彈窗組件,實現(xiàn)高復用性和開發(fā)效率的提升。
一、開發(fā)思路
1. 先封裝一個正常的彈窗組件
2. 使用Vue.extend動態(tài)構造組件實例,并掛載到新創(chuàng)建的DOM節(jié)點
3. 需要支持Promise的方式捕獲確定和取消操作,同時支持確定按鈕異步關閉。比如點確定后調(diào)個接口,接口返回處理后,再讓彈窗關閉。
二、開發(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,把彈窗關掉了 -->
<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: {
// 點擊遮罩是否關閉彈窗
type: Boolean,
default: true,
},
showCancelButton: {
type: Boolean,
default: true,
},
cancelButtonName: {
type: String,
default: "取消",
},
showConfirmButton: {
type: Boolean,
default: true,
},
confirmButtonName: {
type: String,
default: "確定",
},
showClose: {
// 是否顯示右上角關閉按鈕
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 | ||
點擊遮罩是否關閉彈窗 | clickMaskClose | true | ||
| 是否顯示取消按鈕 | showCancelButton | true | ||
| 取消按鈕名稱 | cancelButtonName | “取消” | ||
| 是否顯示確定按鈕 | showConfirmButton | true | ||
| 確定按鈕名稱 | confirmButtonName | “確定” | ||
是否顯示右上角關閉按鈕 | showClose | true | ||
| 彈窗占屏幕寬度比例 | width | 60% | 支持字符串形式的值,會直接賦給width樣式 | |
| 彈窗類型 | type | 否 | success/warning/info/danger | |
| 關閉前回調(diào) | beforeClose | (action, instance, done) => {} |
注意事項:
1. 彈窗、遮罩都是以fixed絕對定位,為了讓新彈窗遮蓋就彈窗,不顯示到一個平面,所以z-index不能寫死。
設置Vue的全局變量Vue.prototype.dialogZIndex,默認是1000,當創(chuàng)建新彈窗時,dialogZIndex + 3,新彈窗created生命周期函數(shù)中獲取最新的dialogZIndex,使用動態(tài)樣式的方式,設置給遮罩、彈窗體。
2. 由于有點擊遮罩關閉彈窗的功能,為了避免點擊彈窗事件冒泡到外層的遮罩,觸發(fā)關閉彈窗的動作,給彈窗dom綁定一個點擊事件,@click.stop="clickDialog",clickDialog是空方法,不處理。
3. 點擊確定、取消、關閉、遮罩觸發(fā)的關閉動作,都不直接修改visible屬性,而是調(diào)用了this.beforeClose(),該方法的第三個參數(shù)是this.done的執(zhí)行結果,可以看出,this.done返回了一個方法。當該方法執(zhí)行時,會判斷當前是何種方式觸發(fā)的關閉,并做相應處理,然后關閉彈窗。這是實現(xià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)聽關閉事件
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)構造組件實例,并掛載到新創(chuàng)建的DOM節(jié)點,是整個實現(xiàn)的核心。
const DialogConstructor = Vue.extend(MyDialog);
registerDialog.js文件被main.js引用的時候,這句代碼被執(zhí)行了。

MyDialog是包含組件選項的對象,Vue.extend實際構建了彈窗組件的子類,該子類繼承了彈窗組件所有的屬性和方法。
createModal返回一個Promise,這是為了方便使用then和catch捕獲到確定和取消的事件。
在該方法中,實例化了上面用Vue.extend創(chuàng)建的彈窗子類,存在變量instance中,通過propsData將用戶參數(shù)傳給組件,并提供beforeClose方法的默認值,不做異步處理,直接調(diào)用done方法往下。創(chuàng)建一個div,插入到body中,然后是用instance.$mount()方法將彈窗組件掛載到這個div中。并手動設置彈窗顯示狀態(tài)為true,此時彈窗已經(jīng)打開。
另外,監(jiān)聽visible的值變化,當visible為false時,銷毀當前彈窗實例,并從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秒后關閉");
setTimeout(() => {
done();
}, 2000);
} else {
done();
}
},
}).then(() => {
console.log("確認完畢");
}).catch(() => {
console.log("取消完畢");
});點擊確定后,會先打印出“2秒后關閉”,等待兩秒后關閉,點擊取消或者關閉按鈕,會直接關閉。
需要注意的是,如果要先異步處理,再關閉彈窗,要寫在beforeClose中。如果是關閉彈窗后執(zhí)行的邏輯,可以寫在then或者catch中。
總結
到此這篇關于如何封裝一個可用js直接調(diào)用的彈窗組件的文章就介紹到這了,更多相關js直接調(diào)用彈窗組件封裝內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JavaScript輸出為[object?Object]問題的解決辦法
平時我們在接收后端返回的json對象通常是一個字符串類型的object,所以一般我們要對這個object進行類型轉化后,我們才能使用object里面的數(shù)據(jù),這篇文章主要給大家介紹了關于JavaScript輸出為[object?Object]問題的解決辦法,需要的朋友可以參考下2023-11-11
BootStrap智能表單實戰(zhàn)系列(四)表單布局介紹
這篇文章主要介紹了BootStrap智能表單實戰(zhàn)系列(四)表單布局介紹,表單布局分為自動布局和自定義布局兩種,本文通過代碼給大家介紹,介紹的非常詳細,具有參考借鑒價值,感興趣的朋友一起學習吧2016-06-06
JavaScript中Array.map()的使用與技巧分享(附實際應用代碼)
Array.map()適合需要返回新數(shù)組、進行鏈式調(diào)用或不修改原數(shù)組的場景,它與forEach()的區(qū)別在于有返回值,基本語法中,callback函數(shù)處理每個元素并返回新值,本文介紹了JavaScript中Array.map()的使用與技巧分享(附實際應用代碼),需要的朋友可以參考下2025-02-02
鼠標事件的screenY,pageY,clientY,layerY,offsetY屬性詳解
這篇文章主要介紹了鼠標事件的screenY,pageY,clientY,layerY,offsetY屬性詳解,需要的朋友可以參考下2015-03-03
JavaScript函數(shù)柯里化實現(xiàn)原理及過程
這篇文章主要介紹了JavaScript函數(shù)柯里化實現(xiàn)原理及過程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-12-12
file模式訪問網(wǎng)頁時iframe高度自適應解決方案
最近做到iframe的高度自適應這個問題;發(fā)現(xiàn)自己做的網(wǎng)頁是通過file方式訪問的,將網(wǎng)頁代碼放到apache下通過http協(xié)議訪問,在iframe加載的時候調(diào)用如下js方法:果然網(wǎng)頁高度能夠自適應(對于其他方案應該也有效果,我沒有注意去嘗試)感興趣的朋友可以了解下2013-01-01
JS+CSS實現(xiàn)彈出全屏灰黑色透明遮罩效果的方法
這篇文章主要介紹了JS+CSS實現(xiàn)彈出全屏灰黑色透明遮罩效果的方法,詳細分析了彈出遮罩層效果的實現(xiàn)方法以及完整的實例代碼,需要的朋友可以參考下2014-12-12

