You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

221 lines
6.3 KiB

<template>
<transition name="fade">
<div v-if="visible" :class="['position-message', type, position]" :style="positionStyle">
<img :src="defaultImages[type]" class="message-image" />
<span>{{ currentMessage }}</span>
</div>
</transition>
</template>
<script>
export default {
props: {
type: {
type: String,
default: 'info',
validator: val => ['info', 'success', 'error', 'warning'].includes(val)
},
position: {
type: String,
default: 'top',
validator: val => ['top', 'bottom'].includes(val)
},
message: String,
target: null,
offset: {
type: Number,
default: 10
},
duration: {
type: Number,
default: 2000
}
},
data() {
return {
visible: false,
positionStyle: {},
currentMessage: this.message, // 使用内部数据存储message
defaultImages: {
success: require('@/assets/message_Success.png'),
info: require('@/assets/message_Warning.png'),
warning: require('@/assets/message_Warning.png'),
error: require('@/assets/message_error.png')
},
timer: null
}
},
watch: {
// 监听props的message变化
message(newVal) {
this.currentMessage = newVal;
this.$nextTick(() => {
this.calculatePosition();
});
}
},
computed: {
iconClass() {
return {
info: 'el-icon-info',
success: 'el-icon-success',
error: 'el-icon-error'
}[this.type]
}
},
methods: {
show() {
this.visible = true
this.$nextTick(() => {
this.calculatePosition();
// 设置自动隐藏计时器(duration为0表示一直显示)
if (this.duration > 0) {
this.clearTimer();
this.timer = setTimeout(() => {
this.hide();
}, this.duration);
}
});
return this;
},
hide() {
this.visible = false;
this.clearTimer();
return this;
},
updateMessage(newMessage) {
this.currentMessage = newMessage;
this.$nextTick(() => {
this.calculatePosition();
});
return this;
},
updateConfig(newConfig) {
// 更新多个配置项
if (newConfig.message !== undefined) {
this.currentMessage = newConfig.message;
}
if (newConfig.type) {
this.$emit('update:type', newConfig.type);
}
if (newConfig.position) {
this.$emit('update:position', newConfig.position);
}
this.$nextTick(() => {
this.calculatePosition();
});
return this;
},
destroy() {
this.hide();
// 等待动画完成后移除DOM
setTimeout(() => {
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
this.$destroy();
}, 300);
},
clearTimer() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
},
calculatePosition() {
if (!this.$el || !this.visible) return;
const targetEl = typeof this.target === 'string'
? document.querySelector(this.target)
: this.target?.$el || this.target;
if (!targetEl?.getBoundingClientRect) {
console.error('Invalid target:', this.target);
return;
}
const targetRect = targetEl.getBoundingClientRect();
const messageWidth = this.$el.offsetWidth;
const viewportWidth = window.innerWidth;
const messageHeight = this.$el.offsetHeight;
const scrollY = window.scrollY || window.pageYOffset;
// 默认居中位置
let left = targetRect.left + targetRect.width / 2 - messageWidth / 2;
let top = 0;
// 边界检测
if (left < 5) {
left = 5; // 左侧留一点边距
} else if (left + messageWidth > viewportWidth - 5) {
left = viewportWidth - messageWidth - 5; // 右侧留一点边距
}
if (this.position === 'top') {
top = targetRect.top + scrollY - this.offset - messageHeight;
// 如果上方空间不足,则显示在下方
if (top < scrollY) {
top = targetRect.bottom + scrollY + this.offset;
}
} else {
top = targetRect.bottom + scrollY + this.offset;
// 如果下方超出可视区域,则显示在上方
if (top + messageHeight > scrollY + window.innerHeight) {
top = targetRect.top + scrollY - this.offset - messageHeight;
}
}
this.positionStyle = {
top: `${top}px`,
left: `${left}px`,
right: 'auto'
};
},
},
beforeDestroy() {
this.clearTimer();
}
}
</script>
<style scoped lang="scss">
.position-message {
position: absolute;
min-width: 100px;
padding: 14px 21px;
border-radius: 4px;
z-index: 9999;
display: flex;
align-items: center;
background: #FFFFFF;
box-sizing: border-box;
box-shadow: 0px 3px 8px 0px rgba(0, 0, 0, 0.16);
pointer-events: none;
span {
letter-spacing: 0.08em;
color: #626573;
white-space: nowrap;
}
}
.message-image {
width: 18px;
height: 18px;
margin-right: 10px;
flex-shrink: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s, transform 0.3s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
transform: translateY(-10px);
}
</style>