|
|
@ -1,9 +1,8 @@ |
|
|
|
<template> |
|
|
|
<transition name="fade"> |
|
|
|
<div v-if="visible" :class="['position-message', type, position]" id="qq" :style="positionStyle"> |
|
|
|
<!-- <i :class="iconClass"></i>/ --> |
|
|
|
<div v-if="visible" :class="['position-message', type, position]" :style="positionStyle"> |
|
|
|
<img :src="defaultImages[type]" class="message-image" /> |
|
|
|
<span>{{ message }}</span> |
|
|
|
<span>{{ currentMessage }}</span> |
|
|
|
</div> |
|
|
|
</transition> |
|
|
|
</template> |
|
|
@ -14,7 +13,7 @@ export default { |
|
|
|
type: { |
|
|
|
type: String, |
|
|
|
default: 'info', |
|
|
|
validator: val => ['info', 'success', 'error'].includes(val) |
|
|
|
validator: val => ['info', 'success', 'error', 'warning'].includes(val) |
|
|
|
}, |
|
|
|
position: { |
|
|
|
type: String, |
|
|
@ -22,22 +21,37 @@ export default { |
|
|
|
validator: val => ['top', 'bottom'].includes(val) |
|
|
|
}, |
|
|
|
message: String, |
|
|
|
target: null, // 目标元素/DOM选择器 |
|
|
|
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: { |
|
|
@ -53,15 +67,67 @@ export default { |
|
|
|
show() { |
|
|
|
this.visible = true |
|
|
|
this.$nextTick(() => { |
|
|
|
this.calculatePosition(); // 确保 DOM 更新后计算 |
|
|
|
this.calculatePosition(); |
|
|
|
|
|
|
|
// 设置自动隐藏计时器(duration为0表示一直显示) |
|
|
|
if (this.duration > 0) { |
|
|
|
this.clearTimer(); |
|
|
|
this.timer = setTimeout(() => { |
|
|
|
this.hide(); |
|
|
|
}, this.duration); |
|
|
|
} |
|
|
|
}); |
|
|
|
console.log('zoulma???????', this.visible); |
|
|
|
return this // 支持链式调用 |
|
|
|
|
|
|
|
return this; |
|
|
|
}, |
|
|
|
hide() { |
|
|
|
this.visible = false |
|
|
|
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; |
|
|
@ -75,28 +141,43 @@ export default { |
|
|
|
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 < 0) { |
|
|
|
left = targetRect.left; // 左侧贴边 |
|
|
|
} else if (left + messageWidth > viewportWidth) { |
|
|
|
left = viewportWidth - messageWidth - (viewportWidth -targetRect.right); // 右侧贴边 |
|
|
|
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; |
|
|
|
} |
|
|
|
if(this.position == 'top'){ |
|
|
|
top =`${targetRect.top + window.scrollY - this.offset - messageHeight}px` |
|
|
|
} else { |
|
|
|
top = `${targetRect.top + window.scrollY + this.offset + messageHeight}px` |
|
|
|
top = targetRect.bottom + scrollY + this.offset; |
|
|
|
// 如果下方超出可视区域,则显示在上方 |
|
|
|
if (top + messageHeight > scrollY + window.innerHeight) { |
|
|
|
top = targetRect.top + scrollY - this.offset - messageHeight; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this.positionStyle = { |
|
|
|
top, |
|
|
|
top: `${top}px`, |
|
|
|
left: `${left}px`, |
|
|
|
right: 'auto', // 清除 right 定位 |
|
|
|
right: 'auto' |
|
|
|
}; |
|
|
|
}, |
|
|
|
}, |
|
|
|
beforeDestroy() { |
|
|
|
this.clearTimer(); |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |
|
|
|
|
|
|
@ -112,133 +193,29 @@ export default { |
|
|
|
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; |
|
|
|
} |
|
|
|
.position-message i { |
|
|
|
margin-right: 6px; |
|
|
|
font-size: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.position-message.info { |
|
|
|
/* background-color: #f4f4f5; |
|
|
|
color: #909399; |
|
|
|
border: 1px solid #e9e9eb; */ |
|
|
|
} |
|
|
|
|
|
|
|
.position-message.success { |
|
|
|
/* background-color: #f0f9eb; |
|
|
|
color: #67c23a; |
|
|
|
border: 1px solid #e1f3d8; */ |
|
|
|
} |
|
|
|
|
|
|
|
.position-message.error { |
|
|
|
/* background-color: #fef0f0; |
|
|
|
color: #f56c6c; |
|
|
|
border: 1px solid #fde2e2; */ |
|
|
|
flex-shrink: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.fade-enter-active, |
|
|
|
.fade-leave-active { |
|
|
|
transition: opacity 0.3s; |
|
|
|
transition: opacity 0.3s, transform 0.3s; |
|
|
|
} |
|
|
|
|
|
|
|
.fade-enter, |
|
|
|
.fade-leave-to { |
|
|
|
opacity: 0; |
|
|
|
transform: translateY(-10px); |
|
|
|
} |
|
|
|
</style> |
|
|
|
|
|
|
|
<!-- <template> |
|
|
|
<transition name="fade"> |
|
|
|
<div |
|
|
|
v-if="visible" |
|
|
|
:class="['position-message', type]" |
|
|
|
:style="positionStyle" |
|
|
|
> |
|
|
|
<i :class="iconClass"></i> |
|
|
|
<span>{{ message }}</span> |
|
|
|
</div> |
|
|
|
</transition> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
export default { |
|
|
|
computed: { |
|
|
|
iconClass() { |
|
|
|
return { |
|
|
|
info: 'el-icon-info', |
|
|
|
success: 'el-icon-success', |
|
|
|
error: 'el-icon-error' |
|
|
|
}[this.type] |
|
|
|
} |
|
|
|
}, |
|
|
|
props: { |
|
|
|
type: { |
|
|
|
type: String, |
|
|
|
default: 'info', |
|
|
|
validator: val => ['info', 'success', 'error'].includes(val) |
|
|
|
}, |
|
|
|
position: { |
|
|
|
type: String, |
|
|
|
default: 'top', |
|
|
|
validator: val => ['top', 'bottom'].includes(val) |
|
|
|
}, |
|
|
|
message: String, |
|
|
|
target: null, // 目标元素/DOM选择器 |
|
|
|
offset: { |
|
|
|
type: Number, |
|
|
|
default: 5 |
|
|
|
} |
|
|
|
}, |
|
|
|
data() { |
|
|
|
return { |
|
|
|
visible: false, |
|
|
|
positionStyle: {} |
|
|
|
} |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
hide() { |
|
|
|
this.visible = false |
|
|
|
}, |
|
|
|
calculatePosition() { |
|
|
|
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; |
|
|
|
|
|
|
|
if (this.position === 'bottom') { |
|
|
|
this.positionStyle = { |
|
|
|
top: `${targetRect.bottom + window.scrollY + this.offset}px`, |
|
|
|
left: `${targetRect.left + targetRect.width / 2 - messageWidth / 2}px`, |
|
|
|
}; |
|
|
|
} else { |
|
|
|
// 默认显示在上方(原逻辑) |
|
|
|
this.positionStyle = { |
|
|
|
top: `${targetRect.top + window.scrollY - this.offset - this.$el.offsetHeight}px`, |
|
|
|
left: `${targetRect.left + targetRect.width / 2 - messageWidth / 2}px`, |
|
|
|
}; |
|
|
|
} |
|
|
|
}, |
|
|
|
show() { |
|
|
|
this.visible = true; |
|
|
|
this.$nextTick(this.calculatePosition); |
|
|
|
return this; |
|
|
|
}, |
|
|
|
}, |
|
|
|
}; |
|
|
|
</script> --> |