寫Vue或者是react 都會遇見彈框的問題。也嘗試了多種辦法來寫彈框,一直都不太滿意,今天特地看了一下 Element UI 的源碼,模仿着寫了一個簡易版。
大概有一下幾個問題:
1、彈框的層級問題,如果在嵌套的組件里面使用了彈框,可能會出現彈框的層級不夠高
2、彈框的函數調用方式
首先第一點:彈框的層級
如果將彈框放置在最外層,body下面。就不會有層級問題。
第二點:彈框的函數調用
首先我們可以思考,將組件的實例拿到,然而初學的時候好像只有 通過 refs 能拿到組件的對象,然后調用顯示隱藏
其實我們可以通過 VUE.extend 這個函數,對組件進行初始化,然后可以拿到 組件對象
對於 Vue.extend 不太清楚的,建議自己去百度學習一下。
下面給出彈框的代碼: alert.vue 文件下面
<template> <div class="_alert" v-show="visible"> <div class="wind-alert"> <div class="wind-alert-bg"></div> <div class="wind-alert-dialog animate-scale"> <div class="wind-alert-title">{{title}}</div> <div class="wind-alert-content">{{content}}</div> <div class="wind-alert-btn" @click="close">{{btn}}</div> </div> </div> </div> </template> <script> export default { name:"rule_alert", data() { return { title: '提示', content: '', btn: '確定', visible:false } }, methods: { close() { this.visible = false; this._promise && this._promise.resolve() } }, watch: { '$route' () { this.close(); } } } </script> <style> .wind-alert-dialog { top: 30%; width: 80%; left: 50%; opacity: 1; position: fixed; margin-left: -40%; font-size: 14px; text-align: center; font-family: 'Microsoft Yahei'; background: #FFFFFF; border-radius: 8px; z-index: 999999999; box-sizing: content-box; } .wind-alert-bg { top: 0; left: 0; width: 100%; height: 100%; opacity: 0.3; display: block; position: fixed; z-index: 999999998; background-color: #000000; } .wind-alert-title { font-size: 17px; padding: 20px 5px 0; } .wind-alert-content { padding: 5px 15px 20px 15px; border-bottom: 1px solid #ededed; } .wind-alert-btn { color: #0582cd; font-size: 15px; line-height: 40px; font-weight: bold; } .animate-scale { animation-name: scale; animation-duration: 0.375s; } @keyframes scale { 0%{ transform: scale(0); } 100% { transform: scale(1); } } </style>
接下來,就是將這個組件,進行初始化,並且注入一些自己的方法和屬性
這個地方的注入,是一個公共的方法,后面可以引入其他類型的彈框,比如 comfirm 類型的
新建 plugin.js
import Alert from "@/components/alert"; import Vue from "vue"; //原始組件 var components = { Alert:Alert } var instance = {}; //緩存組件的實例 var Ruler = {}; //組件的集合 var body = document.body || document.documentElement; var root = document.createElement("div"); body.appendChild(root); //初始化構造vue組件,並且注入自己的代碼 const initComponents = function(type,options){ options = options || {}; type = type || ''; if(components[type]){ if(!instance[type]){ //避免重復的初始化 var div = document.createElement('div'); root.appendChild(div); const MessageBoxConstructor = Vue.extend(components[type]); instance[type] = new MessageBoxConstructor({ el: div }); } var ins = instance[type]; //復制屬性 for(var i in options){ ins[i] = options[i]; } Vue.nextTick(()=>{ ins.visible = true; }) return new Promise(function(resolve,reject){ //注入當前的 promise ins._promise = { resolve, reject }; }).finally(()=>{ //ins.visible = false; //可以在這里監聽,不管結果如何,最后執行一段代碼 }); }else{ return Promise.reject("組件不存在"); }; } //開始注冊組件 var Ruler = {}; //組件的集合 //主動關閉某個組件彈窗 type 組件類型名稱, methods false 取消關閉, true 確認關閉 Ruler.closeComponents = function (type,methods) { if(instance[type] && instance[type]._promise){ if(methods){ instance[type]._promise.resolve(); }else{ instance[type]._promise.reject(); } instance[type].visible = false; } } //對彈出組件的初始化處理 function popupHandle(i,options){ if(typeof options == "string"){ options = { msg: options } } return initComponents(i,options); } for(var i in components){ Ruler[i] = popupHandle.bind(components[i],i); } export default{ install(Vue){ Vue.prototype.$Ruler = Ruler; } }
接下來就是在 main.js 里面引入了:
import plugin from "@/plugin/plugin"; Vue.use(plugin);
然后在任意的地方使用
this.$Ruler.Alert("這是一個提示").then((ret)=>{ console.log("then",ret); }).catch((e)=>{ console.log("catch",e); });
注意: Vue.extend 初始化的組件,其內部 this.$router 是 undefined ,還有 this.$store 也是,可以直接import 后使用,
也可以 在組件里面 導入 router 之后,和data 平級,寫一個router,,,,這樣,組件在傳入 Vue.extend的時候,就能夠訪問到 this.$router 了
或者在 Vue.extend 以后 new 的時候加入 router ,如:
instance[type] = new MessageBoxConstructor({ el: div, router:router });
其原理在於: Vue.extend 函數,返回的一個繼承了 Vue 的子類。
也就是說
new MessageBoxConstructor 的時候,等同於 new Vue