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 相关文章推荐
jQuery 操作XML入门
Dec 25 Javascript
js取得url地址参数实例
Feb 22 Javascript
jquery实现弹出层完美居中效果
Mar 03 Javascript
JS实现鼠标单击与双击事件共存
Mar 08 Javascript
jquery如何根据值设置默认的选中项
Mar 17 Javascript
jQuery判断div随滚动条滚动到一定位置后停止
Apr 02 Javascript
javascript实现checkbox复选框实例代码
Jan 10 Javascript
JavaScript计算器网页版实现代码分享
Jul 15 Javascript
react-router中的属性详解
Jun 01 Javascript
Vue EventBus自定义组件事件传递
Jun 25 Javascript
jQuery实现颜色打字机的完整代码
Mar 19 jQuery
element-ui中dialog弹窗关闭按钮失效的解决
Sep 22 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 从数据库提取二进制图片的处理代码
2009/09/09 PHP
php语言中使用json的技巧及json的实现代码详解
2015/10/27 PHP
symfony2.4的twig中date用法分析
2016/03/18 PHP
Laravel Intervention/image图片处理扩展包的安装、使用与可能遇到的坑详解
2017/11/14 PHP
为jquery.ui.dialog 增加“自动记住关闭时的位置”的功能
2009/11/24 Javascript
javascript cookies操作集合
2010/04/12 Javascript
纯js实现背景图片切换效果代码
2010/11/14 Javascript
JS页面延迟执行一些方法(整理)
2013/11/11 Javascript
jquery实现删除一个元素后面的所有元素功能
2015/12/21 Javascript
两种js监听滚轮事件的实现方法
2016/05/13 Javascript
Bootstrap布局之栅格系统详解
2016/06/13 Javascript
基于node.js依赖express解析post请求四种数据格式
2017/02/13 Javascript
AngularJS constant和value区别详解
2017/02/28 Javascript
微信小程序 密码输入(源码下载)
2017/06/27 Javascript
Angularjs使用过滤器完成排序功能
2017/09/20 Javascript
Angular中点击li标签实现更改颜色的核心代码
2017/12/08 Javascript
JS实现利用闭包判断Dom元素和滚动条的方向示例
2019/08/26 Javascript
vue使用showdown并实现代码区域高亮的示例代码
2019/10/17 Javascript
python获得linux下所有挂载点(mount points)的方法
2015/04/29 Python
Python学习之Django的管理界面代码示例
2018/02/10 Python
Python使用pickle模块储存对象操作示例
2018/08/15 Python
python 梯度法求解函数极值的实例
2019/07/10 Python
python多线程实现TCP服务端
2019/09/03 Python
PYTHON发送邮件YAGMAIL的简单实现解析
2019/10/28 Python
从pandas一个单元格的字符串中提取字符串方式
2019/12/17 Python
纯css3(无图片/js)制作的几个社交媒体网站的图标
2013/03/21 HTML / CSS
如果一个类实现了多个接口但是这些接口有相同的方法名将会怎样
2013/06/16 面试题
中学老师的自我评价
2013/11/07 职场文书
关于旷工的检讨书
2014/02/02 职场文书
金融管理专业毕业生求职信
2014/03/12 职场文书
就业协议书盖章的注意事项
2014/09/28 职场文书
县委党的群众路线教育实践活动工作情况报告
2014/10/25 职场文书
盲山观后感
2015/06/11 职场文书
教学副校长工作总结
2015/08/13 职场文书
pycharm2021激活码使用教程(永久激活亲测可用)
2021/03/30 Python
Python中seaborn库之countplot的数据可视化使用
2021/06/11 Python