Vue $mount實(shí)戰(zhàn)之實(shí)現(xiàn)消息彈窗組件
之前的項(xiàng)目一直在使用Element-UI框架,element中的Notification、Message組件使用時(shí)不需要在html寫標(biāo)簽,而是使用js調(diào)用。那時(shí)就很疑惑,為什么element ui使用this.$notify、this.$message就可以實(shí)現(xiàn)這樣的功能?
1、實(shí)現(xiàn)消息彈窗組件的幾個(gè)問(wèn)題
- 如何在任何組件中使用this.$message就可以顯示消息?
- 如何將消息的dom節(jié)點(diǎn)插入到body中?
- 同時(shí)出現(xiàn)多個(gè)消息彈窗時(shí),消息彈窗的z-index如何控制?
2、效果預(yù)覽

3、代碼實(shí)現(xiàn)
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'){
// 調(diào)用onClose方法,以從p-message.js中的instances數(shù)組中移除當(dāng)前組件,不移除的話就占空間了
this.onClose();
}
},
// 銷毀組件
destroyElement(){
_offEvent(this.$el, 'transitionend', this.destroyElement);
// 手動(dòng)銷毀組件
this.$destroy(true);
this.$el.parentNode.removeChild(this.$el);
},
},
watch: {
// 監(jiān)聽(tīng)closed,如果它為true,則銷毀message組件
closed(newVal){
if(newVal){
this.show = false;
// message過(guò)渡完成后再去銷毀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;
// 存儲(chǔ)message組件實(shí)例,如需有關(guān)閉所有message的功能就需要將每個(gè)message組件都存儲(chǔ)起來(lái)
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銷毀時(shí)會(huì)調(diào)用傳遞進(jìn)去的onClose,而onClose的處理就是將指定id的message組件從instances中移除
options.onClose = function (){
Message._close(id, userOnClose);
};
/* 這里傳遞給PMessageControl的data不會(huì)覆蓋PMessage.vue中原有的data,而是與PMessage.vue中原有的data進(jìn)行合并,類似
* 與mixin,包括傳遞methods、生命周期函數(shù)也是一樣 */
let instance = new PMessageControl({
data: options
});
// 傳遞vNode
if(isVNode(instance.message)){
instance.$slots.default = [instance.message];
instance.message = null;
}
instance.id = id;
// 渲染元素,隨后使用原生appendChild將dom插入到頁(yè)面中
instance.$mount();
let $el = instance.$el;
// message彈窗的z-index由popupManager來(lái)提供
$el.style.zIndex = popupManager.getNextZIndex();
document.body.appendChild($el);
// 將message顯示出來(lái)
instance.show = true;
console.log(instance)
instances.push(instance);
return instance;
};
// message簡(jiǎn)化操作
['success','error'].forEach(function (item) {
Message[item] = options => {
if(typeof options === 'string'){
options = {
message: options
}
}
options.type = item;
return Message(options);
}
});
/**
* 從instances刪除指定message,內(nèi)部使用
* @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;
}
}
};
// 關(guān)閉所有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{ /* 使圖標(biāo)與內(nèi)容能夠垂直居中 */
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{ /* 使圖標(biāo)與內(nèi)容能夠垂直居中 */
display: table-cell;
vertical-align: middle;
padding-left: 15px;
}
}
main.js
// 引入pMessage組件 import pMessage from './components/p-message/p-index.js'; // 將pMessage綁定到Vue.prototype中。這樣在組件中就可以通過(guò)this.$pMessage()的形式來(lái)使用了 Vue.prototype.$pMessage = pMessage;
相關(guān)文章
Vue實(shí)現(xiàn)實(shí)時(shí)刷新時(shí)間的功能
這篇文章主要為大家詳細(xì)介紹了如何Vue利用實(shí)現(xiàn)實(shí)時(shí)刷新時(shí)間的功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解下2023-12-12
vue+elementui(對(duì)話框中form表單的reset問(wèn)題)
這篇文章主要介紹了vue+elementui(對(duì)話框中form表單的reset問(wèn)題),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
vue3?實(shí)現(xiàn)右鍵菜單編輯復(fù)制粘貼功能
在瀏覽器中,Vue3編輯器自帶默認(rèn)右鍵菜單,然而,在Electron桌面應(yīng)用中,這一功能需自行編寫代碼實(shí)現(xiàn),本文詳細(xì)介紹了如何在Vue3中手動(dòng)實(shí)現(xiàn)右鍵菜單的編輯、復(fù)制、粘貼功能,并提供了代碼示例,更多細(xì)節(jié)和相關(guān)教程可參考腳本之家的其他文章2024-10-10
vue打包部署到springboot的實(shí)現(xiàn)示例
項(xiàng)目開(kāi)發(fā)中,一般我們都會(huì)使用SpringBoot+Vue進(jìn)行前后端開(kāi)發(fā),本文主要介紹了vue打包部署到springboot的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07
vue實(shí)現(xiàn)同時(shí)設(shè)置多個(gè)倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)同時(shí)設(shè)置多個(gè)倒計(jì)時(shí),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05

