固定列、流体高度与多级表头递归封装
概述
扩展基础表格时,最稳妥的方式是沿 Element Plus Table 官方示例逐个对照实现,边做边发现 API 边界。本章依次落地固定列、固定表头、流体高度、append 底部操作区,然后重点处理多级表头——它把列配置从一维平铺推进成树形嵌套,从而逼出递归组件 VTableColumn 与带 children 的列类型。用 JSON 表达嵌套列结构,本质上是把"表格列模板"数据化,为列权限、后端下发列配置、列拖拽与持久化打基础。
把列结构数据化之后,VTable 的演进方向就从“更多功能”转向“更可被程序驱动”:列权限、后端列配置、拖拽排序、显隐持久化都建立在“列是数据”这个前提上。这一步也为把表格接入低代码 / 配置化列表页埋下了伏笔。
学习目标
- 理解沿官方示例对照实现是摸清 VTable 合理边界的务实路线
- 认识固定列(列
fixed)与固定表头(表格height)靠透传即可接入 - 区分流体高度
max-height与固定高度:长到上限再内部滚动 - 用
append插槽承载表格底部操作区(如 Add Item) - 理解多级表头把列配置从平铺推进成嵌套树形
- 抽
VTableColumn递归组件,并在列类型并入children、插槽与数据化 - 认识列数据化为列权限、后端下发、拖拽持久化打基础
- 理解 VTableColumn 递归后组件内部结构开始成熟
- 掌握“先 slot 验证、再回收成配置驱动”的渐进封装节奏
一、沿官方示例对照实现,边做边发现边界
表格功能点很多(斑马线、边框、状态、固定头、固定列、流体高度、多级表头),一边对照官方示例落到自己的 VTable,很快能看清楚哪些能力原生 props 就够了、哪些必须扩展列配置、哪些会逼你改表格内部结构。核心不是炫技,而是继续摸清 VTable 的合理边界。
二、固定列与固定表头靠透传即可接入
固定列依赖列上的 fixed,固定表头依赖表格的 height,这两项并没有要求重写表格组件——只要 VTable 保持对原生 table props 和 column props 的透传,这类能力自然接进来。验证重点不是"怎么写功能",而是"当前透传设计够不够";若固定列和固定表头都能直接接进来,说明基础 VTable 的 props 透传方向是对的。操作列固定到右侧是后台表格非常常见的真实需求。
const columns = [
{ prop: "date", label: "Date", fixed: true },
{ label: "Operations", width: 120, fixed: "right" }
]三、流体高度不是固定高度的替代品
流体高度是给表格设置 max-height:数据变多时只增长到这个上限,超过之后内部滚动。与固定高度(一开始就锁死高度)的差异在于,它先随内容增长、增长到上限后再内部滚动。流体高度更适合数据条数浮动较大的表格区,数据少时更紧凑,数据多时也不会无限拉长,是后台列表页非常实用的中间方案。
<el-table :data="tableData" max-height="300" />四、append 插槽承载底部操作区
做流体高度时顺手把"新增一条数据"的按钮放到表格底部,最自然的落点就是前一节已打通的 append 插槽。表格级插槽不只是演示用,真实业务里也很常见:底部新增按钮、批量操作提示、加载更多提示。这一节正好说明表格级 slot 透传不是多余设计,它与列级插槽用途不同但都会用到。
五、多级表头把列配置从平铺推进成嵌套
Element Plus 原生多级表头是 el-table-column 里面再套 el-table-column,这意味着列结构不再是"一维数组平铺"而是"递归嵌套"。一旦走到这里,VTable 必须思考是继续用 slot 让页面自己嵌套,还是让 columns 支持 children 递归。多级表头通常是 VTable 从"基础封装"进入"递归结构组件"阶段的分界线。
六、多级表头两路线:slot 嵌套原生 vs children 递归
第一种直接把原生多级 el-table-column 结构当作 slot 塞给 VTable,更直接适合快速落地;第二种把列结构改造成 JSON,让 columns 支持 children 再由组件内部递归渲染,更适合通用组件封装。两条路线不互斥:可以先用 slot 验证,再回收成配置驱动。
const columns = [{
label: "Address Info",
children: [
{ prop: "state", label: "State" },
{ prop: "city", label: "City" }
]
}]七、抽 VTableColumn 递归组件收口列树渲染
把原本写在 VTable 里的那一大坨 el-table-column 抽成独立递归组件 VTableColumn,是自然的演进——一旦支持 children、headerSlot、defaultSlot,列结构本身已值得成为独立组件。VTable 负责表格容器,VTableColumn 负责列树渲染,职责更清晰,也意味着 VTable 内部结构开始成熟。
<el-table-column v-bind="props">
<template v-if="props.children?.length">
<VTableColumn v-for="(child, i) in props.children" :key="i" v-bind="child" />
</template>
</el-table-column>八、列类型并入 children 与插槽,JSON 表达即数据化
列类型不再是 Element Plus 原始列类型的简单别名,而是进入项目自己的扩展模型:除 prop、label、width、fixed 外,还可能有 children、defaultSlot、headerSlot。用 JSON 结构表达多级表头,真正价值是把"表格列模板"数据化——一旦列结构数据化,后续就能做权限控制哪些列显示、后端返回列配置、列拖拽排序、列持久化显示隐藏。这一步通常是 VTable 组件类型系统正式成长的节点。
九、列数据化后的延伸:权限、下发与持久化
列结构一旦是 JSON,就能被程序驱动而不只是手写。最常见的三个延伸:一是列权限,按角色过滤 columns 里某些敏感列;二是后端下发,列表页的列配置由接口返回,前端不再硬编码;三是显隐与排序持久化,用户拖拽 / 隐藏列后把结果存到本地或用户配置。这些都建立在“列即数据”之上,也是 VTable 从组件走向“配置化列表方案”的关键一步。
// 按权限过滤列
const visibleColumns = columns.filter(
(c) => !c.roles || c.roles.some((r) => userRoles.includes(r))
)十、封装节奏:先 slot 验证,再回收成配置
多级表头和递归组件不必一步到位。更稳的节奏是:先用原生 el-table-column 的 slot 把效果跑通,确认交互与结构没问题,再把列结构回收成 columns + children 的配置驱动。这样能在“快速验证”和“长期可维护”之间取得平衡,也避免一开始就在递归组件里反复调试。封装深度要匹配当前业务成熟度,而不是越抽象越好。
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 固定列和固定表头不好接 | 误以为要重写内部结构 | 先确认 VTable 是否已透传原生 props 和列 props |
max-height 与固定高度搞混 | 没理解流体高度目标 | 固定高度锁死,流体高度长到上限再滚 |
| 多级表头只能手写模板嵌套 | 列类型还是平铺结构 | 列类型加 children 走递归组件 |
VTable 里列模板越来越乱 | 所有 column 逻辑堆一个组件 | 抽独立 VTableColumn 递归处理 |
| 操作列在多级表头里难处理 | 以为多级和插槽是两套方案 | 递归列类型里继续保留 defaultSlot / headerSlot |
页面层给操作列塞空 prop | 列类型边界不准确 | 保持 prop 可选,让类型服务真实场景 |
| 列权限不知道怎么接 | 列还是写死模板 | 列数据化后按 roles 过滤 columns |
| 想让后端控制列 | 前端硬编码 | columns 改为接口下发再渲染 |
| 用户拖拽列顺序要记住 | 没做持久化 | 显隐/排序存本地或用户配置 |
| 多级表头递归报错 | VTableColumn 没判 children | 渲染前先判 children?.length |
| 一上来就做递归组件 | 业务还没验证 | 先 slot 验证再回收配置 |
| 列类型越来越胖 | 把展示与数据混一起 | 列类型只描述结构与渲染入口 |
| 操作列在树形里重复 | 忘了保留 slot | 递归列类型继续带 defaultSlot |
| 配置化列表想一步到位 | 抽象过度 | 按业务成熟度渐进封装 |
| 后端列和本地列冲突 | 没做合并策略 | 约定本地覆盖 / 后端优先规则 |