优雅解决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...

相关推荐
Bruce_Liuxiaowei6 小时前
一键清理Chrome浏览器缓存:批处理与PowerShell双脚本实现
前端·chrome·缓存
怒放的生命19916 小时前
Vue 2 vs Vue 3对比 编译原理不同深度解析
前端·javascript·vue.js
GDAL6 小时前
html返回顶部实现方式对比
前端·html·返回顶部
Violet_YSWY6 小时前
ES6 () => ({}) 语法解释
前端·ecmascript·es6
LYFlied7 小时前
【每日算法】LeetCode 279. 完全平方数(动态规划)
前端·算法·leetcode·面试·动态规划
小北方城市网7 小时前
第7课:Vue 3应用性能优化与进阶实战——让你的应用更快、更流畅
前端·javascript·vue.js·ai·性能优化·正则表达式·json
向下的大树7 小时前
React 环境搭建 + 完整 Demo 教程
前端·react.js·前端框架
IT_陈寒7 小时前
Python性能翻倍的5个隐藏技巧:让你的代码跑得比同事快50%
前端·人工智能·后端
Можно7 小时前
GET与POST深度解析:区别、适用场景与dataType选型指南
前端·javascript
爱上妖精的尾巴7 小时前
5-41 WPS JS宏 数组迭代基础测试与双数组迭代的使用方法测试
前端·javascript·wps