如何封裝一個可用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ù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
關于微信小程序使用echarts/數(shù)據(jù)刷新重新渲染/圖層遮擋問題
這篇文章主要介紹了微信小程序使用echarts/數(shù)據(jù)刷新重新渲染/圖層遮擋問題,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07JavaScript實現(xiàn)飛機大戰(zhàn)游戲
這篇文章主要為大家詳細介紹了JavaScript實現(xiàn)飛機大戰(zhàn)游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09cookie 最近瀏覽記錄(中文escape轉碼)具體實現(xiàn)
cookie 最近瀏覽記錄(中文escape轉碼)具體實現(xiàn),需要的朋友可以參考一下2013-06-06