欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue $mount实战之实现消息弹窗组件

程序员文章站 2023-12-04 15:31:06
之前的项目一直在使用element-ui框架,element中的notification、message组件使用时不需要在html写标签,而是使用js调用。那时就很疑惑,为...

之前的项目一直在使用element-ui框架,element中的notificationmessage组件使用时不需要在html写标签,而是使用js调用。那时就很疑惑,为什么element ui使用this.$notifythis.$message就可以实现这样的功能?

1、实现消息弹窗组件的几个问题

  • 如何在任何组件中使用this.$message就可以显示消息?
  • 如何将消息的dom节点插入到body中?
  • 同时出现多个消息弹窗时,消息弹窗的z-index如何控制?

2、效果预览

Vue $mount实战之实现消息弹窗组件

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;