index.jshtml
import Notification from './src/main.js'; export default Notification;
src/main.js vue
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.vueelement-ui
<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>