如何封裝一個(gè)可用js直接調(diào)用的彈窗組件
前言
在Vue開發(fā)中,正常的組件通常以模板方式調(diào)用,但對(duì)于彈窗這種呈現(xiàn)方式,使用模板的方式開發(fā)過(guò)于繁瑣,怎樣才能讓彈窗組件可以像原生alert()
一樣通過(guò)函數(shù)調(diào)用觸發(fā)彈窗呢?本文將基于Vue2,詳細(xì)講解如何封裝一個(gè)可通過(guò)JavaScript函數(shù)直接調(diào)用的彈窗組件,實(shí)現(xiàn)高復(fù)用性和開發(fā)效率的提升。
一、開發(fā)思路
1. 先封裝一個(gè)正常的彈窗組件
2. 使用Vue.extend動(dòng)態(tài)構(gòu)造組件實(shí)例,并掛載到新創(chuàng)建的DOM節(jié)點(diǎn)
3. 需要支持Promise的方式捕獲確定和取消操作,同時(shí)支持確定按鈕異步關(guān)閉。比如點(diǎn)確定后調(diào)個(gè)接口,接口返回處理后,再讓彈窗關(guān)閉。
二、開發(fā)過(guò)程
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" > <!-- 這里添加一個(gè)stop的事件,在點(diǎn)擊彈窗區(qū)域時(shí),阻止向上冒泡觸發(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: { // 點(diǎn)擊遮罩是否關(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: { // 彈窗占屏幕寬度,默認(rèn)60% type: String, default: "60%", }, type: { // 提示類型,文案前的圖標(biāo)會(huì)有所不同 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>
該組件提供了幾個(gè)props
屬性 | 字段名 | 是否必填 | 默認(rèn)值 | 備注 |
---|---|---|---|---|
彈窗顯示狀態(tài) | visible | 是 | ||
彈窗標(biāo)題 | title | "提示" | ||
內(nèi)容 | content | 支持html和文本 | ||
是否顯示遮罩 | showMask | true | ||
點(diǎn)擊遮罩是否關(guān)閉彈窗 | clickMaskClose | true | ||
是否顯示取消按鈕 | showCancelButton | true | ||
取消按鈕名稱 | cancelButtonName | “取消” | ||
是否顯示確定按鈕 | showConfirmButton | true | ||
確定按鈕名稱 | confirmButtonName | “確定” | ||
是否顯示右上角關(guān)閉按鈕 | showClose | true | ||
彈窗占屏幕寬度比例 | width | 60% | 支持字符串形式的值,會(huì)直接賦給width樣式 | |
彈窗類型 | type | 否 | success/warning/info/danger | |
關(guān)閉前回調(diào) | beforeClose | (action, instance, done) => {} |
注意事項(xiàng):
1. 彈窗、遮罩都是以fixed絕對(duì)定位,為了讓新彈窗遮蓋就彈窗,不顯示到一個(gè)平面,所以z-index不能寫死。
設(shè)置Vue的全局變量Vue.prototype.dialogZIndex,默認(rèn)是1000,當(dāng)創(chuàng)建新彈窗時(shí),dialogZIndex + 3,新彈窗created生命周期函數(shù)中獲取最新的dialogZIndex,使用動(dòng)態(tài)樣式的方式,設(shè)置給遮罩、彈窗體。
2. 由于有點(diǎn)擊遮罩關(guān)閉彈窗的功能,為了避免點(diǎn)擊彈窗事件冒泡到外層的遮罩,觸發(fā)關(guān)閉彈窗的動(dòng)作,給彈窗dom綁定一個(gè)點(diǎn)擊事件,@click.stop="clickDialog",clickDialog是空方法,不處理。
3. 點(diǎn)擊確定、取消、關(guān)閉、遮罩觸發(fā)的關(guān)閉動(dòng)作,都不直接修改visible屬性,而是調(diào)用了this.beforeClose(),該方法的第三個(gè)參數(shù)是this.done的執(zhí)行結(jié)果,可以看出,this.done返回了一個(gè)方法。當(dāng)該方法執(zhí)行時(shí),會(huì)判斷當(dāng)前是何種方式觸發(fā)的關(guān)閉,并做相應(yīng)處理,然后關(guān)閉彈窗。這是實(shí)現(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, } }); // 掛載到臨時(shí)DOM const container = document.createElement('div') document.body.appendChild(container) instance.$mount(container); // 手動(dòng)打開彈窗 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) => { // 彈窗層級(jí)累加,保證先彈的框在下面 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的原型對(duì)象上。
$myConfirm方法接受一個(gè)options對(duì)象,里面就是彈窗組件要求的props。調(diào)用該方法,首先修改Vue原型對(duì)象上的全局變量dialogZIndex,維護(hù)彈窗絕對(duì)定位的層級(jí)高度。然后調(diào)用了createModal方法,該方法會(huì)使用Vue.extend動(dòng)態(tài)構(gòu)造組件實(shí)例,并掛載到新創(chuàng)建的DOM節(jié)點(diǎn),是整個(gè)實(shí)現(xiàn)的核心。
const DialogConstructor = Vue.extend(MyDialog);
registerDialog.js文件被main.js引用的時(shí)候,這句代碼被執(zhí)行了。
MyDialog是包含組件選項(xiàng)的對(duì)象,Vue.extend實(shí)際構(gòu)建了彈窗組件的子類,該子類繼承了彈窗組件所有的屬性和方法。
createModal返回一個(gè)Promise,這是為了方便使用then和catch捕獲到確定和取消的事件。
在該方法中,實(shí)例化了上面用Vue.extend創(chuàng)建的彈窗子類,存在變量instance中,通過(guò)propsData將用戶參數(shù)傳給組件,并提供beforeClose方法的默認(rèn)值,不做異步處理,直接調(diào)用done方法往下。創(chuàng)建一個(gè)div,插入到body中,然后是用instance.$mount()方法將彈窗組件掛載到這個(gè)div中。并手動(dòng)設(shè)置彈窗顯示狀態(tài)為true,此時(shí)彈窗已經(jīng)打開。
另外,監(jiān)聽visible的值變化,當(dāng)visible為false時(shí),銷毀當(dāng)前彈窗實(shí)例,并從body中移除創(chuàng)建的div。
使用事件訂閱監(jiān)聽到confirm和cancel事件,分別觸發(fā)Promise的resolve和reject。
3.頁(yè)面使用
代碼示例
this.$myConfirm({ title: "梁家輝", content: "<div>我話講完,<strong style='color: red;'>誰(shuí)贊成,誰(shuí)反對(duì)?</strong></div>", type: "success", beforeClose: (action, instance, done) => { if (action == "confirm") { console.log("2秒后關(guān)閉"); setTimeout(() => { done(); }, 2000); } else { done(); } }, }).then(() => { console.log("確認(rèn)完畢"); }).catch(() => { console.log("取消完畢"); });
點(diǎn)擊確定后,會(huì)先打印出“2秒后關(guān)閉”,等待兩秒后關(guān)閉,點(diǎn)擊取消或者關(guān)閉按鈕,會(huì)直接關(guān)閉。
需要注意的是,如果要先異步處理,再關(guān)閉彈窗,要寫在beforeClose中。如果是關(guān)閉彈窗后執(zhí)行的邏輯,可以寫在then或者catch中。
總結(jié)
到此這篇關(guān)于如何封裝一個(gè)可用js直接調(diào)用的彈窗組件的文章就介紹到這了,更多相關(guān)js直接調(diào)用彈窗組件封裝內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Element ui 下拉多選時(shí)新增一個(gè)選擇所有的選項(xiàng)
這篇文章主要介紹了Element ui 下拉多選時(shí) 新增一個(gè)選擇所有的選項(xiàng),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08關(guān)于微信小程序使用echarts/數(shù)據(jù)刷新重新渲染/圖層遮擋問(wèn)題
這篇文章主要介紹了微信小程序使用echarts/數(shù)據(jù)刷新重新渲染/圖層遮擋問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07JavaScript實(shí)現(xiàn)飛機(jī)大戰(zhàn)游戲
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)飛機(jī)大戰(zhàn)游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09layui動(dòng)態(tài)表頭的實(shí)現(xiàn)代碼
這篇文章主要介紹了layui動(dòng)態(tài)表頭的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08cookie 最近瀏覽記錄(中文escape轉(zhuǎn)碼)具體實(shí)現(xiàn)
cookie 最近瀏覽記錄(中文escape轉(zhuǎn)碼)具體實(shí)現(xiàn),需要的朋友可以參考一下2013-06-06JavaScript用select實(shí)現(xiàn)日期控件
這篇文章主要介紹了JavaScript用select實(shí)現(xiàn)日期控件的相關(guān)資料,需要的朋友可以參考下2015-07-07JavaScript控制語(yǔ)句及搭建前端服務(wù)器的過(guò)程詳解
這篇文章主要介紹了JavaScript控制語(yǔ)句及搭建前端服務(wù)器,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04