前提回顾
- 在项目开发中我们经常使用的组件注册分为两种,一个是全局注册和另一个是局部注册,假设我们的业务场景是用户在浏览注册页面时,点击页面中的注册按钮后,前端根据用户的注册信息先做一次简单的验证,并根据验证弹出一个对应消息提示弹框
- 我们拿到这个需求后,便开始着手准备要通过局部注册消息弹框组件的方法来实现这个场景,在通过局部注册消息弹框组件的方法解决完这个需求后,自然是沾沾自喜,紧接着又迎来了一个需求,该需求是用户在点击该注册按钮时,点击几次就要出现几次这个消息弹框,你开始犯了难,并思考难道我要在页面中提前插入n个组件标签,不然我怎么知道用户要点击几次注册按钮?
- 在你还没有解决第二个需求的时候,又一个需求来了,第三个需求是不仅仅是注册页面需要用到这个消息弹框组件,在其他多个页面中也需要用到这个消息弹框组件。
- 基于上述的业务需求,我们可以通过vue.extend编程式的使用组件,从而实现功能性的动态的消息提示弹框
局部注册消息弹框组件
- 先通过局部注册的方法来实现消息弹框组件
- 效果图如下:

- 构造目录如下:

- 'src/main.js'文件的代码:
import Vue from 'vue'
import App from './App.vue'
import './assets/css.css';
Vue.config.productionTip = false
new Vue({
render: h => h(App)
}).$mount('#app')
import vue from 'vue';
var bus=new vue()
export default bus;
<template>
<div id="app">
<button @click="handleShowMessage">点击出现弹框</button>
<TMessage :offsetTop='50'></TMessage>
<TMessage :offsetTop='100'></TMessage>
<TMessage :offsetTop='150'></TMessage>
</div>
</template>
<script>
import TMessage from './components/TMessage/TMessage.vue';
import bus from './bus/bus';
export default {
name:'app',
data() {
return {
}
},
components: {
TMessage,
},
methods: {
handleShowMessage(){
console.log(TMessage);
bus.$emit('showMessage')
}
},
}
</script>
<style>
#app {
display: flex;
justify-content: center;
}
#app button{
margin-top: 250px;
}
</style>
- 'src/components/TMessage/TMessage.vue'文件的代码:
<template>
<transition name="message-fade">
<div :class="[
'message',
'message-' + type,
center ? 'is-center' : ''
]"
:style="{top: offset + 'px'}"
v-if="!closed"
>
<p class="message-content">提示信息:{{message}}</p>
<i class="icon icon-close"></i>
</div>
</transition>
</template>
<script>
export default {
name: 'TMessage',
data() {
return {
message: '这是默认信息',
type: 'success',
center: true,
offset: 20,
closed: true,
duration: 1000,
timer: null,
}
},
mounted() {
this.offset=this.offsetTop
bus.$on('showMessage',()=>{
this.closed=false;
this.timer = setTimeout(() => {
if (!this.closed) {
this.close();
}
}, this.duration);
})
},
props:['offsetTop'],
methods: {
close() {
this.closed = true;
}
}
}
</script>
- 写到这里,我们实现的效果为(动图如下):

- 'src/assets/css.css'文件的代码:
.message {
min-width: 380px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
border-radius: 4px;
border-width: 1px;
border-style: solid;
border-color: #EBEEF5;
position: fixed;
left: 50%;
top: 20px;
z-index: 999999999;
transform: translateX(-50%);
background-color: #edf2fc;
transition: opacity .3s, transform .4s, top .4s;
overflow: hidden;
padding: 15px 15px 15px 20px;
display: flex;
align-items: center
}
.message.is-center {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center
}
.message p {
margin: 0
}
.message-info .message-content {
color: #909399
}
.message-success {
background-color: #f0f9eb;
border-color: #e1f3d8
}
.message-success .message-content {
color: #67C23A
}
.message-warning {
background-color: #fdf6ec;
border-color: #faecd8
}
.message-warning .message-content {
color: #E6A23C
}
.message-error {
background-color: #fef0f0;
border-color: #fde2e2
}
.message-error .message-content {
color: #F56C6C
}
.message-content {
padding: 0;
font-size: 14px;
line-height: 1
}
.message-content:focus {
outline-width: 0
}
.message .icon-close {
position: absolute;
top: 50%;
right: 15px;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
cursor: pointer;
color: #C0C4CC;
font-size: 16px
}
.message .icon-close:focus {
outline-width: 0
}
.message .icon-close:hover {
color: #909399
}
.message-fade-enter, .message-fade-leave-active {
opacity: 0;
transform: translate(-50%, -100%)
}
编程式的使用组件
- 上方在通过局部注册消息弹框组件时体现的局限性:灵活性低、可复用性低、代码观感较差
- 紧接着我们就要使用vue.extend来实现消息提示弹框,做到编程式的使用组件
- 该构造目录为:

- 'src/App.vue'文件的代码:
<template>
<div id="app">
<button @click="handleShowMessage">点击出现弹框</button>
</div>
</template>
<script>
import Message from './components/TMessage/TMessage.js';
export default {
name:'app',
data() {
return {
}
},
methods: {
handleShowMessage(){
return Message('我好帅啊我好帅啊我好帅啊')
}
},
}
</script>
<style>
#app {
display: flex;
justify-content: center;
}
#app button{
margin-top: 250px;
}
</style>
- 'src/components/TMessage/TMessage.vue'文件的代码:
<template>
<transition name="message-fade">
<div :class="[
'message',
'message-' + type,
center ? 'is-center' : ''
]"
:style="{top: offset + 'px'}"
v-if="!closed"
>
<p class="message-content">提示信息:{{message}}</p>
<i class="icon icon-close"></i>
</div>
</transition>
</template>
<script>
export default {
name: 'TMessage',
data() {
return {
message: '这是默认信息',
type: 'success',
center: true,
offset: 20,
closed: false,
duration: 1000,
timer: null,
}
},
mounted() {
},
methods: {
close() {
this.closed = true;
}
}
}
</script>
- 'src/components/TMessage/TMessage.js'文件的代码:
import Vue from 'vue';
import TMessage from "./TMessage.vue";
function Message(data) {
data = data || {};
if (typeof data === 'string') {
data = {
message: data
}
}
const TMessageClass = Vue.extend(TMessage);
let instance = new TMessageClass({
data
});
instance.$mount();
console.log(instance.$el,'现在才可以访问$el');
document.body.appendChild(instance.$el);
}
export default Message
- 写到这里,我们来看一下效果,如下图:

解决消息弹框覆盖问题
- 我们已经做到了每点击一次按钮就出现一个消息弹框组件,但是因为定位的问题出现了相互覆盖,所以得再接着去'TMessage.js'文件中去完善逻辑:
import Vue from 'vue';
import TMessage from "./TMessage.vue";
let instances = [];
function Message(data) {
data = data || {};
if (typeof data === 'string') {
data = {
message: data
}
}
data.onClose = function() {
console.log('onClose');
console.log(instance,'instance');
Message.close(instance);
};
const TMessageClass = Vue.extend(TMessage);
let instance = new TMessageClass({
data
});
instance.$mount();
document.body.appendChild(instance.$el);
let offset = data.offset || 20;
let offsetTop = offset;
instances.forEach( item => {
offsetTop += item.$el.offsetHeight + offset;
});
instance.$el.style.top = offsetTop + 'px';
instances.push(instance);
}
Message.close = function(instance) {
instances = instances.filter( item => item !== instance );
};
export default Message
- 'src/components/TMessage/TMessage.vue'文件的代码:
<template>
<transition name="message-fade">
<div :class="[
'message',
'message-' + type,
center ? 'is-center' : ''
]"
:style="{top: offset + 'px'}"
v-if="!closed"
>
<p class="message-content">提示信息:{{message}}</p>
<i class="icon icon-close"></i>
</div>
</transition>
</template>
<script>
export default {
name: 'TMessage',
data() {
return {
message: '这是默认信息',
type: 'success',
center: true,
offset: 20,
closed: false,
duration: 1000,
timer: null,
onClose: null
}
},
mounted() {
this.timer = setTimeout(() => {
if (!this.closed) {
this.close();
}
}, this.duration);
},
methods: {
close() {
this.closed = true;
if (typeof this.onClose === 'function') {
this.onClose();
}
}
}
}
</script>
- 写到这里,我们来看一下效果,如下图:

优化消息弹框消失的效果
- 我们可以进一步的优化消息弹框消失的效果,效果图如下:

- 'src/components/TMessage/TMessage.vue'文件的代码:
<template>
<transition name="message-fade">
<div :class="[
'message',
'message-' + type,
center ? 'is-center' : ''
]"
:style="{top: offset + 'px'}"
v-if="!closed"
>
<p class="message-content">提示信息:{{message}}</p>
<i class="icon icon-close"></i>
</div>
</transition>
</template>
<script>
export default {
name: 'TMessage',
data() {
return {
message: '这是默认信息',
type: 'success',
center: true,
offset: 20,
closed: false,
duration: 1000,
timer: null,
onClose: null
}
},
mounted() {
this.timer = setTimeout(() => {
if (!this.closed) {
this.close();
}
}, this.duration);
},
methods: {
close() {
this.closed = true;
if (typeof this.onClose === 'function') {
this.onClose();
}
}
}
}
</script>
- 'src/components/TMessage/TMessage.js'文件的代码:
import Vue from 'vue';
import TMessage from "./TMessage.vue";
let instances = [];
function Message(data) {
data = data || {};
if (typeof data === 'string') {
data = {
message: data
}
}
const TMessageClass = Vue.extend(TMessage);
let instance = new TMessageClass({
data
});
instance.$mount();
document.body.appendChild(instance.$el);
data.onClose = function() {
console.log('onClose');
Message.close(instance);
};
let offset = data.offset || 20;
let offsetTop = offset;
instances.forEach( item => {
offsetTop += item.$el.offsetHeight + offset;
});
instance.$el.style.top = offsetTop + 'px';
instances.push(instance);
}
Message.close = function(instance) {
let removeHeight = instance.$el.offsetHeight + instance.offset;
let index = instances.findIndex( item => item === instance );
instances = instances.filter( item => item !== instance );
for (let i = index; i<instances.length; i++) {
instances[i].$el.style.top = parseFloat(instances[i].$el.style.top) - removeHeight + 'px';
}
};
export default Message
<template>
<div id="app">
<button @click="handleShowMessage">点击出现弹框</button>
</div>
</template>
<script>
import Message from './components/TMessage/TMessage.js';
export default {
name:'app',
data() {
return {
}
},
methods: {
handleShowMessage(){
return Message('我好帅啊我好帅啊我好帅啊')
}
},
}
</script>
<style>
#app {
display: flex;
justify-content: center;
}
#app button{
margin-top: 250px;
}
</style>
终极版实现版
- 我们在上方'src/App.vue'文件中是通过引入
TMessage.js后再通过Message()的方式调用使用该组件的,还可以将调用方式挂载到Vue全局上,来看看怎么操作:
- 'src/main.js'文件的代码
import Vue from 'vue'
import App from './App.vue'
import './assets/css.css';
import Message from '../src/components/TMessage/TMessage';
Vue.config.productionTip = false
Vue.prototype.$message = Message;
new Vue({
render: h => h(App)
}).$mount('#app')
<template>
<div id="app">
<button @click="handleShowMessage">点击出现弹框</button>
</div>
</template>
<script>
export default {
name:'app',
data() {
return {
}
},
methods: {
handleShowMessage(){
this.$message.error('我好帅啊我好帅啊我好帅啊')
this.$message.success('我好帅啊我好帅啊我好帅啊')
this.$message.info('我好帅啊我好帅啊我好帅啊')
this.$message.warning('我好帅啊我好帅啊我好帅啊')
}
},
}
</script>
<style>
#app {
display: flex;
justify-content: center;
}
#app button{
margin-top: 250px;
}
</style>
- 'src/components/TMessage/TMessage.vue'文件的代码:
<template>
<transition name="message-fade">
<div :class="[
'message',
'message-' + type,
center ? 'is-center' : ''
]"
:style="{top: offset + 'px'}"
v-if="!closed"
>
<p class="message-content">提示信息:{{message}}</p>
<i class="icon icon-close"></i>
</div>
</transition>
</template>
<script>
export default {
name: 'TMessage',
data() {
return {
message: '这是默认信息',
type: 'success',
center: true,
offset: 20,
closed: false,
duration: 1000,
timer: null,
onClose: null
}
},
mounted() {
this.timer = setTimeout(() => {
if (!this.closed) {
this.close();
}
}, this.duration);
},
methods: {
close() {
this.closed = true;
if (typeof this.onClose === 'function') {
this.onClose();
}
}
}
}
</script>
- 'src/components/TMessage/TMessage.js'文件的代码:
import Vue from 'vue';
import TMessage from "./TMessage.vue";
let instances = [];
function Message(data) {
data = data || {};
if (typeof data === 'string') {
data = {
message: data
}
}
data.onClose = function() {
console.log('onClose');
Message.close(instance);
};
const TMessageClass = Vue.extend(TMessage);
let instance = new TMessageClass({
data
});
instance.$mount();
document.body.appendChild(instance.$el);
let offset = data.offset || 20;
let offsetTop = offset;
instances.forEach( item => {
offsetTop += item.$el.offsetHeight + offset;
});
instance.$el.style.top = offsetTop + 'px';
instances.push(instance);
}
Message.close = function(instance) {
let removeHeight = instance.$el.offsetHeight + instance.offset;
let index = instances.findIndex( item => item === instance );
instances = instances.filter( item => item !== instance );
for (let i = index; i<instances.length; i++) {
instances[i].$el.style.top = parseFloat(instances[i].$el.style.top) - removeHeight + 'px';
}
};
['info', 'success', 'error', 'warning'].forEach( type => {
Message[type] = function(data) {
if (typeof data === 'string') {
data = {
message: data
}
}
data.type = type;
return Message(data);
};
} );
export default Message
- 完结撒花,最后来看一下效果图:
