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