table-grid-plugin

开箱即用的 Vue3 高性能数据表格组件。纯 div + 虚拟滚动实现,支持百万级数据流畅渲染、树形、固定列与表头、多级表头、选择、自定义列模板与插槽、单元格合并、展开行与移动端卡片模式。渲染层统一 TSX,运行时零依赖。
1,963
Install
npm install table-grid-plugin
Latest Version:1.0.10
Vue:3
License:MIT
Last Updated:Sep 1, 2026
Links: npm
Maintainer: mylee7532

table-grid-plugin

开箱即用的 Vue3 高性能数据表格组件(XDataGrid)。

纯 div 实现 · 自研虚拟滚动内核 · 运行时零依赖(仅依赖 vue)· 百万级数据流畅渲染 · 移动端自动切换为卡片浏览。渲染层统一使用 TSX(h 函数),不依赖任何底层表格库。

内置能力:虚拟滚动 / 树形表格(含懒加载)/ 多级表头 / 固定列与表头 / 单选·多选·树形级联选择 / 单元格合并 / 展开行 / 排序 / 列筛选 / 分页(本地与远程)/ 滚动加载更多(load-more)/ 表尾合计 / 单元格编辑 / 对象字段(点路径 key)/ 工具栏(列设置·搜索·通用操作按钮)/ 提示框 Tooltip / 列设置缓存(IndexedDB)/ 骨架屏 / 移动端卡片模式。


目录


1. 安装

npm i table-grid-plugin

vue(≥ 3.2)作为唯一的 peerDependency,安装本插件前请确认目标项目已安装 Vue3。

2. 引入(快速开始)

2.1 全局注册(推荐)

// main.ts
import { createApp } from 'vue'
import XDataGrid from 'table-grid-plugin'
// 样式随组件按需引入(sideEffects 已声明 scss)
import 'table-grid-plugin/dist/table-grid-plugin.css'
import App from './App.vue'

const app = createApp(App)
app.use(XDataGrid) // 自动注册为全局组件 <XDataGrid />
app.mount('#app')

2.2 局部使用组件

<script setup>
import XDataGrid from 'table-grid-plugin'

const columns = [
  { key: 'name', label: '姓名', width: 140, fixed: 'left' },
  { key: 'age', label: '年龄', width: 100, sortable: true },
  { key: 'city', label: '城市', width: 140, filterable: true }
]
const data = [
  { id: 1, name: '张三', age: 18, city: '北京' },
  { id: 2, name: '李四', age: 22, city: '上海' }
]
</script>

<template>
  <XDataGrid
    :columns="columns"
    :data="data"
    row-key="id"
    style="height: 400px"
  />
</template>

组件必须给定一个确定的高度(容器高度或 height 属性),虚拟滚动依此计算可视行区间。

2.3 Composition API:useTable

useTable 提供与组件同源的响应式状态,可脱离组件单独使用(如自建视图层或后台逻辑复用):

import { ref } from 'vue'
import { useTable } from 'table-grid-plugin'

const columns = ref([{ key: 'name', label: '姓名', width: 140 }])
const data = ref([{ id: 1, name: '张三' }])
const containerRef = ref<HTMLElement | null>(null)

const state = useTable(
  { columns, data, rowKey: 'id' } as any,
  () => {},
  containerRef
)
// state.scroller.range.value → 当前可视行区间
// state.selection.getSelectedRows() → 选中行

3. 演示 Demo

仓库 demo/ 目录内置完整演示项目,覆盖下方全部功能点,每个示例对应右侧「代码参考」栏:

# 演示面板 覆盖功能
① 百万渲染 虚拟滚动 · 固定列 · 斑马纹 · 密度 · 骨架屏 · 提示框插槽
② 树形表格 多级表头 · 固定列 · 树形级联选择 · 严格勾选 · 默认选中 · 懒加载 · tree-toggle
③ 单元格合并 span-method 的 rowspan / colspan 合并
④ 排序筛选分页 工具栏(操作按钮)· 选择 · 排序 · 筛选 · 分页 · 合计 · 双击编辑 · 默认排序
⑤ 展开行 type:'expand' 列 / expandable / #expand-row 插槽 / expand-change
⑥ 提示框 showOverflow:'tooltip' / showTooltip / tipRenderer / #tip-<key> 插槽
⑦ 事件与实例 row-click / header-click / scroll 等事件 · scrollTo 等实例方法
⑧ 远程数据加载 远程排序/筛选/分页 · 骨架屏 · 空态 · defaultSort / columnFilters 初始条件
⑨ 列设置 列设置抽屉 · 拖拽排序 · 显隐 · IndexedDB 缓存(id)
⑩ 移动端卡片 卡片模式 · 手势切换 · cardProps 字段配置
⑪ 空数据 默认美化空态 · #empty 插槽自定义(内置按钮等交互)
⑫ 动态加载(分页/加载方式切换) pagination + loadMode · 列设置抽屉切换「分页 / 动态加载」· @page-change 统一回调追加数据 · 底部「加载进度分页条」· 重置后已加载页码归零
⑬ 对象字段 点路径 key({ key: 'chengjie.yw' } 读取嵌套对象)· 编辑/排序/合计全支持

启动演示:

npm i
npm run demo:dev        # 本地开发(默认 http://localhost:5177)
npm run demo:build      # 构建产物输出到 demo-dist/

4. API 文档

4.1 Props 参数表

所有属性均非响应式强关联,声明即生效。

属性 类型 必填 默认值 说明
columns ColumnConfig[] ✔ — 列配置(支持多级嵌套 → 自动多级表头)
rowKey string | (row)=>Key ✔ — 行键解算:字段名或函数。选择/树/展开/合并的稳定追踪依赖它
id string — — 表格唯一标识。配置后启用列设置缓存(IndexedDB)(按列配置签名自动失效);同时启用分页页码持久化(见 5.8)
data TableRowRecord[] — [] 表格数据(Record<string, unknown>[])
rowHeight number — 44 单行高度(px),虚拟滚动固定行高模型
height string | number — 100% 容器高度
width string | number — 100% 容器宽度
scroll ScrollConfig — — 滚动配置(x 横向 / y 纵向)
fixedHeader boolean — true 是否固定表头
selectMode 'none'|'single'|'multiple' — 'none' 选择模式
selectable (row)=>boolean — — 行是否可被选择(返回 false 的行禁用勾选)
reserveSelection boolean — false 数据刷新后是否保留选择(基于行键)
checkStrictly boolean — false 是否严格勾选:false 启用上下级关联选择;true 时上下级独立勾选、不再联动 / 半选
defaultCheckedKeys Array<string|number> — — 默认选中的节点键数组;自动应用关联规则(连带选中下级、向上归约父级)
treeProps TreeProps — — 树形配置(children 字段 / 懒加载等,见 4.9)
expandable boolean — false 是否启用展开行(配合 #expand-row 插槽;未配置 expand 列时首个数据列自动挂展开箭头)。同一时刻仅一行可展开,高度自适应插槽内容
spanMethod SpanMethod — — 单元格合并方法
arrayMerge boolean|ArrayMergeConfig — false 数组对象自动合并:把数组嵌套列(如 cj:[{xk,cj}])展开为多子行并对非数组列纵向合并。true 全量自动合并;{ mergeColumns: ['name'] } 仅合并指定列(见 4.11 / 5.23)
locale Partial<LocaleMessages> — — 表格级 key 型语言包覆盖(见 7.2)
translations TranslationsMap — — 表格级「文本词典」:语言码 -> {中文文本: 译文},翻译表头 label 与内置公共文案,粒度最优先(见 7.1)
stripe boolean — false 斑马纹
border boolean — true 显示单元格边框
showRowHover boolean — true 行 hover 高亮
density 'compact'|'default'|'loose' — 'default' 密度(行高倍率 0.85 / 1 / 1.25)
mobileBreakpoint number — 500 容器宽度 ≤ 该值时切换为卡片模式
cardProps CardFieldConfig — — 卡片模式字段配置(见 4.10)
theme ThemeTokens — — 主题 token(注入 CSS 变量)
overscan number — 8 虚拟滚动上下缓冲行数
loading boolean — false 显示加载态
skeleton boolean — false loading 为 true 时以骨架屏展示(默认转圈)
emptyText string — '暂无数据' 空数据文案
hiddenColumns string[] — [] 需要隐藏的列键集合(与实例方法隐藏叠加)
columnOrder Array<string|number> — — 列顺序控制(顶层层级键);与列 key 或组内任叶子 key 匹配
defaultSort SortInfo[] — — 默认排序(可多列,[{ key, order }])
sortMode 'local'|'remote' — 'local' 排序方式:本地内部排序 / 远程触发 sort-change 由外层处理
columnFilters FilterState[] — — 列筛选条件([{ key, values }])
filterMode 'local'|'remote' — 'local' 筛选方式:本地 / 远程
pagination Pagination — — 分页配置;提供该对象即启用分页(见 4.6)
loadMore LoadMoreConfig — — 滚动到底部自动加载更多;提供该对象即启用,且自动隐藏分页条(见 4.7 / 5.21)
loadMode 'page'|'scroll' — 'page' 数据加载方式:'page'(默认)常规分页显示;'scroll' 动态加载——滚动到底部触发 page-change{current,pageSize}(与分页回调一致),数据由外层按页追加累积(虚拟滚动承载),底部展示「加载进度分页条」。配置 pagination 时可在「列设置」抽屉切换该方式(见 5.21.1)
showFooter boolean — false 是否显示表尾合计行
footerSummary FooterSummaryConfig — — 可配置合计统计行(见 4.8)
editable boolean — false 是否允许单元格编辑(配合列的 editable / editRules)
resizable boolean — true 是否允许拖拽调整列宽(列级 resizable 优先)
columnMount 'body'|'container' — 'container' 列设置抽屉挂载位置:'container' 挂载到表格容器内部,与表格成整体单元;'body' 挂载到 <body>,全屏全局抽屉
toolbar ToolbarConfig — — 工具栏配置(见 4.3)

4.2 ColumnConfig 列配置

属性 类型 必填 默认值 说明
key string ✔ — 列键:数据取值字段 / 插槽名;支持点路径读取嵌套对象(如 'chengjie.yw',见 5.22)
label string ✔ — 表头文本
type 'default'|'selection'|'checkbox'|'radio'|'expand'|'index'|'custom' — 'default' 列类型(selection/checkbox 多选列、radio 单选列、expand 展开列、index 序号列)
width number — — 固定宽度(px),设置后不参与剩余空间分配
minWidth number — — 最小宽度:未设 width 时自适应分配但不得小于该值
fixed 'left'|'right' — — 固定列表位置
align 'left'|'center'|'right' — 'left' 单元格对齐
headerAlign 'left'|'center'|'right' — align 表头对齐
showOverflow boolean|'tooltip' — — 内容溢出截断;'tooltip' 时悬停弹出全文(含一键复制)
showTooltip boolean — — 强制开启提示框(内容未截断也显示)
tipRenderer TipRenderer — — 提示框自定义渲染函数(优先级低于 #tip-<key> / #tip 插槽)
cellRender CellRenderer — — 单元格渲染函数(兜底,优先级低于插槽)
headerRender HeaderRenderer — — 表头渲染函数(兜底)
children ColumnConfig[] — — 多级表头子列
sortable boolean|'custom' — — 是否参与排序;'custom' 时配合 sorter 走自定义排序
sorter (a, b) => number — — 自定义排序函数(优先级最高)
filterable boolean — — 是否显示列筛选图标并参与筛选
filterOptions string[] — — 筛选候选选项(缺省取该列全部去重值)
filterMultiple boolean — — 是否多选筛选(默认单值切换)
summable boolean|FooterSummary — — 是否参与表尾合计(列级旧配置,见 5.9)
editable boolean — — 是否可编辑(双击进入编辑,需启用 editable)
arrayField boolean — — 数组合并列标记:key 为点路径(如 cj.xk)时显式声明为数组列(首个字段为数组字段),用于数据为空或无法自动识别数组类型的兜底(见 5.23)
editRules EditValidateResult|(value, row)=>EditValidateResult — — 编辑校验规则:返回 { pass, message? }
editType 'text'|'number'|'select' — 'text' 编辑输入类型
editOptions Array<{ label, value }> — — 编辑下拉选项(editType='select' 时预留)
resizable boolean — — 是否允许拖拽调整该列列宽(覆盖全局 resizable)
slotName string — — 单元格插槽名:配置后该列优先取名为 slotName 的插槽渲染(默认用列 key 作为插槽名),典型用于头像 / 图片 / 富内容自定义展示(见 5.24)
actions TableAction[] — — 操作列按钮组配置:提供后本列渲染为操作列(PC 可见按钮不超过 actionLimit 时全量平铺,超出收起为「actionLimit-1 个 + 更多」下拉,示例:详情 + 更多;移动端卡片平铺为底部操作条),见 4.5
actionLimit number — 2 操作列展示的控件数量上限:可见按钮不超过该值时全量平铺;超出则收起为「前 actionLimit-1 个按钮 + 「更多」下拉」,需配合 actions

4.3 ToolbarConfig 工具栏配置

属性 类型 默认值 说明
columnSetting boolean — 是否显示列设置入口(仅图标,打开列设置抽屉;挂载位置由顶层 columnMount 控制)
search boolean — 是否显示搜索入口(点击触发 toolbar-search 事件,交互由使用方实现)
buttonNames boolean — 是否显示按钮名称开关(配合操作按钮显隐文字)
actions ToolbarAction[] — 通用操作(权限)按钮配置(见 4.4)
showTitle boolean — 是否显示标题区域(#toolbar-title / #title 插槽)

4.4 ToolbarAction 操作按钮

属性 类型 默认值 说明
key string — 权限标识:点击触发 toolbar-action 事件并回传(设置 onClick 时不再自动触发)
label string — 按钮名称(跟随「按钮名」开关显隐)
icon ToolbarIcon — 图标:全局注册图标名 / Element Plus 图标组件 / 自定义图标组件 / 渲染函数 / 内联 SVG 源码(?raw 导入)
disabled boolean|()=>boolean — 是否禁用(响应式函数典型用法:勾选数 === 0 时禁用)
loading boolean|()=>boolean — 是否加载中(旋转图标占位并禁用,任务完成后恢复)
iconSize number 14 图标像素大小(覆盖全局默认 --tbl-action-icon-size)
onClick (action, event) => void — 自定义点击事件:提供后替代全局 toolbar-action
keepName boolean — 是否始终显示名称(忽略「按钮名」开关)

4.45 TableAction 操作列按钮

在列配置 ColumnConfig.actions 中声明(见 5.24)。每个按钮即一个 TableAction:

属性 类型 默认值 说明
key string — 操作唯一标识:点击触发统一 action 事件时回传该 key
label string — 按钮文本(「更多 ⋯」下拉内亦显示)
type 'default'|'primary'|'success'|'warning'|'danger' 'default' 按钮语义:影响默认配色(primary 主题色 / danger 危险红等)
visible (row) => boolean — 权限 / 行判定:返回 false 时该按钮对当前行隐藏(典型:按角色 / 状态控制可见性)
disabled boolean|(row) => boolean — 是否禁用:布尔或基于当前行动态判定(返回 true 禁用,如「已完成」禁删)
render (row) => string|VNode — 自定义按钮内容渲染(优先级高于 label,可返回图标 + 文本)
onClick (row) => boolean|Promise<boolean>|void — 点击回调(传入当前行):返回 false(或异步 resolve false)时阻止触发统一 action 事件

4.5 Pagination 分页配置

属性 类型 默认值 说明
pageSize number — 页大小
current number — 当前页(从 1 起)
pageSizes number[] — 可选页大小
remote boolean — 是否远程分页(本地分页时内部切片)
total number — 数据总条数(远程分页时由外层提供;本地分页未设时自动取数据条数)

4.6 LoadMoreConfig 加载更多配置

提供 loadMore 对象即启用「滚动到底部自动加载更多」:滚动条滚动到距底部阈值内触发 load-more 回调,动态查询下一页并追加到 data 自动累加为大表格;该模式下自动隐藏分页条(无需「下一页」按钮)。

属性 类型 默认值 说明
loading boolean — 是否正在加载更多:加载中底部显示 loading 提示条并禁止重复触发;置回 false 后才允许再次触发 load-more
finished boolean — 是否已无更多数据:底部显示「已加载全部数据」,停止触发 load-more
threshold number 40 距离底部多少像素内视为到达底部,触发 load-more
total number — 数据总条数。提供后底部渲染「加载进度分页条」:展示与常规分页条一致(左侧「共 X 条」+ 上一页/下一页 + 页码窗口),页码始终最多 7 个、超出自动省略号折叠,省略号可点击滑动窗口,已加载页可点击、未加载页置灰禁点(见 5.21)
pageSize number 20 每页条数。与 total 配合计算总页数并判断某页是否已加载

4.7 FooterSummaryConfig 合计配置

属性 类型 默认值 说明
label string '合计' 统计行显示名称
fields FooterStatsField[] — 多字段统计配置

FooterStatsField:

属性 类型 说明
field string 目标字段名(表格字段,即 ColumnConfig.key)
method 'sum'|'average'|'count'|{ type, multiplier } 统计方式:默认求和;{ type:'sum', multiplier } 为「每行值 × 系数再累积」
fn (rows) => number|string 自定义统计函数(优先级最高,覆盖 method)
formatter (value) => string 展示格式化

4.9 TreeProps 树形配置

属性 类型 默认值 说明
children string 'children' 子节点字段名
hasChildren string 'children' 是否存在子节点字段名(懒加载占位判断)
label string — 展示文本字段名(配置后用于树形占位显示)
disabled string|(row) => boolean — 复选框禁用:字段名或判定函数
lazy boolean false 是否懒加载
loadChildren (row) => Promise<row[]> — 懒加载子节点回调

4.9 CardFieldConfig 卡片字段配置

属性 类型 说明
fields string[] 展示顺序(未给出则按列序)
labelMap Record<string, string> 字段展示标签映射(缺省用列 label)
visibleFields string[] 仅展示这些字段(未给出则展示全部非控制列)

4.14 ArrayMergeConfig 数组合并配置

arrayMerge 接受 boolean 或如下对象:

属性 类型 默认值 说明
mergeColumns string[] 全部非数组列 指定需要纵向合并(rowspan)的非数组列 key 列表;仅列出的列跨子行合并,其余非数组列逐子行重复展示。序号 / 选择(复选框 / 单选)列属组级列,始终整组合并,不受此配置影响
取值 效果
false / 缺省 关闭数组合并,数组列按原始数据原样显示
true 自动识别数组列并展开,所有非数组列纵向 rowspan 合并
{ mergeColumns: ['name'] } 仅 name 列纵向合并,其余非数组列(班级 / 备注等)逐子行重复展示

4.9 事件

事件名 参数 说明
selection-change (info: SelectionInfo) 选择变化。返回完整选择信息,区分「完全选中」与「半选」
expand-change (row, expanded) 展开行切换。点击展开/收起某行时触发,row 为目标行、expanded 是否展开。同一时刻仅一行可展开,可在展开时动态调用接口更新插槽数据
tree-toggle (row, expanded) 树节点展开 / 收起
scroll ({ scrollLeft, scrollTop }) 滚动事件
card-change ({ index, row }) 卡片切换(移动端)
row-click (row, index, evt) 行点击
row-dbl-click (row, index, evt) 行双击
header-click (column, evt) 表头点击
sort-change ({ sort, key, order }) 排序变化。key 为触发字段;order='asc' 上箭头升序 / 'desc' 下箭头降序 / null 清除
filter-change ({ filters, key }) 列筛选变化(key 为触发字段)
page-change ({ current, pageSize }) 分页变化
load-more () 滚动到底部自动加载更多(提供 loadMore 配置时生效)。到达底部阈值即触发一次;loadMore.loading 置回 false(或数据条数增长)后允许再次触发
load-mode-change (mode) 在「列设置」抽屉切换数据加载方式时触发;mode 为 'page'(分页)或 'scroll'(动态加载)
edit-change ({ row, column, value, trigger }) 单元格编辑提交(校验通过后;trigger 为 double-click/enter/blur)
column-resize ({ key, width }) 拖拽调整列宽
search ({ keyword }) 本地搜索触发(setSearch 内部)
toolbar-search ({ event }) 点击工具栏「搜索」按钮(交互由使用方实现)
toolbar-action ({ key, event }) 点击工具栏通用操作按钮(未设 onClick 时触发)
action ({ key, row, event }) 点击操作列按钮(key 为 TableAction.key;按钮配置了 onClick 且返回 false 时不触发本事件,见 5.24)

4.11 实例方法

通过组件 ref 访问:

方法 说明
getSelection() 获取选中行数组(完全选中的行)
getSelectRowKeys() 获取选中行键数组
getSelectionInfo() 手动获取完整选择信息 SelectionInfo(含半选)
toggleRowSelection(row, selected?) 切换某行选择(树形关联模式下自动级联)
setTreeExpand(row, expanded) 设置树节点展开状态
refreshData() 刷新数据(懒加载清缓存)
scrollTo(index) 滚动到指定虚拟行
getColumnWidths() 获取各叶子列最终宽度
recalcWidths() 重新计算列宽
setColumnOrder(keys) 设置列顺序(顶层层级键数组)
setHiddenColumns(keys) 设置列显隐(隐藏指定列键集合)
showColumn(key) / hideColumn(key) 显示 / 隐藏某列
setColumnWidth(key, width) 更新某列宽
clearSort() / getSort() 清空排序 / 获取当前排序
clearFilter() / getFilter() 清空列筛选 / 获取当前筛选条件
setCurrentPage(page) / setPageSize(size) 设置当前页 / 页大小
sortBy(key, order?) 对指定列执行排序切换
setSearch(keyword) 设置全局搜索关键词(本地过滤),空串清除
reset() 重置内部状态

4.14 插槽 Slots

插槽名 作用域 说明
default { row, column, $index, $rowIndex } 单元格默认模板(兜底)
#<列key> { row, column, $index, $rowIndex } 指定列的单元格模板(默认插槽名 = 列 key)
#<slotName> 同上 列配置了 slotName 时以该名插槽渲染(可显示图片 / 富内容,见 5.24)
#header { column, level, colSpan, rowSpan } 表头默认模板
#header-<列key> 同上 指定列表头模板
#expand-row { row } 展开行全宽内容模板
#tip { row, column, $index, $rowIndex } 全局提示框内容模板(自定义悬停浮层)
#tip-<列key> 同上 指定列提示框内容模板(优先级最高)
#toolbar-title / #title — 工具栏标题
#toolbar-top — 位于标题栏(#toolbar-title 标题行)与附加行之间的插槽行,供搜索条件展示等扩展使用。渲染在工具栏内部、标题行之下,而非工具栏顶部
#toolbar-actions { showNames, toggleNames } 工具栏自定义操作插槽
#empty — 空数据自定义内容(可内置按钮等快捷交互;开启指针事件以支持点击)

插槽优先级高于 cellRender / headerRender。控制列(selection/expand/index)的点击事件已被组件内部接管。

4.15 类型定义

import type {
  TableProps, TableEvents, TableInstance, SelectionInfo,
  ColumnConfig, TreeProps, SpanMethod, SpanCellContext,
  TableRowRecord, RowKeyResolver, CardFieldConfig,
  ThemeTokens, SelectMode, SortInfo, FilterState,
  Pagination, LoadMoreConfig, FooterSummaryConfig, ToolbarConfig, ToolbarAction, TableAction
} from 'table-grid-plugin'

SelectionInfo(selection-change 回调与 getSelectionInfo() 的返回值,区分「完全选中」与「半选」):

interface SelectionInfo {
  /** 完全选中节点键 */
  checkedKeys: Array<string | number>
  /** 半选(部分选中)节点键 */
  indeterminateKeys: Array<string | number>
  /** 完全选中节点的原始数据(含被级联选中的父级与其下级) */
  checked: TableRowRecord[]
  /** 半选(通常为父级)节点的原始数据 */
  indeterminate: TableRowRecord[]
  /** 快捷字段:完全选中节点原始数据,等价于 checked */
  selection: TableRowRecord[]
}

5. 功能详解

5.1 百万级数据渲染(虚拟滚动)

不含原生 <table>,Body 基于行高做数学定位,仅渲染可视区间 + 缓冲行,滚动复杂度为 O(1)。

<XDataGrid
  :columns="columns"
  :data="100000Rows"   <!-- 十万行即可流畅,百万级亦无压力 -->
  row-key="id"
  stripe
  :loading="loading"
  :skeleton="skeleton"
  :density="'compact'"
  :row-height="44"
/>
子项 说明
虚拟滚动内核 src/core/virtual,固定行高、O(1) 区间计算
缓冲行 overscan 默认 8,滚动前后预渲染
大列数量 叶子列线性序列渲染,minWidth 支持剩余空间自适应分配
密度切换 density:compact(0.85) / default(1) / loose(1.25)
加载态 loading 转圈或 skeleton 骨架屏(见 5.17)

5.2 列配置与多级表头

叶子的 children 自动生成多级表头,宽度按子树叶子累计。

const columns = [
  { key: 'name', label: '姓名', width: 140 },
  {
    label: '成绩(多级)',
    children: [
      { key: 'chinese', label: '语文', width: 110 },
      { key: 'math', label: '数学', width: 110 },
      { key: 'english', label: '英语', width: 110 }
    ]
  }
]
子项 说明
多级表头 任意层级嵌套,自动计算 colspan / rowspan
表头插槽 #header 与 #header-<key> 自定义
宽度分配 width 优先;未设走 minWidth 自适应
列显隐 hiddenColumns 控制隐藏集合

5.3 固定列 / 固定表头

const columns = [
  { key: 'name', label: '姓名', width: 140, fixed: 'left' },
  { key: 'age', label: '年龄', width: 100 },
  // ...中间滚动列
  { key: 'action', label: '操作', width: 120, fixed: 'right' }
]
子项 说明
固定列 `fixed: 'left'
固定表头 fixed-header + 表头随 Body 横向滚动同步
边缘状态 贴近左/右边缘时自动隐藏固定阴影

5.4 树形表格与懒加载

通过 treeProps 指定 children 字段;懒加载场景用 lazy + loadChildren。

<XDataGrid :columns="columns" :data="treeData" row-key="id" :tree-props="treeProps">
</XDataGrid>
const treeProps = {
  children: 'children',                 // 子节点字段名(默认 'children')
  hasChildren: 'hasChildren',           // 存在子节点字段(懒加载占位判断)
  lazy: true,                           // 是否懒加载
  loadChildren: async (row) => {
    const res = await fetch('/api/children?id=' + row.id)
    return res.json()
  }
}
子项 说明
展开/收起 首列缩进 + 箭头,非叶行才显示
懒加载 顶层仅声明 hasChildren: true,展开时经 loadChildren 异步取子节点;已加载节点缓存,重复展开不重复请求
深度缩进 每级 --tbl-tree-indent 像素
树状态 treeProps 完全受控,可配合 setTreeExpand 编程展开
树节点事件 tree-toggle 回传 (row, expanded)

5.5 选择(多选 / 单选 / 全选 / 树形级联)

普通扁平数据选择:

<XDataGrid
  :columns="columns"
  :data="data"
  row-key="id"
  select-mode="multiple"
  :selectable="row => !row.disabled"
  @selection-change="onChange"
>
</XDataGrid>
// selection-change 回调收到完整选择信息,区分「完全选中」与「半选」
function onChange({ checked, checkedKeys, indeterminate, indeterminateKeys, selection }) {
  console.log('完全选中(含级联)节点数', checked.length)
  console.log('半选父级数', indeterminate.length)
  console.log('完全选中原始数据', checked)
}

树形级联选择(默认关联模式):

<XDataGrid
  :columns="columns"
  :data="treeData"
  row-key="id"
  select-mode="multiple"
  :default-checked-keys="[11]"          <!-- 默认选中 11,自动连带选中下级并归约父级 -->
  :check-strictly="false"               <!-- 省略即默认:上下级关联选择 -->
  @selection-change="onChange"
>
</XDataGrid>
const gridRef = ref(null)
function readSelection() {
  const info = gridRef.value?.getSelectionInfo()  // 手动获取
  console.log(info.checkedKeys, info.indeterminateKeys)
}
子项 说明
多选/单选 select-mode 切换,自动在首列追加 checkbox / radio(或显式配置 type:'selection'/'radio' 列)
表头全选 多选模式表头全选控件,支持半选(indeterminate)
行禁用 selectable 返回 false 的行勾选禁用
跨页保留 选择基于行键 Set<Key> 存储,数据变换仍稳定
编程控制 toggleRowSelection / getSelection / getSelectRowKeys / getSelectionInfo
上下级关联 默认开启:子级全选 → 父级自动选中;父级选中 → 子级全选;子级部分选中 → 父级半选。与节点是否展开无关,未展开节点的状态同样正确同步
严格勾选 :check-strictly="true" 后上下级独立勾选,点击哪个节点仅切换该节点,不联动、无半选
默认选中 defaultCheckedKeys 指定初始选中节点键,自动应用关联规则
结果区分 回调 / getSelectionInfo() 均返回 SelectionInfo,含 checked(完全选中)与 indeterminate(半选)两组原始数据与键

5.6 排序

列配置 sortable: true 即显示上/下三角(点上=升序、点下=降序、再次点击清除)。

<XDataGrid
  :columns="columns"
  :data="data"
  row-key="id"
  :default-sort="[{ key: 'amount', order: 'desc' }]"
  :sort-mode="'local'"     <!-- 本地排序(默认);remote 则触发事件由外层处理 -->
  @sort-change="onSortChange"
>
</XDataGrid>
function onSortChange({ key, order }) {
  // order:'asc' 上箭头升序 / 'desc' 下箭头降序 / null 清除
}
子项 说明
本地排序 sortMode='local'(默认):内部对数据排序
远程排序 sortMode='remote':仅触发 sort-change,数据由外层请求后回填
默认排序 defaultSort 初始条件
自定义排序 列 sorter 函数(优先级最高)或 sortable:'custom'
编程控制 sortBy(key, order) / clearSort() / getSort()

5.7 列筛选

列配置 filterable: true 即显示筛选图标,点击弹出候选下拉。

<XDataGrid
  :columns="columns"
  :data="data"
  row-key="id"
  :filter-mode="'local'"        <!-- 本地筛选(默认);remote 触发事件由外层处理 -->
  :column-filters="[{ key: 'city', values: ['北京'] }]"
  @filter-change="onFilterChange"
>
</XDataGrid>
const columns = [
  {
    key: 'city', label: '城市',
    filterable: true,
    filterOptions: ['北京', '上海', '广州'], // 候选;缺省取该列全部去重值
    filterMultiple: true                     // 多选筛选
  }
]
function onFilterChange({ key, filters }) {
  // key:触发筛选的字段;filters:当前全部筛选条件
}
子项 说明
本地筛选 filterMode='local'(默认):内部过滤数据
远程筛选 filterMode='remote':仅触发 filter-change
候选选项 filterOptions 自定义(缺省去重取值)
单/多选 filterMultiple 多选
编程控制 clearFilter() / getFilter()

5.8 分页

提供 pagination 对象即启用分页。

<XDataGrid
  :columns="columns"
  :data="data"
  row-key="id"
  :pagination="{ pageSize: 20, pageSizes: [10, 20, 50], current: 1 }"
  @page-change="onPageChange"
>
</XDataGrid>
子项 说明
本地分页 未设 remote 时内部切片,total 自动取数据条数
远程分页 pagination.remote = true + total 由外层提供,翻页触发 page-change
页大小切换 pageSizes 下拉,触发 page-change
滑窗页码 常规分页与「动态加载」分页统一:页码始终最多 7 个(当前页 ±2 + 首尾页),超出自动折叠为可点击省略号,点击窗口向该侧滑动,布局稳定不随页数增多而变形
页码 tooltip 悬停页码/省略号弹出带三角箭头的浮层(复用 tip 视觉),指向所悬停的页码
编程控制 setCurrentPage(page) / setPageSize(size)
页码持久化 配置 id 后,当前页码 / 每页条数经 IndexedDB 持久化;刷新 / 重进页面自动恢复上次页码(越界自动钳制;远程分页恢复时触发 page-change 由外层重新拉取)。数据不落库,避免占用过多存储
加载方式持久化 配置 id 后在「列设置」抽屉切换的「数据加载方式(分页 / 动态加载)」同样经 IndexedDB 持久化;刷新后自动恢复加载方式,并触发 load-mode-change 供外层按新方式处理

id 同时用于列设置缓存(5.15)与分页页码持久化——二者以独立子键存储,互不冲突:

<XDataGrid
  id="order-table"
  :columns="columns"
  :data="data"
  row-key="id"
  :pagination="{ pageSize: 20, pageSizes: [10, 20, 50] }"
  @page-change="onPageChange"
>
</XDataGrid>

5.9 表尾合计统计行

showFooter 开启合计行;两种配置来源:

A. 可配置统计行(推荐):footerSummary 支持多字段、多统计方式:

<XDataGrid
  :columns="columns"
  :data="data"
  row-key="id"
  show-footer
  :footer-summary="footerSummary"
>
</XDataGrid>
const footerSummary = {
  label: '合计',
  fields: [
    { field: 'amount', formatter: (v) => '¥' + Number(v).toLocaleString() },  // 默认求和
    { field: 'progress', method: 'average' },                                  // 平均
    { field: 'id', method: { type: 'sum', multiplier: 0.5 } },                 // 系数求和(每行值 × 0.5 再累积)
    { field: 'status', fn: (rows) => rows.filter((r) => r.status === '已完成').length } // 自定义函数
  ]
}

B. 列级配置(旧):列 summable: true 默认求和;或传入 { type, custom, formatter } 聚合对象。

子项 说明
统计方式 sum 求和(默认)/ average 平均 / count 计数 / { type, multiplier } 系数求和 / fn 自定义函数
格式化 每个字段独立 formatter
标签 label 配置统计行名称(默认「合计」)
横向联动 统计行独立横向滚动,与表体/表头 scrollLeft 同步,列宽对齐

5.10 单元格编辑

列配置 editable: true + 组件 editable 启用后,双击单元格进入编辑(回车提交 / 失焦提交 / Esc 取消)。

<XDataGrid
  :columns="columns"
  :data="data"
  row-key="id"
  editable
  @edit-change="onEditChange"
>
</XDataGrid>
const columns = [
  {
    key: 'amount', label: '金额(元)',
    editable: true,
    editType: 'number',                      // 数字输入,提交自动 Number()
    editRules: (v) => Number.isFinite(Number(v))
      ? { pass: true }
      : { pass: false, message: '请输入数字' } // 校验失败不入库
  }
]
function onEditChange({ row, column, value, trigger }) {
  // trigger:'double-click' 进入 / 'enter' 回车 / 'blur' 失焦
}
子项 说明
触发方式 双击进入,回车 / 失焦提交,Esc 取消
校验规则 editRules 静态对象或函数,返回 { pass, message? };校验失败不入库
输入类型 editType:text / number(提交自动转 Number)
提交事件 edit-change 回传 { row, column, value, trigger }

5.11 工具栏

提供 toolbar 对象即显示顶部工具栏(零依赖,苹果风描边按钮)。

<XDataGrid
  ref="tableRef"
  :columns="columns"
  :data="data"
  row-key="id"
  :toolbar="toolbar"
  @toolbar-search="onToolbarSearch"
  @toolbar-action="onToolbarAction"
>
  <template #toolbar-title>订单明细</template>
</XDataGrid>
import 导出Icon from '@/icons/export.svg?raw'   // ?raw 内联 SVG 作为图标

const toolbar = reactive({
  columnSetting: true,   // 列设置入口(打开全局抽屉)
  search: true,          // 搜索按钮(触发 @toolbar-search)
  buttonNames: true,     // 按钮名开关(控制操作按钮文字显隐)
  actions: [
    {
      key: 'export', label: '导出', icon: 导出Icon, iconSize: 12,
      loading: () => exporting.value,            // 响应式 loading(旋转图标 + 禁用)
      onClick: async (action, event) => { /* 自定义点击,替代全局 toolbar-action */ }
    },
    {
      key: 'delete', label: '删除',
      icon: () => h('svg', { viewBox: '0 0 24 24' }, [h('path', { d: '...' })]),
      disabled: () => checkedKeys.value.length === 0,   // 未勾选即禁用
      onClick: (action, event) => alert('删除')
    }
  ]
})

// 搜索按钮:使用方自行实现交互(点击后展开本地搜索栏)
function onToolbarSearch() {
  searchBar.value = !searchBar.value
  tableRef.value?.setSearch(keyword.value)   // 本地关键词过滤(可不清除时传 '')
}
子项 说明
列设置 图标按钮打开列设置抽屉(默认挂载于表格容器内成整体单元;columnMount="body" 变全屏全局抽屉)
搜索 触发 toolbar-search 事件,交互由使用方实现
按钮名开关 控制所有操作按钮文字显隐(状态持久化)
操作按钮 actions:图标(组件 / 渲染函数 / 内联 SVG 源码 / 全局注册名)+ 名称
响应式状态 disabled / loading 支持函数式响应判定
自定义点击 onClick 提供后替代全局 toolbar-action
移动端折叠 按钮过多时前 3 个直接展示,其余收进「更多」⋯ 弹出面板
插槽 #toolbar-title / #toolbar-actions

5.12 提示框 Tooltip

悬停单元格弹出 Teleport 到 body 的暗色卡片浮层,智能定位(贴边自动翻转 + 方向箭头),不超出表格容器与视口。支持四种配置方式:

<XDataGrid :columns="columns" :data="data" row-key="id">
  <!-- 方式四(优先级最高):插槽自定义浮层内容 -->
  <template #tip-name="{ row }">
    <div>{{ row.name }} · {{ row.city }} · {{ row.desc }}</div>
  </template>
</XDataGrid>
const columns = [
  { key: 'name', label: '姓名', width: 100 },
  {
    key: 'remark', label: '备注', minWidth: 180,
    showOverflow: 'tooltip',   // ① 溢出省略,悬停显示全文 + 一键复制
  },
  {
    key: 'desc', label: '描述', width: 110,
    showTooltip: true,         // ② 强制开启,未截断也显示
  },
  {
    key: 'tip', label: '提示', width: 130,
    tipRenderer: ({ row }) =>  // ③ 列级渲染函数返回自定义浮层
      h('div', { style: 'display:grid;gap:4px' }, [h('span', row.tip), h('span', row.name)]),
  }
]
子项 说明
溢出全文 showOverflow:'tooltip',省略时弹出全文 + 一键复制
强制开启 showTooltip: true,未截断也显示
渲染函数 tipRenderer 列级函数返回自定义 VNode
插槽 #tip-<key> / #tip 作用域插槽(优先级最高)
自适应列 未设 width 的自适应列默认开启溢出提示
智能定位 贴边自动左右对齐、上下翻转、方向箭头,约束在表格容器与视口内

5.13 单元格合并

通过 spanMethod 返回 { rowspan, colspan }。连续同名合并需自行扫描数据。

function spanMethod({ rowIndex, columnIndex, rowCount, columnCount, row }) {
  if (columnIndex === 1) {
    return { rowspan: groupSpan(rowIndex) } // 返回 0 表示被前一行吞噬
  }
  if (columnIndex === 4) {
    return { colspan: 2 }                    // 横向合并(顶端对齐)
  }
  return undefined
}
子项 说明
跨行合并 rowspan 垂直合并
跨列合并 colspan 水平合并(顶端对齐)
行高自适应 合并行高按 rowspan × 行高 计算
上下文 row / rowIndex / columnIndex / rowCount / columnCount

5.14 展开行

两种触发方式:

  • 显式展开列:配置 { type: 'expand' } 列,行内出现「展开/收起」按钮;
  • 隐式触发::expandable="true" 且未配置 expand 列时,首个数据列自动挂上展开箭头。
<XDataGrid
  ref="gridRef"
  :columns="columns"
  :data="data"
  row-key="id"
  expandable
  @expand-change="(row, expanded) => { if (expanded) loadDetail(row) }"
>
  <!-- 展开行全宽内容:订单明细 / 子表 / 图表等(高度自适应插槽内容) -->
  <template #expand-row="{ row }">
    <div class="expand-detail">
      <div>订单号:{{ row.order }}</div>
      <div v-for="d in row.detail" :key="d.goods">{{ d.goods }} ×{{ d.qty }} ¥{{ d.price }}</div>
    </div>
  </template>
</XDataGrid>
const columns = [
  { type: 'expand', width: 60, align: 'center' },   // 展开按钮列
  { key: 'order', label: '订单号', width: 150 },
  { key: 'amount', label: '金额(元)', width: 140, align: 'right' },
]

// 展开回调:可在此动态调用接口更新插槽数据(row 为引用,赋值即触发插槽更新)
async function loadDetail(row) {
  const detail = await fetchDetail(row.id)      // 模拟请求接口
  row.detail = detail                            // 更新后高度自动重新测量
}
子项 说明
展开列 type: 'expand' 独立按钮列
隐式触发 expandable 未配置 expand 列时,首个数据列自动挂展开箭头
展开行插槽 #expand-row 全宽内容(渲染为独立虚拟行插入坐标,滚动/合并统一计算)
单行展开 同一时刻仅一行可展开:展开新行自动收起上一行(无需批量方法)
高度自适应 展开行高度随插槽内容自动测量排布(虚拟滚动按实际高度计算)
展开事件 expand-change 回传 (row, expanded);展开时可动态加载接口数据更新插槽

5.15 列设置与缓存

通过工具栏「列设置」入口打开列设置抽屉:复选框控制字段显隐、拖拽调整非固定字段顺序(固定列锚定不可拖)。

抽屉挂载位置由顶层 columnMount 配置(默认 'container'):

  • 'container'(默认):挂载到表格容器内部,遮罩/面板局限于表格区域,与表格形成一个整体单元;
  • 'body':挂载到 <body>,全屏全局抽屉。
<XDataGrid
  ref="gridRef"
  id="demo-columns"          <!-- 配置 id 启用 IndexedDB 缓存 -->
  :columns="columns"
  :data="data"
  row-key="id"
  :column-mount="'container'"
  :toolbar="{ columnSetting: true }"
>
</XDataGrid>
// 实例方法控制列显隐与顺序
gridRef.value?.hideColumn('status')            // 隐藏某列
gridRef.value?.showColumn('status')            // 显示某列
gridRef.value?.setHiddenColumns(['status'])    // 批量隐藏
gridRef.value?.setColumnOrder(['id', 'name', 'amount', 'city']) // 列顺序
子项 说明
列设置抽屉 勾选显隐 + 拖拽排序,固定列锚定不可拖
挂载位置 columnMount:'container'(默认)表格容器内部成整体单元 / 'body' 全屏全局抽屉
IndexedDB 缓存 仅配置 id 时启用;列顺序 / 显隐自动持久化,刷新页面保留
缓存失效 缓存版本 + 列配置签名双重校验:程序升级或 columns 结构变化时自动失效,恢复初始化
重置 抽屉「重置」按钮清除缓存并恢复初始化

5.16 远程数据模式

sort-mode='remote'、filter-mode='remote'、pagination.remote=true 时,组件只触发事件,数据由外层请求后回填。

<XDataGrid
  :columns="columns"
  :data="pageRows"
  row-key="id"
  :loading="loading"
  :sort-mode="'remote'"
  :filter-mode="'remote'"
  :pagination="pagination"
  :default-sort="[{ key: 'amount', order: 'desc' }]"
  :column-filters="[{ key: 'city', values: ['北京'] }]"
  @sort-change="onSortChange"
  @filter-change="onFilterChange"
  @page-change="onPageChange"
>
</XDataGrid>
const pagination = ref({ pageSize: 20, current: 1, remote: true, total: 0 })

async function fetchData({ sort, filters, page, pageSize }) {
  loading.value = true
  const res = await api.query({ sort, filters, page, pageSize })
  pageRows.value = res.list
  pagination.value.total = res.total
  loading.value = false
}

function onSortChange({ sort }) {
  pagination.value.current = 1
  fetchData({ sort, filters, page: 1, pageSize: pagination.value.pageSize })
}
function onPageChange({ current, pageSize }) {
  fetchData({ sort, filters, page: current, pageSize })
}
子项 说明
远程排序 sortMode='remote':仅触发 sort-change
远程筛选 filterMode='remote':仅触发 filter-change
远程分页 pagination.remote=true + total 由外层提供
初始条件 defaultSort / columnFilters 设置首屏条件
加载态 请求期间 loading 展示(可配 skeleton)

5.17 加载态 / 骨架屏 / 空态

<XDataGrid
  :columns="columns"
  :data="data"
  row-key="id"
  :loading="loading"
  :skeleton="skeleton"        <!-- true 时 loading 显示骨架屏(默认转圈) -->
  empty-text="暂无数据"        <!-- 空数据文案(默认内置图标+文案的美化空态) -->
>
  <template #empty>            <!-- 自定义空态:表头保留,仅表体内容区为空 -->
    <div style="text-align: center; padding: 24px">
      <p>暂无关联数据</p>
      <button @click="loadData">加载示例数据</button>
    </div>
  </template>
</XDataGrid>
子项 说明
加载转圈 loading=true 默认展示加载中遮罩(不占据布局空间)
骨架屏 loading + skeleton 展示 6 行骨架占位
空态 无数据时表头保留,仅表体内容区为空;默认展示与表格风格一致的图标 + emptyText 文案
自定义空态 #empty 插槽完全自定义(可内置图标 / 标题 / 描述 / 快捷按钮等交互元素,空态区域自动开启点击)

5.18 移动端卡片模式

容器宽度 ≤ mobileBreakpoint 时自动切换为卡片逐条浏览(按容器宽度而非屏幕宽度判定)。默认断点 500px,可通过 mobile-breakpoint 自定义。

<XDataGrid
  :columns="columns"
  :data="data"
  row-key="id"
  :mobile-breakpoint="900"
  :card-props="cardProps"
  :toolbar="toolbar"
  :pagination="pagination"
  @card-change="onCardChange"
>
</XDataGrid>
const cardProps = {
  fields: ['name', 'product', 'amount', 'city', 'status'], // 展示顺序
  labelMap: { amount: '金额(元)', product: '商品名称' },     // 自定义标签
  visibleFields: ['id', 'name', 'city', 'product', 'amount', 'status'] // 字段过滤
}
function onCardChange({ index, row }) {
  console.log('当前卡片:', index + 1, row)
}
子项 说明
断点切换 容器宽度 ≤ 断点(默认 500,可配 mobile-breakpoint)自动切换
卡片呈现 一行数据 = 一张卡,字段「标签-值」列转行;合并记录以整组呈现
手势导航 按下后左滑 / 右滑切换上一条 / 下一条(带动画)
字段配置 fields 排序 / visibleFields 过滤 / labelMap 标签
工具栏适配 按钮过多自动收进「更多」⋯;分页条压缩为「上一页 / 下一页 + 条数」
卡片切换事件 card-change 携带 { index, row }

5.19 密度 / 斑马纹 / 行高 / 边框

<XDataGrid
  :columns="columns"
  :data="data"
  row-key="id"
  :density="'compact'"
  stripe
  :row-height="44"
  :show-row-hover="true"
  :border="true"
>
</XDataGrid>
子项 说明
密度 compact(0.85) / default(1) / loose(1.25) 行高倍率
斑马纹 stripe 偶数行底色
行悬停 showRowHover 控制高亮
边框 border 控制单元格分隔线
行高 rowHeight 基准行高(默认 44,实际 = 行高 × 密度倍率)

5.20 事件与实例方法

完整事件见 4.12 事件,实例方法见 4.13 实例方法。典型组合使用:

<XDataGrid
  ref="gridRef"
  :columns="columns"
  :data="data"
  row-key="id"
  @row-click="(row, index, evt) => …"
  @row-dbl-click="(row, index, evt) => …"
  @header-click="(column, evt) => …"
  @scroll="({ scrollLeft, scrollTop }) => …"
  @selection-change="(info) => …"
  @sort-change="({ key, order }) => …"
  @edit-change="({ row, column, value }) => …"
  @expand-change="(row, expanded) => …"
>
</XDataGrid>
const gridRef = ref(null)
gridRef.value?.scrollTo(60)                  // 滚动到第 60 个虚拟行
gridRef.value?.toggleRowSelection(row, true) // 切换某行选择
gridRef.value?.setCurrentPage(2)             // 设置当前页
gridRef.value?.sortBy('amount', 'desc')      // 按字段排序
gridRef.value?.setColumnWidth('name', 220)   // 更新列宽
gridRef.value?.clearSort()                   // 清空排序
gridRef.value?.clearFilter()                 // 清空筛选

5.21 滚动到底部加载更多(load-more)

滚动条滚动到距底部阈值内自动触发回调,动态查询下一页数据并追加到 data,自动累加形成大表格数据(虚拟滚动承载,累计万级 / 十万级仍流畅)。该模式下自动隐藏分页条,无需「下一页」按钮。

<XDataGrid
  :columns="columns"
  :data="rows"
  row-key="id"
  :load-more="loadMore"
  @load-more="fetchNextPage"
>
</XDataGrid>
const rows = ref([])
const loadMore = ref({ loading: false, finished: false, threshold: 60, total: 400, pageSize: 40 })

async function fetchNextPage() {
  if (loadMore.value.finished || loadMore.value.loading) return
  loadMore.value.loading = true
  const chunk = await api.queryNextPage()   // 动态查询下一页
  rows.value.push(...chunk)                 // 追加到 data,自动累加大表格
  loadMore.value.loading = false            // 置回 false 才允许再次触发
  if (noMore) loadMore.value.finished = true // 无更多数据,停止触发并显示「已加载全部数据」
}
子项 说明
触发方式 滚动条滚动到距底部 threshold(默认 40)px 内触发 load-more 回调
防重复 触发一次后置 pending,loadMore.loading 置回 false(或数据条数增长)后复位,才允许再次触发
底部提示 loading 为 true 时底部显示「加载中…」提示条;finished 为 true 时显示「已加载全部数据」并停止触发
隐藏分页 提供 loadMore 后自动不渲染分页条,无需「下一页」按钮
页码窗口 页码始终最多展示 7 个(含首尾页),超出自动折叠为省略号,布局稳定不随页数增多而变形
滑动省略号 折叠的省略号可点击:每次点击窗口向该侧滑动一段以到达更远页码;已加载页码可点击滚动、未加载页码置灰禁点
已加载页码 已加载页(主色浅底):点击后滚动条滚动到该页首行
未加载页码 未加载页(虚线描边)置灰(disabled)禁止点击,滚动加载到该页后自动变为可点击
大表支持 数据不断追加由虚拟滚动承载,累计大表格数据依旧流畅

该进度分页条仅桌面表格模式展示(移动端卡片为顺序浏览)。数据无需持久化,加载进度随会话即时计算。

5.21.1 数据加载方式切换

当配置了 pagination(分页信息)后,除了固定的远程分页显示外,还可以在「列设置」抽屉中默认提供「数据加载方式」切换,让使用方在「分页显示」与「动态加载显示」之间自由切换:

  • 分页(page,默认):常规分页显示,点击页码/上一页/下一页触发 page-change{current,pageSize},外层按页替换数据;
  • 动态加载(scroll):底部展示「加载进度分页条」,滚动条滚动到距底部 40px 内触发 page-change{current,pageSize}(与分页回调完全一致),外层按页追加数据并自动累积为大表格(虚拟滚动承载);已加载页码可点击滚动到该页首行、未加载页码置灰禁点。重置/清空数据后,已加载的页码随同归零(起始仅第 1 页已加载)。
<XDataGrid
  :columns="columns"
  :data="rows"
  row-key="id"
  :pagination="pagination"     <!-- 配置分页信息即可在「列设置」抽屉切换加载方式 -->
  :load-mode="loadMode"        <!-- 'page' 分页显示 / 'scroll' 动态加载(默认 'page') -->
  :toolbar="{ columnSetting: true }"
  @page-change="onPageChange"          <!-- 统一回调(两种模式一致) -->
  @load-mode-change="onLoadModeChange" <!-- 抽屉切换加载方式时触发 -->
>
</XDataGrid>
const pagination = { pageSize: 40, total: 4000, remote: true }
const loadMode = ref('scroll') // 默认动态加载,可经抽屉切换

function onPageChange({ current }) {
  const chunk = api.queryPage(current)             // 按页查询
  if (loadMode.value === 'scroll') {
    rows.value.push(...chunk)      // 动态加载:追加累积
  } else {
    rows.value = chunk            // 分页:按页替换
  }
}
function onLoadModeChange(mode) {
  loadMode.value = mode
  loadFirstPage()                 // 切换后回到第 1 页重新加载
}
// 重置:清空数据后已加载页码随同归零(起始仅第 1 页已加载)
function reset() {
  rows.value = []
  loadFirstPage()
}
子项 说明
开关入口 配置 pagination 且未配置 loadMore 时,「列设置」抽屉自动出现「数据加载方式」切换(分页 / 动态加载)
统一回调 两种模式均通过 @page-change="{ current, pageSize }" 回调;外层据 loadMode 决定替换还是追加数据
切换复位 抽屉里切换方式时回到第 1 页并重新加载;@load-mode-change="(mode) => ..." 可感知切换
切换持久化 配置 id 后,切换的加载方式经 IndexedDB 持久化,刷新 / 重进页面自动恢复到上次选择的方式
动态加载 滚动到底部触发 page-change,数据由外层每次追加累积,虚拟滚动承载大表格,数据量达到 pagination.total 即停止
加载进度条 scroll 模式下底部展示与常规分页条一致的「加载进度分页条」;页码窗口 ≤ 7、可点击省略号滑动,已加载页可点击滚动、未加载页置灰禁点
重置归零 动态加载模式下数据被清空/减少(重置)时,已加载页码随同归零,起始仅第 1 页已加载、可从头重新滚动加载

5.22 对象字段(点路径 key)

列 key 支持「点路径」形式,直接读取行数据中的嵌套对象字段。数据为 { name: '张三', chengjie: { yw: 92, sx: 88 } } 时,配置 { key: 'chengjie.yw' } 即读取 chengjie.yw 的值。

// 数据
const rows = ref([
  { id: 1, name: '张三', chengjie: { yw: 92, sx: 88, yy: 95 } },
  { id: 2, name: '李四', chengjie: { yw: 78, sx: 96, yy: 82 } },
])
// 列配置:key 使用点路径读取嵌套对象字段
const columns = [
  { type: 'index', label: '#', width: 60, align: 'center' },
  { key: 'name', label: '学生', width: 120, fixed: 'left', align: 'center' },
  { key: 'chengjie.yw', label: '语文', width: 110, align: 'center', sortable: true, editable: true, summable: true },
  { key: 'chengjie.sx', label: '数学', width: 110, align: 'center', sortable: true, editable: true, summable: true },
  { key: 'chengjie.yy', label: '英语', width: 110, align: 'center', sortable: true, editable: true, summable: true },
]
<XDataGrid :columns="columns" :data="rows" row-key="id" :show-footer="true" editable>
</XDataGrid>
子项 说明
取值显示 单元格自动按点路径读取嵌套对象字段渲染
编辑写回 双击编辑后按点路径写回嵌套对象(缺失中间节点自动创建)
排序 / 筛选 排序、列筛选按点路径取值比较 / 过滤
表尾合计 summable / footer-summary 字段同样支持点路径
移动端卡片 卡片标题与字段取值均支持点路径
全局搜索 工具栏搜索对点路径列同样命中
点路径插槽 点路径列的插槽名同样可用,如 #chengjie.yw="{ row }"

5.23 数组对象自动合并

把「数组嵌套对象」数据按数组项展开为多子行,并对非数组合并列做纵向 rowspan 合并,适合「一对多」明细(如一个订单多行商品、一位学生多个科目)。

数据结构:

const rows = ref([
  { id: 1, name: '张三', cls: '一班', cj: [
      { xk: '语文', cj: 90 },
      { xk: '数学', cj: 100 },
    ] },
  { id: 2, name: '李四', cls: '一班', cj: [{ xk: '英语', cj: 88 }] },
])

列配置:数组列用「点路径」key,首个段为数组字段,其余段为数组项字段。

const columns = [
  { key: 'name', label: '姓名', width: 110, fixed: 'left' },
  { key: 'cls', label: '班级', width: 90 },
  { key: 'cj.xk', label: '学科', width: 120 },   // 数组列:展开后逐项显示学科
  { key: 'cj.cj', label: '成绩', width: 90 },    // 数组列
  { key: 'remark', label: '备注', minWidth: 160 },
]

启用(全量合并):

<XDataGrid :columns="columns" :data="rows" row-key="id" :array-merge="true" />

启用(仅合并姓名列,其余非数组列逐子行重复展示):

<XDataGrid :columns="columns" :data="rows" row-key="id" :array-merge="{ mergeColumns: ['name'] }" />
子项 说明
自动识别 由示例数据 + 点路径 key 自动判定数组列与父字段;无需额外声明
显式声明 数据为空或无法自动识别时,对数组列配置 arrayField: true 兜底
全量合并 arrayMerge=true 对全部非数组合并列做 rowspan 纵向合并
按列合并 arrayMerge={ mergeColumns: ['name'] } 仅合并指定列,其余列逐子行展示
组级列 序号 / 选择(复选框 / 单选)列代表整条记录,始终随数组展开整组合并,不受 mergeColumns 限制
斑马纹 合并组视为一行着色:同一组内所有子行背景一致,不再因展开子行交替条纹
多父字段 多个数组父字段按同下标「zip」展开(同一组内下标对应)
段值读取 展开子行中父字段替换为对应数组项对象,点路径 cj.xk 自然命中项内字段
空数组 数组为空时保留 1 行占位(表头仍展示,非数组合并列不纵向合并)
虚拟滚动 展开后的子行进入统一虚拟行坐标,百万级明细仍保持流畅
单元格能力 数组列上排序 / 筛选 / 提示框 / 列模板 / 插槽均有效
移动端卡片 卡片模式下,每个「展开组」自动合并为一张卡片,滑动或底部按钮切换
唯一行键 展开子行自动注入内部唯一键(源行键 + 下标),选择 / 展开互不串扰

5.24 操作列与列自定义插槽

A. 操作列(actions)

在列配置 columns 中某列的 actions 数组声明操作按钮(TableAction)。最适合「详情 / 修改 / 删除 / 审批」等业务操作;按钮可基于当前行数据做权限显隐(visible)与状态禁用(disabled)。

<XDataGrid :columns="columns" :data="rows" row-key="id" @action="onAction" />

<script setup>
const columns = [
  { key: 'name', label: '姓名' },
  {
    key: 'ops',
    label: '操作',
    fixed: 'right',
    actionLimit: 2,   // 最多直接展示 2 个(默认即 2),超出收进「更多 ⋯」
    actions: [
      { key: 'detail', label: '详情' },           // 触发统一 @action
      { key: 'edit', label: '修改', type: 'primary',
        onClick: (row) => openEdit(row) },      // 自带点击逻辑
      { key: 'approve', label: '审批', type: 'success',
        visible: (row) => row.status === '进行中' },   // 权限 / 行判定
      { key: 'remove', label: '删除', type: 'danger',
        disabled: (row) => row.status === '已完成',    // 状态禁用
        onClick: (row) => confirm('确认删除?') || false }, // false 阻止事件
      { key: 'copy', label: '复制' },
    ],
  },
]

function onAction({ key, row }) {
  // 未配置 onClick(或返回非 false)的按钮统一在此处理
  console.log(key, row)
}
<\/script>
子项 说明
visible 权限 / 显隐判定(返回 false 隐藏),典型按角色 / 状态控制
disabled 布尔或基于当前行函数(返回 true 禁用置灰,禁用态不触发事件)
type default / primary / success / warning / danger,影响按钮配色
icon 按钮图标(置于文字之前):内联 SVG 字符串 / 渲染函数(返回 VNode)/ Vue 组件 / 全局注册图标名
onClick 传入当前行的点击回调;返回 false(或异步 resolve false)阻止统一 @action
actionLimit 列级展示的控件数量上限,默认 2:可见按钮超出时收起为「前 actionLimit-1 个按钮 + 「更多」下拉」
折叠体验 PC 可见按钮不超过 actionLimit 时全量平铺;超出则显示「前 actionLimit-1 个按钮 + 「更多」下拉触发按钮」(文本 + 箭头,点击展开,点击外部自动收起)
移动端 卡片模式下 actions 列自动适配为卡片底部操作条,全部按钮平铺、可换行,点击不触发翻页
事件 未拦截的按钮点击触发统一 @action({ key, row, event })
列级插槽覆盖 若操作列还配置了 slotName / 插槽名,且模板提供了对应自定义插槽,则整个操作列交由插槽完全渲染(不再渲染按钮组),用于高度自定制的操作 UI
数组合并 操作列属「非数组合并列」,在数组展开时整组合并(整组共享一组操作按钮)
B. 列自定义插槽(slotName)

列默认以 key 作为单元格插槽名。配置 slotName 后,该列改用 #<slotName> 插槽渲染任意自定义内容(图片、徽章、混合富文本等),未提供对应插槽时回落到列 key 取值。

<XDataGrid :columns="columns" :data="rows" row-key="id">
  <template #avatar="{ row }">
    
    <span>{{ row.name }}</span>
  </template>
  <template #status="{ row }">
    <span class="tag" :class="`is-${row.tag}`">{{ row.status }}</span>
  </template>
</XDataGrid>

<script setup>
const columns = [
  { key: 'name', label: '姓名', slotName: 'avatar' },   // 用 #avatar 插槽渲染头像
  { key: 'status', label: '状态', slotName: 'status' },  // 用 #status 插槽渲染徽章
]
<\/script>
子项 说明
插槽名 配置 slotName 后使用 #<slotName>;缺省用列 key
作用域 插槽作用域 { row, column, $index, $rowIndex },可直接取当前行数据
优先级 插槽 > slotName 对应插槽 > 列 cellRender > 默认取值
富内容 不限于纯文本,可渲染图片 / 徽章 / 按钮等任意 VNode
移动端卡片 cardProps.fields 不显式排除时,插槽列按列序展示;操作列自动变为操作条,不作为普通字段

6. 主题定制(CSS 变量)

所有主题通过 CSS 变量(--tbl-*)暴露,可全局覆盖或按 .tbl-root 作用域覆盖。字体渲染沿用 Element UI 默认策略,不强制自定义字体栈。

.tbl-root {
  --tbl-primary-color: #409eff;       /* 品牌色 */
  --tbl-header-bg: #f5f7fa;           /* 表头背景 */
  --tbl-body-bg: #ffffff;             /* 表体背景 */
  --tbl-row-hover-bg: #f5f7fa;        /* 行悬停 */
  --tbl-row-selected-bg: #ecf5ff;     /* 选中行 */
  --tbl-stripe-even-bg: #fafafa;      /* 斑马纹 */
  --tbl-border-color: #ebeef5;        /* 分割线 */
  --tbl-text-color: #303133;          /* 主文本 */
  --tbl-text-secondary-color: #909399;/* 次要文本 */
  --tbl-font-size: 14px;              /* 字号 */
  --tbl-cell-padding: 0 12px;         /* 单元格内边距 */
  --tbl-fixed-shadow: 6px 0 12px -6px rgba(0,0,0,.15);      /* 左固定阴影 */
  --tbl-fixed-shadow-inv: -6px 0 12px -6px rgba(0,0,0,.15); /* 右固定阴影 */
  --tbl-control-selection-w: 48px;    /* 选择列宽 */
  --tbl-control-expand-w: 48px;       /* 展开列宽 */
  --tbl-control-index-w: 56px;        /* 序号列宽 */
  --tbl-tree-indent: 16px;            /* 树缩进/级 */
  --tbl-action-icon-size: 14px;       /* 工具栏操作按钮图标尺寸 */
}

6.1 主题颜色可配置(API)

交互元素(复选框 / 单选 / 开关 / 排序 / 筛选 / 分页)的品牌色跟随 --tbl-primary-color,默认值保持组件蓝色 #409eff。通过「全局主题」或「实例 theme prop」两种方式配置,颜色变更对所有已渲染与新渲染的表格实时生效。

import { setGlobalTheme, getGlobalTheme, resetGlobalTheme } from 'table-grid-plugin'

// 全局主题:对所有表格(已渲染 + 新渲染)实时生效
setGlobalTheme({ primaryColor: '#f56c6c' })   // 传子集即局部覆盖
const snap = getGlobalTheme()                  // 读取当前全局主题快照
resetGlobalTheme()                             // 恢复组件默认蓝色
<!-- 实例级覆盖:仅当前表格生效,优先级高于全局主题 -->
<XDataGrid :columns="columns" :data="rows" row-key="id"
  :theme="{ primaryColor: '#67c23a' }" />

ThemeTokens 各键(颜色即可配置):

Token CSS 变量 默认 说明
primaryColor --tbl-primary-color #409eff 品牌色:控制复选框 / 单选 / 开关 / 排序 / 筛选 / 分页高亮
headerBg --tbl-header-bg #f5f7fa 表头背景
bodyBg --tbl-body-bg #ffffff 表体背景
rowHoverBg --tbl-row-hover-bg #f5f7fa 行悬停背景
rowSelectedBg --tbl-row-selected-bg #ecf5ff 选中行背景
stripeEvenBg --tbl-stripe-even-bg #fafafa 斑马纹偶数行背景
borderColor --tbl-border-color #ebeef5 分割线颜色
textColor --tbl-text-color #303133 主文本颜色
textSecondaryColor --tbl-text-secondary-color #909399 次要文本颜色
fontSize --tbl-font-size 14px 字号
cellPadding --tbl-cell-padding 0 12px 单元格内边距
fixedShadow --tbl-fixed-shadow — 固定列阴影

优先级:实例 theme prop(根节点内联)> 全局主题(注入 <style>)> SCSS 默认变量。二者可叠加:全局设品牌色,单个表格用 theme prop 局部覆盖。

7. 国际化(i18n)

7.1 文本词典翻译(推荐):翻表头 / 按钮 / 分页等公共内容

默认语言为中文,label 即 key:传入目标语言译文词典即可翻译表头 label 以及分页、按钮、空态等内置公共文案;表体数据不翻译。切回中文时直接显示 label(中文为「无效语言包」,无需也不应收录)。

// 词典:语言码 -> { 中文文本: 译文 }
const dict = {
  en: { 姓名: 'Name', 部门: 'Department', 得分: 'Score', 评级: 'Grade' },
}
import { setLocale, setTranslations, setSystemLocale, lang, tr } from 'table-grid-plugin'

setTranslations(dict)                     // 全局注册词典,所有表格实时生效
setLocale({ lang: 'en-US', autoSystem: false })  // 切英文(切中文→直显 label)
setSystemLocale(false)                    // 关闭系统语言自动跟随
setLocale({ lang: 'zh-CN', autoSystem: false })  // 回中文
tr('姓名')                                // 按当前语言翻译:'Name'(中文下返回'姓名')

表格列使用纯中文 label(即翻译 key):

<XDataGrid
  :columns="[{ key:'name', label:'姓名' }, { key:'dept', label:'部门' }]"
  :data="rows" row-key="id"
/>

表格级可用 translations prop 做实例覆盖(粒度最优先),便于同一页面多语言差异:

<XDataGrid :columns="columns" :data="rows" row-key="id"
  :translations="{ en: { 姓名: 'Name', 部门: 'Department', 得分: 'Score', 评级: 'Rating' } }" />

内置已预置一份「中→英」默认词典(分页 / 空态 / 列设置 / 搜索 / 合计等公共文案开箱即英文);用户 setTranslations / :translations 词典优先覆盖。

7.2 切换机制与内置语言包(key 型,进阶)

默认语言为中文,且不自动跟随系统:只要未显式调用 setLocale / setSystemLocale(true),任何表格(含未配置语言包的表)都保持中文。需要跟随或切换时再手动控制。语言码 en-US/en、zh-CN/zh 均可,词典按短码命中。

import { setLocale, setSystemLocale, lang, getMessages } from 'table-grid-plugin'

setLocale({ lang: 'en-US' })            // 手动切换为英文(默认不跟随系统,见下)
setSystemLocale(false)                   // 关闭系统语言自动跟随(默认即为关闭,可完全手动控制)
setSystemLocale(true)                    // 显式开启自动跟随(无配置时仍会随系统语言切换)
setLocale({ lang: 'zh-CN', autoSystem: false }) // 回中文且不跟随系统

表格级 locale prop 做 key 型实例覆盖,粒度最优先:

<XDataGrid :columns="columns" :data="rows" row-key="id"
  :locale="{ empty: '暂无数据', total: '共 {n} 条' }" />

7.3 语言包结构(内置 zh-CN / en-US 完整覆盖)

文本使用 {n} 占位符插值。全部消息键:

消息 key zh-CN 示例 en-US 示例 说明
loading 加载中… Loading… 加载态
empty 暂无数据 No data 空态文案
loadMoreLoading 加载中… Loading… load-more 加载中
loadMoreFinished 已加载全部数据 All data loaded load-more 已加载全部
expand / collapse 展开 / 收起 Expand / Collapse 树 / 展开行按钮 title
sortAsc / sortDesc 升序 / 降序 Ascending / Descending 排序提示
filter / filterClear 筛选 / 清除 Filter / Clear 筛选
columnSetting 列设置 Columns 列设置抽屉标题
search 搜索 Search 工具栏搜索占位
buttonNames / buttonNamesTooltip 按钮文字 Labels 按钮文字开关
more 更多 More 溢出更多
prevPage / nextPage 上一页 / 下一页 Previous / Next 分页翻页
pageSizeUnit 条/页 / page 每页条数单位
total 共 {n} 条 Total {n} 分页总条数({n} 插值)
footerLabel 合计 Total 表尾合计名称
prevItem / nextItem 上一项 / 下一项 Previous / Next 卡片切换
reset 重置 Reset 列设置重置
close 关闭 Close 关闭
dragHint 拖动左侧把手即可调整字段顺序 Drag the left handle to reorder fields 列设置拖拽提示
fixedNotHidden 固定列不可隐藏 Fixed columns are always visible 固定列提示
fieldCount {n} 个字段 {n} fields 字段个数({n} 插值)
expandRow 展开内容 Detail 展开行兜底内容

语言包是按扁平 key 全量收录的完整对象;传入 Partial<LocaleMessages> 即可增量覆盖,未覆盖项回退内置语言包默认文案(props.locale > 全局 setLocale > 内置语言包)。

8. 浏览器兼容性

  • 使用 Set、CSS 变量、sticky 定位、ResizeObserver、IndexedDB。
  • 支持所有现代浏览器(Chrome / Edge / Firefox / Safari,含移动端)。
  • 不做 IE 兼容。

9. 常见问题 FAQ

Q1:为什么表格空白 / 总高度为 0?

虚拟滚动依赖容器高度。请为 <XDataGrid> 或外层设置确定高度,例如 style="height: 400px"。

Q2:rowKey 不传会怎样?

选择、树形、展开、合并、宽高追踪都依赖稳定的行键。若省略可能导致跨页选择错乱。必须提供。

Q3:cellRender 返回的 HTML 字符串没有渲染成富文本?

返回值为 VNode/文本,非 innerHTML。如需富文本请返回 h() 创建的 VNode,或使用插槽 + v-html。

Q4:表格列宽与容器不一致?

width 为固定占位;未设 width 的列按 minWidth 在剩余空间内自适应分配。可调用实例方法 recalcWidths() 在容器变化后重算。

Q5:数据量非常大时还卡顿吗?

Body 只渲染可视区间(约 视图高度/行高 + 2×overscan 行)。滚动只更新偏移量,不做重计算,十万级数据首屏与滚动均保持在毫秒级。

Q6:展开行怎么触发?

两种方式:配置 type:'expand' 列(行内按钮),或 :expandable="true" 且不配置 expand 列(首个数据列自动挂展开箭头)。内容经 #expand-row 插槽渲染。同一时刻仅一行可展开,展开行高度自适应插槽内容;@expand-change="(row, expanded)" 回调可在展开时动态加载接口数据更新插槽。

Q7:列设置如何持久化?

配置 id 属性后,用户调整的列顺序 / 显隐自动写入 IndexedDB,刷新页面保留;columns 配置结构变化或点击「重置」时自动失效。

License

MIT

Related Packages

element-tree-grid

element tree table with vue

9,097 225
open-grid

초경량·제로 의존성 데이터 그리드 — React·Vue·Angular·jQuery·Vanilla 어디서나. 가...

4,707 1
arthub-table

High-performance canvas-based table/grid component for Vue 3 with TypeScript sup...

62,969
comfortable-js

Simple, Fast, Customizable Table Component in JavaScript.

5,506 26
vue-tablekit

High-performance virtualized data grid for Vue 3, with DOM node recycling, cell...

2,017 2

Version History

Version Released Vue License
1.0.10 ^3.2.0 MIT
1.0.9 ^3.2.0 MIT
1.0.8 ^3.2.0 MIT
1.0.7 ^3.2.0 MIT
1.0.6 ^3.2.0 MIT