news 2026/10/23 21:57:48

Varlet Table 组件完全指南:从基础表格到固定表头与样式定制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Varlet Table 组件完全指南:从基础表格到固定表头与样式定制
  • UI组件
  • 前端

【免费下载链接】varlet

A Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.

项目地址:https://gitcode.com/gh_mirrors/va/varlet
点击查看免费下载

Varlet 的Table是一个刻意保持"极简"的数据表格组件:它不解析列配置、不处理排序筛选,只负责用 Material Design 的视觉语言把你写的原生<table>结构包裹成一张带卡片阴影、行悬停、滚动容器的标准表格。读完本文,你将掌握var-table的全部属性(full-width、elevation、plain、bordered、surface、scroller-height)与两个插槽的实际用法,理解固定表头背后的overflow+sticky实现原理,并能通过样式变量完成精细的视觉定制。

组件的整体结构

在动手写模板之前,先看一下 Table 在仓库中的组成(所有路径均相对于仓库根目录):

文件作用
packages/varlet-ui/src/table/Table.vue组件主文件,定义渲染结构与类名逻辑
packages/varlet-ui/src/table/props.ts全部 Prop 的类型定义与默认值
packages/varlet-ui/src/table/table.less样式实现与全部 CSS 变量默认值
packages/varlet-ui/src/table/index.ts安装逻辑,并暴露tableProps供全局默认值配置
packages/varlet-ui/types/table.d.tsTypeScript 类型声明(TableProps、Table)
packages/varlet-ui/src/table/tests/index.spec.js各 Prop 与插槽的单元/挂载测试
packages/varlet-ui/src/table/example/index.vue官方文档站对应的完整示例页

从源码结构看,Table的模板只有三层结构:最外层.var-table容器负责卡片形态(阴影、圆角、plain/bordered/surface-low 修饰),中间的.var-table__main是滚动容器(承载scroller-height),内部的<table class="var-table__table">通过默认插槽渲染用户提供的thead/tbody;若存在footer插槽,再在底部追加一个.var-table__footer区块。组件本身对单元格没有任何逻辑介入,排序、选择、展开等能力需要业务层自行组合,这也是它"极简"定位的体现。

[index.ts](https://link.gitcode.com/i/18ff7284daac3d87d5ebd24973ec0131)中除了标准的withInstall(Table),还调用了withPropsDefaultsSetter(Table, tableProps),因此可以在应用中通过Table.setPropsDefaults一次性修改组件的全局默认属性,而不必逐个传参。

基本使用

最直接的用法是把原生表格结构放进var-table。组件会套用--table-row-height(默认46px)的行高、--table-row-padding(默认8px 16px)的单元格内边距、表头下边框与行悬停背景等完整样式:

<template> <var-table> <thead> <tr> <th>姓名</th> <th>数学</th> <th>英语</th> </tr> </thead> <tbody> <tr> <td>耗子君</td> <td>124</td> <td>38</td> </tr> <tr> <td>火猫桑</td> <td>100</td> <td>135</td> </tr> </tbody> </var-table> </template>

对应的真实示例页见 example/index.vue。两点值得注意的细节来自 table.less:

  • .var-table__main上设置了overflow: auto且表格min-width: 100%,所以当列总宽超出容器时,横向滚动条出现在.var-table__main这一层,而不是破坏卡片外框;
  • 行悬停效果通过tr:hover { background: var(--table-tbody-tr-hover-background) }实现,带0.25s的背景过渡,td用background: inherit继承行背景,保证悬停色铺满整行。

卡片形态之外:plain、bordered 与 surface

默认形态下,Table是一张 Material 风格的"卡片"。Table.vue中类名的拼装逻辑如下:

[!plain, formatElevation(elevation, 1)] // plain 为真时不渲染 elevation 类 [surfaceLow, n('--surface-low')] [bordered, n('--bordered')] [plain, n('--plain')]

也就是说,plain不仅改变背景,还会取消阴影类(测试tests/index.spec.js 中明确断言:设置plain: true后不再包含var-elevation--1)。各形态的视觉效果由 table.less 中的修饰类切换 CSS 变量实现:

  • var-table--plain:将--table-background置为transparent、border-radius置0,行悬停背景换成更弱的--table-plain-row-hover-background(默认hsla(var(--hsl-on-surface), 0.04));
  • var-table--bordered:给容器加border: var(--table-border);同时--plain--bordered组合会恢复--table-border-radius的圆角;
  • var-table--surface-low:把--table-background与行悬停背景切换为--table-surface-low-background/--table-surface-low-row-hover-background,默认都指向 MD3 的var(--color-surface-container-highest),即"弱背景层级"。

纯表格(plain)

设置plain可以一并去掉卡片阴影、背景色和圆角,适合把表格嵌入已有卡片或列表容器内部:

<template> <var-table plain> <thead> <tr> <th>姓名</th> <th>数学</th> <th>英语</th> </tr> </thead> <tbody> <tr> <td>耗子君</td> <td>124</td> <td>38</td> </tr> <tr> <td>火猫桑</td> <td>100</td> <td>135</td> </tr> </tbody> </var-table> </template>

plain + bordered

同时使用plain和bordered,可以用外边框展示更纯粹的表格形态——背景全透明,仅靠--table-border描出轮廓:

<template> <var-table plain bordered> <thead> <tr> <th>姓名</th> <th>数学</th> <th>英语</th> </tr> </thead> <tbody> <tr> <td>耗子君</td> <td>124</td> <td>38</td> </tr> <tr> <td>火猫桑</td> <td>100</td> <td>135</td> </tr> </tbody> </var-table> </template>

弱背景色(surface="low")

通过surface="low"使用更接近 MD3 的弱背景层级。源码中surfaceLow是一个计算属性,当且仅当props.surface === 'low'时挂载--surface-low类,因此目前'low'是唯一合法取值(见 props.ts 中的TableSurface = 'low'):

<template> <var-table surface="low"> <thead> <tr> <th>姓名</th> <th>数学</th> <th>英语</th> </tr> </thead> <tbody> <tr> <td>耗子君</td> <td>124</td> <td>38</td> </tr> <tr> <td>火猫桑</td> <td>100</td> <td>135</td> </tr> </tbody> </var-table> </template>

三种形态的取舍可以概括为:卡片浮层用默认形态、嵌在既有容器内用plain、需要强轮廓线用plain bordered、需要主题色分层(深色主题下尤其明显)用surface="low"。

尾部插槽:挂载 Pagination 等页面级元素

footer插槽用于在表格主体下方插入内容,最常见的是插入var-pagination实现前端分页。Table.vue中用v-if="$slots.footer"控制.var-table__footer的渲染——没有使用时不产生任何 DOM(测试用例 "table should not render footer without footer slot" 验证了这一点),使用时则自带min-height: var(--table-row-height)与border-top: var(--table-footer-border-top):

<script setup> import { ref } from 'vue' const list = ref(gen(1, 10)) function gen(current, size) { return Array.from({ length: size }).map((_, index) => { const id = (current - 1) * size + index + 1 return { name: `Name ${id}`, math: id, english: id, } }) } function get(current, size) { list.value = gen(current, size) } </script> <template> <var-table> <thead> <tr> <th>姓名</th> <th>数学</th> <th>英语</th> </tr> </thead> <tbody> <tr v-for="l in list" :key="l.name"> <td>{{ l.name }}</td> <td>{{ l.math }}</td> <td>{{ l.english }}</td> </tr> </tbody> <template #footer> <div class="table-example-footer"> <var-pagination :simple="false" :current="1" :total="100" :size-option="[5, 10]" @change="get" /> </div> </template> </var-table> </template> <style> .table-example-footer { display: flex; justify-content: flex-end; align-items: center; height: 60px; padding: 0 16px; } </style>

一个容易踩坑的结构细节:默认插槽里的内容最终落在.var-table__table(<table>元素)内部,因此thead/tbody必须直接写在默认插槽中;而footer插槽位于滚动容器之外,放在其中的分页器在纵向滚动时始终可见,不会被表格内容推走。

固定表头:scroller-height 与 sticky 的组合

数据量大的表格通常需要"局部纵向滚动 + 表头吸顶"。Table的做法是把scroller-height作为max-height内联到.var-table__main(该元素本身就是overflow: auto的滚动容器),再由用户给thead加上position: sticky; top: 0。浏览器原生支持在table-header-group上吸附,配合 table.less 中thead { display: table-header-group }的显式声明即可生效:

<script setup> import { ref } from 'vue' const list = ref(gen(1, 10)) function gen(current, size) { return Array.from({ length: size }).map((_, index) => { const id = (current - 1) * size + index + 1 return { name: `Name ${id}`, math: id, english: id, } }) } function get(current, size) { list.value = gen(current, size) } </script> <template> <var-table scroller-height="400px"> <thead style="position: sticky; top: 0"> <tr> <th>姓名</th> <th>数学</th> <th>英语</th> </tr> </thead> <tbody> <tr v-for="l in list" :key="l.name"> <td>{{ l.name }}</td> <td>{{ l.math }}</td> <td>{{ l.english }}</td> </tr> </tbody> <template #footer> <div class="table-example-footer"> <var-pagination :simple="false" :current="1" :total="100" :size-option="[5, 10]" @change="get" /> </div> </template> </var-table> </template> <style> .table-example-footer { display: flex; justify-content: flex-end; align-items: center; height: 60px; padding: 0 16px; } </style>

实现原理可以拆解为三步:

  1. scroller-height经toSizeUnit转换后写入.var-table__main的max-height。toSizeUnit的定义在 utils/elements.ts:数字值自动补px单位(如400→400px),字符串则原样透传,所以scroller-height="400px"与scroller-height="400"等价;
  2. .var-table__main天生是overflow: auto容器,内容超出max-height即在卡片内部出现纵向滚动条,而不是撑开整个页面;
  3. thead声明position: sticky; top: 0后,在滚动容器内滚动时表头行钉在容器顶部。表头行背景为--table-background(非 plain 模式下不透明),滚过的行内容不会从表头下透出。

测试用例tests/index.spec.js 分别用scrollerHeight: '300px'与scrollerHeight: 300两种入参,断言.var-table__main的style中包含max-height: 300px,覆盖了字符串与数字两种类型。

scroller-height是3.2.0版本引入的 Prop,在旧版本中需要用外部容器限制高度来近似实现同样效果。

API 参考

Props

来自 props.ts 与 types/table.d.ts:

Prop描述类型默认值
full-widthtable的宽度(包含可滚动部分)string | number100%
elevation海拔高度(阴影等级),可选值为truefalse和0-24的等级string | number | booleantrue
plain是否以纯表格形态渲染,不带卡片阴影、背景色和圆角booleanfalse
bordered是否显示外边框booleanfalse
surface弱背景色风格,仅支持'low''low'-
scroller-height3.2.0滚动容器最大高度,可用于实现纵向局部滚动、固定表头等功能string | number-

几个与源码对应的行为细节:

  • full-width最终作为内联width写到.var-table__table上,同样经toSizeUnit处理;默认100%表示表格至少占满容器(less 中还声明了min-width: 100%),传入更大的固定宽度(如full-width="1200")可让窄屏下出现横向滚动;
  • elevation的处理逻辑在 utils/components.ts 的formatElevation:false时返回null(完全无阴影类);true时被Table传入的默认等级1替代,即挂载var-elevation--1;数字/字符串等级则生成var-elevation--N,对应的box-shadow来自 styles/elevation.less 中0到24的全套定义;且plain为真时 elevation 类被整体跳过,阴影自然消失;
  • 测试用例tests/index.spec.js 对以上行为均有断言:默认渲染包含var-elevation--1类、elevation: 3切换为var-elevation--3、fullWidth: 200时.var-table__table内联样式为width: 200px。

Slots

名称描述参数
defaultTable 的内容,须为合法的表格结构(thead/tbody等)-
footerTable 尾部内容,位于滚动容器之外-

样式变量

以下为组件使用的 CSS 变量,可以通过 StyleProvider 组件进行样式定制。变量默认值集中定义在 table.less 的:root块中:

变量名默认值作用位置
--table-background#fff容器与thead/tbody行背景、footer 背景
--table-surface-low-backgroundvar(--color-surface-container-highest)surface="low"时的表格背景
--table-surface-low-row-hover-backgroundvar(--color-surface-container-highest)surface="low"时的行悬停背景
--table-plain-row-hover-backgroundhsla(var(--hsl-on-surface), 0.04)plain时的行悬停背景
--table-border-radius2px卡片圆角(plain时为0)
--table-borderthin solid var(--color-outline)bordered时的外边框
--table-thead-border-bottomthin solid var(--color-outline)表头下边框
--table-thead-th-text-colorrgba(0, 0, 0, 0.6)表头文字颜色
--table-thead-th-text-alignleft表头对齐方式
--table-thead-th-font-size14px表头字号
--table-thead-th-font-weight500表头字重
--table-thead-tr-border-bottomthin solid var(--color-outline)多行表头中非末行行边框
--table-tbody-tr-hover-background#f5f5f5数据行悬停背景
--table-tbody-tr-border-bottomthin solid var(--color-outline)数据行分隔线
--table-tbody-td-text-color#555单元格文字颜色
--table-tbody-td-font-size14px单元格字号
--table-tbody-td-text-alignleft单元格对齐方式
--table-row-height46pxth/td高度,也是 footer 的min-height
--table-row-padding8px 16pxth/td内边距
--table-footer-border-topthin solid var(--color-outline)footer 上边框

由于这些变量是层层覆盖的设计(例如--plain修饰类只是重写了--table-background和悬停色,并未复制整套规则),定制时遵循"改变量而不覆盖类名"的原则,plain、surface="low"等形态就能自动继承你的定制值。

小结

var-table的设计边界非常清晰:它提供 Material 风格的视觉外壳(卡片阴影、行悬停、分隔线、MD3 弱背景)、可控的滚动容器(scroller-height)和页面级扩展点(footer插槽),而把表格内容组织、数据获取完全留给使用者。当你只需要"一张长得体面的表"时,默认形态 + 样式变量已经足够;当需要固定表头、嵌入分页、与主题色分层联动时,上文给出的sticky表头与surface="low"组合可以直接照搬。更复杂的数据网格场景(列配置驱动、排序、跨行合并等)则建议评估仓库中另外的>

  • UI组件
  • 前端

【免费下载链接】varlet

A Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.

项目地址:https://gitcode.com/gh_mirrors/va/varlet
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 21:57:28

圆圈挑战:基于Fitts定律的视觉认知负荷测量工具

1. 项目概述&#xff1a;一个被低估的视觉认知训练原型“圆圈挑战”这个词最近在多个内容平台反复出现&#xff0c;但几乎没人说清楚它到底是什么。我第一次见到它&#xff0c;是在某高校人机交互实验室的内部测试记录里——不是短视频里的花式转圈&#xff0c;也不是健身App里…

作者头像 李华
网站建设 2026/10/22 10:46:07

高德鸿蒙AI Native规模化交付:DevEco CLI工程化实战解析

1. 项目背景&#xff1a;为什么高德会把宝押在鸿蒙上这两年鸿蒙从一个“手机系统”逐步长成了“全场景生态”&#xff0c;这个趋势做移动端的人应该都有体感。高德作为国民级地图应用&#xff0c;用户基数和使用场景摆在那里&#xff0c;适配鸿蒙不是一道选择题&#xff0c;而是…

作者头像 李华
网站建设 2026/10/21 5:07:31

YOLO11花卉图像分割识别:从数据集标注到PyQt界面部署全流程

简介&#xff1a;本资源面向深度学习与计算机视觉方向的开发者、学生及研究人员&#xff0c;提供一套基于Python与PyTorch的花卉图像分割识别完整项目&#xff0c;核心采用YOLOv11算法实现目标检测与图像分割&#xff0c;并集成PyQt界面支持摄像头实时识别。压缩包共726个文件&…

作者头像 李华
网站建设 2026/10/23 9:45:22

SpringBoot家政预约系统架构设计与实践

1. 项目背景与核心价值家政保洁预约管理系统是近年来随着"懒人经济"和"宅经济"兴起而快速发展的数字化解决方案。作为一名经历过多个企业级项目开发的Java工程师&#xff0c;我发现传统家政行业普遍存在预约流程混乱、服务人员调度低效、客户体验差等痛点。…

作者头像 李华