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后$冲突的解决办法
Jul 09 Javascript
jQuery中json对象的复制方式介绍(数组及对象)
Jun 08 Javascript
JavaScript获取网页支持表单字符集的方法
Apr 02 Javascript
javascript委托(Delegate)blur和focus用法实例分析
May 26 Javascript
浅谈angularJS 作用域
Jul 05 Javascript
jquery实现滑动特效代码
Aug 10 Javascript
jquery实现模拟百分比进度条渐变效果代码
Oct 29 Javascript
jquery获取select选中值的方法分析
Dec 22 Javascript
解决bootstrap中使用modal加载kindeditor时弹出层文本框不能输入的问题
Jun 05 Javascript
vuejs数据超出单行显示更多,点击展开剩余数据实例
May 05 Javascript
layui实现checkbox的目录树tree的例子
Sep 12 Javascript
通过实例解析chrome如何在mac环境中安装vue-devtools插件
Jul 10 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中截取中文字符串的代码小结
2011/07/17 PHP
微博短链接算法php版本实现代码
2012/09/15 PHP
PHP赋值的内部是如何跑的详解
2019/01/13 PHP
JQuery 国际象棋棋盘 实现代码
2009/06/26 Javascript
JavaScript学习笔记(二) js对象
2011/10/25 Javascript
JavaScript中的anchor()方法使用详解
2015/06/08 Javascript
详解AngularJS中的filter过滤器用法
2016/01/04 Javascript
Node.js 数据加密传输浅析
2016/11/16 Javascript
利用JS判断鼠标移入元素的方向
2016/12/11 Javascript
EasyUI实现下拉框多选功能
2017/11/07 Javascript
react-router 路由切换动画的实现示例
2018/12/03 Javascript
js仿360开机效果
2019/12/26 Javascript
js中位数不足自动补位扩展padLeft、padRight实现代码
2020/04/06 Javascript
vue相同路由跳转强制刷新该路由组件操作
2020/08/05 Javascript
如何实现vue的tree组件
2020/12/03 Vue.js
JavaScript实现点击自制菜单效果
2021/02/02 Javascript
[15:41]教你分分钟做大人——灰烬之灵
2015/03/11 DOTA
[30:37]【全国守擂赛】第三周擂主赛 Dark Knight vs. Leopard Gaming
2020/05/04 DOTA
python网络编程之TCP通信实例和socketserver框架使用例子
2014/04/25 Python
pymongo实现控制mongodb中数字字段做加法的方法
2015/03/26 Python
Python实现二叉搜索树
2016/02/03 Python
使用python中的in ,not in来检查元素是不是在列表中的方法
2018/07/06 Python
python format 格式化输出方法
2018/07/16 Python
Flask框架Jinjia模板常用语法总结
2018/07/19 Python
PyQt5实现类似别踩白块游戏
2019/01/24 Python
Pycharm插件(Grep Console)自定义规则输出颜色日志的方法
2020/05/27 Python
HTML5中微数据概述及在搜索引擎中的使用举例
2013/02/07 HTML / CSS
HTML5页面中尝试调起APP功能
2017/09/12 HTML / CSS
上海奥佳笔试题面试题
2016/11/16 面试题
党在我心中演讲稿
2014/09/02 职场文书
项目经理岗位职责
2015/01/31 职场文书
2015年感恩母亲节的演讲稿
2015/03/18 职场文书
2015年环境整治工作总结
2015/05/22 职场文书
岁月神偷观后感
2015/06/11 职场文书
听证会主持词
2015/07/03 职场文书
三严三实学习心得体会(精选N篇)
2016/01/05 职场文书