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


Posted in Javascript onApril 22, 2019

之前的项目一直在使用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;
Javascript 相关文章推荐
js 获取时间间隔实现代码
May 12 Javascript
jQuery控制TR显示隐藏的三种常用方法
Aug 21 Javascript
PhotoShop给图片自动添加边框及EXIF信息的JS脚本
Feb 15 Javascript
JQuery插入DOM节点的方法
Jun 11 Javascript
JS 数字转换为大写金额的简单实例
Aug 04 Javascript
用AngularJS的指令实现tabs切换效果
Aug 31 Javascript
js循环map 获取所有的key和value的实现代码(json)
May 09 Javascript
vue完成项目后,打包成静态文件的方法
Sep 03 Javascript
js获取form表单中name属性的值
Feb 27 Javascript
JS表格的动态操作完整示例
Jan 13 Javascript
原生js实现购物车
Sep 23 Javascript
深入理解vue中的slot与slot-scope
Apr 22 #Javascript
浅析vue插槽和作用域插槽的理解
Apr 22 #Javascript
详解50行代码,Node爬虫练手项目
Apr 22 #Javascript
Vue匿名插槽与作用域插槽的合并和覆盖行为
Apr 22 #Javascript
详解Vue 匿名、具名和作用域插槽的使用方法
Apr 22 #Javascript
详解Node.js一行命令上传本地文件到服务器
Apr 22 #Javascript
使用 vue 实例更好的监听事件及vue实例的方法
Apr 22 #Javascript
You might like
php数组函数序列之array_unshift() 在数组开头插入一个或多个元素
2011/11/07 PHP
自制PHP框架之路由与控制器
2017/05/07 PHP
PHP空值检测函数与方法汇总
2017/11/19 PHP
PHP const定义常量及global定义全局常量实例解析
2020/05/28 PHP
flexigrid 参数说明
2010/11/23 Javascript
jQuery获取文本节点之 text()/val()/html() 方法区别
2011/03/01 Javascript
上传的js验证(图片/文件的扩展名)
2013/04/25 Javascript
jQuery实现鼠标可拖动调整表格列宽度
2014/05/26 Javascript
js判断是否按下了Shift键的方法
2015/01/27 Javascript
JS组件系列之Bootstrap Icon图标选择组件
2016/01/28 Javascript
jQuery给指定的table动态添加删除行的操作方法
2016/10/12 Javascript
JS排序之冒泡排序详解
2017/04/08 Javascript
使用vue和datatables进行表格的服务器端分页实例代码
2017/06/07 Javascript
JS实现快速比较两个字符串中包含有相同数字的方法
2017/09/11 Javascript
利用ECharts.js画K线图的方法示例
2018/01/10 Javascript
详解Chart.js轻量级图表库的使用经验
2018/05/22 Javascript
微信小程序实现人脸检测功能
2018/05/25 Javascript
深入浅析var,let,const的异同点
2018/08/07 Javascript
在Python中处理XML的教程
2015/04/29 Python
Python获取本机所有网卡ip,掩码和广播地址实例代码
2018/01/22 Python
python smtplib模块实现发送邮件带附件sendmail
2018/05/22 Python
python实现从pdf文件中提取文本,并自动翻译的方法
2018/11/28 Python
在Python 中实现图片加框和加字的方法
2019/01/26 Python
Python3实现mysql连接和数据框的形成(实例代码)
2020/01/17 Python
python读取当前目录下的CSV文件数据
2020/03/11 Python
Python连接Hadoop数据中遇到的各种坑(汇总)
2020/04/14 Python
基于python实现把json数据转换成Excel表格
2020/05/07 Python
BeautifulSoup中find和find_all的使用详解
2020/12/07 Python
Python 带星号(* 或 **)的函数参数详解
2021/02/23 Python
美国猫狗药物和用品网站:PetCareRx
2017/01/05 全球购物
美国领先的水果篮送货公司和新鲜水果供应商:The Fruit Company
2018/02/13 全球购物
二年级学生评语大全
2014/04/23 职场文书
收入及婚姻状况证明
2014/11/20 职场文书
2015年“世界无车日”活动方案
2015/05/06 职场文书
党风廉政教育心得体会2016
2016/01/22 职场文书
PostgreSQL自动更新时间戳实例代码
2021/11/27 PostgreSQL