Preview Updated 2026-05-10

Table

Data table — multi-column sort, column reorder & resize, pinned columns, filtering, pagination, row expansion, tree data, summary footer, sticky header, global search, column visibility.

CfTable is one of the most feature-complete components in the library. Design choices:

  • Client-side derivation by default — global search, per-column filtering, sorting, and pagination all run inside the component. Switch to server mode by passing a numeric pagination.total.
  • Column state is uncontrolled by default — visibility / order / widths live in internal state; emit through update:columnsState to fully control them.
  • Zero third-party dependencies — HTML5 DnD for column reorder, PointerEvent for resize, native position: sticky for pinned columns and the header.

Basic usage

columns + rows + optional rowKey. column.format formats values; column.render returns VNodes / strings.

订单号客户金额状态日期
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

Row selection

Single-select via selectable="single", multi-select via selectable="multiple". v-model accepts a string or string[] keyed by rowKey.

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

Multi-column sort

With multi-sort enabled, hold Shift / while clicking a header to add a secondary sort. Clicking the same column again cycles asc → desc → off. The header shows the column’s index in the sort queue.

仓库语言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 priority follows the order of the sort array. For full control bind :sort to your own ref.

Column resize + reorder

With resizable, every column gets a 6px col-resize handle on its right edge. Dragging updates internal state in real time; @update:columns-state exposes the new widths for persistence. reorderable makes headers drag-and-drop 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

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

Per-column overrides: resizable: false / reorderable: false.

Pinned columns + sticky header

column.fixed: 'left' | 'right' pins columns to either side so they stay visible during horizontal scroll. Combine with sticky-header + :height for a sticky top row.

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 和右侧 状态 列被钉住;中间区域可横向滚动;表头粘在顶部。

Row expansion + tree data

expandable adds an expand button per row:

  • Pass expand-render to render custom expanded content (order details, nested charts, etc.).
  • If a row carries a children array, the table treats it as tree data and indents each level by tree-indent pixels.
名称大小最近修改
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 lifts expansion state up to the parent — handy for syncing to the URL or localStorage.

Toolbar + summary + pagination + filters

Set toolbar="auto" to surface global search + column visibility menu. column.filterable puts a funnel button in each column header; filterType toggles between select / number-range / plain text. column.summary accepts 'sum' | 'avg' | 'count' or a function — a summary row is rendered at the bottom and updates with the filtered result.

订单号产品套餐单价数量合计
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 }" is uncontrolled. For full control bind pagination to a current value and listen to @update:pagination.

Virtual scrolling

With virtual enabled, the body only renders visible rows + an overscan buffer above/below — DOM count stays in the dozens. Requires every row to use a fixed row-height (default 36px). Combine with :height + sticky-header.

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

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

A 50,000-row dataset renders ~30 <tr> elements at any time. Sort / filter / pinned columns / global search all keep working. Note: in virtual mode, expandable rows are also constrained to the row height — for rich expanded content prefer a Modal or disable virtual scrolling.

Row reorder

row-reorderable adds a ⋮⋮ drag handle column on the left. Drag a row to reorder; @update:rows emits the new array. Use @row-reorder if you also want from/to indices.

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

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

Only top-level (level 0) rows are draggable; tree children stay put.

Inline editing

Add editable: true to a column with optional editType: 'text' | 'number' | 'select'. Double-click a cell to enter edit mode:

  • editValidate rejects invalid values; on failure the cell stays in edit mode.
  • Commit: Enter / blur. Cancel: Esc.
  • @cell-edit emits { row, column, oldValue, newValue, index } — the parent decides how to write back.
姓名角色配额
Janeadmin5
Bobeditor3
Aliceviewer1

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

The component never mutates rows directly: every commit goes through cell-edit, so you choose whether to apply locally, hit an API, or merge.

CSV export

exportable renders an Export button in the toolbar. Clicking it downloads the filtered result. The CSV is UTF-8 with a BOM, so Excel opens it correctly without garbling.

  • Skip a column: column.exportable: false
  • Custom cell serialization: column.exportRender(value, row, i) => string
  • File name: export-file-name, defaults to 'table'
  • Fully custom: @export gives you the CSV string — download or upload as you wish.
  • Imperative: the component exposes exportCsv() via 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,导出时输出原始数字而不是格式化后的 ¥。

Persist column state

Setting persistKey="<key>" makes the table write the current columnsState (hidden / order / widths) to localStorage['cf-table:<key>'] and restore it on next mount.

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

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

When fully controlled (you pass :columns-state), persistence is skipped — you decide where to save it.

Auto rowSpan merge

Add mergeRows: true to a column and consecutive cells with equal values are merged into a single rowSpan. For finer control pass a function: mergeRows: (cur, prev) => cur.groupId === prev.groupId.

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

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

Merges never cross tree levels (different parentKey rows are not joined).

Cell selection + one-tap copy

cell-selectable makes the table behave like Excel:

  • Click a cell → select it
  • Shift-click / mouse drag → rectangular selection
  • Ctrl/⌘ + C → copies as TSV to the clipboard, ready to paste into Excel / Numbers / Google Sheets
姓名数学英语物理化学生物
张三9288798491
李四8591888679
王五7895817385
赵六9682949088
钱七7379718075

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

column.format is honored — what you see is what you copy.

Column virtualization (bidirectional)

When you have 100+ columns of simple cells (think 2-D matrices), enabling col-virtual + a default col-width virtualizes the horizontal axis too. Combine with virtual for full bidirectional virtual scrolling.

数据集: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 rows × 121 columns = 24,200 cells, but the actual DOM stays under ~800 nodes. Note: column.fixed still pins columns, but try to keep total fixed width under one third of the viewport so the visible area isn’t crowded.

Tree-aware drag (across parents)

Add tree-reorderable on top of row-reorderable and the drop UI splits each target row into three zones, like Finder / Excel / Kanban tools:

  • Top quarter → drop above (same parent)
  • Bottom quarter → drop below (same parent)
  • Middle half → drop inside as a child

Ancestor-into-descendant moves are blocked.

名称大小
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 emits { fromKey, toKey, pos: 'above' | 'below' | 'inside', rows } so you can persist the new tree shape.

TSV paste-back

On top of cell-selectable, enable cell-pastable. Click (or shift-frame) a starting cell, paste TSV / CSV from your clipboard with Ctrl/⌘ + V, and the data flows back into cells starting at that anchor. column.editable decides which cells accept writes; column.editValidate rejects invalid values.

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 returns { applied, skipped } — handy for a “write 12 cells, skipped 3” toast.

Undo / redo history

history-enabled records every change to sort / filter / search / pagination / columnsState into an in-memory stack. Ctrl/⌘ + Z undoes, Ctrl/⌘ + Shift + Z / Ctrl + Y redoes. history-depth caps the stack size (default 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 fires { canUndo, canRedo } for toolbar buttons. Component instance also exposes imperative undo() / redo().

Variable row heights

virtual defaults to a single rowHeight. For rows of varying height (markdown body, image gallery, etc.), pass :get-row-height="(idx) => 42" and the table builds cumulative offsets, then binary-searches for the visible window on each scroll.

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 should be a pure function — the component recomputes offsets when row count changes. If real heights are only known after rendering, return an estimate first and update once measured. A future auto-row-height mode will wire ResizeObserver in for you.

Row-level edit mode

Set edit-mode="row" and double-click any row to enter row-edit mode: every editable column becomes an input simultaneously, and a Save / Cancel row appears below. Enter commits, Esc cancels. Validation failure keeps the row in edit mode.

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

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

Each cell change still emits @cell-edit (same as cell mode); the parent decides whether to commit individually or batch them.

Batch action toolbar

batch-actions + selectable="multiple": when at least one row is selected, a sticky bar appears above the table — built-in count / Export selected / Clear, plus a #batch-actions slot for custom buttons (which receives 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 rows

pinned-row-keys lifts those rows to the top and makes them sticky inside the scroll container. Useful for “watchlist” / “high priority” rows that should always stay visible.

币对价格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 defaults to a fixed rowHeight. When row content really needs to render before height is known (rich text, images, nested layout), enable auto-row-height: the component runs ResizeObserver on every visible row and writes measured heights back into the offset table.

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.

The first paint uses rowHeight as an estimate; once measurements arrive, padTop / padBottom are recomputed. Less code than hand-writing getRowHeight, but the very first scroll may see a tiny scrollbar jitter while the system calibrates.

Server-side debounce

Typing in the search box or filter dropdown shouldn’t pound your backend on every keystroke. Pass :server-debounce="300" (ms) to delay update:globalSearch and update:filters until the input is idle. Other signals (sort click, page change) are explicit clicks and remain synchronous.

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

Server-side mode

Once pagination.total is a number, the component stops doing client-side slicing. sort / filter / pagination changes are emitted as events and you fetch from the backend:

<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

PropTypeDefaultDescription
columnsTableColumn[]Column defs; nest children for grouped headers
rowsT[]Data rows; row[childrenField] is children
rowKeystring | (row, i) => stringiUnique row id for selection / expansion
size'sm' | 'md' | 'lg''md'Density
variant'default' | 'bordered' | 'striped''default'Visual variant
hoverablebooleantrueRow hover highlight
stickyHeaderbooleanfalseSticky header (also pass height)
heightnumber | stringBody max height; scrolls when exceeded
loadingbooleanfalseShow loading overlay
emptyTextstring'No data'Empty state text
sortTableSort | TableSort[] | nullCurrent sort (controlled)
defaultSortsamenullInitial sort (uncontrolled)
multiSortbooleanfalseEnable shift-click multi-sort
filtersRecord<string, unknown>Filter values (controlled)
defaultFilterssame{}Initial filters (uncontrolled)
globalSearchstringGlobal search (controlled)
defaultGlobalSearchstring''Initial search (uncontrolled)
paginationTablePagination | falsePagination; false disables
defaultPaginationTablePaginationInitial pagination (uncontrolled)
selectable'single' | 'multiple'Row selection mode
modelValuestring | string[] | nullnullSelected row keys
expandablebooleanfalseShow expand toggle
expandRender(row, i) => anyCustom expanded content
expandedRowKeysstring[]Expanded keys (controlled)
defaultExpandedRowKeysstring[][]Initial expanded (uncontrolled)
childrenFieldstring'children'Tree data field
treeIndentnumber16Per-level indent (px)
columnsStateTableColumnsStateHidden / order / widths (controlled)
defaultColumnsStatesameInitial columns state (uncontrolled)
resizablebooleanfalseEnable resize globally
reorderablebooleanfalseEnable reorder globally
showSummarybooleanfalseAuto-render a summary row from column.summary
summaryTableSummaryRow[]Extra manual summary rows
toolbar'auto' | 'none''none'Toolbar (search + column menu + export)
virtualbooleanfalseEnable virtual scroll (requires height + fixed rowHeight)
rowHeightnumber36Per-row height in virtual mode (px)
overscannumber6Extra rows above/below the viewport
rowReorderablebooleanfalseEnable row drag-and-drop reorder
exportablebooleanfalseShow CSV export button in toolbar
exportFileNamestring'table'Export file name (without extension)
persistKeystringPersist columnsState in localStorage under this key
serverDebouncenumber0Debounce (ms) for update:globalSearch / update:filters
cellSelectablebooleanfalseExcel-style cell selection + Ctrl/⌘+C copy as TSV
colVirtualbooleanfalseEnable column virtualization (recommended for 100+ columns)
colWidthnumber120Default column width when virtualizing (px)
colOverscannumber4Extra columns rendered on each side
treeReorderablebooleanfalseCombined with rowReorderable, enable above/below/inside drop zones
cellPastablebooleanfalseAccept Ctrl/⌘+V to paste TSV / CSV back into cells
historyEnabledbooleanfalseSnapshot stack for undo / redo across sort/filter/search/page/columnsState
historyDepthnumber50Max snapshots kept
getRowHeight(index) => numberPer-row height in virtual mode for variable-height rows
editMode'cell' | 'row''cell''row' enters whole-row edit on double-click
batchActionsbooleanfalseSticky batch action bar when rows are selected
pinnedRowKeysstring[]Row keys pinned to the top via sticky
autoRowHeightbooleanfalseResizeObserver auto-measures variable-height rows

TableColumn

FieldTypeDescription
keystringRequired unique id
titlestringHeader text
dataIndexstringData field; falls back to key
width / minWidth / maxWidthnumber | stringGeometry
align'left' | 'center' | 'right'Alignment
ellipsisbooleanTruncate overflow
sortablebooleanEnable sort on this column
sortFn(a, b) => numberCustom sort comparator
filterablebooleanEnable filter on this column
filterType'text' | 'select' | 'number-range' | 'date-range'Filter UI type
filterOptions{label,value}[]Options for select
filterFn(filterValue, row, i) => booleanCustom filter
fixed'left' | 'right'Pin column
resizable / reorderable / hideablebooleanPer-column overrides
hiddenbooleanHidden by default
render(value, row, i) => anyCustom cell render
format(value, row, i) => stringString formatter
headerRender() => anyCustom header
childrenTableColumn[]Grouped headers
summary'sum' | 'avg' | 'count' | (rows) => anyAggregation
summaryRender(value) => anyCustom summary cell
mergeRowsboolean | 'consecutive' | (cur, prev) => booleanAuto-merge rowSpan for consecutive equal cells
editableboolean | (row, i) => booleanWhether this column is double-click editable
editType'text' | 'number' | 'select'Editor type
editOptions{label,value}[]Options for select editor
editValidate(value, row, i) => booleanReject commit if it returns false
exportablebooleanWhen false, column is skipped in CSV export
exportRender(value, row, i) => stringCell stringification on export
cellClassstring | (row, i) => stringCell class

Events / Slots

Event / SlotPayloadDescription
update:sort / sort-changeTableSort | TableSort[] | nullSort changed
update:filtersRecord<string, unknown>Filter values changed
update:globalSearchstringSearch box changed
update:paginationTablePaginationPage / page size changed
update:modelValuestring | string[] | nullSelected rows changed
update:columnsStateTableColumnsStateReorder / hide / resize
update:expandedRowKeysstring[]Expansion changed
update:rows / row-reorderT[] / {from,to,rows}Row drag reorder
tree-reorder{fromKey, toKey, pos, rows}Tree-aware drag (above/below/inside)
cell-edit{row, column, oldValue, newValue, index}Inline cell commit
cell-paste{applied, skipped}TSV paste finished
exportstringCSV export hook (receives the CSV string)
history-change{canUndo, canRedo}History stack changed
row-click(row, i)Row click (when not selectable)
#header:<key>Custom header node
#cell:<key>{ row, index, value }Custom cell
#emptyCustom empty state
#toolbar-left / #toolbar-rightToolbar extension slots

Performance notes

  • All derivations (filtered → sorted → paginated → flat tree) run via computed/useMemo and only re-run on dependency change.
  • Column resize / reorder updates the column state object, not the entire table — only the affected column re-evaluates its style.
  • For 1k+ rows, combine pagination + sticky-header + height. For 10k+, use expandable + children as a “lazy tree” or wait for the upcoming <CfVirtualTable> (real virtual scroll).
  • React side keeps Row extracted; you can wrap it with memo externally. Vue side uses v-for diffing for cells.

反馈与讨论

Table · Discussion

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