|
|
|
<template>
|
|
|
|
<transition name="fade">
|
|
|
|
<div v-if="visible" :class="['position-message', type, position]" id="qq" :style="positionStyle">
|
|
|
|
<i :class="iconClass"></i>
|
|
|
|
<span>{{ message }}</span>
|
|
|
|
</div>
|
|
|
|
</transition>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
export default {
|
|
|
|
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: 10
|
|
|
|
}
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
visible: false,
|
|
|
|
positionStyle: {}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
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(); // 确保 DOM 更新后计算
|
|
|
|
});
|
|
|
|
console.log('zoulma???????', this.visible);
|
|
|
|
return this // 支持链式调用
|
|
|
|
},
|
|
|
|
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;
|
|
|
|
const viewportWidth = window.innerWidth;
|
|
|
|
const messageHeight = this.$el.offsetHeight;
|
|
|
|
|
|
|
|
// 默认居中位置
|
|
|
|
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(this.position == 'top'){
|
|
|
|
top =`${targetRect.top + window.scrollY - this.offset - messageHeight}px`
|
|
|
|
}else{
|
|
|
|
top = `${targetRect.top + window.scrollY + this.offset + messageHeight}px`
|
|
|
|
}
|
|
|
|
this.positionStyle = {
|
|
|
|
top,
|
|
|
|
left: `${left}px`,
|
|
|
|
right: 'auto', // 清除 right 定位
|
|
|
|
};
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
.position-message {
|
|
|
|
position: absolute;
|
|
|
|
min-width: 100px;
|
|
|
|
padding: 8px 12px;
|
|
|
|
border-radius: 4px;
|
|
|
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
|
z-index: 9999;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
background: red;
|
|
|
|
}
|
|
|
|
|
|
|
|
.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;
|
|
|
|
}
|
|
|
|
|
|
|
|
.fade-enter-active,
|
|
|
|
.fade-leave-active {
|
|
|
|
transition: opacity 0.3s;
|
|
|
|
}
|
|
|
|
|
|
|
|
.fade-enter,
|
|
|
|
.fade-leave-to {
|
|
|
|
opacity: 0;
|
|
|
|
}
|
|
|
|
</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> -->
|