element notification源码
index.js
import Notification from './src/main.js'; export default Notification;
src/main.js
import Vue from 'vue'; import Main from './main.vue'; import { PopupManager } from 'element-ui/src/utils/popup'; import { isVNode } from 'element-ui/src/utils/vdom'; const NotificationConstructor = Vue.extend(Main); let instance; let instances = []; let seed = 1; const Notification = function (options) { if (Vue.prototype.$isServer) return; options = options || {}; // 自定义关闭 const userOnClose = options.onClose; const id = 'notification_' + seed++; // 位置 const position = options.position || 'top-right'; // 关闭事件 options.onClose = function () { Notification.close(id, userOnClose); }; // 创建notification实例 instance = new NotificationConstructor({ data: options }); if (isVNode(options.message)) { instance.$slots.default = [options.message]; options.message = 'REPLACED_BY_VNODE'; } instance.id = id; // 挂载 instance.$mount(); // 添加到body document.body.appendChild(instance.$el); // 显示 instance.visible = true; // 设置dom instance.dom = instance.$el; // 设置z-index instance.dom.style.zIndex = PopupManager.nextZIndex(); // 偏移量 let verticalOffset = options.offset || 0; // 过滤查找,相同位置的notification,每次增加(自身高度+16),避免覆盖 instances.filter(item => item.position === position).forEach(item => { verticalOffset += item.$el.offsetHeight + 16; }); // 最后一个也加16 verticalOffset += 16; // 设置为当前的偏移量 instance.verticalOffset = verticalOffset; // 放入数组 instances.push(instance); // 返回 return instance; }; // 循环类型为这4个中类型 ['success', 'warning', 'info', 'error'].forEach(type => { // 给Notification增加这几种类型函数 Notification[type] = options => { // 如果是字符串,转变为对象 if (typeof options === 'string' || isVNode(options)) { options = { message: options }; } // 设置type类型 options.type = type; return Notification(options); }; }); // 关闭 Notification.close = function (id, userOnClose) { let index = -1; const len = instances.length; // 过滤,查找id const instance = instances.filter((instance, i) => { if (instance.id === id) { index = i; return true; } return false; })[0]; // 不存在,返回 if (!instance) return; // 找到后有自定义关闭,执行 if (typeof userOnClose === 'function') { userOnClose(instance); } // 删除 instances.splice(index, 1); if (len <= 1) return; // 记录instance位置 const position = instance.position; // 记录instance高度 const removedHeight = instance.dom.offsetHeight; // 循环查找位置 for (let i = index; i < len - 1; i++) { if (instances[i].position === position) { // 对应的位置减去(本身高度+16) instances[i].dom.style[instance.verticalProperty] = parseInt(instances[i].dom.style[instance.verticalProperty], 10) - removedHeight - 16 + 'px'; } } }; // 全部关闭 Notification.closeAll = function () { for (let i = instances.length - 1; i >= 0; i--) { instances[i].close(); } }; export default Notification;
src/main.vue
<template> <transition name="el-notification-fade"> <div :class="['el-notification', customClass, horizontalClass]" v-show="visible" :style="positionStyle" @mouseenter="clearTimer()" @mouseleave="startTimer()" @click="click" role="alert" > <i class="el-notification__icon" :class="[ typeClass, iconClass ]" v-if="type || iconClass"> </i> <div class="el-notification__group" :class="{ 'is-with-icon': typeClass || iconClass }"> <h2 class="el-notification__title" v-text="title"></h2> <div class="el-notification__content" v-show="message"> <slot> <p v-if="!dangerouslyUseHTMLString">{{ message }}</p> <p v-else v-html="message"></p> </slot> </div> <div class="el-notification__closeBtn el-icon-close" v-if="showClose" @click.stop="close"></div> </div> </div> </transition> </template> <script type="text/babel"> let typeMap = { success: 'success', info: 'info', warning: 'warning', error: 'error' }; export default { data() { return { visible: false,//是否显示 title: '',// 标题 message: '',//说明文字 duration: 4500,//显示时间, 毫秒。设为 0 则不会自动关闭 type: '',// 主题样式,如果不在可选值内将被忽略 showClose: true,//是否显示关闭按钮 customClass: '',//自定义类名 iconClass: '',//自定义图标的类名。若设置了 type,则 iconClass 会被覆盖 onClose: null,//关闭时的回调函数 onClick: null,//点击 Notification 时的回调函数 closed: false,//是否关闭 verticalOffset: 0,//偏移的距离,在同一时刻,所有的 Notification 实例应当具有一个相同的偏移量 timer: null,//定时器 dangerouslyUseHTMLString: false,//是否将 message 属性作为 HTML 片段处理 position: 'top-right'//位置 }; }, computed: { // type类 typeClass() { return this.type && typeMap[this.type] ? `el-icon-${ typeMap[this.type] }` : ''; }, // 水平方向上的类 horizontalClass() { return this.position.indexOf('right') > -1 ? 'right' : 'left'; }, // 垂直方向上的类名 verticalProperty() { return /^top-/.test(this.position) ? 'top' : 'bottom'; }, // 上下的偏移量 positionStyle() { return { [this.verticalProperty]: `${ this.verticalOffset }px` }; } }, watch: { // 监听是否关闭 closed(newVal) { // 关闭 if (newVal) { this.visible = false; // 添加transitionend事件 this.$el.addEventListener('transitionend', this.destroyElement); } } }, methods: { destroyElement() { // 移除transitionend事件 this.$el.removeEventListener('transitionend', this.destroyElement); this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, // 点击事件 click() { // 如果设置了点击的回调,则执行 if (typeof this.onClick === 'function') { this.onClick(); } }, // 关闭事件 close() { this.closed = true; // 如果设置了点击关闭的回调,则执行 if (typeof this.onClose === 'function') { this.onClose(); } }, // 清除定时器 clearTimer() { clearTimeout(this.timer); }, // 开启定时器 startTimer() { if (this.duration > 0) { this.timer = setTimeout(() => { if (!this.closed) { this.close(); } }, this.duration); } }, // 键盘按下事件 keydown(e) { if (e.keyCode === 46 || e.keyCode === 8) { this.clearTimer(); // detele 取消倒计时 } else if (e.keyCode === 27) { // esc关闭消息 if (!this.closed) { this.close(); } } else { this.startTimer(); // 恢复倒计时 } } }, mounted() { // 默认开启定时器 if (this.duration > 0) { this.timer = setTimeout(() => { if (!this.closed) { this.close(); } }, this.duration); } // 增加监听 document.addEventListener('keydown', this.keydown); }, beforeDestroy() { // 移除 document.removeEventListener('keydown', this.keydown); } }; </script>