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
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>
|