1 问题描述
在使用 element-plus
框架的 el-popover
组件的时候,如果这个组件的内部有 el-select
组件时,当选中了其中一项后 el-popover
会自动关闭。 如下示例: zhonghuitech.github.io/vfg/#/popov...
代码:
html
<el-popover placement="bottom" :width="500" trigger="click">
<template #default>
<el-card style="max-width: 500px;border:0px solid red;--el-card-border-color:#ffffff" shadow="never">
<template #header>
<div class="card-header">
<span style="font-weight: bold;font-size: large;">筛选</span>
</div>
</template>
<el-form ref="taskRef">
<el-row>
<el-col :span="12">
<el-form-item prop="itemCode" style="margin-left: 10px;">
<el-select v-model="form.type" placeholder="" :teleported="true" placement="bottom">
<el-option v-for="dict in type_dic" :key="dict.value" :label="dict.label"
:value="dict.value"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div style="display: flex;justify-content:space-between">
<span>
<el-button @click="cancel" link>取 消</el-button>
<el-button type="primary" @click="submitFilterForm">确 定</el-button>
</span>
</div>
</template>
</el-card>
</template>
<template #reference>
<el-button type="primary" @click="proBtnClick">问题示例</el-button>
</template>
</el-popover>
2 问题原因
官方文档上有相关的问题issue
,原因如下:
github.com/element-plu... github.com/element-plu...
给出的解决方式是将 el-popover
的 teleported
设置为 false
3 解法一:将 teleported 设置为 false
网上通用的作法是将 el-popover
的 teleported
属性设置为 false
,问题是得到了解决。但又出现了新的问题: zhonghuitech.github.io/vfg/#/popov...
新的问题 是 el-popover
的 options
部分很多导致高度不够,多余的部分显示不出来。
4 解法二:优雅解决
通过将teleported
的属性设置为false
显然不是我们想要的解决方案。那只剩下最后一条路,那就是自己手动控制el-popover
的显示与隐藏。
html
<el-popover placement="bottom" :width="500" trigger="click" :visible="popoverVisible">
设置变量 popoverVisible
js
const popoverVisible = ref(false)
const data = reactive({
form: { type: '' }
})
const { form } = toRefs(data);
function proBtnClick() {
popoverVisible.value = true
}
但这里要解决一个关键问题:当鼠标点击发生在 el-popover 区域外时,需要关闭 popover
。怎么解决这个问题?
4.1 解决鼠标在区域外点击时关闭的问题
解决这个问题的思路是监听全局的点击事件,然后判断这个点击事件的发生是否在 popover
的区域内。 先上源码:
html
<el-popover placement="bottom" :width="500" trigger="click" :visible="popoverVisible">
<template #default>
<div ref="elPopoverFilterCardWrapDivRef">
<el-card style="max-width: 500px;border:0px solid red;--el-card-border-color:#ffffff" shadow="never">
<template #header>
<div class="card-header">
<span style="font-weight: bold;font-size: large;">筛选</span>
</div>
</template>
<el-form ref="taskRef">
<el-row>
<el-col :span="12">
<el-form-item prop="itemCode" style="margin-left: 10px;">
<el-select v-model="form.type" placeholder="" :teleported="true" placement="bottom">
<el-option v-for="dict in type_dic" :key="dict.value" :label="dict.label"
:value="dict.value"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div style="display: flex;justify-content:space-between">
<span>
<el-button @click="cancel" link>取 消</el-button>
<el-button type="primary" @click="submitFilterForm">确 定</el-button>
</span>
</div>
</template>
</el-card>
</div>
</template>
<template #reference>
<el-button type="primary" @click="proBtnClick">优雅解决</el-button>
</template>
</el-popover>
JS部分代码如下:
js
const popoverVisible = ref(false)
const data = reactive({
form: { type: '' }
})
const { form } = toRefs(data);
function proBtnClick(e) {
popoverVisible.value = true
e.stopPropagation();
}
const elPopoverFilterCardWrapDivRef = ref(null)
const isPopoverChildComponent = (element, parent) => {
if (element === parent) {
// ref 只能作用于原生 html 组件上,不能在 el-card 之上,所以只能在 el-card 上包一层。
console.log(element)
return true;
}
if (element.parentElement) {
return isPopoverChildComponent(element.parentElement, parent);
}
return false;
};
const handleGlobalClick = (event) => {
nextTick(() => {
// debugger
if (isPopoverChildComponent(event.target, elPopoverFilterCardWrapDivRef.value)) {
console.log('点击发生在 Popover 或其子组件上');
} else {
console.log('点击发生在 Popover 之外');
popoverVisible.value = false
}
});
};
const registerGlobalClick = () => {
window.addEventListener('click', handleGlobalClick);
};
const unregisterGlobalClick = () => {
window.removeEventListener('click', handleGlobalClick);
};
onMounted(() => {
// 组件挂载时注册全局点击事件
registerGlobalClick();
});
onUnmounted(() => {
// 组件卸载时取消注册全局点击事件
unregisterGlobalClick();
});
几点说明: 1)elPopoverFilterCardWrapDivRef
用来关联popover
内容部分的组件(注意:这个一定要原生的 html 组件才行,如 div) 2)handleGlobalClick
处理全局的点击事件 3)isPopoverChildComponent
判断点击事件是否来自popover
之内
3.4 遇到新问题
el-select
组件通过上面的方法解决了,但是,如果添加 el-date-picker
这个组件,仍然会有问题。
看起来上面的机制失效了。手工 debug 看,发现找父组件,最后找到了 body 上,没有找到 elPopoverFilterCardWrapDivNewRef
3.4 新问题解决
无论是 el-popover
还是 el-date-picker
都是基于 popper
这个开源库来的。el-date-picker
里有一个 popper-class
,我们可以设置一个特殊的 class
类,然后往上找的时候,如果找到的就说明点击事件是发生在 popover
的内部。
html
popper-class="ItemValueDataPickerPopoverVitualClass"
JS部分代码稍做修改即可:
js
const isPopoverChildComponent = (element, parent) => {
if (element && element.classList) {
if (element.classList.contains('ItemValueDataPickerPopoverVitualClass')) {
console.log('ItemValueDataPickerPopoverVitualClass')
return true;
}
}
if (element === parent) {
// ref 只能作用于原生 html 组件上,不能在 el-card 之上,所以只能在 el-card 上包一层。
console.log(element)
return true;
}
if (element.parentElement) {
return isPopoverChildComponent(element.parentElement, parent);
}
return false;
};