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实现对ajax请求面向对象的封装
Jan 08 Javascript
jQuery插件开发精品教程让你的jQuery提升一个台阶
Jan 27 Javascript
JS功能代码集锦
May 04 Javascript
Jquery和Js获得元素标签名称的方法总结
Oct 08 Javascript
纯JavaScript手写图片轮播代码
Oct 20 Javascript
JS实现颜色动态淡化效果
Mar 06 Javascript
JS自动生成动态HTML验证码页面
Jun 14 Javascript
jquery鼠标悬停导航下划线滑出效果
Sep 29 jQuery
vue input输入框模糊查询的示例代码
May 22 Javascript
使用jQuery实现购物车
Oct 29 jQuery
jQuery实现可以扩展的日历
Dec 01 jQuery
基于javascript实现放大镜特效
Dec 03 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实现ping
2006/10/09 PHP
一个简单的自动发送邮件系统(三)
2006/10/09 PHP
PHP初学入门
2006/11/19 PHP
php中jpgraph类库的使用介绍
2013/08/08 PHP
php二维数组排序方法(array_multisort usort)
2013/12/25 PHP
PHP简单实现断点续传下载的方法
2015/09/25 PHP
详解关于php的xdebug配置(编辑器vscode)
2019/01/29 PHP
js拼接html注意问题示例探讨
2014/07/14 Javascript
jQuery遍历之next()、nextAll()方法使用实例
2014/11/08 Javascript
JS实现仿腾讯微博无刷新删除微博效果代码
2015/10/16 Javascript
JavaScript DOM 学习总结(五)
2015/11/24 Javascript
JavaScript对象数组排序实例方法浅析
2016/06/15 Javascript
jQuery新窗口打开外链接
2016/07/21 Javascript
微信小程序(应用号)开发新闻客户端实例
2016/10/24 Javascript
关于定制FileField中的上传文件名称问题
2017/08/22 Javascript
ES6中Array.copyWithin()函数的用法实例详解
2017/09/16 Javascript
详解webpack+express多页站点开发
2017/12/22 Javascript
fullpage.js最后一屏滚动方式
2018/02/06 Javascript
详解KOA2如何手写中间件(装饰器模式)
2018/10/11 Javascript
vue使用v-if v-show页面闪烁,div闪现的解决方法
2018/10/12 Javascript
在 Vue.js中优雅地使用全局事件的方法
2019/02/01 Javascript
Python使用Flask框架获取当前查询参数的方法
2015/03/21 Python
python开发环境PyScripter中文乱码问题解决方案
2016/09/11 Python
Django中STATIC_ROOT和STATIC_URL及STATICFILES_DIRS浅析
2018/05/08 Python
Python实现的爬虫刷回复功能示例
2018/06/07 Python
pycharm 解除默认unittest模式的方法
2018/11/30 Python
Python WEB应用部署的实现方法
2019/01/02 Python
解决Django migrate不能发现app.models的表问题
2019/08/31 Python
python3 求约数的实例
2019/12/05 Python
python实现logistic分类算法代码
2020/02/28 Python
在python里创建一个任务(Task)实例
2020/04/25 Python
解决python虚拟环境切换无效的问题
2020/04/30 Python
西班牙购买行李箱和背包网站:Maletas Greenwich
2019/10/08 全球购物
如何用Python来进行查询和替换一个文本字符串
2014/01/02 面试题
结婚典礼证婚词
2014/01/11 职场文书
大四毕业生自荐书
2014/07/05 职场文书