寫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
