Vue $mount实战之实现消息弹窗组件
程序员文章站
2023-12-04 15:31:06
之前的项目一直在使用element-ui框架,element中的notification、message组件使用时不需要在html写标签,而是使用js调用。那时就很疑惑,为...
之前的项目一直在使用element-ui
框架,element中的notification
、message
组件使用时不需要在html写标签,而是使用js调用。那时就很疑惑,为什么element ui
使用this.$notify
、this.$message
就可以实现这样的功能?
1、实现消息弹窗组件的几个问题
- 如何在任何组件中使用this.$message就可以显示消息?
- 如何将消息的dom节点插入到body中?
- 同时出现多个消息弹窗时,消息弹窗的z-index如何控制?
2、效果预览
3、代码实现
pmessage.vue
<template> <transition name="message-fade"> <div class="p-message" :class="[type, extraclass]" v-show="show" @mouseenter="cleartimer" @mouseleave="starttimer"> <div class="p-message-container"> <i class="p-message-icon" :class="`p-message-icon-${type}`"></i> <div class="p-message-content"> <slot class="p-message-content"> <div v-html="message"></div> </slot> </div> </div> </div> </transition> </template> <script> // 绑定事件 function _addevent(el, eventname, fn){ if(document.addeventlistener){ el.addeventlistener(eventname, fn, false); }else if(window.attachevent){ el.attactevent('on' + eventname, fn); } }; // 解绑事件 function _offevent(el, eventname, fn){ if(document.removeeventlistener){ el.removeeventlistener(eventname, fn, false); }else if(window.detachevent){ el.detachevent('on' + eventname, fn); } }; export default { name: "pmessage", data(){ return { type: 'success', duration: 3000, extraclass: '', message: '', timer: null, closed: false, show: false } }, methods: { starttimer(){ if(this.duration > 0){ this.timer = settimeout(() => { if(!this.closed){ this.close(); } }, this.duration); } }, cleartimer(){ cleartimeout(this.timer); }, close(){ this.closed = true; if(typeof this.onclose === 'function'){ // 调用onclose方法,以从p-message.js中的instances数组中移除当前组件,不移除的话就占空间了 this.onclose(); } }, // 销毁组件 destroyelement(){ _offevent(this.$el, 'transitionend', this.destroyelement); // 手动销毁组件 this.$destroy(true); this.$el.parentnode.removechild(this.$el); }, }, watch: { // 监听closed,如果它为true,则销毁message组件 closed(newval){ if(newval){ this.show = false; // message过渡完成后再去销毁message组件及移除元素 _addevent(this.$el, 'transitionend', this.destroyelement); } } }, mounted() { this.starttimer(); } } </script> <style lang="stylus"> @import "p-message.styl" </style>
p-message.js
import vue from 'vue'; import pmessage from './pmessage.vue'; import {popupmanager} from "../../common/js/popup-manager"; let pmessagecontrol = vue.extend(pmessage); let count = 0; // 存储message组件实例,如需有关闭所有message的功能就需要将每个message组件都存储起来 let instances = []; const isvnode = function (node) { return node !== null && typeof node === 'object' && object.prototype.hasownproperty.call(node, 'componentoptions'); }; const message = function (options) { options = options || {}; if(typeof options === 'string'){ options = { message: options }; } let id = 'message_' + ++count; let useronclose = options.onclose; // pmsesage.vue销毁时会调用传递进去的onclose,而onclose的处理就是将指定id的message组件从instances中移除 options.onclose = function (){ message._close(id, useronclose); }; /* 这里传递给pmessagecontrol的data不会覆盖pmessage.vue中原有的data,而是与pmessage.vue中原有的data进行合并,类似 * 与mixin,包括传递methods、生命周期函数也是一样 */ let instance = new pmessagecontrol({ data: options }); // 传递vnode if(isvnode(instance.message)){ instance.$slots.default = [instance.message]; instance.message = null; } instance.id = id; // 渲染元素,随后使用原生appendchild将dom插入到页面中 instance.$mount(); let $el = instance.$el; // message弹窗的z-index由popupmanager来提供 $el.style.zindex = popupmanager.getnextzindex(); document.body.appendchild($el); // 将message显示出来 instance.show = true; console.log(instance) instances.push(instance); return instance; }; // message简化操作 ['success','error'].foreach(function (item) { message[item] = options => { if(typeof options === 'string'){ options = { message: options } } options.type = item; return message(options); } }); /** * 从instances删除指定message,内部使用 * @param id * @param useronclose * @private */ message._close = function (id, useronclose) { for(var i = 0, len = instances.length; i < len; i++){ if(instances[i].id === id){ if(typeof useronclose === 'function'){ useronclose(instances[i]); } instances.splice(i, 1); break; } } }; // 关闭所有message message.closeall = function () { for(var i = instances.length - 1; i >= 0; i--){ instances.close(); } }; export default message;
popup-manager.js
let zindex = 1000; let haszindexinited = false; const popupmanager = { // 获取索引 getnextzindex(){ if(!haszindexinited){ haszindexinited = true; return zindex; } return zindex++; } }; export {popupmanager}; p-index.js import pmessage from './p-message.js'; export default pmessage; p-message.styl .p-message{ position: fixed; top: 20px; left: 50%; padding: 8px 15px; border-radius: 4px; background-color: #fff; color: #000; transform: translatex(-50%); transition: opacity .3s, transform .4s; &.message-fade-enter, &.message-fade-leave-to{ opacity: 0; transform: translatex(-50%) translatey(-30px); } &.message-fade-enter-to, &.message-fade-leave{ opacity: 1; transform: translatex(-50%) translatey(0); } &.error{ color: #ff3737; } .p-message-icon{ /* 使图标与内容能够垂直居中 */ display: table-cell; vertical-align: middle; width: 64px; height: 45px; &.p-message-icon-success{ background: url("../../assets/images/icons/message-icon/icon_success.png") no-repeat 0 0; } &.p-message-icon-error{ background: url("../../assets/images/icons/message-icon/icon_error.png") no-repeat 0 0; } } .p-message-content{ /* 使图标与内容能够垂直居中 */ display: table-cell; vertical-align: middle; padding-left: 15px; } }
main.js
// 引入pmessage组件 import pmessage from './components/p-message/p-index.js'; // 将pmessage绑定到vue.prototype中。这样在组件中就可以通过this.$pmessage()的形式来使用了 vue.prototype.$pmessage = pmessage;