
5 changed files with 360 additions and 60 deletions
@ -0,0 +1,222 @@ |
|||
<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; |
|||
|
|||
// 默认居中位置 |
|||
let left = targetRect.left + targetRect.width / 2 - messageWidth / 2; |
|||
// 边界检测 |
|||
if (left < 0) { |
|||
left = targetRect.left; // 左侧贴边 |
|||
} else if (left + messageWidth > viewportWidth) { |
|||
left = viewportWidth - messageWidth - (viewportWidth -targetRect.right); // 右侧贴边 |
|||
} |
|||
|
|||
this.positionStyle = { |
|||
top: `${targetRect.bottom + window.scrollY + this.offset}px`, |
|||
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> --> |
@ -0,0 +1,33 @@ |
|||
import Vue from 'vue' |
|||
import Tooltip from '@/components/Tooltip.vue' |
|||
|
|||
const PositionMessageConstructor = Vue.extend(Tooltip) |
|||
|
|||
// 自动隐藏功能扩展
|
|||
function showMessage(options) { |
|||
const instance = new PositionMessageConstructor({ |
|||
propsData: options |
|||
}).$mount() |
|||
|
|||
document.body.appendChild(instance.$el) |
|||
|
|||
const showInstance = instance.show() |
|||
|
|||
// 添加自动隐藏
|
|||
if (options.duration !== 0) { |
|||
setTimeout(() => { |
|||
showInstance.hide() |
|||
setTimeout(() => { |
|||
document.body.removeChild(instance.$el) |
|||
}, 300) // 等待动画结束
|
|||
}, options.duration || 2000) |
|||
} |
|||
|
|||
return showInstance |
|||
} |
|||
|
|||
export default { |
|||
install(Vue) { |
|||
Vue.prototype.$positionMessage = showMessage |
|||
} |
|||
} |
Loading…
Reference in new issue