写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%; 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; 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