开发预览 更新于 2026-05-10

Table 表格

数据表格 —— 多列排序、列拖拽 / 调宽、固定列、过滤、分页、行展开、树形数据、总计行、粘性表头、全局搜索 与 列可见性。

CfTable 是组件库里功能最完整的”主力”组件之一。设计取舍:

  • 客户端模式默认派生 —— 全局搜索、列过滤、排序、分页全部在组件内完成;改成服务端模式只需要给 pagination.total 一个数字。
  • 列状态非受控为主 —— 隐藏 / 顺序 / 宽度走内置 state,必要时可通过 update:columnsState 全受控。
  • 0 第三方依赖 —— HTML5 DnD 处理列拖拽,PointerEvent 处理调宽,原生 position: sticky 处理固定列与表头。

基础用法

columns + rows + 可选 rowKeycolumn.format 做展示格式化,column.render 接收渲染函数返回 VNode / 字符串。

订单号客户金额状态日期
O-1042Alice Chen¥1,280已支付2026-05-09
O-1041Bob Wang¥698已发货2026-05-08
O-1040Carol Liu¥3,450处理中2026-05-08
O-1039Dave Zhang¥218已退款2026-05-07

选择行

单选 selectable="single" / 多选 selectable="multiple"v-model 接受 stringstring[],由 rowKey 决定。

姓名角色团队
Alice前端Web
Bob后端API
Carol设计UX
Dave运维Infra
选中:["1","3"]

多列排序

打开 multi-sort 后,按住 Shift / 点列头追加次级排序。同列再次点击循环 asc → desc → 取消。表头显示当前列在排序队列中的序号。

仓库语言1Stars2Forks
facebook/reactJavaScript232,00047,800
preactjs/preactJavaScript36,8002,050
angular/angularTypeScript96,10025,400
sveltejs/svelteTypeScript81,0004,200
vuejs/coreTypeScript47,8008,200
solidjs/solidTypeScript32,4001,100
lit/litTypeScript19,200980

点列头切换排序;按住 Shift 再点别的列追加次级排序。当前:按语言升序,再按 stars 降序。

排序优先级 = sort 数组顺序。完全受控时把 :sort 绑到自己的 ref 上即可。

拖拽调宽 + 拖拽换序

打开 resizable 后每列右边出现 6px 的 col-resize 区。拖拽实时更新内部 state,@update:columns-state 把新宽度送出来用于持久化。reorderable 让表头可拖拽换序。

包名版本周下载体积许可证
react19.0.128,940,0006.1 KBMIT
vue3.5.344,920,00036.8 KBMIT
lodash4.17.2153,200,00024.3 KBMIT
axios1.7.451,800,00013.4 KBMIT
tailwindcss4.1.012,400,000MIT

↔ 拖拽列右边缘改宽度;按住表头拖到目标列改顺序。

单列可以用 resizable: false / reorderable: false 屏蔽。

固定列 + 粘性表头

column.fixed: 'left' | 'right' 把列钉在两侧,超长内容横向滚动时仍可见。配合 sticky-header + :height 表头同时粘顶。

ID主机名RegionCPU内存磁盘网络在线状态
srv-1000web-1.us-east.protoforge.ious-east-130%50%20%1.0 GB/s42 天running
srv-1001web-2.eu-west.protoforge.ioeu-west-137%61%25%4.2 GB/s45 天idle
srv-1002web-3.ap-south.protoforge.ioap-south-144%72%30%7.4 GB/s48 天warning
srv-1003web-1.us-east.protoforge.ious-east-151%83%35%1.6 GB/s51 天running
srv-1004web-2.eu-west.protoforge.ioeu-west-158%54%40%4.8 GB/s54 天idle
srv-1005web-3.ap-south.protoforge.ioap-south-165%65%45%7.1 GB/s57 天warning
srv-1006web-1.us-east.protoforge.ious-east-172%76%50%1.3 GB/s60 天running
srv-1007web-2.eu-west.protoforge.ioeu-west-179%87%55%4.5 GB/s63 天idle
srv-1008web-3.ap-south.protoforge.ioap-south-186%58%60%7.7 GB/s66 天warning
srv-1009web-1.us-east.protoforge.ious-east-133%69%65%1.0 GB/s69 天running
srv-1010web-2.eu-west.protoforge.ioeu-west-140%80%70%4.2 GB/s72 天idle
srv-1011web-3.ap-south.protoforge.ioap-south-147%51%75%7.4 GB/s75 天warning

左侧 ID 和右侧 状态 列被钉住;中间区域可横向滚动;表头粘在顶部。

行展开 + 树形数据

expandable 给每行加展开按钮:

  • expand-render —— 渲染自定义展开内容(订单详情、嵌套图表等)。
  • 行数据自带 children —— 自动当作树形数据递归渲染,每层 tree-indent 像素的缩进。
名称大小最近修改
src/2026-05-09
components/2026-05-09
Table.vue32.4 KB2026-05-09
variants.ts8.2 KB2026-05-09
index.ts1.1 KB2026-05-08
dist/2026-05-10
README.md4.2 KB2026-05-09

v-model:expanded-row-keys 把展开状态外置到父组件,方便保存到 URL 或 localStorage。

工具栏 + 总计行 + 分页 + 过滤

toolbar="auto" 打开就出现全局搜索与列可见性菜单。column.filterable 给每列加漏斗按钮,filterType 切到 select / number-range / 默认文本。column.summary'sum' | 'avg' | 'count' 或函数;表底自动出一行总计,跟着过滤结果实时变。

订单号产品套餐单价数量合计
S-10000Pro · 月付Pro¥121¥12
S-10001Pro · 年付Pro¥1202¥240
S-10002Team · 月付Team¥493¥147
S-10003Team · 年付Team¥4804¥1,920
S-10004Enterprise · 年付Enterprise¥1,2905¥6,450
S-10005Pro · 月付Pro¥126¥72
S-10006Pro · 年付Pro¥1207¥840
S-10007Team · 月付Team¥491¥49
S-10008Team · 年付Team¥4802¥960
S-10009Enterprise · 年付Enterprise¥1,2903¥3,870
S-10010Pro · 月付Pro¥124¥48
S-10011Pro · 年付Pro¥1205¥600
S-10012Team · 月付Team¥496¥294
S-10013Team · 年付Team¥4807¥3,360
S-10014Enterprise · 年付Enterprise¥1,2901¥1,290
S-10015Pro · 月付Pro¥122¥24
S-10016Pro · 年付Pro¥1203¥360
S-10017Team · 月付Team¥494¥196
S-10018Team · 年付Team¥4805¥2,400
S-10019Enterprise · 年付Enterprise¥1,2906¥7,740
S-10020Pro · 月付Pro¥127¥84
S-10021Pro · 年付Pro¥1201¥120
S-10022Team · 月付Team¥492¥98
S-10023Team · 年付Team¥4803¥1,440
24 行平均 ¥35390¥32,614

工具栏自带全局搜索;点列头漏斗图标做单列过滤;总计行自动求和 / 平均 / 计数;分页跟随过滤后结果。

:default-pagination="{ page: 1, pageSize: 8 }" 是非受控分页。完全受控用 pagination 绑当前对象,并监听 @update:pagination

虚拟滚动

打开 virtual 后表体只渲染当前可见的行 + 上下 overscan 缓冲,DOM 节点数恒定在几十个。要求所有行使用固定 row-height(缺省 36px)。建议配合 :height + sticky-header

数据集大小:50,000 行 · 实际渲染的 DOM 节点数始终 ~30。

订单 ID币对价格数量方向时间
T-000000BTC$ 1,0000.13sell07:49:04
T-000001ETH$ 1,039.980.26buy07:49:03
T-000002SOL$ 1,079.870.39buy07:49:02
T-000003AVAX$ 1,119.550.52sell07:49:01
T-000004DOT$ 1,158.940.65buy07:49:00
T-000005MATIC$ 1,197.920.78buy07:48:59
T-000006LINK$ 1,236.420.91sell07:48:58
T-000007ARB$ 1,274.321.04buy07:48:57
T-000008BTC$ 1,311.531.17buy07:48:56
T-000009ETH$ 1,347.971.30sell07:48:55
T-000010SOL$ 1,383.541.43buy07:48:54
T-000011AVAX$ 1,418.151.56buy07:48:53
T-000012DOT$ 1,451.711.69sell07:48:52
T-000013MATIC$ 1,484.151.82buy07:48:51
T-000014LINK$ 1,515.371.95buy07:48:50
T-000015ARB$ 1,545.312.08sell07:48:49
T-000016BTC$ 1,573.882.21buy07:48:48
T-000017ETH$ 1,601.022.34buy07:48:47
T-000018SOL$ 1,626.662.47sell07:48:46

50000 行的数据集只渲染 ~30 个 <tr>。继续兼容排序、过滤、固定列、全局搜索。注意:虚拟模式下 expandable 的展开行高也被固定,复杂的展开内容请用 Modal 替代或关闭虚拟。

行拖拽换序

row-reorderable 在最左侧加一列 ⋮⋮ 拖把柄。拖动整行换序,@update:rows 把新数组送出来;想监听 from/to 索引就用 @row-reorder

步骤负责人预计状态
需求评审Alice0.5ddone
设计稿Bob2din-progress
API 草案Carol1din-progress
前端实现Dave3dtodo
联调Eve1dtodo
测试 + 上线Frank0.5dtodo

左侧的 ⋮⋮ 把柄可拖动整行换序;上层通过 @update:rows 拿到新数组。

仅作用在顶层(level 0)行;树形子行自身不能拖。

内联编辑

给列加 editable: true + 可选 editType: 'text' | 'number' | 'select',双击单元格进入编辑:

  • editValidate 拦截非法值,校验失败时保留编辑态。
  • 提交时机:Enter / blur;取消:Esc。
  • @cell-edit 抛出 { row, column, oldValue, newValue, index },由你写回数据源。
姓名角色配额
Janeadmin5
Bobeditor3
Aliceviewer1

双击单元格进入编辑:text / select / number。Enter / blur 提交,Esc 取消。 数字列加了 editValidate,负数会被拒绝。

组件不直接 mutate 你的 rows:所有提交都通过 cell-edit 事件,业务方决定要不要写回、是否调接口、是否合并。

CSV 导出

exportable 让工具栏出现 Export 按钮,点击导出当前过滤后的结果。导出文件含 UTF-8 BOM,Excel 直接打开不乱码。

  • 跳过某列:column.exportable: false
  • 自定义单元格序列化:column.exportRender(value, row, i) => string
  • 文件名:export-file-name,缺省 'table'
  • 完全自定义:@export 事件接到 CSV 字符串,自己 download 或上传到服务端。
  • 命令式调用:组件 exportCsv() 已通过 defineExpose 暴露。
发票号客户产品金额状态日期
INV-001Acme Inc.Pro Plan · 年付¥12,000已支付2026-04-01
INV-002GlobexTeam · 月付¥480已支付2026-04-12
INV-003InitechEnterprise · 年付¥156,000待支付2026-04-15
INV-004VandelayPro Plan · 月付¥120已退款2026-04-22

工具栏右边出现 Export 按钮;导出的是当前过滤 / 搜索后的结果,文件名带 BOM 的 UTF-8 CSV。 amount 列指定了 exportRender,导出时输出原始数字而不是格式化后的 ¥。

列状态持久化

persistKey="<key>" 让组件把当前 columnsState(隐藏 / 顺序 / 宽度)写到 localStorage['cf-table:<key>'],下次访问自动恢复。

ID姓名角色团队
1Jane LiuEngineerPayments
2Bob WangDesignerBrand
3Alice ChenPMGrowth
4Tim WongEngineerPlatform
5Sue ZhangEngineerPayments

调宽 / 拖列 / 隐藏列 后刷新页面,状态从 localStorage['cf-table:people-list'] 恢复。

完全受控时(外部传 :columns-state)会跳过自动读写,这时由你决定怎么持久化。

自动 rowSpan 合并

给某列加 mergeRows: true,连续相同值的单元格会自动合并 rowSpan。需要更细粒度的合并条件可以传函数:mergeRows: (cur, prev) => cur.groupId === prev.groupId

项目分支步骤状态耗时
webmain安装依赖OK8s
类型检查OK11s
单测OK24s
feat/login安装依赖OK8s
单测FAIL6s
apimain编译OK14s
集成测试OK38s

项目 + 分支两列开了 mergeRows: true,连续相同值自动合并 rowSpan。

合并不会跨树形层级(不同 parentKey 的子行不会被合并到一起)。

单元格选区 + 一键复制

cell-selectable 让表格行为接近 Excel:

  • 单击单元格 → 选中
  • Shift 单击 / 鼠标拖动 → 矩形选区
  • Ctrl/⌘ + C → 复制为 TSV 到剪贴板,可以直接粘到 Excel / Numbers / Google Sheets
姓名数学英语物理化学生物
张三9288798491
李四8591888679
王五7895817385
赵六9682949088
钱七7379718075

像 Excel 一样:单击选单元格、Shift 单击 / 拖动扩展矩形选区,Ctrl/⌘ + C 复制为 TSV,可以直接粘到 Excel / Numbers。

column.format 会被尊重 —— 复制出去的就是显示的字符串。

列虚拟化(双向虚拟化)

100+ 列且每列内容简单(如二维数据矩阵)时,启用 col-virtual + 默认 col-width 让横向滚动也只渲染可见列;和 virtual 一起开就是双向虚拟化。

数据集:200 行 × 121 列 · 行 + 列双向虚拟化,DOM < 800 个节点。

IDc0c1c2c3c4c5c6c7c8c9c10c11c12c13c14c15c16c17c18c19c20c21c22c23c24c25c26c27c28c29c30c31c32c33c34c35c36c37c38c39c40c41c42c43c44c45c46c47c48c49c50c51c52c53c54c55c56c57c58c59c60c61c62c63c64c65c66c67c68c69c70c71c72c73c74c75c76c77c78c79c80c81c82c83c84c85c86c87c88c89c90c91c92c93c94c95c96c97c98c99c100c101c102c103c104c105c106c107c108c109c110c111c112c113c114c115c116c117c118c119
R-00000714212835424956637077849198105112119126133140147154161168175182189196203210217224231238245252259266273280287294301308315322329336343350357364371378385392399406413420427434441448455462469476483490497504511518525532539546553560567574581588595602609616623630637644651658665672679686693700707714721728735742749756763770777784791798805812819826833
R-000113202734414855626976839097104111118125132139146153160167174181188195202209216223230237244251258265272279286293300307314321328335342349356363370377384391398405412419426433440447454461468475482489496503510517524531538545552559566573580587594601608615622629636643650657664671678685692699706713720727734741748755762769776783790797804811818825832839846
R-00022633404754616875828996103110117124131138145152159166173180187194201208215222229236243250257264271278285292299306313320327334341348355362369376383390397404411418425432439446453460467474481488495502509516523530537544551558565572579586593600607614621628635642649656663670677684691698705712719726733740747754761768775782789796803810817824831838845852859
R-0003394653606774818895102109116123130137144151158165172179186193200207214221228235242249256263270277284291298305312319326333340347354361368375382389396403410417424431438445452459466473480487494501508515522529536543550557564571578585592599606613620627634641648655662669676683690697704711718725732739746753760767774781788795802809816823830837844851858865872
R-000452596673808794101108115122129136143150157164171178185192199206213220227234241248255262269276283290297304311318325332339346353360367374381388395402409416423430437444451458465472479486493500507514521528535542549556563570577584591598605612619626633640647654661668675682689696703710717724731738745752759766773780787794801808815822829836843850857864871878885
R-00056572798693100107114121128135142149156163170177184191198205212219226233240247254261268275282289296303310317324331338345352359366373380387394401408415422429436443450457464471478485492499506513520527534541548555562569576583590597604611618625632639646653660667674681688695702709716723730737744751758765772779786793800807814821828835842849856863870877884891898
R-000678859299106113120127134141148155162169176183190197204211218225232239246253260267274281288295302309316323330337344351358365372379386393400407414421428435442449456463470477484491498505512519526533540547554561568575582589596603610617624631638645652659666673680687694701708715722729736743750757764771778785792799806813820827834841848855862869876883890897904911
R-00079198105112119126133140147154161168175182189196203210217224231238245252259266273280287294301308315322329336343350357364371378385392399406413420427434441448455462469476483490497504511518525532539546553560567574581588595602609616623630637644651658665672679686693700707714721728735742749756763770777784791798805812819826833840847854861868875882889896903910917924
R-0008104111118125132139146153160167174181188195202209216223230237244251258265272279286293300307314321328335342349356363370377384391398405412419426433440447454461468475482489496503510517524531538545552559566573580587594601608615622629636643650657664671678685692699706713720727734741748755762769776783790797804811818825832839846853860867874881888895902909916923930937
R-0009117124131138145152159166173180187194201208215222229236243250257264271278285292299306313320327334341348355362369376383390397404411418425432439446453460467474481488495502509516523530537544551558565572579586593600607614621628635642649656663670677684691698705712719726733740747754761768775782789796803810817824831838845852859866873880887894901908915922929936943950
R-0010130137144151158165172179186193200207214221228235242249256263270277284291298305312319326333340347354361368375382389396403410417424431438445452459466473480487494501508515522529536543550557564571578585592599606613620627634641648655662669676683690697704711718725732739746753760767774781788795802809816823830837844851858865872879886893900907914921928935942949956963
R-0011143150157164171178185192199206213220227234241248255262269276283290297304311318325332339346353360367374381388395402409416423430437444451458465472479486493500507514521528535542549556563570577584591598605612619626633640647654661668675682689696703710717724731738745752759766773780787794801808815822829836843850857864871878885892899906913920927934941948955962969976
R-0012156163170177184191198205212219226233240247254261268275282289296303310317324331338345352359366373380387394401408415422429436443450457464471478485492499506513520527534541548555562569576583590597604611618625632639646653660667674681688695702709716723730737744751758765772779786793800807814821828835842849856863870877884891898905912919926933940947954961968975982989
R-00131691761831901972042112182252322392462532602672742812882953023093163233303373443513583653723793863934004074144214284354424494564634704774844914985055125195265335405475545615685755825895966036106176246316386456526596666736806876947017087157227297367437507577647717787857927998068138208278348418488558628698768838908979049119189259329399469539609679749819889953
R-00141821891962032102172242312382452522592662732802872943013083153223293363433503573643713783853923994064134204274344414484554624694764834904975045115185255325395465535605675745815885956026096166236306376446516586656726796866937007077147217287357427497567637707777847917988058128198268338408478548618688758828898969039109179249319389459529599669739809879942916
R-001519520220921622323023724425125826527227928629330030731432132833534234935636337037738439139840541241942643344044745446146847548248949650351051752453153854555255956657358058759460160861562262963664365065766467167868569269970671372072773474174875576276977678379079780481181882583283984685386086787488188889590290991692393093794495195896597297998699318152229
R-0016208215222229236243250257264271278285292299306313320327334341348355362369376383390397404411418425432439446453460467474481488495502509516523530537544551558565572579586593600607614621628635642649656663670677684691698705712719726733740747754761768775782789796803810817824831838845852859866873880887894901908915922929936943950957964971978985992071421283542
R-0017221228235242249256263270277284291298305312319326333340347354361368375382389396403410417424431438445452459466473480487494501508515522529536543550557564571578585592599606613620627634641648655662669676683690697704711718725732739746753760767774781788795802809816823830837844851858865872879886893900907914921928935942949956963970977984991998613202734414855
R-00182342412482552622692762832902973043113183253323393463533603673743813883954024094164234304374444514584654724794864935005075145215285355425495565635705775845915986056126196266336406476546616686756826896967037107177247317387457527597667737807877948018088158228298368438508578648718788858928999069139209279349419489559629699769839909975121926334047546168

200 行 × 121 列 = 24200 单元格,DOM 实际不到 800 个节点。注意:列虚拟化下 column.fixed 仍然钉住,但要求被钉的列总宽不要超过视口的 1/3,否则可视区会很拥挤。

树形拖拽(跨父节点)

row-reorderable 之上加 tree-reorderable,拖拽行为升级为 Excel / Finder / 看板风格的”上下/中间”三段:

  • 拖到目标行上 1/4 → 放在它前面(同父)
  • 拖到目标行下 1/4 → 放在它后面(同父)
  • 拖到中间一半 → 拖目标行作为它的子节点

不会让你把祖先放进自己的后代里。

名称大小
src/
components/
Table.vue32 KB
Modal.vue8 KB
utils/
dom.ts4 KB
index.ts1 KB
tests/
table.spec.ts12 KB

拖某行的 ⋮⋮ 把柄到目标行:上 1/4 = 放到上面,下 1/4 = 放到下面,中间 = 拖**进**目标行做子节点。

@tree-reorder 事件给到 { fromKey, toKey, pos: 'above' | 'below' | 'inside', rows },业务方可以决定要不要持久化到后端。

TSV 反向粘贴

cell-selectable 上再加 cell-pastable:单击或框选起点单元格,按 Ctrl/⌘ + V 粘贴整片 TSV / CSV,从起点开始按行写回;column.editable 控制每格能否写、column.editValidate 拦截非法值。

SKU价格库存
SKU-100010050
SKU-100111346
SKU-100212642
SKU-100313938
SKU-100415234
SKU-100516530
SKU-100617826
SKU-100719122

操作步骤:① 单击任意单元格作为粘贴起点(或者 Shift 框选一片范围)。② 在 Excel / Numbers / Google Sheets 里复制一片单元格。③ 回到这里按 Ctrl/⌘ + V,TSV 自动按起点写回; editValidate 不通过的格子被跳过;负数库存会被拒绝。

@cell-paste 事件返回 { applied, skipped },可以拿来给个 toast 提示。

历史撤销

history-enabled 把 sort / filter / search / pagination / columnsState 的每一次变化记到内存栈,监听 Ctrl/⌘ + Z 撤销、Ctrl/⌘ + Shift + Z / Ctrl + Y 重做。history-depth 控制最多保留多少步(默认 50)。

ID客户金额状态
O-1000Alice¥100paid
O-1001Bob¥137pending
O-1002Carol¥174refunded
O-1003Dave¥211paid
O-1004Eve¥248pending
O-1005Frank¥285refunded
O-1006Alice¥322paid
O-1007Bob¥359pending
O-1008Carol¥396refunded
O-1009Dave¥433paid
O-1010Eve¥470pending
O-1011Frank¥507refunded
O-1012Alice¥544paid
O-1013Bob¥581pending
O-1014Carol¥618refunded
O-1015Dave¥655paid
O-1016Eve¥692pending
O-1017Frank¥729refunded
O-1018Alice¥766paid
O-1019Bob¥803pending
O-1020Carol¥840refunded
O-1021Dave¥877paid
O-1022Eve¥914pending
O-1023Frank¥951refunded
O-1024Alice¥988paid
O-1025Bob¥125pending
O-1026Carol¥162refunded
O-1027Dave¥199paid
O-1028Eve¥236pending
O-1029Frank¥273refunded

点列头 / 改过滤 / 翻页都进历史栈。Ctrl/⌘ + Z 撤销,Ctrl/⌘ + Shift + Z 重做。 当前:可撤销 = · 可重做 =

@history-change 事件吐 { canUndo, canRedo },方便业务方亮 / 灭对应按钮。组件实例上还暴露 undo() / redo() 命令式调用。

变高行虚拟滚动

virtual 默认要求所有行高一致。如果每行高度不固定(比如 body 是 markdown 渲染、有多张图片),传 :get-row-height="(idx) => 42" 让组件按累计偏移做二分查找定位可见窗口。

1000 行 · 每行高度通过 get-row-height 函数计算(短的 42px,长的 ~120px)。

ID标题正文
N-0Note #1lorem ipsum dolor sit amet
N-1Note #2lorem ipsum dolor sit amet lorem ipsum dolor sit amet
N-2Note #3lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet
N-3Note #4lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet
N-4Note #5lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet
N-5Note #6lorem ipsum dolor sit amet
N-6Note #7lorem ipsum dolor sit amet lorem ipsum dolor sit amet
N-7Note #8lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet
N-8Note #9lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet
N-9Note #10lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet
N-10Note #11lorem ipsum dolor sit amet
N-11Note #12lorem ipsum dolor sit amet lorem ipsum dolor sit amet
N-12Note #13lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet
N-13Note #14lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet

注意:getRowHeight 必须纯函数,组件会在每次行数变化时重算累计偏移。如果高度真的需要等 DOM 渲染后才知道,目前的建议是先返回一个估值,DOM 测量完成后再 setState 改回真实值;后续会出 auto-row-height 模式自动用 ResizeObserver。

行编辑模式

edit-mode="row" 打开,双击任意行进入整行编辑:所有 editable 列同时变为 input,行尾单独多出一行 Save / Cancel 按钮。Enter 提交、Esc 取消,校验失败保留编辑态。

姓名邮箱角色配额
Jane Liu[email protected]admin5
Bob Wang[email protected]editor3
Alice Chen[email protected]viewer1

双击任意行进入整行编辑:所有可编辑列同时变成 input。底部出现 Save / Cancel;Enter 也可提交,Esc 取消。

每个 cell 改动都通过 @cell-edit 事件抛出(与单格编辑一致),业务方决定是否调接口、是否合并提交。组件不 mutate rows

批量操作工具栏

batch-actions + selectable="multiple":选中至少一行后,表头上方出现 sticky 的批量操作条,自带 计数 / 导出选中 / 清空,可以通过 #batch-actions 插槽塞自定义按钮(接到 selectedKeys)。

标题负责人截止状态
Task #1 · fix bugAlice2026-05-10todo
Task #2 · design reviewBob2026-05-11doing
Task #3 · API draftCarol2026-05-12done
Task #4 · merge PRDave2026-05-13todo
Task #5 · release notesAlice2026-05-14doing
Task #6 · fix bugBob2026-05-15done
Task #7 · design reviewCarol2026-05-16todo
Task #8 · API draftDave2026-05-17doing
Task #9 · merge PRAlice2026-05-18done
Task #10 · release notesBob2026-05-19todo
Task #11 · fix bugCarol2026-05-20doing
Task #12 · design reviewDave2026-05-21done

勾选任意行后顶部出现批量操作条。内置 "导出选中" / "清空",再加上业务自定义的 "标记完成" / "删除"。

冻结行

pinned-row-keys 的列表里所有 rowKey 会置顶并 sticky 到表体顶部,下面的内容滚动时它们一直可见。常用于”自选”、“高优先级”等需要永远先看到的行。

币对价格24h成交量
BTC/USDT$ 67,842+1.24%32.4B
ETH/USDT$ 3,521-0.82%18.9B
SOL/USDT$ 184.1+3.41%4.7B
AVAX/USDT$ 42.7+0.93%780M
DOT/USDT$ 7.31-1.04%420M
MATIC/USDT$ 0.84-2.18%690M
LINK/USDT$ 18.4+0.21%510M
ARB/USDT$ 0.94+1.92%320M
ATOM/USDT$ 8.2+0.42%190M
NEAR/USDT$ 5.6-0.74%230M

BTC / ETH 被固定在表体顶部 sticky;下面的列表可以滚动,固定行始终可见。

auto-row-height

virtual 默认按固定 rowHeight。如果 row 内容确实需要等浏览器渲染才能知道高度(富文本、图片、嵌套布局),开启 auto-row-height:组件内置 ResizeObserver 监听每个可见行的真实高度并写回累积偏移表。

600 行,body 长度从 1× 到 6× 不等。auto-row-height 自动用 ResizeObserver 测每行真实高度。

ID标题正文
N-0Note 1lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-1Note 2lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-2Note 3lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-3Note 4lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-4Note 5lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-5Note 6lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-6Note 7lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-7Note 8lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-8Note 9lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-9Note 10lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-10Note 11lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-11Note 12lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-12Note 13lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-13Note 14lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-14Note 15lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-15Note 16lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-16Note 17lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.
N-17Note 18lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit. lorem ipsum dolor sit amet, consectetur adipiscing elit.

第一次渲染会用 rowHeight 作为估值;测量完成后 padTop / padBottom 自动重算。比手写 getRowHeight 函数更省心,但代价是首次滚动可能短暂看到滚动条抖动(系统校准位移)。

服务端模式 · 防抖

输入框 / 过滤面板的状态变化每一帧都触发会把后端打爆。给 :server-debounce="300"(ms)让 update:globalSearchupdate:filters 在静默 300ms 后才发出。其它信号(点排序 / 翻页)是显式点击,不需要防抖,所以保持同步触发。

<CfTable
  :columns="columns"
  :rows="rows"
  :pagination="{ page, pageSize, total }"
  :server-debounce="300"
  @update:globalSearch="(q) => loadData({ search: q })"
  @update:filters="(f) => loadData({ filters: f })"
/>

服务端模式

只要 pagination.total 是个数字,组件就自己做客户端 slice:sort / filter / pagination 都通过事件抛给上层,由你去请求服务端:

<CfTable
  :columns="columns"
  :rows="rows"
  :sort="sort"
  :filters="filters"
  :pagination="{ page, pageSize, total }"
  :loading="loading"
  @update:sort="(s) => loadData({ sort: s, filters, page, pageSize })"
  @update:filters="(f) => loadData({ sort, filters: f, page: 1, pageSize })"
  @update:pagination="(p) => loadData({ sort, filters, ...p })"
  @update:globalSearch="(q) => loadData({ search: q })"
/>

API

TableProps

Prop类型默认说明
columnsTableColumn[]列定义;可嵌套 children 形成多行表头
rowsT[]数据行;row[childrenField] 是子行
rowKeystring | (row, i) => stringi行唯一 key,影响选择 / 展开
size'sm' | 'md' | 'lg''md'整体密度
variant'default' | 'bordered' | 'striped''default'视觉变体
hoverablebooleantrue行 hover 高亮
stickyHeaderbooleanfalse表头粘顶(建议同时给 height
heightnumber | string表体最大高度,超出滚动
loadingbooleanfalse显示加载遮罩
emptyTextstring'暂无数据'空数据提示
sortTableSort | TableSort[] | null当前排序(受控)
defaultSort同上null初始排序(非受控)
multiSortbooleanfalse启用 Shift 多列排序
filtersRecord<string, unknown>列过滤值(受控)
defaultFilters同上{}初始过滤值(非受控)
globalSearchstring全局搜索值(受控)
defaultGlobalSearchstring''初始搜索(非受控)
paginationTablePagination | false分页配置;false 强制不分页
defaultPaginationTablePagination初始分页(非受控)
selectable'single' | 'multiple'行选择模式
modelValuestring | string[] | nullnull选中行 key
expandablebooleanfalse启用展开按钮
expandRender(row, i) => any自定义展开内容
expandedRowKeysstring[]展开行(受控)
defaultExpandedRowKeysstring[][]初始展开行(非受控)
childrenFieldstring'children'树形数据字段名
treeIndentnumber16树形每层缩进(px)
columnsStateTableColumnsState列状态:隐藏 / 顺序 / 宽度(受控)
defaultColumnsState同上初始列状态(非受控)
resizablebooleanfalse全局开启列调宽
reorderablebooleanfalse全局开启列拖拽换序
showSummarybooleanfalse自动按 column.summary 渲染总计行
summaryTableSummaryRow[]额外手动总计行
toolbar'auto' | 'none''none'工具栏:搜索 + 列可见性 + 导出
virtualbooleanfalse启用虚拟滚动(要求 height + 固定 rowHeight
rowHeightnumber36虚拟模式下每行高度(px)
overscannumber6虚拟模式上下额外渲染的行数
rowReorderablebooleanfalse启用行拖拽换序(最左侧出现拖把柄)
exportablebooleanfalse工具栏出现 CSV 导出按钮
exportFileNamestring'table'导出文件名(不含扩展名)
persistKeystringlocalStorage 自动持久化 columnsState 的 key
serverDebouncenumber0全局搜索 / 过滤事件的防抖(ms)
cellSelectablebooleanfalseExcel 风格单元格选区 + Ctrl/⌘+C 复制 TSV
colVirtualbooleanfalse启用列虚拟化(推荐 100+ 列时开启)
colWidthnumber120列虚拟化的默认列宽(px)
colOverscannumber4列虚拟化左右额外渲染的列数
treeReorderablebooleanfalse配合 rowReorderable 启用 above/below/inside 三段拖拽
cellPastablebooleanfalse启用 Ctrl/⌘+V 粘贴 TSV 反向写回
historyEnabledbooleanfalse启用快照栈:sort/filter/search/page/columnsState 全部入栈
historyDepthnumber50历史栈最大深度
getRowHeight(index) => number虚拟滚动下指定每行高度,用于变高行
editMode'cell' | 'row''cell''row' 时双击整行进入编辑,所有 editable 列同时变 input
batchActionsbooleanfalse多选状态下顶部出 sticky 操作条
pinnedRowKeysstring[]这些 rowKey 对应的行置顶 sticky
autoRowHeightbooleanfalseResizeObserver 自动测量变高行

TableColumn

字段类型说明
keystring必填唯一 ID
titlestring表头文字
dataIndexstring数据字段;缺省回退到 key
width / minWidth / maxWidthnumber | string几何
align'left' | 'center' | 'right'对齐
ellipsisboolean文本溢出截断
sortableboolean启用此列排序
sortFn(a, b) => number自定义排序函数
filterableboolean启用此列过滤
filterType'text' | 'select' | 'number-range' | 'date-range'过滤面板类型
filterOptions{label,value}[]select 模式下的选项
filterFn(filterValue, row, i) => boolean自定义过滤函数
fixed'left' | 'right'固定列
resizable / reorderable / hideableboolean单列覆盖全局开关
hiddenboolean默认隐藏
render(value, row, i) => any自定义单元格渲染
format(value, row, i) => string仅做字符串格式化
headerRender() => any自定义表头
childrenTableColumn[]多行表头分组
summary'sum' | 'avg' | 'count' | (rows) => any总计聚合
summaryRender(value) => any总计单元格自定义渲染
mergeRowsboolean | 'consecutive' | (cur, prev) => boolean连续相同值自动合并 rowSpan
editableboolean | (row, i) => boolean该列是否可双击编辑
editType'text' | 'number' | 'select'编辑器类型
editOptions{label,value}[]select 模式选项
editValidate(value, row, i) => boolean提交校验,返回 false 阻止
exportablebooleanfalse 时 CSV 导出跳过该列
exportRender(value, row, i) => string导出时单元格的字符串化方式
cellClassstring | (row, i) => string单元格 class

Events / Slots

Event / SlotPayload说明
update:sort / sort-changeTableSort | TableSort[] | null排序变化
update:filtersRecord<string, unknown>过滤值变化
update:globalSearchstring搜索框变化
update:paginationTablePagination翻页 / 改页大小
update:modelValuestring | string[] | null选中行变化
update:columnsStateTableColumnsState拖拽 / 隐藏 / 调宽
update:expandedRowKeysstring[]展开行变化
update:rows / row-reorderT[] / {from,to,rows}行拖拽换序
tree-reorder{fromKey, toKey, pos, rows}树形拖拽(含 above/below/inside)
cell-edit{row, column, oldValue, newValue, index}单元格内联编辑提交
cell-paste{applied, skipped}TSV 粘贴完成后
exportstringCSV 导出(拿到 csv 字符串后自定义处理)
history-change{canUndo, canRedo}历史栈状态变化
row-click(row, i)选择关闭时单击行
#header:<key>自定义表头节点
#cell:<key>{ row, index, value }自定义单元格
#empty自定义空状态
#toolbar-left / #toolbar-right工具栏左右扩展槽

性能笔记

  • 内部所有派生(filtered → sorted → paginated → flat tree)都用 computed/useMemo,只在依赖变时重算。
  • 列调宽 / 拖拽换序更新的是列状态对象,不会触发整张表重排——只有命中变化的列重新计算 style。
  • 行 1k+ 的场景建议同时启用 pagination + sticky-header + height。如果必须一次渲染上万行,可以用 expandable + children 做”折叠树”分批展示;后续会出独立的 <CfVirtualTable> 做真正的虚拟滚动。
  • 在 React 端 Row 组件已经独立化,可在外部用 memo 进一步包装;在 Vue 端单元格 render 函数会被 v-for 自动 patch。

反馈与讨论

Table 表格 的讨论

0
0 / 600
一键发送
正在加载评论...