如何用vue實現(xiàn)模態(tài)框組件
基本上每個項目都需要用到模態(tài)框組件,由于在最近的項目中,alert組件和confirm是兩套完全不一樣的設(shè)計,所以我將他們分成了兩個組件,本文主要討論的是confirm組件的實現(xiàn)。
組件結(jié)構(gòu)
- <template>
- <div class="modal" v-show="show" transition="fade">
- <div class="modal-dialog">
- <div class="modal-content">
- <!--頭部-->
- <div class="modal-header">
- <slot name="header">
- <p class="title">{{modal.title}}</p>
- </slot>
- <a v-touch:tap="close(0)" class="close" href="javascript:void(0)"></a>
- </div>
- <!--內(nèi)容區(qū)域-->
- <div class="modal-body">
- <slot name="body">
- <p class="notice">{{modal.text}}</p>
- </slot>
- </div>
- <!--尾部,操作按鈕-->
- <div class="modal-footer">
- <slot name="button">
- <a v-if="modal.showCancelButton" href="javascript:void(0)" class="button {{modal.cancelButtonClass}}" v-touch:tap="close(1)">{{modal.cancelButtonText}}</a>
- <a v-if="modal.showConfirmButton" href="javascript:void(0)" class="button {{modal.confirmButtonClass}}" v-touch:tap="submit">{{modal.confirmButtonText}}</a>
- </slot>
- </div>
- </div>
- </div>
- </div>
- <div v-show="show" class="modal-backup" transition="fade"></div>
- </template>
模態(tài)框結(jié)構(gòu)分為三部分,分別為頭部、內(nèi)部區(qū)域和操作區(qū)域,都提供了slot,可以根據(jù)需要定制。
樣式
- .modal {
- position: fixed;
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
- z-index: 1001;
- -webkit-overflow-scrolling: touch;
- outline: 0;
- overflow: scroll;
- margin: 30/@rate auto;
- }
- .modal-dialog {
- position: absolute;
- left: 50%;
- top: 0;
- transform: translate(-50%,0);
- width: 690/@rate;
- padding: 50/@rate 40/@rate;
- background: #fff;
- }
- .modal-backup {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 1000;
- background: rgba(0, 0, 0, 0.5);
- }
這里只是一些基本樣式,沒什么好說的,這次項目是在移動端,用了淘寶的自適應(yīng)布局方案,@rate是切稿時候的轉(zhuǎn)換率。
接口定義
- /**
- * modal 模態(tài)接口參數(shù)
- * @param {string} modal.title 模態(tài)框標(biāo)題
- * @param {string} modal.text 模態(tài)框內(nèi)容
- * @param {boolean} modal.showCancelButton 是否顯示取消按鈕
- * @param {string} modal.cancelButtonClass 取消按鈕樣式
- * @param {string} modal.cancelButtonText 取消按鈕文字
- * @param {string} modal.showConfirmButton 是否顯示確定按鈕
- * @param {string} modal.confirmButtonClass 確定按鈕樣式
- * @param {string} modal.confirmButtonText 確定按鈕標(biāo)文字
- */
- props: ['modalOptions'],
- computed: {
- /**
- * 格式化props進(jìn)來的參數(shù),對參數(shù)賦予默認(rèn)值
- */
- modal: {
- get() {
- let modal = this.modalOptions;
- modal = {
- title: modal.title || '提示',
- text: modal.text,
- showCancelButton: typeof modal.showCancelButton === 'undefined' ? true : modal.showCancelButton,
- cancelButtonClass: modal.cancelButtonClass ? modal.showCancelButton : 'btn-default',
- cancelButtonText: modal.cancelButtonText ? modal.cancelButtonText : '取消',
- showConfirmButton: typeof modal.showConfirmButton === 'undefined' ? true : modal.cancelButtonClass,
- confirmButtonClass: modal.confirmButtonClass ? modal.confirmButtonClass : 'btn-active',
- confirmButtonText: modal.confirmButtonText ? modal.confirmButtonText : '確定',
- };
- return modal;
- },
- },
- },
這里定義了接口的參數(shù),可以自定義標(biāo)題、內(nèi)容、是否顯示按鈕和按鈕的樣式,用一個computed來做參數(shù)默認(rèn)值的控制。
模態(tài)框內(nèi)部方法
- data() {
- return {
- show: false, // 是否顯示模態(tài)框
- resolve: '',
- reject: '',
- promise: '', // 保存promise對象
- };
- },
- methods: {
- /**
- * 確定,將promise斷定為完成態(tài)
- */
- submit() {
- this.resolve('submit');
- },
- /**
- * 關(guān)閉,將promise斷定為reject狀態(tài)
- * @param type {number} 關(guān)閉的方式 0表示關(guān)閉按鈕關(guān)閉,1表示取消按鈕關(guān)閉
- */
- close(type) {
- this.show = false;
- this.reject(type);
- },
- /**
- * 顯示confirm彈出,并創(chuàng)建promise對象
- * @returns {Promise}
- */
- confirm() {
- this.show = true;
- this.promise = new Promise((resolve, reject) => {
- this.resolve = resolve;
- this.reject = reject;
- });
- return this.promise; //返回promise對象,給父級組件調(diào)用
- },
- },
在模態(tài)框內(nèi)部定義了三個方法,最核心部分confirm方法,這是一個定義在模態(tài)框內(nèi)部,但是是給使用模態(tài)框的父級組件調(diào)用的方法,該方法返回的是一個promise對象,并將resolve和reject存放于modal組件的data中,點擊取消按鈕時,斷定為reject狀態(tài),并將模態(tài)框關(guān)閉掉,點確定按鈕時,斷定為resolve狀態(tài),模態(tài)框沒有關(guān)閉,由調(diào)用modal組件的父級組件的回調(diào)處理完成后手動控制關(guān)閉模態(tài)框。
調(diào)用
- <!-- template -->
- <confirm v-ref:dialog :modal-options.sync="modal"></confirm>
- <!-- methods -->
- this.$refs.dialog.confirm().then(() => {
- // 點擊確定按鈕的回調(diào)處理
- callback();
- this.$refs.dialog.show = false;
- }).catch(() => {
- // 點擊取消按鈕的回調(diào)處理
- callback();
- });
用v-ref創(chuàng)建一個索引,就很方便拿到模態(tài)框組件內(nèi)部的方法了。這樣一個模態(tài)框組件就完成了。
其他實現(xiàn)方法
在模態(tài)框組件中,比較難實現(xiàn)的應(yīng)該是點擊確定和取消按鈕時,父級的回調(diào)處理,我在做這個組件時,也參考了一些其實實現(xiàn)方案。
使用事件轉(zhuǎn)發(fā)
這個方法是我的同事實現(xiàn)的,用在上一個項目,采用的是$dispatch和$broadcast來派發(fā)或廣播事件。
首先在根組件接收dispatch過來的transmit事件,再將transmit事件傳遞過來的eventName廣播下去
- events: {
- /**
- * 轉(zhuǎn)發(fā)事件
- * @param {string} eventName 事件名稱
- * @param {object} arg 事件參數(shù)
- * @return {null}
- */
- 'transmit': function (eventName, arg) {
- this.$broadcast(eventName, arg);
- }
- },
其次是模態(tài)框組件內(nèi)部接收從父級組件傳遞過來的確定和取消按鈕所觸發(fā)的事件名,點擊取消和確定按鈕的時候觸發(fā)
- // 接收事件,獲得需要取消和確定按鈕的事件名
- events: {
- 'tip': function(obj) {
- this.events = {
- cancel: obj.events.cancel,
- confirm: obj.events.confirm
- }
- }
- }
- // 取消按鈕
- cancel:function() {
- this.$dispatch('transmit',this.events.cancel);
- }
- // 確定按鈕
- submit: function() {
- this.$dispatch('transmit',this.events.submit);
- }
在父級組件中調(diào)用模態(tài)框如下:
- this.$dispatch('transmit','tip',{
- events: {
- confirm: 'confirmEvent'
- }
- });
- this.$once('confirmEvent',function() {
- callback();
- }
先是傳遞tip事件,將事件名傳遞給模態(tài)框,再用$once監(jiān)聽確定或取消按鈕所觸發(fā)的事件,事件觸發(fā)后進(jìn)行回調(diào)。
這種方法看起來是不是很暈?所以vue 2.0取消了$dispatch和$broadcast,我們在最近的項目中雖然還在用1.0,但是也不再用$dispatch和$broadcast,方便以后的升級。
使用emit來觸發(fā)
這種方法來自vue-bootstrap-modal,點擊取消和確定按鈕的時候分別emit一個事件,直接在組件上監(jiān)聽這個事件,這種做法的好處是事件比較容易追蹤。
- // 確定按鈕
- ok () {
- this.$emit('ok');
- if (this.closeWhenOK) {
- this.show = false;
- }
- },
- // 取消按鈕
- cancel () {
- this.$emit('cancel');
- this.show = false;
- },
調(diào)用:
- <modal title="Modal Title" :show.sync="show" @ok="ok" @cancel="cancel">
- Modal Text
- </modal>
但是我們在使用的時候經(jīng)常會遇到這樣的場景,在一個組件的內(nèi)部,經(jīng)常會用到多個對話框,對話框可能只是文字有點區(qū)別,回調(diào)不同,這時就需要在template中為每個對話框都寫一次<modal></modal>,有點麻煩。不想每次寫,可以用v-for來遍歷,這篇文章關(guān)于 vue 彈窗組件的一些感想有我與作者的討論,可以參考一下。
參考資料