优雅解决el-popover内有select时在选择后会自动关闭

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-popoverteleported 设置为 false

3 解法一:将 teleported 设置为 false

网上通用的作法是将 el-popoverteleported 属性设置为 false,问题是得到了解决。但又出现了新的问题: zhonghuitech.github.io/vfg/#/popov...

新的问题el-popoveroptions 部分很多导致高度不够,多余的部分显示不出来。

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;
    };

DEMO 地址:zhonghuitech.github.io/vfg/#/popov...

源码:github.com/zhonghuitec...

相关推荐
陈奕迅本讯36 分钟前
HTML5和CSS3拔高
前端·css3·html5
画船听雨眠aa1 小时前
vue项目创建与运行(idea)
前端·javascript·vue.js
大码猴1 小时前
用好git的几个命令,领导都夸你干的好~
前端·后端·面试
℡52Hz★1 小时前
如何正确定位前后端bug?
前端·vue.js·vue·bug
学不完了是吧1 小时前
html、js、css实现爱心效果
前端·css·css3
小丁爱养花2 小时前
Spring MVC:设置响应
java·开发语言·前端
优联前端2 小时前
Web 音视频(二)在浏览器中解析视频
前端·javascript·音视频·优联前端·webav
涔溪2 小时前
2025年前端面试题汇总
前端
16年上任的CTO2 小时前
一文大白话讲清楚webpack基本使用——6——热更新及其原理
前端·webpack·node.js·热更新·hmr·热重载