{T}

固定列、流体高度与多级表头递归封装

概述

扩展基础表格时,最稳妥的方式是沿 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 透传方向是对的。操作列固定到右侧是后台表格非常常见的真实需求。

ts
const columns = [
  { prop: "date", label: "Date", fixed: true },
  { label: "Operations", width: 120, fixed: "right" }
]

三、流体高度不是固定高度的替代品

流体高度是给表格设置 max-height:数据变多时只增长到这个上限,超过之后内部滚动。与固定高度(一开始就锁死高度)的差异在于,它先随内容增长、增长到上限后再内部滚动。流体高度更适合数据条数浮动较大的表格区,数据少时更紧凑,数据多时也不会无限拉长,是后台列表页非常实用的中间方案。

Vue SFC
<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 验证,再回收成配置驱动。

ts
const columns = [{
  label: "Address Info",
  children: [
    { prop: "state", label: "State" },
    { prop: "city", label: "City" }
  ]
}]

七、抽 VTableColumn 递归组件收口列树渲染

把原本写在 VTable 里的那一大坨 el-table-column 抽成独立递归组件 VTableColumn,是自然的演进——一旦支持 childrenheaderSlotdefaultSlot,列结构本身已值得成为独立组件。VTable 负责表格容器,VTableColumn 负责列树渲染,职责更清晰,也意味着 VTable 内部结构开始成熟。

Vue SFC
<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 原始列类型的简单别名,而是进入项目自己的扩展模型:除 proplabelwidthfixed 外,还可能有 childrendefaultSlotheaderSlot。用 JSON 结构表达多级表头,真正价值是把"表格列模板"数据化——一旦列结构数据化,后续就能做权限控制哪些列显示、后端返回列配置、列拖拽排序、列持久化显示隐藏。这一步通常是 VTable 组件类型系统正式成长的节点。

九、列数据化后的延伸:权限、下发与持久化

列结构一旦是 JSON,就能被程序驱动而不只是手写。最常见的三个延伸:一是列权限,按角色过滤 columns 里某些敏感列;二是后端下发,列表页的列配置由接口返回,前端不再硬编码;三是显隐与排序持久化,用户拖拽 / 隐藏列后把结果存到本地或用户配置。这些都建立在“列即数据”之上,也是 VTable 从组件走向“配置化列表方案”的关键一步。

ts
// 按权限过滤列
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
配置化列表想一步到位抽象过度按业务成熟度渐进封装
后端列和本地列冲突没做合并策略约定本地覆盖 / 后端优先规则

延伸阅读