查看: 143|回复: 3

Vue3 Element Plus 表格筛选面板溢出滚动条适配

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在 Vue3 + Element Plus 的中后台项目里,给 el-table 的某一列挂上 filters 做部门、类型之类的快捷筛选是很常见的需求。但当下拉筛选条目一多,筛选面板就会顶出可视区域,而且面板自身没有滚动条,用户根本点不到底部的“筛选/重置”按钮。本文围绕一次真实改造,还原从代码写法、问题定位到最终样式的完整过程,重点说明为什么只限制内层滚动容器不够,必须同时约束父容器高度。

一、筛选列的基础实现

示例场景是一个出库单列表,列上展示单号、人员与部门信息,部门筛选数据来自一个 key-value 映射 hook,用 computed 转成 Element Plus 需要的 { text, value } 数组:
  1. // 部门键值映射 hook
  2. const departmentHook = useDepartmentKeyValMap();
  3. // 表格列筛选列表
  4. const departmentFilters = computed(() =>
  5.   departmentHook.departmentKeyValList.value.map((item) => ({
  6.     text: item.label as string,
  7.     value: item.value as string
  8.   }))
  9. );
复制代码

对应模板中,列组件必须设置 column-key,因为筛选回调里要用它反查行数据字段:
  1. <el-table
  2.   class="custom-table"
  3.   ref="outOrderTableRef"
  4.   :data="outOrderList"
  5.   v-loading="outOrderLoading"
  6.   :border="false"
  7.   highlight-current-row
  8.   stripe
  9.   style="width: 100%; height: 100%"
  10.   @row-click="onReceiveOrderTableRowClick">
  11.   <el-table-column
  12.     property="ckNo"
  13.     column-key="llDeptId"
  14.     :filters="departmentFilters"
  15.     :filter-method="filterHandler"
  16.     filter-placement="bottom-end"
  17.     header-align="left"
  18.     sortable>
  19.     <template #header>
  20.       <span>待入库的物资</span>
  21.       <BasePreventReClickButton
  22.         class="refresh-btn"
  23.         type="primary"
  24.         :icon="RefreshRight"
  25.         link
  26.         @click="onRefreshClick" />
  27.     </template>
  28.     <template #default="scope">
  29.       <div class="table-column-content">
  30.         {{ scope.row.ckNo }} {{ userHook.lookupMoniker(scope.row.llPersonId) }}
  31.       </div>
  32.       <div class="table-column-content">
  33.         {{ departmentHook.lookupDepartmentName(scope.row.llDeptId) }}
  34.       </div>
  35.     </template>
  36.   </el-table-column>
  37. </el-table>
复制代码

筛选回调与刷新逻辑如下。注意 filterHandler 中的 property 取自 column.columnKey,所以没有 column-key 的列无法走这套通用判断;同时刷新前调用 clearFilter 清空筛选状态,避免旧条件残留:
  1. // 筛选出库单
  2. const filterHandler = (value: string, row: ICkMaster, column: TableColumnCtx<ICkMaster>) => {
  3.   let property = column.columnKey; // 要求表格筛选的列 el-table-column 必须要设置 column-key
  4.   if (property in row) {
  5.     return row[property as keyof ICkMaster] === value;
  6.   }
  7.   return false;
  8. };
  9. // 清除表格筛选
  10. const clearFilter = () => {
  11.   if (outOrderTableRef.value) {
  12.     outOrderTableRef.value.clearFilter();
  13.   }
  14. };
  15. // 刷新出库单列表数据
  16. const onRefreshClick = async () => {
  17.   try {
  18.     outOrderLoading.value = true;
  19.     clearFilter();
  20.     tableData.value = [];
  21.     let result = await ckdQueryMasterListForRkService();
  22.     outOrderList.value = result.data;
  23.   } finally {
  24.     outOrderLoading.value = false;
  25.   }
  26. };
复制代码

二、问题现象与定位难点

部门数量一多,筛选面板就开始向下溢出屏幕底部,并且面板本身没有滚动条,只能看到上半截。第一次尝试是给列加 filter-class-name,然后用全局样式限制内部滚动区域:
  1. <el-table-column
  2.   property="ckNo"
  3.   column-key="llDeptId"
  4.   :filters="departmentFilters"
  5.   :filter-method="filterHandler"
  6.   filter-placement="bottom-end"
  7.   header-align="left"
  8.   sortable
  9.   filter-class-name="dept-filter-pop">
复制代码
  1. <style lang="scss">
  2. /* scoped 只作用于组件内部元素,筛选面板被 Teleport 到 body 下,必须用全局样式 */
  3. .dept-filter-pop .el-table-filter__content .el-scrollbar .el-table-filter__wrap {
  4.   max-width: 180px;   /* 限制面板宽度,内容超出时出现横向滚动,不设置宽度时默认 100% */
  5.   max-height: 450px !important;  /* 限制列表区高度,必须加 !important 才能覆盖内置样式 */
  6.   overflow-y: auto !important;   /* 强制出现垂直滚动条 */
  7. }
  8. </style>
复制代码

这时列表区确实能滚动了,但把 max-height 从 450px 调到 500px,面板又溢出屏幕。也就是说,限高只解决了“列表太长”,没有解决“整个面板太高”。

根本原因要从面板的 DOM 结构看起。Element Plus 的筛选弹层大致是三层嵌套:
  1. <div class="el-table-filter__content">
  2.   <!-- 搜索输入框,开启 filterable 时才有 -->
  3.   <div class="el-table-filter__search-wrap">...</div>
  4.   <!-- 复选框列表,被 el-scrollbar 包裹 -->
  5.   <div class="el-scrollbar">
  6.     <div class="el-scrollbar__wrap el-table-filter__wrap">
  7.       <div class="el-checkbox-group">
  8.         <label class="el-checkbox">选项1</label>
  9.       </div>
  10.     </div>
  11.   </div>
  12. </div>
  13. <div class="el-table-filter__bottom">
  14.   <button>筛选</button>
  15.   <button>重置</button>
  16. </div>
复制代码

el-table-filter__wrap 只包住复选框组,而 el-table-filter__content 才是包含搜索框与列表的父容器。只给 wrap 设 500px,面板总高约等于搜索框 30px + 列表 500px + 底部按钮 40px ≈ 570px。弹层通过 Teleport 挂到 body,位置按视口计算,视口装不下 570px 时就整体溢出,而且父容器没有 max-height,自身不产生滚动条,所以用户看不到完整内容。

三、同时约束父子容器的高度

在同一个选择器下追加对 .el-table-filter__content 的限高,父容器不再被搜索框与列表无限撑开,整块面板高度就落到约 500px + 40px 的可控范围内:
  1. <style lang="scss">
  2. /* 列表滚动区 */
  3. .dept-filter-pop .el-table-filter__content .el-scrollbar .el-table-filter__wrap {
  4.   max-width: 180px;
  5.   max-height: 500px !important;
  6.   overflow-y: auto !important;
  7. }
  8. /* 内容父容器,从源头限制整体高度 */
  9. .dept-filter-pop .el-table-filter__content {
  10.   max-height: 500px !important;
  11. }
  12. </style>
复制代码

这里两个细节不能省:一是必须使用全局样式,组件内的 scoped 选中不到 Teleport 出去的元素;二是 max-height 与 overflow-y 需要 !important,否则会被 Element Plus 内置的弹层样式覆盖。

四、用视口单位做动态适配

固定像素值在大屏和小屏上表现不一致,更稳妥的写法是用 vh 计算,把屏幕高度换算成面板可用高度,并留出底部按钮与边距的空间:
  1. <style lang="scss">
  2. .dept-filter-pop .el-table-filter__content .el-scrollbar .el-table-filter__wrap {
  3.   max-width: 180px;
  4.   max-height: calc(60vh - 100px) !important; /* 视口高度减去预留空间 */
  5.   min-height: 40px;
  6.   overflow-y: auto !important;
  7. }
  8. .dept-filter-pop .el-table-filter__content {
  9.   max-height: calc(60vh - 100px) !important;
  10.   min-height: 40px;
  11. }
  12. </style>
复制代码

如果希望面板尽量占满可用空间,也可以按“内层减预留、外层再少减一点给按钮留位”的思路分配:
  1. .dept-filter-pop .el-table-filter__content .el-scrollbar .el-table-filter__wrap {
  2.   max-width: 180px;
  3.   max-height: calc(100vh - 200px) !important; // 用视口高度减去预留空间
  4.   overflow-y: auto !important;
  5. }
  6. .dept-filter-pop .el-table-filter__content {
  7.   max-height: calc(100vh - 160px) !important; // 给底部按钮留出空间
  8.   overflow: hidden;                            // 防止内容溢出父容器
  9. }
复制代码

这样无论用户用 1080p 显示器还是 768p 笔记本,面板高度都会随视口收缩,不会再顶出屏幕。

五、适用场景与排查顺序

这套思路适用于所有基于 Teleport 渲染、内容长度不可控的 Element Plus 弹层组件:列筛选、级联下拉、多选面板等。排查时按以下顺序即可:先确认列是否设置了 column-key 并绑定了 filter-method;再确认选择器是否命中弹层(用 filter-class-name 加自定义类隔离,避免影响其他表格);接着分别检查内层滚动容器与外层内容容器的高度是否都被限制;最后用 calc(vh) 替换固定像素值做自适应。只要父容器的高度被约束住,弹层就不会因为子元素堆积而超出可视区域,这是本文问题的核心结论。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Vue3 Element Plus 表格筛选面板溢出滚动条适配

这个坑我也踩过,楼主的判断思路是对的。补充几点我自己踩出来的经验,可能对你有帮助。 首先 el-table 的筛选面板 DOM 结构是两段式的:`.el-table-filter__content` 里是筛选项列表,`.el-table-filter__bottom` 里是"筛选 / 重置"按钮。所以只给 `__content` 加 `max-height + overflow-y: auto` 确实够不到底部按钮——因为 `__content` 虽然滚了,但整个面板的总高度没有约束,底部那一条会被顶到视口外面去。真正的做法是给外层 `.el-table-filter` 设一个 `max-height`(比如 `calc(100vh - 某偏移)` 或者直接 `min(60vh, 400px)` 之类的值),再让 `__content` 用 `flex: 1` 或者 `max-height` 自适应撑满剩余空间滚动,`__bottom` 保持固定高度。这样面板整体不会超出屏幕,内容区自己滚,按钮始终可见。 另外提醒一下 `filter-class-name` 的生效范围:它加在面板根节点上,但如果你项目里是 ``,全局样式写在 scoped 里是够不到的,得单独开一个非 scoped 的 style 块或者用 `:deep()` 从某个公共父级往下穿透,否则会出现"类名加上了但样式
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Vue3 Element Plus 表格筛选面板溢出滚动条适配

楼主这个场景太真实了,el-table 的 filters 面板条目一多就顶出屏幕,底部按钮点不到,这个坑我也踩过。看到你说“只限制内层滚动容器不够,必须同时约束父容器高度”,这一点我深有同感,Element Plus 的筛选面板本身是挂在 popper 里的,高度约束往往是多层嵌套的,单独改内层确实容易被外层撑开。 另外你强调的 column-key 这点也很关键,很多人筛选回调里拿不到字段就是因为漏了这个。等楼主把后面的样式方案补完,想看看你最终是怎么处理 popper 层级和最大高度的,是先算视口再动态设还是纯 CSS 兜底。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Vue3 Element Plus 表格筛选面板溢出滚动条适配

这个场景太真实了,el-table 的 filters 面板条目一多确实容易顶出屏幕,而且默认没有滚动条,用户看不到底部按钮很抓狂。 楼主提到两个点我觉得特别实用:一是 column-key 和 filterHandler 里用 column.columnKey 反查字段的配合,之前我遇到过没设 column-key 导致筛选回调拿不到属性的坑;二是刷新前先 clearFilter 清空筛选状态,不然刷新后旧条件还挂着,数据过滤结果会让人困惑。 关于“只限制内层滚动容器不够,必须同时约束父容器高度”这个结论我很感兴趣,因为平时调这类下拉面板,往往就是给内层加 max-height 和 overflow-y,结果外层还是撑开。感觉关键在于 Element Plus 筛选项面板的 DOM 结构里,滚动区域和定位容器是两层,父级没高度约束时内层 max-height 就失效了。 不过帖子好像写到第二次尝试那里就截断了,后面最终样式方案没贴出来。方便的话想看看完整版,尤其是父容器高度的约束是写在 filter-class-name 对应的全局样式里,还是通过 filter-placement 配合 popper 的 append-to-body 去处理?另外部门筛选数据很多时,有没有考虑过用远程搜索或者折叠展示来减少面板高度?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-30 06:42 , Processed in 0.028751 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部