npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

table-grid-plugin

v1.0.10

Published

开箱即用的 Vue3 高性能数据表格组件。纯 div + 虚拟滚动实现,支持百万级数据流畅渲染、树形、固定列与表头、多级表头、选择、自定义列模板与插槽、单元格合并、展开行与移动端卡片模式。渲染层统一 TSX,运行时零依赖。

Downloads

1,700

Readme

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 | loadingtrue 时以骨架屏展示(默认转圈) | | 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 }) | 单元格编辑提交(校验通过后;triggerdouble-click/enter/blur) | | column-resize | ({ key, width }) | 拖拽调整列宽 | | search | ({ keyword }) | 本地搜索触发(setSearch 内部) | | toolbar-search | ({ event }) | 点击工具栏「搜索」按钮(交互由使用方实现) | | toolbar-action | ({ key, event }) | 点击工具栏通用操作按钮(未设 onClick 时触发) | | action | ({ key, row, event }) | 点击操作列按钮(keyTableAction.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'

SelectionInfoselection-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 支持剩余空间自适应分配 | | 密度切换 | densitycompact(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' | 'right',sticky 定位 + 边缘阴影 | | 固定表头 | 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? };校验失败不入库 | | 输入类型 | editTypetext / 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(或数据条数增长)后复位,才允许再次触发 | | 底部提示 | loadingtrue 时底部显示「加载中…」提示条;finishedtrue 时显示「已加载全部数据」并停止触发 | | 隐藏分页 | 提供 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 }">
    <img class="avatar" :src="row.avatar" />
    <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/enzh-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 定位、ResizeObserverIndexedDB
  • 支持所有现代浏览器(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