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之WEB开发调试利器:Firebug 下载
Jan 13 Javascript
jquery.AutoComplete.js中文修正版(支持firefox)
Apr 09 Javascript
关于jquery ajax 调用带参数的webservice返回XML数据一个小细节
Jul 31 Javascript
Js实现手机发送验证码时按钮延迟操作
Jun 20 Javascript
jQuery移动端图片上传组件
Jun 12 Javascript
面试常见的js算法题
Mar 23 Javascript
vue拦截器Vue.http.interceptors.push使用详解
Apr 22 Javascript
详解如何在 vue 项目里正确地引用 jquery 和 jquery-ui的插件
Jun 01 jQuery
jQuery实现键盘回车搜索功能
Jul 25 jQuery
javascript实现文字无缝滚动效果
Aug 26 Javascript
Bootstrap4如何定制自己的颜色和风格
Feb 26 Javascript
微信小程序APP页面的之间的相互传递参数以及自定义组件
Apr 19 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函数
2006/12/06 PHP
基于Windows下Apache PHP5.3.1安装教程
2010/01/08 PHP
PHP 命令行工具 shell_exec, exec, passthru, system详细使用介绍
2011/09/11 PHP
PHP中如何实现常用邮箱的基本判断
2014/01/07 PHP
教你如何开启shopnc b2b2c 伪静态
2014/10/21 PHP
PHP函数checkdnsrr用法详解(Windows平台用法)
2016/03/21 PHP
php arsort 数组降序排序详细介绍
2016/11/17 PHP
javascript 读取XML数据,在页面中展现、编辑、保存的实现
2009/10/27 Javascript
JQuery 图片延迟加载并等比缩放插件
2009/11/09 Javascript
ExtJs grid行 右键菜单的两种方法
2010/06/19 Javascript
JavaScript避免代码的重复执行经验技巧分享
2014/04/17 Javascript
JS获取iframe中longdesc属性的方法
2015/04/01 Javascript
在JavaScript中用getMinutes()方法返回指定的分时刻
2015/06/10 Javascript
JS中递归函数
2016/06/17 Javascript
全面接触神奇的Bootstrap导航条实战篇
2016/08/01 Javascript
Node.js + Redis Sorted Set实现任务队列
2016/09/19 Javascript
Javascript中数组去重与拍平的方法示例
2017/02/03 Javascript
webpack踩坑之路图片的路径与打包
2017/09/05 Javascript
vue在index.html中引入静态文件不生效问题及解决方法
2019/04/29 Javascript
JS async 函数的含义和用法实例总结
2020/04/08 Javascript
Vue之封装公用变量以及实现方式
2020/07/31 Javascript
python引用DLL文件的方法
2015/05/11 Python
python Pexpect 实现输密码 scp 拷贝的方法
2019/01/03 Python
Python使用指定端口进行http请求的例子
2019/07/25 Python
简单了解python变量的作用域
2019/07/30 Python
python多线程实现TCP服务端
2019/09/03 Python
python取均匀不重复的随机数方式
2019/11/27 Python
使用Pandas将inf, nan转化成特定的值
2019/12/19 Python
keras 权重保存和权重载入方式
2020/05/21 Python
澳大利亚购买健身器材网站:Gym Direct
2019/12/19 全球购物
爸爸的花儿落了教学反思
2014/02/20 职场文书
贷款担保申请书
2014/05/20 职场文书
五四青年节优秀演讲稿范文
2014/05/28 职场文书
工作失职检讨书(精华篇)
2014/10/15 职场文书
大学生暑期实践报告
2015/07/13 职场文书
导游词之平津战役纪念馆
2019/11/04 职场文书