Browse Source

定位提示弹出框改造-可以动态修改文案,手动控制显示隐藏

pull/120/head
zq 1 day ago
parent
commit
9fd57d14f3
  1. 249
      src/components/Tooltip.vue
  2. 49
      src/utils/tooltip.js
  3. 51
      src/views/elementGroups.vue

249
src/components/Tooltip.vue

@ -1,9 +1,8 @@
<template> <template>
<transition name="fade"> <transition name="fade">
<div v-if="visible" :class="['position-message', type, position]" id="qq" :style="positionStyle"> <div v-if="visible" :class="['position-message', type, position]" :style="positionStyle">
<!-- <i :class="iconClass"></i>/ -->
<img :src="defaultImages[type]" class="message-image" /> <img :src="defaultImages[type]" class="message-image" />
<span>{{ message }}</span> <span>{{ currentMessage }}</span>
</div> </div>
</transition> </transition>
</template> </template>
@ -14,7 +13,7 @@ export default {
type: { type: {
type: String, type: String,
default: 'info', default: 'info',
validator: val => ['info', 'success', 'error'].includes(val) validator: val => ['info', 'success', 'error', 'warning'].includes(val)
}, },
position: { position: {
type: String, type: String,
@ -22,22 +21,37 @@ export default {
validator: val => ['top', 'bottom'].includes(val) validator: val => ['top', 'bottom'].includes(val)
}, },
message: String, message: String,
target: null, // /DOM target: null,
offset: { offset: {
type: Number, type: Number,
default: 10 default: 10
},
duration: {
type: Number,
default: 2000
} }
}, },
data() { data() {
return { return {
visible: false, visible: false,
positionStyle: {}, positionStyle: {},
defaultImages:{ currentMessage: this.message, // 使message
defaultImages: {
success: require('@/assets/message_Success.png'), success: require('@/assets/message_Success.png'),
info: require('@/assets/message_Warning.png'), info: require('@/assets/message_Warning.png'),
warning: require('@/assets/message_Warning.png'), warning: require('@/assets/message_Warning.png'),
error: require('@/assets/message_error.png') error: require('@/assets/message_error.png')
} },
timer: null
}
},
watch: {
// propsmessage
message(newVal) {
this.currentMessage = newVal;
this.$nextTick(() => {
this.calculatePosition();
});
} }
}, },
computed: { computed: {
@ -53,15 +67,67 @@ export default {
show() { show() {
this.visible = true this.visible = true
this.$nextTick(() => { this.$nextTick(() => {
this.calculatePosition(); // DOM this.calculatePosition();
// duration0
if (this.duration > 0) {
this.clearTimer();
this.timer = setTimeout(() => {
this.hide();
}, this.duration);
}
}); });
console.log('zoulma???????', this.visible);
return this // return this;
}, },
hide() { 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() { calculatePosition() {
if (!this.$el || !this.visible) return;
const targetEl = typeof this.target === 'string' const targetEl = typeof this.target === 'string'
? document.querySelector(this.target) ? document.querySelector(this.target)
: this.target?.$el || this.target; : this.target?.$el || this.target;
@ -75,28 +141,43 @@ export default {
const messageWidth = this.$el.offsetWidth; const messageWidth = this.$el.offsetWidth;
const viewportWidth = window.innerWidth; const viewportWidth = window.innerWidth;
const messageHeight = this.$el.offsetHeight; const messageHeight = this.$el.offsetHeight;
const scrollY = window.scrollY || window.pageYOffset;
// //
let left = targetRect.left + targetRect.width / 2 - messageWidth / 2; let left = targetRect.left + targetRect.width / 2 - messageWidth / 2;
let top = 0; let top = 0;
// //
if (left < 0) { if (left < 5) {
left = targetRect.left; // left = 5; //
} else if (left + messageWidth > viewportWidth) { } else if (left + messageWidth > viewportWidth - 5) {
left = viewportWidth - messageWidth - (viewportWidth -targetRect.right); // left = viewportWidth - messageWidth - 5; //
} }
if(this.position == 'top'){
top =`${targetRect.top + window.scrollY - this.offset - messageHeight}px` if (this.position === 'top') {
}else{ top = targetRect.top + scrollY - this.offset - messageHeight;
top = `${targetRect.top + window.scrollY + this.offset + messageHeight}px` //
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 = { this.positionStyle = {
top, top: `${top}px`,
left: `${left}px`, left: `${left}px`,
right: 'auto', // right right: 'auto'
}; };
}, },
}, },
beforeDestroy() {
this.clearTimer();
}
} }
</script> </script>
@ -112,133 +193,29 @@ export default {
background: #FFFFFF; background: #FFFFFF;
box-sizing: border-box; box-sizing: border-box;
box-shadow: 0px 3px 8px 0px rgba(0, 0, 0, 0.16); box-shadow: 0px 3px 8px 0px rgba(0, 0, 0, 0.16);
span{ pointer-events: none;
span {
letter-spacing: 0.08em; letter-spacing: 0.08em;
color: #626573; color: #626573;
white-space: nowrap;
} }
} }
.message-image { .message-image {
width: 18px; width: 18px;
height: 18px; height: 18px;
margin-right: 10px; margin-right: 10px;
} flex-shrink: 0;
.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-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.3s; transition: opacity 0.3s, transform 0.3s;
} }
.fade-enter, .fade-enter,
.fade-leave-to { .fade-leave-to {
opacity: 0; opacity: 0;
transform: translateY(-10px);
} }
</style> </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> -->

49
src/utils/tooltip.js

@ -3,31 +3,56 @@ import Tooltip from '@/components/Tooltip.vue'
const PositionMessageConstructor = Vue.extend(Tooltip) const PositionMessageConstructor = Vue.extend(Tooltip)
// 自动隐藏功能扩展 // 存储实例引用,便于全局控制
const instances = []
function showMessage(options) { function showMessage(options) {
const instance = new PositionMessageConstructor({ const instance = new PositionMessageConstructor({
propsData: options propsData: options
}).$mount() }).$mount()
document.body.appendChild(instance.$el) document.body.appendChild(instance.$el)
instances.push(instance)
const showInstance = instance.show() const showInstance = instance.show()
// 添加自动隐藏 // 添加自动隐藏(duration为0表示一直显示)
if (options.duration !== 0) { if (options.duration !== 0) {
setTimeout(() => { const duration = options.duration || 2000
showInstance.hide()
setTimeout(() => { setTimeout(() => {
document.body.removeChild(instance.$el) instance.hide()
}, 300) // 等待动画结束 setTimeout(() => {
}, options.duration || 2000) if (instance.$el && instance.$el.parentNode) {
instance.$el.parentNode.removeChild(instance.$el)
}
// 从实例数组中移除
const index = instances.indexOf(instance)
if (index > -1) {
instances.splice(index, 1)
}
}, 300) // 等待动画结束
}, duration)
} }
return showInstance return showInstance
} }
// 隐藏所有tooltip
function hideAll() {
instances.forEach(instance => {
instance.hide()
setTimeout(() => {
if (instance.$el && instance.$el.parentNode) {
instance.$el.parentNode.removeChild(instance.$el)
}
}, 300)
})
instances.length = 0
}
export default { export default {
install(Vue) { install(Vue) {
Vue.prototype.$positionMessage = showMessage Vue.prototype.$positionMessage = showMessage
} Vue.prototype.$hideAllPositionMessages = hideAll
}
} }

51
src/views/elementGroups.vue

@ -74,11 +74,17 @@
@change="radioChange" :rules="rules.language" label-key="name" :disabledKeys="['1']" @change="radioChange" :rules="rules.language" label-key="name" :disabledKeys="['1']"
value-key="id" /> value-key="id" />
</div> </div>
<div class="ele-item"> <div class="ele-item">
<label for="">数组套对象类型下拉框</label> <label for="">数组套对象类型下拉框</label>
<!-- :extraItem="{label:'全部',value:'99999'}" 传入必须是 labelvalue 组件内会自动改为自定义的 labelKeyvalueKey--> <!-- :extraItem="{label:'全部',value:'99999'}" 传入必须是 labelvalue 组件内会自动改为自定义的 labelKeyvalueKey-->
<div class="flex">
<GuipSelect width="150px" v-model="form.card" clearable label="卡片" :default-value="form.card" @change="selectChangeTest" <GuipSelect width="150px" v-model="form.card" clearable label="卡片" :default-value="form.card" @change="selectChangeTest"
prop="card" :options="options" valueKey="id1" labelKey="id2" :extraItem="{label:'全部',value:'99999'}"/> prop="card" :options="options" valueKey="id1" labelKey="id2" :extraItem="{label:'全部',value:'99999'}"/>
<p>j卢卡斯就到啦</p>
<GuipInput ref="GuipInput" v-model="form.input2" :maxlength="100" @change="handleInput"
@blur="handleInput" prop="input2" @input="handleInput" @focus="handleInput" placeholder="这是自定义默认提示语" />
</div>
</div> </div>
<div class="ele-item"> <div class="ele-item">
<label for="">纯数组[1,2]下拉框</label> <label for="">纯数组[1,2]下拉框</label>
@ -90,6 +96,8 @@
<GuipSelect width="150px" v-model="form.card1" clearable label="卡片" :default-value="form.card" @change="selectChangeTest" <GuipSelect width="150px" v-model="form.card1" clearable label="卡片" :default-value="form.card" @change="selectChangeTest"
prop="card" :options="languageOptions"/> prop="card" :options="languageOptions"/>
</div> </div>
<el-button type="primary" @click="submitForm">Submit</el-button>
<div> <div>
<h3 for="">表格(表头自定义自定义渲染固定列)</h3> <h3 for="">表格(表头自定义自定义渲染固定列)</h3>
<GuipButton type="primary" @click="toggleAllSelection">全选按钮</GuipButton> <GuipButton type="primary" @click="toggleAllSelection">全选按钮</GuipButton>
@ -237,6 +245,7 @@
</GuipInput> </GuipInput>
<!-- <el-input placeholder="oieuwroieuwi" style="width:400px;height:60px"></el-input> --> <!-- <el-input placeholder="oieuwroieuwi" style="width:400px;height:60px"></el-input> -->
</div> </div>
<div class="ele-item"> <div class="ele-item">
<label for="">单选框</label> <label for="">单选框</label>
<el-radio v-model="radio1" :label="1">选项一</el-radio> <el-radio v-model="radio1" :label="1">选项一</el-radio>
@ -494,7 +503,6 @@
</CustomDropdown> </CustomDropdown>
</div> </div>
<el-button type="primary" @click="submitForm">Submit</el-button>
</el-form> </el-form>
<div style="width: 100%;margin: 20px 0;height: 20px;background-color: antiquewhite;"> <div style="width: 100%;margin: 20px 0;height: 20px;background-color: antiquewhite;">
@ -523,12 +531,18 @@
<el-button @click="save" type="primary">保存</el-button> <el-button @click="save" type="primary">保存</el-button>
</div> --> </div> -->
</div> </div>
<div class="flex ele-item"> <div class="flex ele-item gap10" style="flex-wrap: wrap;">
<label for="">一些功能集合</label> <label for="">一些功能集合</label>
<GuipButton type="primary" ref="button1" :btnstyle="{ width: '300px' }" @click="openDialog3('button1','bottom')">下方弹出消息提示 <GuipButton type="primary" ref="button1" :btnstyle="{ width: '300px' }" @click="openDialog3('button1','bottom')">下方弹出消息提示
</GuipButton> </GuipButton>
<GuipButton type="primary" ref="button2" :btnstyle="{ width: '300px' }" @click="openDialog3('button2','top')">上方弹出消息提示 <GuipButton type="primary" ref="button2" :btnstyle="{ width: '300px' }" @click="openDialog3('button2','top')">上方弹出消息提示
</GuipButton> </GuipButton>
<GuipButton type="primary" ref="button3" :btnstyle="{ width: '300px' }" @click="openDialog4('button3','top')">一直显示消息提示
</GuipButton>
<GuipButton type="primary" ref="button5" :btnstyle="{ width: '300px' }" @click="openDialog5">动态更改提示文案
</GuipButton>
<GuipButton type="primary" :btnstyle="{ width: '300px' }" @click="openDialog6">主动隐藏消息提示
</GuipButton>
<GuipButton type="primary" :btnstyle="{ width: '300px' }" @click="openDialog">打开弹框标题巨左按钮居右 <GuipButton type="primary" :btnstyle="{ width: '300px' }" @click="openDialog">打开弹框标题巨左按钮居右
</GuipButton> </GuipButton>
<GuipButton type="primary" :btnstyle="{ width: '300px' }" @click="openDialog1">打开弹框标题巨中按钮居中 <GuipButton type="primary" :btnstyle="{ width: '300px' }" @click="openDialog1">打开弹框标题巨中按钮居中
@ -744,9 +758,12 @@ export default {
phone: [ phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' } { required: true, message: '请输入手机号', trigger: 'blur' }
], ],
input2: [
{ required: true, message: '请输入手机号', trigger: 'blur' }
],
age: [ age: [
{ required: true, message: '请输入年龄', trigger: 'blur' } { required: true, message: '请输入年龄', trigger: 'blur' }
] ],
}, },
usernameRules: [{ required: true, message: 'Username is required', trigger: 'blur' }],//rules usernameRules: [{ required: true, message: 'Username is required', trigger: 'blur' }],//rules
msg: "测试", msg: "测试",
@ -940,7 +957,6 @@ export default {
height: '50px' height: '50px'
}, },
plain: false, plain: false,
options: [{ options: [{
value: '选项1', value: '选项1',
label1: '黄金hhhhhh', label1: '黄金hhhhhh',
@ -972,6 +988,8 @@ export default {
label1: '双皮奶hhhhhhhhh', label1: '双皮奶hhhhhhhhh',
label: '北京烤鸭' label: '北京烤鸭'
}], }],
tooltip:null
} }
}, },
mounted() { mounted() {
@ -1162,15 +1180,40 @@ export default {
openDialog2() { openDialog2() {
this.dialogVisible2 = true; this.dialogVisible2 = true;
// // tooltip
// this.tooltip.destroy()
// // tooltip
// this.$hideAllPositionMessages()
},
openDialog6(){
this.tooltip.hide()
}, },
openDialog3(el,pos){ openDialog3(el,pos){
// type // type
this.$positionMessage({ this.$positionMessage({
duration:3000,
type: 'success', type: 'success',
message: '操作成功', message: '操作成功',
target: this.$refs[el], // DOM target: this.$refs[el], // DOM
position: pos // 'bottom' position: pos // 'bottom'
}) // 3 }) // 3
console.log(this.tooltip,'tooltip',el,pos);
},
openDialog4(el){
//
this.tooltip = this.$positionMessage({
type: 'info',
message: '这个提示会一直显示',
target: this.$refs[el],
duration: 0 // 0
})
},
openDialog5(){
this.tooltip.updateMessage('更新后的文本内容----')
}, },
// //
handleConfirm() { handleConfirm() {

Loading…
Cancel
Save