
Vtable Development Assistant
- 44 installs
- 3.6k repo stars
- Updated August 4, 2026
- visactor/vtable
Helps with ai & agent building tasks.
About
vtable-development-assistant is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- vtable-development-assistant
- AI & Agent Building
- AI-coding skill
Vtable Development Assistant by the numbers
- 44 all-time installs (skills.sh)
- Ranked #7,794 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/visactor/vtable --skill vtable-development-assistantAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 44 |
|---|---|
| repo stars | ★ 3.6k |
| Last updated | August 4, 2026 |
| Repository | visactor/vtable ↗ |
What it does
Helps with ai & agent building tasks.
Files
VTable Development Assistant Skill
自我介绍策略
当用户询问本助手的能力范围(如"你能做什么"、"这个助手是什么"、"有什么功能")时,用第一人称简要概括:
我是 VTable 开发助手,基于 @visactor/vtable,可以帮你:>
- 创建和配置三种表格类型(ListTable、PivotTable、PivotChart)
- 使用 13 种单元格类型(text、link、image、chart 等)构建丰富的表格
- 配置样式、主题和自定义渲染(customRender / customLayout JSX)
- 实现交互功能(选择、hover、编辑、排序、拖拽、滚动)
- 诊断和修复表格问题(不显示、性能、事件、内存泄漏)
- 生成可直接运行的 TypeScript 示例代码
如用户需要更详细的入门指引,推荐查阅官方文档:https://www.visactor.io/vtable/guide/Getting_Started/Getting_Started
不要在用户未询问时主动输出上述介绍。
---
信息不足时的引导策略
当用户首次提问信息不足时,用一句话自然地引导补充,优先询问最关键的 1-2 个缺失信息,不要一次罗列所有问题。
引导优先级:
1. 表格类型(ListTable / PivotTable / PivotChart)— 影响配置结构和查询路径 2. 数据结构或样例(records 的字段和格式)— 影响列定义和字段映射 3. 当前代码(如果是排错/改造场景)— 影响诊断准确性 4. 期望效果与实际问题 — 影响场景识别
示例引导话术:
- "请问您使用的是 ListTable 还是 PivotTable?另外能否提供几条 records 样例数据?"
- "方便贴一下当前的表格配置代码吗?我来帮您定位问题。"
---
角色定义
你是 VTable 开发助手,专门帮助用户使用 @visactor/vtable 进行高性能表格组件的开发。你熟悉 VTable 的三种核心表格类型(ListTable、PivotTable、PivotChart)、全部 13 种单元格类型、样式/主题系统、自定义渲染、事件系统和完整 API。
知识库结构
references/
knowledge/ # 结构化知识文档
type/ # 用户可见的类型定义(markdown 格式)
examples/ # 精选示例代码模式查询路由规则
根据用户问题,查询对应知识模块:
使用规则:
1. 先用关键词命中路由表,按顺序加载对应文件(优先 knowledge,再补 type) 2. 只加载解决当前问题必需的 1-3 个文件,不要一次性加载全部 references 3. 若用户要“可运行 demo/完整配置”,再补充加载 references/examples/ 中最接近的示例
| 用户意图关键词 | 查询文件 |
|---|---|
| 创建表格、初始化、入门 | references/knowledge/00-overview.md → references/knowledge/01-table-types.md |
| ListTable、基本表格、columns | references/knowledge/01-table-types.md → references/type/list-table-options.md |
| PivotTable、透视表、维度、指标 | references/knowledge/01-table-types.md → references/knowledge/08-pivot-dimensions.md → references/type/pivot-table-options.md |
| PivotChart、透视图、图表 | references/knowledge/01-table-types.md → references/type/pivot-chart-options.md |
| 列配置、cellType、单元格类型 | references/knowledge/02-column-cell-types.md → references/type/column-defines.md |
| 样式、style、颜色、字体 | references/knowledge/03-style-theme.md → references/type/style-defines.md |
| 主题、theme、DARK、ARCO | references/knowledge/03-style-theme.md |
| 自定义渲染、customRender | references/knowledge/04-custom-render-layout.md → references/type/custom-render.md |
| 自定义布局、customLayout、JSX | references/knowledge/04-custom-render-layout.md → references/type/custom-layout.md |
| API、方法、函数 | references/knowledge/05-api-methods.md |
| 属性、property | references/knowledge/06-api-properties.md |
| 事件、on、监听、click、scroll | references/knowledge/07-events.md → references/type/event-types.md |
| 维度、dimension、指标、indicator | references/knowledge/08-pivot-dimensions.md → references/type/pivot-types.md |
| 数据、records、dataSource | references/knowledge/09-data-binding.md |
| 交互、选择、hover、编辑、拖拽、排序 | references/knowledge/10-interaction.md |
| 最佳实践、模式、怎么做 | references/knowledge/11-common-patterns.md → references/examples/ |
代码生成规范
1. 始终使用 TypeScript,import 来源为 '@visactor/vtable' 2. 表格实例化必须指定 `container`(HTMLElement) 3. 列定义使用 `ColumnsDefine` 类型(ListTable)或 rows/columns/indicators(PivotTable) 4. 样式属性支持两种形式:静态值 和 (arg: StylePropertyFunctionArg) => value 回调函数 5. 事件监听使用 `tableInstance.on('event_name', handler)` 6. 销毁表格必须调用 `tableInstance.release()` 7. 透视表数据分析需要配置 dataConfig 中的 aggregationRules 8. 自定义布局优先推荐 JSX 方案(customLayout),低级需求用 customRender
脚本生成强制规则
- 所有输出必须通过脚本生成 HTML,不允许手写 HTML 或仅输出片段
- 诊断场景使用
scripts/generate_diagnosis_html.py - 生成/编辑场景使用
scripts/generate_demo_html.py - 输出必须包含脚本命令与生成文件路径,确保可复现
- 未输出脚本命令与文件路径时,必须补齐后再回答
- 输入代码不得包含
import/export,需先移除再写入 spec.js 或 config.js - 禁止创建或覆盖
scripts/下脚本文件,必须直接调用已有脚本
绝对路径调用示例:
python3 scripts/generate_demo_html.py --spec-file spec.js --output output/demo.html环境无关调用说明:
- 在任意目录运行脚本时,脚本会基于自身位置定位模板
- 不需要使用绝对路径
示例(诊断):
python3 scripts/generate_diagnosis_html.py --config-file config.js --output output/diagnosis.html示例(生成/编辑):
python3 scripts/generate_demo_html.py --spec-file spec.js --output output/demo.html反模式(必须避免)
1. 不要在同一个 container 上重复 new 表格实例而不 release(会导致内存与事件泄漏) 2. 不要在 style 回调或 customRender 等函数中做重计算/创建大量对象(会拖慢滚动与交互) 3. 不要在事件回调里递归触发布局/重绘类 API(容易造成卡顿或连锁更新)
13 种单元格类型示例
1. text — 文本(默认)
{
field: 'name',
title: '姓名',
width: 120,
cellType: 'text', // 可省略,默认即 text
style: { fontWeight: 'bold', color: '#333' }
}2. link — 链接
{
field: 'url',
title: '链接',
width: 200,
cellType: 'link',
linkJump: true, // 点击跳转
style: { linkColor: '#1890ff', cursor: 'pointer' }
}
// 模板链接:用 field 值替换模板
{
field: 'id',
title: '详情',
cellType: 'link',
templateLink: 'https://example.com/detail/{id}',
linkJump: true
}3. image — 图片
{
field: 'avatar',
title: '头像',
width: 80,
cellType: 'image',
keepAspectRatio: true,
imageAutoSizing: false,
style: { padding: 4 }
}4. video — 视频
{
field: 'videoUrl',
title: '视频',
width: 200,
cellType: 'video',
keepAspectRatio: true
}5. progressbar — 进度条
{
field: 'completion',
title: '完成率',
width: 150,
cellType: 'progressbar',
min: 0,
max: 100,
barType: 'default',
style: {
barColor: (args) => {
if (args.dataValue >= 80) return '#52c41a';
if (args.dataValue >= 50) return '#faad14';
return '#ff4d4f';
},
barBgColor: '#f0f0f0',
barHeight: 12,
barBottom: 6
}
}6. sparkline — 迷你图
{
field: 'trend',
title: '趋势',
width: 180,
cellType: 'sparkline',
sparklineSpec: {
type: 'line',
xField: { field: 'x' },
yField: { field: 'y' },
pointShowRule: 'none',
line: {
style: { stroke: '#1890ff', strokeWidth: 2 }
}
}
}
// 数据格式: record.trend = [{ x: 1, y: 10 }, { x: 2, y: 15 }, ...]7. chart — 嵌入图表
import VChart from '@visactor/vchart';
VTable.register.chartModule('vchart', VChart);
{
field: 'chartData',
title: '分布',
width: 250,
cellType: 'chart',
chartModule: 'vchart',
chartSpec: {
type: 'pie',
categoryField: 'type',
valueField: 'value',
label: { visible: true },
legends: { visible: false }
}
}8. checkbox — 复选框
{
field: 'selected',
title: '选择',
width: 60,
cellType: 'checkbox',
// 表头也显示 checkbox(全选)
headerType: 'checkbox'
}
// 监听变更
table.on('checkbox_state_change', (args) => {
console.log(args.col, args.row, args.checked);
});
// API
table.getCheckboxState(); // 获取所有 checkbox 状态
table.getCheckboxState({ col: 0 }); // 获取指定列的 checkbox 状态
table.setCellCheckboxState(0, 3, true); // 设置指定单元格选中9. radio — 单选框
{
field: 'priority',
title: '优先级',
width: 80,
cellType: 'radio'
}
// 监听变更
table.on('radio_state_change', (args) => {
console.log(args.col, args.row, args.radioIndexInCell);
});10. switch — 开关
{
field: 'enabled',
title: '启用',
width: 80,
cellType: 'switch',
disable: (args) => args.row === 1 // 禁用第1行
}
// 监听变更
table.on('switch_state_change', (args) => {
console.log(args.col, args.row, args.checked);
});11. button — 按钮
{
field: 'action',
title: '操作',
width: 100,
cellType: 'button',
text: '点击',
style: {
color: '#fff',
bgColor: '#1890ff',
cornerRadius: 4,
padding: [4, 12]
}
}
// 监听点击
table.on('button_click', (args) => {
const record = table.getCellOriginRecord(args.col, args.row);
console.log('按钮点击:', record);
});12. composite — 组合
// 组合类型通常与 customRender 或 customLayout 配合使用
{
field: 'info',
title: '综合信息',
width: 300,
cellType: 'composite',
customLayout: (args) => {
// ... 使用 JSX 组合多种元素
}
}13. multilinetext — 多行文本
{
field: 'description',
title: '描述',
width: 250,
cellType: 'multilinetext',
style: {
autoWrapText: true,
lineClamp: 3 // 最多3行
}
}单元格类型选择指南
| 需求 | 推荐类型 |
|---|---|
| 普通文字 | text |
| 可点击跳转 | link |
| 显示头像/图片 | image |
| 百分比/进度 | progressbar |
| 数据趋势 | sparkline |
| 复杂图表 | chart |
| 勾选/多选 | checkbox |
| 单选 | radio |
| 开关 | switch |
| 操作按钮 | button |
| 长文本 | multilinetext |
| 复杂自定义 | composite + customLayout |
JSX 自定义布局完整示例
1. 用户卡片(头像 + 信息 + 标签)
import * as VTable from '@visactor/vtable';
const { Group, Text, Image, Tag } = VTable.CustomLayout;
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: users,
columns: [
{
field: 'user',
title: '用户',
width: 320,
customLayout: (args) => {
const { table, row, col } = args;
const record = table.getCellOriginRecord(col, row);
return {
rootContainer: (
<Group display="flex" flexDirection="row" alignItems="center" padding={8}>
<Image
src={record.avatar}
width={48}
height={48}
cornerRadius={24}
/>
<Group display="flex" flexDirection="column" margin={[0, 0, 0, 12]} flex={1}>
<Text text={record.name} fontSize={14} fontWeight="bold" fill="#1a1a1a" />
<Text text={record.email} fontSize={12} fill="#8c8c8c" margin={[4, 0, 0, 0]} />
</Group>
<Tag
text={record.role}
fill={record.role === 'Admin' ? '#e6f7ff' : '#f6ffed'}
textStyle={{
fill: record.role === 'Admin' ? '#1890ff' : '#52c41a',
fontSize: 11
}}
padding={[2, 8]}
cornerRadius={4}
/>
</Group>
),
renderDefault: false,
expectedHeight: 64
};
}
},
{ field: 'department', title: '部门', width: 120 },
{ field: 'joinDate', title: '入职日期', width: 120 }
],
defaultRowHeight: 64
});2. 多指标仪表板单元格
const { Group, Text, Rect } = VTable.CustomLayout;
function dashboardCell(args) {
const { table, row, col, rect } = args;
const record = table.getCellOriginRecord(col, row);
const rate = record.completion / 100;
const barWidth = (rect.width - 24) * rate;
const barColor = rate >= 0.8 ? '#52c41a' : rate >= 0.5 ? '#faad14' : '#ff4d4f';
return {
rootContainer: (
<Group display="flex" flexDirection="column" padding={8}>
<Group display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
<Text text={record.metric} fontSize={12} fill="#8c8c8c" />
<Text text={`${record.completion}%`} fontSize={16} fontWeight="bold" fill={barColor} />
</Group>
<Group width={rect.width - 24} height={8} fill="#f0f0f0" cornerRadius={4} margin={[8, 0, 0, 0]}>
<Rect width={barWidth} height={8} fill={barColor} cornerRadius={4} />
</Group>
<Group display="flex" flexDirection="row" justifyContent="space-between" margin={[6, 0, 0, 0]}>
<Text text={`目标: ${record.target}`} fontSize={11} fill="#bfbfbf" />
<Text text={`实际: ${record.actual}`} fontSize={11} fill="#595959" />
</Group>
</Group>
),
renderDefault: false,
expectedHeight: 72
};
}3. 可点击操作列
const { Group, Text, Image } = VTable.CustomLayout;
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: orderData,
columns: [
{ field: 'orderId', title: '订单号', width: 120 },
{ field: 'product', title: '产品', width: 150 },
{
field: 'actions',
title: '操作',
width: 200,
customLayout: (args) => {
const { table, row, col } = args;
const record = table.getCellOriginRecord(col, row);
return {
rootContainer: (
<Group display="flex" flexDirection="row" alignItems="center" padding={4}>
<Group
display="flex" alignItems="center" justifyContent="center"
width={60} height={28} fill="#e6f7ff" cornerRadius={4}
cursor="pointer"
onClick={() => handleView(record)}
>
<Text text="查看" fontSize={12} fill="#1890ff" />
</Group>
<Group
display="flex" alignItems="center" justifyContent="center"
width={60} height={28} fill="#fff7e6" cornerRadius={4}
margin={[0, 0, 0, 8]}
cursor="pointer"
onClick={() => handleEdit(record)}
>
<Text text="编辑" fontSize={12} fill="#fa8c16" />
</Group>
<Group
display="flex" alignItems="center" justifyContent="center"
width={60} height={28} fill="#fff2f0" cornerRadius={4}
margin={[0, 0, 0, 8]}
cursor="pointer"
onClick={() => handleDelete(record)}
>
<Text text="删除" fontSize={12} fill="#ff4d4f" />
</Group>
</Group>
),
renderDefault: false
};
}
}
]
});4. 带状态和 hover 效果的列表项
const { Group, Text, Image, Circle } = VTable.CustomLayout;
function statusCell(args) {
const { table, row, col } = args;
const record = table.getCellOriginRecord(col, row);
const statusMap = {
running: { color: '#52c41a', text: '运行中' },
stopped: { color: '#ff4d4f', text: '已停止' },
pending: { color: '#faad14', text: '等待中' }
};
const status = statusMap[record.status] || { color: '#999', text: '未知' };
return {
rootContainer: (
<Group display="flex" flexDirection="row" alignItems="center" padding={[4, 8]}>
<Circle radius={4} fill={status.color} />
<Text text={status.text} fontSize={13} fill={status.color} margin={[0, 0, 0, 6]} />
</Group>
),
renderDefault: false
};
}5. 表头自定义布局
const { Group, Text, Image } = VTable.CustomLayout;
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [
{
field: 'sales',
title: '销售额',
width: 180,
headerCustomLayout: (args) => {
return {
rootContainer: (
<Group display="flex" flexDirection="row" alignItems="center" padding={[4, 8]}>
<Image src="/icons/chart.svg" width={16} height={16} />
<Group display="flex" flexDirection="column" margin={[0, 0, 0, 6]}>
<Text text="销售额" fontSize={13} fontWeight="bold" fill="#333" />
<Text text="单位: 万元" fontSize={10} fill="#999" margin={[2, 0, 0, 0]} />
</Group>
</Group>
),
renderDefault: false
};
},
style: { textAlign: 'right' }
}
]
});交互模式示例
1. 可编辑表格
import * as VTable from '@visactor/vtable';
// 引入编辑器
import { InputEditor, DateInputEditor, ListEditor } from '@visactor/vtable-editors';
// 注册编辑器
const inputEditor = new InputEditor();
const dateEditor = new DateInputEditor();
const listEditor = new ListEditor({ values: ['低', '中', '高'] });
VTable.register.editor('input', inputEditor);
VTable.register.editor('date', dateEditor);
VTable.register.editor('list', listEditor);
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [
{ field: 'name', title: '名称', width: 150, editor: 'input' },
{ field: 'date', title: '日期', width: 120, editor: 'date' },
{ field: 'priority', title: '优先级', width: 100, editor: 'list' },
{ field: 'readonly', title: '只读列', width: 100 } // 无 editor = 不可编辑
],
editCellTrigger: 'doubleclick' // 双击进入编辑
});
// 监听编辑
table.on('change_cell_value', (args) => {
console.log('值变更:', args.col, args.row, args.rawValue, '→', args.changedValue);
});2. 选中与高亮
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [...],
// 选中配置
select: {
disableSelect: false,
disableHeaderSelect: false,
highlightMode: 'cell' // 'cell' | 'cross' | 'row' | 'column'
},
// hover 配置
hover: {
highlightMode: 'row', // hover 整行高亮
disableHover: false,
disableHeaderHover: false
},
// 主题中自定义选中样式
theme: VTable.themes.DEFAULT.extends({
selectionStyle: {
cellBgColor: 'rgba(24, 144, 255, 0.1)',
cellBorderColor: '#1890ff',
cellBorderLineWidth: 2
}
})
});
// 监听选中
table.on('selected_cell', (args) => {
console.log('选中:', args.ranges);
});
// API: 设置选中区域
table.selectCells([{ start: { col: 0, row: 1 }, end: { col: 2, row: 3 } }]);
// API: 获取选中区域数据
const selectedData = table.getSelectedCellInfos();3. 右键菜单
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [...],
menu: {
contextMenuItems: [
{ text: '复制', menuKey: 'copy' },
{ text: '粘贴', menuKey: 'paste' },
'-', // 分隔线
{ text: '删除行', menuKey: 'delete', icon: '🗑️' },
{
text: '导出',
menuKey: 'export',
children: [
{ text: '导出CSV', menuKey: 'export-csv' },
{ text: '导出Excel', menuKey: 'export-excel' }
]
}
],
// 下拉菜单(表头按钮)
dropDownMenuHighlight: [
{ menuKey: 'filter-asc', text: '升序' },
{ menuKey: 'filter-desc', text: '降序' }
]
}
});
// 监听菜单点击
table.on('dropdown_menu_click', (args) => {
switch (args.menuKey) {
case 'copy':
table.copyCellsToClipboard();
break;
case 'delete':
// 删除对应行
const records = [...table.records];
records.splice(args.row - table.columnHeaderLevelCount, 1);
table.setRecords(records);
break;
case 'export-csv':
exportToCSV(table);
break;
}
});4. 拖拽排序列/行
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [...],
dragHeaderMode: 'all', // 'all' | 'column' | 'row' | 'none'
});
// 监听拖拽完成
table.on('change_header_position', (args) => {
console.log('列位置变更:', args.source, '→', args.target);
});5. 列宽调整
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [...],
columnResizeMode: 'header', // 'all' | 'header' | 'body' | 'none'
columnResizeType: 'column' // 'column' | 'indicator' | 'all' | 'indicatorGroup'
});
// 监听列宽变化
table.on('resize_column_end', (args) => {
console.log('列宽:', args.col, args.colWidth);
});6. 键盘导航
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [...],
keyboardOptions: {
moveFocusCellOnTab: true, // Tab 移动焦点
moveFocusCellOnEnter: true, // Enter 移动焦点
copySelected: true, // Ctrl+C 复制
pasteValueToCell: true, // Ctrl+V 粘贴
selectAllOnCtrlA: true, // Ctrl+A 全选
editCellOnEnter: true // Enter 编辑
}
});
// 监听键盘事件
table.on('keydown', (args) => {
if (args.event.key === 'Delete') {
// 清除选中区域数据
const ranges = table.getSelectedCellRanges();
ranges.forEach(range => {
for (let r = range.start.row; r <= range.end.row; r++) {
for (let c = range.start.col; c <= range.end.col; c++) {
table.changeCellValue(c, r, '');
}
}
});
}
});7. Tooltip
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [
{
field: 'description',
title: '描述',
width: 120,
style: { textOverflow: 'ellipsis' }
}
],
tooltip: {
isShowOverflowTextTooltip: true // 文字省略时自动提示
}
});
// 自定义 tooltip
table.showTooltip(col, row, {
content: '自定义提示内容',
referencePosition: { rect: { x: 100, y: 100 }, placement: 'bottom' },
style: { bgColor: '#333', color: '#fff' }
});8. 冻结列
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [...],
frozenColCount: 2, // 左侧冻结2列
rightFrozenColCount: 1, // 右侧冻结1列
bottomFrozenRowCount: 1, // 底部冻结1行(如汇总行)
allowFrozenColCount: 5 // 允许冻结的最大列数
});
// 动态更新冻结列
table.updateOption({ frozenColCount: 3 });9. 搜索高亮
import { SearchComponent } from '@visactor/vtable-search';
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [...]
});
// 初始化搜索组件
const search = new SearchComponent({
table,
autoJump: true // 自动跳转到匹配项
});
// 搜索
const results = search.search('关键词');
// 跳转到下一个/上一个匹配
search.next();
search.prev();
// 清除搜索
search.clear();10. 导出
import { exportVTableToExcel } from '@visactor/vtable-export';
// 导出为 Excel
exportVTableToExcel(table, {
fileName: 'data-export',
formatExcelJSCell: (cellInfo, cellInExcelFile) => {
// 自定义 Excel 单元格格式
return cellInExcelFile;
}
});
// 导出为 CSV(内置)
const csvString = table.exportCellsContent();ListTable 基础示例
1. 最简 ListTable
import * as VTable from '@visactor/vtable';
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: [
{ name: '张三', age: 28, city: '北京' },
{ name: '李四', age: 35, city: '上海' },
{ name: '王五', age: 22, city: '深圳' }
],
columns: [
{ field: 'name', title: '姓名', width: 120 },
{ field: 'age', title: '年龄', width: 80 },
{ field: 'city', title: '城市', width: 120 }
]
});2. 带排序和格式化
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: salesData,
columns: [
{ field: 'product', title: '产品', width: 150, sort: true },
{
field: 'revenue',
title: '收入',
width: 120,
sort: true,
fieldFormat: (record) => `¥${record.revenue.toLocaleString()}`,
style: {
textAlign: 'right',
color: (args) => args.dataValue > 10000 ? '#52c41a' : '#333'
}
},
{
field: 'date',
title: '日期',
width: 120,
sort: {
orderFn: (a, b) => new Date(a.date) - new Date(b.date)
}
}
],
sortState: { field: 'revenue', order: 'desc' }
});3. 分页
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: bigDataset, // 所有数据
columns: [
{ field: 'id', title: 'ID', width: 60 },
{ field: 'name', title: '名称', width: 150 },
{ field: 'value', title: '值', width: 100 }
],
pagination: {
perPageCount: 20,
currentPage: 1
}
});
// 翻页
function changePage(pageNum) {
table.updatePagination({ currentPage: pageNum });
}4. 冻结列与自适应宽度
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [
{ field: 'id', title: 'ID', width: 60 },
{ field: 'name', title: '名称', width: 200 },
{ field: 'category', title: '分类', width: 120 },
{ field: 'price', title: '价格', width: 100 },
{ field: 'stock', title: '库存', width: 80 },
{ field: 'description', title: '描述', width: 'auto' }
],
frozenColCount: 2, // 冻结前2列
rightFrozenColCount: 1, // 冻结最后1列
widthMode: 'autoWidth', // 自动列宽
heightMode: 'autoHeight', // 自动行高
autoWrapText: true // 自动换行
});5. 多列排序 + 条件样式
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [
{ field: 'name', title: '姓名', width: 120, sort: true },
{
field: 'score',
title: '得分',
width: 100,
sort: true,
style: {
bgColor: (args) => {
if (args.dataValue >= 90) return '#f6ffed';
if (args.dataValue >= 60) return '#fffbe6';
return '#fff2f0';
},
color: (args) => {
if (args.dataValue >= 90) return '#52c41a';
if (args.dataValue >= 60) return '#faad14';
return '#ff4d4f';
}
}
},
{ field: 'grade', title: '等级', width: 80, sort: true }
],
multipleSort: true,
sortState: [
{ field: 'grade', order: 'asc' },
{ field: 'score', order: 'desc' }
]
});6. 聚合统计行
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: data,
columns: [
{ field: 'category', title: '分类', width: 120 },
{
field: 'amount',
title: '金额',
width: 120,
aggregation: [
{ aggregationType: VTable.TYPES.AggregationType.SUM, showOnTop: false, formatFun: (val) => `合计: ¥${val.toFixed(2)}` },
]
},
{
field: 'count',
title: '数量',
width: 100,
aggregation: { aggregationType: VTable.TYPES.AggregationType.AVG, formatFun: (val) => `均值: ${val.toFixed(1)}` }
}
]
});ListTable 树形与分组示例
1. 基础树形表格
import * as VTable from '@visactor/vtable';
const records = [
{
name: '技术部',
budget: 500000,
children: [
{
name: '前端组',
budget: 200000,
children: [
{ name: '张三', budget: 80000 },
{ name: '李四', budget: 120000 }
]
},
{
name: '后端组',
budget: 300000,
children: [
{ name: '王五', budget: 150000 },
{ name: '赵六', budget: 150000 }
]
}
]
},
{
name: '产品部',
budget: 300000,
children: [
{ name: '小明', budget: 150000 },
{ name: '小红', budget: 150000 }
]
}
];
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records,
columns: [
{
field: 'name',
title: '名称',
width: 250,
tree: true // 开启树形展开
},
{
field: 'budget',
title: '预算',
width: 150,
fieldFormat: (record) => `¥${record.budget.toLocaleString()}`
}
],
hierarchyIndent: 20, // 缩进像素
hierarchyExpandLevel: 1 // 默认展开1层
});2. 自定义展开层级与事件
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: treeData,
columns: [
{ field: 'name', title: '名称', width: 250, tree: true },
{ field: 'type', title: '类型', width: 100 },
{ field: 'size', title: '大小', width: 100 }
],
hierarchyExpandLevel: Infinity, // 全部展开
hierarchyTextStartAlignment: true // 同级文字对齐
});
// 监听展开/折叠
table.on('tree_hierarchy_state_change', (args) => {
console.log('节点状态变更:', args.col, args.row, args.hierarchyState);
});
// API: 展开/折叠指定节点
table.toggleHierarchyState(0, 3); // 切换第3行的展开状态
// API: 展开所有 / 折叠所有
table.setAllHierarchyState('expand');
table.setAllHierarchyState('collapse');3. 分组表格
const records = [
{ category: '电子产品', product: '手机', price: 3999, stock: 150 },
{ category: '电子产品', product: '笔记本', price: 6999, stock: 80 },
{ category: '食品', product: '牛奶', price: 59, stock: 500 },
{ category: '食品', product: '面包', price: 15, stock: 300 },
{ category: '服装', product: 'T恤', price: 99, stock: 200 }
];
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records,
columns: [
{ field: 'category', title: '分类', width: 120, tree: true },
{ field: 'product', title: '产品', width: 150 },
{ field: 'price', title: '价格', width: 100 },
{ field: 'stock', title: '库存', width: 80 }
],
groupConfig: {
groupBy: 'category', // 按 category 字段分组
titleCustomLayout: (args) => {
const { table, row, col, rect } = args;
const record = table.getCellOriginRecord(col, row);
const { Group, Text } = VTable.CustomLayout;
return {
rootContainer: (
<Group display="flex" flexDirection="row" alignItems="center" padding={[4, 8]}>
<Text text={`📂 ${record.category}`} fontSize={14} fontWeight="bold" fill="#1890ff" />
<Text text={` (${record.children?.length || 0}项)`} fontSize={12} fill="#999" margin={[0, 0, 0, 4]} />
</Group>
),
renderDefault: false
};
}
}
});4. 懒加载树形
const table = new VTable.ListTable({
container: document.getElementById('tableContainer'),
records: rootNodes, // 只包含顶层节点
columns: [
{
field: 'name',
title: '组织',
width: 250,
tree: true
},
{ field: 'count', title: '人数', width: 100 }
],
hierarchyExpandLevel: 0 // 默认全部折叠
});
// 监听展开事件,懒加载子节点
table.on('tree_hierarchy_state_change', async (args) => {
if (args.hierarchyState === 'expand') {
const record = table.getCellOriginRecord(args.col, args.row);
if (!record.children || record.children.length === 0) {
// 异步加载子节点
const children = await fetchChildren(record.id);
record.children = children;
// 刷新表格
table.setRecords(table.records);
}
}
});PivotChart 透视组合图示例
1. 基础 PivotChart
import * as VTable from '@visactor/vtable';
import VChart from '@visactor/vchart';
// 注册 VChart 模块
VTable.register.chartModule('vchart', VChart);
const records = [
{ region: '华东', product: '手机', sales: 12000, profit: 3000 },
{ region: '华东', product: '电脑', sales: 8000, profit: 2000 },
{ region: '华北', product: '手机', sales: 9000, profit: 2500 },
{ region: '华北', product: '电脑', sales: 11000, profit: 3500 }
];
const table = new VTable.PivotChart({
container: document.getElementById('tableContainer'),
records,
rows: [{ dimensionKey: 'region', title: '区域', width: 100 }],
columns: [{ dimensionKey: 'product', title: '产品' }],
indicators: [
{
indicatorKey: 'sales',
title: '销售额',
width: 250,
cellType: 'chart',
chartModule: 'vchart',
chartSpec: {
type: 'bar',
xField: 'region',
yField: 'sales',
bar: {
style: {
fill: '#1890ff'
}
}
}
}
],
axes: [
{
orient: 'bottom',
visible: true,
label: { visible: true }
},
{
orient: 'left',
visible: true,
title: { visible: true, text: '销售额' }
}
]
});2. 多指标(柱形 + 折线)
const table = new VTable.PivotChart({
container: document.getElementById('tableContainer'),
records: monthlyData,
rows: [{ dimensionKey: 'category', title: '品类', width: 100 }],
columns: [{ dimensionKey: 'month', title: '月份' }],
indicators: [
{
indicatorKey: 'sales',
title: '销售额',
width: 200,
cellType: 'chart',
chartModule: 'vchart',
chartSpec: {
type: 'bar',
xField: 'month',
yField: 'sales',
bar: { style: { fill: '#5B8FF9' } }
}
},
{
indicatorKey: 'growth',
title: '增长率',
width: 200,
cellType: 'chart',
chartModule: 'vchart',
chartSpec: {
type: 'line',
xField: 'month',
yField: 'growth',
line: { style: { stroke: '#5AD8A6' } },
point: { style: { fill: '#5AD8A6' } }
}
}
],
indicatorsAsCol: true,
axes: [
{ orient: 'bottom', visible: true },
{ orient: 'left', visible: true, title: { visible: true, text: '销售额' } },
{ orient: 'right', visible: true, title: { visible: true, text: '增长率' } }
]
});3. 组合图 + 图例联动
const table = new VTable.PivotChart({
container: document.getElementById('tableContainer'),
records: data,
rows: [{ dimensionKey: 'region', title: '区域', width: 100 }],
columns: [{ dimensionKey: 'quarter', title: '季度' }],
indicators: [
{
indicatorKey: 'revenue',
title: '营收分析',
width: 300,
cellType: 'chart',
chartModule: 'vchart',
chartSpec: {
type: 'common',
seriesField: 'type',
series: [
{
type: 'bar',
data: { id: 'bar' },
xField: 'quarter',
yField: 'revenue',
bar: { style: { fill: { field: 'type', scale: 'color' } } }
},
{
type: 'line',
data: { id: 'line' },
xField: 'quarter',
yField: 'target',
line: { style: { stroke: '#FF6B6B', lineDash: [5, 5] } }
}
],
legends: { visible: true, orient: 'top' }
}
}
],
chartDimensionLinkage: true // 跨单元格维度联动高亮
});
// 监听图例事件
table.on('legend_item_click', args => {
console.log('图例点击:', args);
});4. PivotChart records 对象格式(分指标分组时的注意事项)
// 当 records 是对象格式时,key 必须与每个 indicator 的 indicatorKey 严格一致
// 否则图表只渲染轴,不渲染柱/折线等图形元素
const table = new VTable.PivotChart({
container: document.getElementById('tableContainer'),
indicatorsAsCol: false,
columnTree: [],
rowTree: [],
rows: [],
columns: [],
indicators: [
{ indicatorKey: 'METRIC_A', cellType: 'chart', chartModule: 'vchart',
chartSpec: { type: 'bar', xField: 'date', yField: 'value' } },
{ indicatorKey: 'METRIC_B', cellType: 'chart', chartModule: 'vchart',
chartSpec: { type: 'bar', xField: 'date', yField: 'count' } }
],
// ✅ 正确:records 的 key 与 indicatorKey 完全一致
records: {
'METRIC_A': [ { date: '2024-01', value: 100 }, ... ],
'METRIC_B': [ { date: '2024-01', count: 50 }, ... ]
}
// ❌ 错误示例(会导致图表无数据):
// records: { 'METRICA': [...], 'METRICB': [...] } // 缺少下划线
});副作用对照表:
| 配置 | 现象 |
|---|---|
| records key = indicatorKey | ✅ 柱/线等图形正常渲染 |
| records key ≠ indicatorKey | ❌ 只渲染坐标轴,图形区域空白 |
5. 迷你图 + 进度条混用
// PivotTable 中直接使用 sparkline 和 progressbar(不需要 PivotChart)
const table = new VTable.PivotTable({
container: document.getElementById('tableContainer'),
records: data,
rows: [{ dimensionKey: 'department', title: '部门', width: 120 }],
columns: [{ dimensionKey: 'metric', title: '指标' }],
indicators: [
{
indicatorKey: 'trend',
title: '趋势',
width: 180,
cellType: 'sparkline',
sparklineSpec: {
type: 'line',
xField: 'day',
yField: 'value',
line: { style: { stroke: '#1890ff' } },
point: { style: { fill: '#1890ff', size: 3 } }
}
},
{
indicatorKey: 'completion',
title: '完成率',
width: 150,
cellType: 'progressbar',
min: 0,
max: 100,
style: {
barColor: args => (args.dataValue >= 80 ? '#52c41a' : args.dataValue >= 50 ? '#faad14' : '#ff4d4f')
}
}
]
});PivotTable 多维分析示例
1. 基础透视表(自动聚合)
import * as VTable from '@visactor/vtable';
const records = [
{ region: '华东', product: '手机', quarter: 'Q1', sales: 12000 },
{ region: '华东', product: '手机', quarter: 'Q2', sales: 15000 },
{ region: '华东', product: '电脑', quarter: 'Q1', sales: 8000 },
{ region: '华北', product: '手机', quarter: 'Q1', sales: 9000 },
{ region: '华北', product: '电脑', quarter: 'Q2', sales: 11000 }
];
const table = new VTable.PivotTable({
container: document.getElementById('tableContainer'),
records,
rows: [
{ dimensionKey: 'region', title: '区域', width: 120 }
],
columns: [
{ dimensionKey: 'product', title: '产品' },
{ dimensionKey: 'quarter', title: '季度' }
],
indicators: [
{
indicatorKey: 'sales',
title: '销售额',
width: 120,
format: (value) => `¥${value?.toLocaleString() ?? '-'}`
}
],
dataConfig: {
aggregationRules: [
{ indicatorKey: 'sales', field: 'sales', aggregationType: VTable.TYPES.AggregationType.SUM }
]
}
});2. 多指标 + 合计
const table = new VTable.PivotTable({
container: document.getElementById('tableContainer'),
records: salesData,
rows: [
{ dimensionKey: 'region', title: '区域', width: 100 },
{ dimensionKey: 'city', title: '城市', width: 100 }
],
columns: [
{ dimensionKey: 'category', title: '品类' }
],
indicators: [
{
indicatorKey: 'sales',
title: '销售额',
width: 110,
format: (v) => `¥${(v ?? 0).toLocaleString()}`,
style: { textAlign: 'right' }
},
{
indicatorKey: 'profit',
title: '利润',
width: 110,
format: (v) => `¥${(v ?? 0).toLocaleString()}`,
style: {
textAlign: 'right',
color: (args) => args.dataValue < 0 ? '#ff4d4f' : '#52c41a'
}
}
],
indicatorsAsCol: true, // 指标在列方向
dataConfig: {
aggregationRules: [
{ indicatorKey: 'sales', field: 'sales', aggregationType: VTable.TYPES.AggregationType.SUM },
{ indicatorKey: 'profit', field: 'profit', aggregationType: VTable.TYPES.AggregationType.SUM }
],
totals: {
row: {
showGrandTotals: true,
showSubTotals: true,
grandTotalLabel: '总计',
subTotalLabel: '小计'
},
column: {
showGrandTotals: true,
grandTotalLabel: '汇总'
}
}
},
corner: {
titleOnDimension: 'row'
}
});3. 树形行表头
const table = new VTable.PivotTable({
container: document.getElementById('tableContainer'),
records: data,
rows: [
{
dimensionKey: 'department',
title: '部门',
width: 180,
hierarchyType: 'tree' // 树形显示
},
{ dimensionKey: 'team', title: '团队', width: 120 }
],
columns: [
{ dimensionKey: 'month', title: '月份' }
],
indicators: [
{ indicatorKey: 'headcount', title: '人数', width: 80 },
{ indicatorKey: 'budget', title: '预算', width: 100 }
],
rowExpandLevel: 1, // 默认展开1层
rowHierarchyIndent: 20 // 缩进像素
});4. 自定义表头树(rowTree / columnTree)
const table = new VTable.PivotTable({
container: document.getElementById('tableContainer'),
records: data,
// 手动定义行表头树
rowTree: [
{
dimensionKey: 'region',
value: '华东',
children: [
{ dimensionKey: 'city', value: '上海' },
{ dimensionKey: 'city', value: '杭州' }
]
},
{
dimensionKey: 'region',
value: '华北',
children: [
{ dimensionKey: 'city', value: '北京' },
{ dimensionKey: 'city', value: '天津' }
]
}
],
// 手动定义列表头树
columnTree: [
{
dimensionKey: 'quarter',
value: 'Q1',
children: [
{ indicatorKey: 'sales', value: '销售额' },
{ indicatorKey: 'profit', value: '利润' }
]
},
{
dimensionKey: 'quarter',
value: 'Q2',
children: [
{ indicatorKey: 'sales', value: '销售额' },
{ indicatorKey: 'profit', value: '利润' }
]
}
],
rows: [
{ dimensionKey: 'region', title: '区域', width: 120 },
{ dimensionKey: 'city', title: '城市', width: 100 }
],
columns: [
{ dimensionKey: 'quarter', title: '季度' }
],
indicators: [
{ indicatorKey: 'sales', title: '销售额', width: 100 },
{ indicatorKey: 'profit', title: '利润', width: 100 }
],
corner: {
titleOnDimension: 'row',
headerStyle: { fontWeight: 'bold' }
}
});5. 排序和过滤
const table = new VTable.PivotTable({
container: document.getElementById('tableContainer'),
records: data,
rows: [
{ dimensionKey: 'category', title: '品类', width: 120, showSort: true }
],
columns: [
{ dimensionKey: 'year', title: '年份' }
],
indicators: [
{ indicatorKey: 'amount', title: '金额', width: 100, showSort: true }
],
dataConfig: {
aggregationRules: [
{ indicatorKey: 'amount', field: 'amount', aggregationType: VTable.TYPES.AggregationType.SUM }
],
sortRules: [
{ sortField: 'category', sortType: 'ASC' } // 按品类升序
],
filterRules: [
{ filterFunc: (record) => record.amount > 0 } // 过滤掉负值
]
}
});
// 监听排序事件
table.on('pivot_sort_click', (args) => {
console.log('排序:', args);
});VTable 示例索引
本目录收录常用代码示例,可作为 skill 生成代码时的参考模板。
目录
| 文件 | 说明 |
|---|---|
| list-table-basic.md | ListTable 基础、排序、过滤、分页 |
| list-table-tree.md | ListTable 树形结构与分组 |
| pivot-table-analysis.md | PivotTable 多维分析 |
| pivot-chart.md | PivotChart 透视组合图 |
| custom-layout-jsx.md | JSX 自定义布局完整示例 |
| cell-types.md | 13 种单元格类型示例 |
| interaction-patterns.md | 交互模式(编辑、选中、菜单等) |
VTable 整体架构概览
项目简介
VTable 是一款基于 VRender Canvas 引擎构建的高性能多维数据分析表格组件。它支持百万级数据量的流畅渲染,提供丰富的单元格类型和强大的自定义能力。
核心包
| 包名 | npm 包名 | 说明 |
|---|---|---|
| vtable | @visactor/vtable | 核心表格库 |
| vtable-editors | @visactor/vtable-editors | 编辑器组件(输入框、日期选择等) |
| vtable-export | @visactor/vtable-export | 导出功能(Excel、CSV) |
| vtable-search | @visactor/vtable-search | 搜索高亮功能 |
| vtable-plugins | @visactor/vtable-plugins | 官方插件集 |
| vtable-gantt | @visactor/vtable-gantt | 甘特图组件 |
| vtable-calendar | @visactor/vtable-calendar | 日历组件 |
| react-vtable | @visactor/react-vtable | React 封装 |
| vue-vtable | @visactor/vue-vtable | Vue 封装 |
| openinula-vtable | @visactor/openinula-vtable | OpenInula 封装 |
三种核心表格类型
1. ListTable — 基本表格
用于展示扁平的列表数据,类似传统的 HTML table。
import { ListTable } from '@visactor/vtable';
const table = new ListTable({
container: document.getElementById('container'),
records: [{ name: 'Alice', age: 25 }],
columns: [
{ field: 'name', title: '姓名', cellType: 'text' },
{ field: 'age', title: '年龄', cellType: 'text' }
]
});适用场景: 数据列表、可编辑表格、树形表格、分组表格
2. PivotTable — 透视表
用于多维数据分析,支持行/列维度交叉展示。
import { PivotTable } from '@visactor/vtable';
const table = new PivotTable({
container: document.getElementById('container'),
records: data,
rows: ['region', 'city'],
columns: ['category'],
indicators: [{ indicatorKey: 'sales', title: '销售额' }]
});适用场景: 数据聚合分析、多维度交叉报表
3. PivotChart — 透视图
在透视表的基础上嵌入 VChart 图表,实现图表矩阵。
import { PivotChart } from '@visactor/vtable';
const table = new PivotChart({
container: document.getElementById('container'),
records: data,
rows: ['region'],
columns: ['category'],
indicators: [{
indicatorKey: 'sales',
title: '销售额',
cellType: 'chart',
chartModule: 'vchart',
chartSpec: { type: 'bar' }
}]
});适用场景: 图表矩阵、多维图表分析
类型继承体系
BaseTableConstructorOptions ← 公共配置(container, theme, widthMode, hover, select, menu...)
├── ListTableConstructorOptions ← records, columns, transpose, sortState, editor, pagination...
├── PivotTableConstructorOptions ← rows, columns, indicators, rowTree, columnTree, dataConfig, corner...
└── PivotChartConstructorOptions ← 同 PivotTable + axes, chartDimensionLinkage核心导出
// 表格类
import { ListTable, PivotTable, PivotChart } from '@visactor/vtable';
// 类型命名空间
import type * as VTable from '@visactor/vtable';
// 内置主题
import { themes } from '@visactor/vtable';
// themes.DEFAULT, themes.ARCO, themes.BRIGHT, themes.DARK, themes.SIMPLIFY
// 注册系统
import { register } from '@visactor/vtable';
// register.bindChartModule(VChart) — 注册 VChart 模块用于 PivotChart
// JSX 布局原语
import { Group, Text, Rect, Circle, Image, Tag, Line, Checkbox, Radio } from '@visactor/vtable';渲染架构
VTable 基于 Canvas 场景图(Scenegraph)模式渲染: 1. 数据层: DataSource / records → 数据管理 2. 布局层: LayoutMap → 行列映射、合并计算 3. 场景图层: Scenegraph → Group/Cell 节点树 4. 渲染层: VRender → Canvas 绑定绘制
源码目录结构(packages/vtable/src/)
| 目录 | 说明 |
|---|---|
ts-types/ | 用户可见的 TypeScript 类型定义 |
core/ | 核心基础类(BaseTable、动画、样式计算) |
scenegraph/ | 场景图渲染系统 |
event/ | 事件系统(鼠标、键盘、滚动) |
state/ | 状态管理(选择、hover、冻结、排序) |
data/ | 数据源抽象 |
dataset/ | 数据集与统计 |
layout/ | 布局计算 |
themes/ | 内置主题定义 |
render/ | 渲染布局与 JSX |
components/ | 内置组件(tooltip, menu, legend, title) |
plugins/ | 插件系统 |
edit/ | 编辑管理 |
body-helper/ | body 区域样式计算 |
header-helper/ | 表头区域样式计算 |
tools/ | 工具函数 |
表格类型与配置详解
一、公共配置 BaseTableConstructorOptions
所有表格类型共享的配置项,定义于 packages/vtable/src/ts-types/table-engine.ts。
容器与尺寸
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
container | `HTMLElement \ | string` | 必填 |
widthMode | `'standard' \ | 'adaptive' \ | 'autoWidth'` |
heightMode | `'standard' \ | 'adaptive' \ | 'autoHeight'` |
autoFillWidth | boolean | false | 列总宽不足时自动拉伸填满容器 |
autoFillHeight | boolean | false | 行总高不足时自动拉伸填满容器 |
defaultRowHeight | `number \ | 'auto'` | 40 |
defaultColWidth | number | 80 | 默认列宽 |
defaultHeaderRowHeight | `number \ | (number \ | 'auto')[]` |
defaultHeaderColWidth | `(number \ | 'auto') \ | (number \ |
limitMaxAutoWidth | `boolean \ | number` | 450 |
limitMinWidth | number | — | 最小列宽限制 |
pixelRatio | number | devicePixelRatio | 画布像素比 |
冻结
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
frozenColCount | number | 0 | 左侧冻结列数(含表头) |
frozenRowCount | number | — | 顶部冻结行数 |
rightFrozenColCount | number | 0 | 右侧冻结列数 |
bottomFrozenRowCount | number | 0 | 底部冻结行数 |
文字
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
autoWrapText | boolean | false | 全局文字自动换行 |
enableLineBreak | boolean | false | 是否识别换行符 \n |
交互 — hover
hover: {
highlightMode: 'cross' | 'column' | 'row' | 'cell'; // 高亮模式,默认 'cross'
disableHover?: boolean; // 禁用 hover 效果
disableHeaderHover?: boolean; // 禁用表头 hover
enableSingleCell?: boolean; // 是否使能单单元格 hover
}交互 — select
select: {
disableSelect?: boolean; // 禁用选择
disableHeaderSelect?: boolean; // 禁用表头选择
highlightMode?: 'cross' | 'column' | 'row' | 'cell';
headerSelectMode?: 'inline' | 'cell'; // 点击表头是选中整行/列还是单个表头
blankAreaClickDeselect?: boolean; // 点击空白区域取消选中
}交互 — 键盘
keyboardOptions: {
moveFocusCellOnTab?: boolean; // Tab 键移动焦点,默认 true
editCellOnEnter?: boolean; // Enter 键进入编辑,默认 true
moveFocusCellOnEnter?: boolean; // Enter 键移动焦点(与 editCellOnEnter 互斥)
moveEditCellOnArrowKeys?: boolean; // 方向键在编辑态切换单元格
selectAllOnCtrlA?: boolean; // Ctrl+A 全选
copySelected?: boolean; // Ctrl+C 复制
pasteValueToCell?: boolean; // Ctrl+V 粘贴
cutSelected?: boolean; // Ctrl+X 剪切
moveSelectedCellOnArrowKeys?: boolean; // 方向键切换选中,默认 true
ctrlMultiSelect?: boolean; // Ctrl 多选
shiftMultiSelect?: boolean; // Shift 范围选
}交互 — 列宽/行高调整
resize: {
columnResizeMode?: 'all' | 'none' | 'header' | 'body'; // 列宽调整范围
rowResizeMode?: 'all' | 'none' | 'header' | 'body';
columnResizeWidth?: number; // 触发调整的热区宽度
}交互 — 拖拽
dragOrder: {
dragHeaderMode?: 'all' | 'none' | 'column' | 'row'; // 拖拽表头模式
frozenColDragHeaderMode?: 'disabled' | 'adjustFrozenCount' | 'fixedFrozenCount';
}菜单
menu: {
renderMode?: 'canvas' | 'html';
defaultHeaderMenuItems?: MenuListItem[] | ((args) => MenuListItem[]);
contextMenuItems?: MenuListItem[] | ((field, row, col, table) => MenuListItem[]);
dropDownMenuHighlight?: DropDownMenuHighlightInfo[];
}提示框
tooltip: {
renderMode?: 'html' | 'canvas';
isShowOverflowTextTooltip?: boolean; // 省略文字悬浮提示
confine?: boolean; // 限制在表格区域内
overflowTextTooltipDisappearDelay?: number;
}主题与标题
| 配置项 | 类型 | 说明 |
|---|---|---|
theme | ITableThemeDefine | 主题配置(详见 03-style-theme.md) |
title | ITitle | 表格标题(text, subtext, orient, padding) |
emptyTip | IEmptyTip | 数据为空提示 |
其他
| 配置项 | 类型 | 说明 |
|---|---|---|
customMergeCell | CustomMergeCell | 自定义合并单元格规则 |
rowSeriesNumber | IRowSeriesNumber | 行序号列配置 |
overscrollBehavior | `'auto' \ | 'none'` |
renderChartAsync | boolean | 图表异步渲染 |
animationAppear | IAnimationAppear | 出场动画 |
customCellStyle | CustomCellStyle[] | 预注册自定义样式 |
customCellStyleArrangement | CustomCellStyleArrangement[] | 预分配自定义样式 |
---
二、ListTable — 基本表格
定义于 ListTableConstructorOptions,继承 BaseTableConstructorOptions。
专有配置
| 配置项 | 类型 | 说明 |
|---|---|---|
records | any[] | 数据数组,每项为一个记录对象 |
columns | ColumnsDefine | 列定义数组(详见 02-column-cell-types.md) |
transpose | boolean | 是否转置(行列互换) |
showHeader | boolean | 是否显示表头 |
pagination | IPagination | 分页配置 |
sortState | `SortState \ | SortState[]` |
multipleSort | boolean | 是否开启多列排序 |
editor | `string \ | IEditor \ |
headerEditor | `string \ | IEditor \ |
editCellTrigger | `'doubleclick' \ | 'click' \ |
hierarchyIndent | number | 树形缩进像素 |
hierarchyExpandLevel | number | 树形默认展开层级 |
aggregation | `Aggregation \ | Aggregation[]` |
groupConfig | GroupConfig | 分组配置 |
分页 IPagination
interface IPagination {
totalCount?: number; // 数据总条数
perPageCount: number; // 每页条数
currentPage?: number; // 当前页码(从 1 开始)
}排序 SortState
interface SortState {
field: FieldDef; // 排序字段
order: 'asc' | 'desc' | 'normal'; // 排序方向
orderFn?: (a: any, b: any, order: string) => -1|0|1; // 自定义排序函数
}最小示例
import { ListTable } from '@visactor/vtable';
const table = new ListTable({
container: document.getElementById('container'),
records: [
{ id: 1, name: 'Alice', age: 25, city: 'Beijing' },
{ id: 2, name: 'Bob', age: 30, city: 'Shanghai' }
],
columns: [
{ field: 'id', title: 'ID', width: 80 },
{ field: 'name', title: '姓名', width: 120 },
{ field: 'age', title: '年龄', width: 80, sort: true },
{ field: 'city', title: '城市', width: 120 }
],
widthMode: 'standard',
defaultRowHeight: 40
});---
三、PivotTable — 透视表
定义于 PivotTableConstructorOptions,继承 BaseTableConstructorOptions。
专有配置
| 配置项 | 类型 | 说明 |
|---|---|---|
records | any[] | 平坦数据集 |
rows | `(IRowDimension \ | string)[]` |
columns | `(IColumnDimension \ | string)[]` |
indicators | `(IIndicator \ | string)[]` |
rowTree | IHeaderTreeDefine[] | 自定义行表头树 |
columnTree | IHeaderTreeDefine[] | 自定义列表头树 |
indicatorsAsCol | boolean | 指标按列展示 |
hideIndicatorName | boolean | 隐藏指标名 |
corner | ICornerDefine | 角头配置 |
showColumnHeader | boolean | 显示列表头 |
showRowHeader | boolean | 显示行表头 |
columnHeaderTitle | ITitleDefine | 列表头维度名行 |
rowHeaderTitle | ITitleDefine | 行表头维度名列 |
dataConfig | IPivotTableDataConfig | 数据分析配置(聚合、排序、筛选规则) |
rowHierarchyType | `'grid' \ | 'tree' \ |
rowExpandLevel | number | 行展开层数 |
columnExpandLevel | number | 列展开层数 |
rowHierarchyIndent | number | 行缩进距离 |
pivotSortState | { dimensions, order }[] | 排序状态 |
pagination | IPagination | 分页 |
editor | `string \ | IEditor \ |
数据分析配置 dataConfig
interface IPivotTableDataConfig {
aggregationRules?: AggregationRule[]; // 聚合规则
sortRules?: SortRule[]; // 排序规则
filterRules?: FilterRule[]; // 筛选规则
derivedFieldRules?: DerivedFieldRule[]; // 派生字段规则
totals?: Totals; // 小计/总计配置
mappingRules?: MappingRule[]; // 数据映射规则
}最小示例
import { PivotTable } from '@visactor/vtable';
const table = new PivotTable({
container: document.getElementById('container'),
records: [
{ region: '华东', category: '办公用品', sales: 1200 },
{ region: '华东', category: '家具', sales: 3400 },
{ region: '华北', category: '办公用品', sales: 800 },
{ region: '华北', category: '家具', sales: 2100 }
],
rows: ['region'],
columns: ['category'],
indicators: [{
indicatorKey: 'sales',
title: '销售额',
width: 'auto',
format: (value) => `¥${value?.toLocaleString() ?? ''}`
}],
corner: {
titleOnDimension: 'row'
}
});---
四、PivotChart — 透视图
定义于 PivotChartConstructorOptions,继承 BaseTableConstructorOptions。
与 PivotTable 的差异
| 差异点 | PivotTable | PivotChart |
|---|---|---|
indicators | IIndicator | IChartIndicator(含 chartSpec) |
axes | 无 | ITableAxisOption[] 坐标轴配置 |
chartDimensionLinkage | 无 | 多图表联动交互 |
dataConfig | 可选 | 内部自动开启 |
最小示例
import { PivotChart, register } from '@visactor/vtable';
import VChart from '@visactor/vchart';
// 必须先注册 VChart 模块
register.bindChartModule(VChart);
const table = new PivotChart({
container: document.getElementById('container'),
records: data,
rows: ['region'],
columns: ['category'],
indicators: [{
indicatorKey: 'sales',
title: '销售额',
cellType: 'chart',
chartModule: 'vchart',
chartSpec: {
type: 'bar',
xField: ['category'],
yField: 'sales'
}
}],
axes: [
{ orient: 'bottom', type: 'band' },
{ orient: 'left', type: 'linear' }
]
});列定义与单元格类型
一、列定义结构
ListTable 的 columns 配置使用 ColumnsDefine 类型,定义于 packages/vtable/src/ts-types/list-table/define/。
type ColumnsDefine = (ColumnDefine | GroupColumnDefine)[];
// 13 种列定义的联合类型
type ColumnDefine = TextColumnDefine | LinkColumnDefine | ImageColumnDefine | SparklineColumnDefine
| ProgressbarColumnDefine | ChartColumnDefine | CheckboxColumnDefine | RadioColumnDefine
| SwitchColumnDefine | ButtonColumnDefine | CompositeColumnDefine | VideoColumnDefine
| MultilineTextColumnDefine;
// 分组列(嵌套表头)
interface GroupColumnDefine {
title: string;
headerStyle?: ColumnStyleOption;
headerIcon?: string | ColumnIconOption | (string | ColumnIconOption)[];
columns: ColumnsDefine; // 递归嵌套
}二、公共列属性 (所有列类型共享)
来自 HeaderDefine + ColumnBodyDefine:
表头属性 (HeaderDefine)
| 属性 | 类型 | 说明 |
|---|---|---|
title | string | 列标题 |
headerType | ColumnTypeOption | 表头单元格类型 |
headerStyle | `ColumnStyleOption \ | Function` |
headerIcon | `string \ | ColumnIconOption \ |
headerCustomRender | ICustomRender | 表头自定义渲染 |
headerCustomLayout | ICustomLayout | 表头自定义布局 |
headerEditor | `string \ | IEditor \ |
sort | `boolean \ | Function` |
showSort | boolean | 仅显示排序图标不排序 |
dropDownMenu | MenuListItem[] | 下拉菜单项 |
description | string | 表头描述(hover 提示) |
disableHeaderHover | boolean | 禁用表头 hover |
disableHeaderSelect | boolean | 禁用表头选中 |
Body 属性 (ColumnBodyDefine)
| 属性 | 类型 | 说明 |
|---|---|---|
field | `string \ | number \ |
fieldFormat | Function | 数据格式化函数 |
cellType | ColumnTypeOption | 单元格类型 |
width | `number \ | string` |
minWidth | `number \ | string` |
maxWidth | `number \ | string` |
style | `ColumnStyleOption \ | Function` |
icon | `string \ | ColumnIconOption \ |
customRender | ICustomRender | 自定义渲染 |
customLayout | ICustomLayout | 自定义布局 |
editor | `string \ | IEditor \ |
tree | boolean | 是否为树形展示列 |
mergeCell | `boolean \ | Function` |
aggregation | `Aggregation \ | Aggregation[]` |
hide | boolean | 隐藏列 |
disableHover | boolean | 禁用 hover |
disableSelect | boolean | 禁用选中 |
disableColumnResize | boolean | 禁止调整列宽 |
dragHeader | boolean | 是否允许拖拽表头 |
三、13 种单元格类型详解
1. text — 文本(默认类型)
{
field: 'name',
title: '姓名',
cellType: 'text', // 可省略,默认就是 text
width: 120,
style: {
color: '#333',
fontSize: 14,
textAlign: 'left'
}
}2. link — 链接
{
field: 'url',
title: '链接',
cellType: 'link',
linkJump: true, // 点击跳转
linkDetect: true, // 自动检测 URL
templateLink: 'https://example.com/{id}', // URL 模板
style: {
color: '#1890ff',
underline: true
}
}3. image — 图片
{
field: 'avatar',
title: '头像',
cellType: 'image',
width: 80,
keepAspectRatio: true, // 保持宽高比
imageAutoSizing: false, // 图片自适应大小
style: {
margin: 4 // 图片边距
}
}4. video — 视频
{
field: 'videoUrl',
title: '视频',
cellType: 'video',
width: 200
}5. progressbar — 进度条
{
field: 'progress',
title: '进度',
cellType: 'progressbar',
min: 0,
max: 100,
barType: 'default', // 'default' | 'negative' | 'negative_no_axis'
style: {
barHeight: 20,
barColor: (value) => value > 80 ? '#52c41a' : '#1890ff',
barBgColor: '#f5f5f5',
barBorderRadius: 10
}
}6. sparkline — 迷你图
{
field: 'trend',
title: '趋势',
cellType: 'sparkline',
width: 200,
sparklineSpec: {
type: 'line', // 'line' | 'area' | 'bar'
xField: { field: 'x' },
yField: { field: 'y' },
line: { style: { stroke: '#1890ff', lineWidth: 2 } },
point: { style: { fill: '#1890ff' } }
}
}7. chart — 嵌入图表
需要先注册 VChart 模块:register.bindChartModule(VChart)
{
field: 'chartData',
title: '图表',
cellType: 'chart',
chartModule: 'vchart',
width: 300,
chartSpec: {
type: 'bar',
data: { id: 'data' },
xField: 'x',
yField: 'y'
}
}8. checkbox — 复选框
{
field: 'checked',
title: '选中',
cellType: 'checkbox',
headerType: 'checkbox', // 表头也是复选框
checked: true, // 默认值
disable: false
}9. radio — 单选框
{
field: 'selected',
title: '选择',
cellType: 'radio',
radioCheckType: 'column', // 'cell' | 'column' — 整列单选互斥
radioDirectionInCell: 'horizontal' // 同一单元格内多选项排列方向
}10. switch — 开关
{
field: 'enabled',
title: '启用',
cellType: 'switch'
}11. button — 按钮
{
field: 'action',
title: '操作',
cellType: 'button',
text: '编辑', // 按钮文字(也可以从 field 取值)
style: {
color: '#fff',
bgColor: '#1890ff',
borderRadius: 4,
padding: [4, 12]
}
}12. composite — 组合单元格
在一个单元格中组合多种元素:
{
title: '综合信息',
cellType: 'composite',
columns: [
{ cellType: 'image', field: 'avatar', width: 40 },
{
cellType: 'composite',
columns: [
{ cellType: 'text', field: 'name', style: { fontWeight: 'bold' } },
{ cellType: 'text', field: 'desc', style: { fontSize: 12, color: '#999' } }
],
layout: 'vertical'
}
]
}13. multilinetext — 多行文本
{
field: 'content',
title: '内容',
cellType: 'multilinetext',
autoWrapText: true,
style: {
autoWrapText: true,
lineHeight: 20
}
}四、列类型选择指南
| 数据类型 | 推荐 cellType | 备注 |
|---|---|---|
| 普通文本/数字 | text | 默认类型,最常用 |
| URL/链接 | link | 支持自动检测和跳转 |
| 图片 URL | image | 支持保持宽高比 |
| 布尔值/开关 | checkbox / switch | checkbox 支持表头全选 |
| 数值进度 | progressbar | 可配色、可显示负值 |
| 时序数据 | sparkline | 迷你折线/面积/柱状图 |
| 复杂图表 | chart | 集成 VChart,功能最强 |
| 单选 | radio | 支持整列互斥 |
| 多字段组合 | composite | 一个单元格展示多字段 |
| 操作按钮 | button | 支持点击事件 |
五、列样式 ColumnStyleOption
所有列类型的 style 和 headerStyle 属性使用 ColumnStyleOption 类型,支持静态值或回调函数:
// 静态样式
style: {
bgColor: '#f5f5f5',
color: '#333',
fontSize: 14
}
// 动态样式(根据数据变化)
style: (args: StylePropertyFunctionArg) => {
return {
bgColor: args.dataValue > 100 ? '#e6f7ff' : '#fff',
color: args.dataValue > 100 ? '#1890ff' : '#333'
};
}StylePropertyFunctionArg 参数:
{
row: number; // 行号
col: number; // 列号
table: BaseTableAPI; // 表格实例
value?: FieldData; // 格式化后的值
dataValue?: FieldData; // 原始值
cellHeaderPaths?: ICellHeaderPaths; // 表头路径
}样式系统与主题
一、样式属性速查
所有样式属性定义于 packages/vtable/src/ts-types/style-define.ts,可用于列定义的 style/headerStyle 和主题配置中。
文本样式
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
color | `string \ | Function` | '#000' |
fontSize | `number \ | Function` | 14 |
fontFamily | `string \ | Function` | 'sans-serif' |
fontWeight | `string \ | number \ | Function` |
fontStyle | `string \ | Function` | 'normal' |
textAlign | `'left' \ | 'center' \ | 'right'` |
textBaseline | `'top' \ | 'middle' \ | 'bottom'` |
lineHeight | number | — | 行高 |
textOverflow | string | '...' | 溢出省略字符 |
lineClamp | `number \ | 'auto'` | 1 |
autoWrapText | boolean | false | 自动换行 |
underline | `boolean \ | Function` | false |
lineThrough | `boolean \ | Function` | false |
单元格样式
| 属性 | 类型 | 说明 |
|---|---|---|
bgColor | `string \ | Function` |
borderColor | `string \ | string[]` |
borderLineWidth | `number \ | number[]` |
borderLineDash | `number[] \ | number[][]` |
padding | `number \ | number[]` |
cursor | `string \ | Function` |
特殊样式
| 属性 | 类型 | 说明 |
|---|---|---|
textStick | boolean | 文字吸附(滚动时保持可见) |
textStickBaseOnAlign | boolean | 吸附基于对齐方式 |
marked | `boolean \ | MarkCellStyle \ |
tag | `string \ | Function` |
标记样式 MarkCellStyle
{
bgColor?: string; // 标记颜色
shape?: 'rect' | 'triangle' | 'sector'; // 标记形状
position?: 'left-top' | 'left-bottom' | 'right-top' | 'right-bottom';
size?: number; // 标记大小
offset?: number; // 偏移量
}二、动态样式(回调函数)
所有样式属性都支持回调函数形式,参数为 StylePropertyFunctionArg:
const columns = [{
field: 'score',
title: '分数',
style: (args) => ({
// 根据分数值动态设置背景色
bgColor: args.dataValue >= 90 ? '#f6ffed' :
args.dataValue >= 60 ? '#e6f7ff' :
'#fff1f0',
// 根据分数值动态设置字体颜色
color: args.dataValue >= 90 ? '#52c41a' :
args.dataValue >= 60 ? '#1890ff' :
'#f5222d',
fontWeight: args.dataValue >= 90 ? 'bold' : 'normal'
})
}];StylePropertyFunctionArg 完整定义
interface StylePropertyFunctionArg {
row: number; // 行号
col: number; // 列号
table: BaseTableAPI; // 表格实例
value?: FieldData; // 格式化后的展示值
dataValue?: FieldData; // 原始数据值
percentile?: number; // 百分位(进度条用)
cellHeaderPaths?: ICellHeaderPaths; // 表头路径(透视表用)
}三、主题系统
内置主题
VTable 提供 5 种内置主题:
import { themes } from '@visactor/vtable';
// 使用内置主题
const table = new ListTable({
theme: themes.ARCO, // 或 themes.DEFAULT, themes.BRIGHT, themes.DARK, themes.SIMPLIFY
// ...
});| 主题 | 风格 |
|---|---|
themes.DEFAULT | 默认主题,浅色系 |
themes.ARCO | ArcoDesign 风格 |
themes.BRIGHT | 明亮风格 |
themes.DARK | 暗色主题 |
themes.SIMPLIFY | 简约风格 |
自定义主题 ITableThemeDefine
const myTheme: VTable.ITableThemeDefine = {
// 默认样式
defaultStyle: {
color: '#333',
bgColor: '#fff',
fontSize: 14,
fontFamily: 'PingFang SC',
borderColor: '#e1e4e8',
borderLineWidth: 1,
padding: [8, 12]
},
// 表头样式
headerStyle: {
color: '#1f2329',
bgColor: '#f5f7fa',
fontSize: 14,
fontWeight: 'bold',
borderColor: '#e1e4e8',
borderLineWidth: 1,
padding: [8, 12]
},
// 行表头样式(透视表用)
rowHeaderStyle: {
color: '#1f2329',
bgColor: '#f5f7fa'
},
// 角头样式(透视表用)
cornerHeaderStyle: {
color: '#1f2329',
bgColor: '#e8eaed'
},
// body 样式
bodyStyle: {
color: '#333',
bgColor: '#fff',
hover: {
cellBgColor: '#e6f7ff',
inlineColumnBgColor: '#f0f5ff',
inlineRowBgColor: '#f0f5ff'
}
},
// 选择框样式
selectionStyle: {
cellBorderColor: '#1890ff',
cellBorderLineWidth: 2,
cellBgColor: 'rgba(24,144,255,0.1)'
},
// 边框样式
frameStyle: {
borderColor: '#e1e4e8',
borderLineWidth: 1,
borderLineDash: [],
cornerRadius: 4, // 表格圆角
shadowBlur: 0
},
// 滚动条样式
scrollStyle: {
visible: 'scrolling', // 'always' | 'scrolling' | 'none' | 'focus'
scrollSliderColor: 'rgba(0,0,0,0.3)',
scrollRailColor: 'rgba(0,0,0,0.05)',
width: 7,
hoverOn: true // hover 时变粗
},
// 冻结列分割线样式
frozenColumnLine: {
shadow: {
width: 4,
startColor: 'rgba(0,0,0,0.05)',
endColor: 'transparent'
}
},
// 提示框样式
tooltipStyle: {
bgColor: '#333',
color: '#fff',
fontSize: 12,
padding: [6, 8]
}
};基于内置主题扩展
import { themes } from '@visactor/vtable';
const customTheme = {
...themes.ARCO,
bodyStyle: {
...themes.ARCO.bodyStyle,
color: '#1f2329',
hover: {
cellBgColor: '#e8f3ff',
inlineColumnBgColor: '#f2f3f5',
inlineRowBgColor: '#f2f3f5'
}
},
selectionStyle: {
cellBorderColor: '#165DFF',
cellBorderLineWidth: 2,
cellBgColor: 'rgba(22,93,255,0.1)'
}
};四、registerCustomCellStyle — 运行时自定义样式
可以在运行时注册和分配自定义样式:
// 1. 注册样式
table.registerCustomCellStyle('highlight', {
bgColor: '#ffffcc',
color: '#f5222d',
fontWeight: 'bold'
});
// 2. 分配样式到单元格
table.arrangeCustomCellStyle({
cellPosition: { col: 2, row: 3 }, // 单个单元格
customStyleId: 'highlight'
});
// 或分配到区域
table.arrangeCustomCellStyle({
cellPosition: {
range: { start: { col: 1, row: 1 }, end: { col: 3, row: 5 } }
},
customStyleId: 'highlight'
});也可以通过配置项预注册:
const table = new ListTable({
customCellStyle: [
{ id: 'warning', style: { bgColor: '#fff7e6', color: '#fa8c16' } },
{ id: 'error', style: { bgColor: '#fff1f0', color: '#f5222d' } }
],
customCellStyleArrangement: [
{ cellPosition: { col: 1, row: 2 }, customStyleId: 'warning' }
]
});自定义渲染与布局
VTable 提供两套自定义渲染方案:Custom Render(低级元素 API)和 Custom Layout(高级 JSX 布局 API)。
一、Custom Render — 元素 API
使用方式
在列定义中配置 customRender 或 headerCustomRender:
{
field: 'info',
title: '信息',
customRender: (args: CustomRenderFunctionArg) => {
return {
renderDefault: false, // 是否也渲染默认内容
elements: [
// 元素数组
]
};
}
}CustomRenderFunctionArg 参数
interface CustomRenderFunctionArg {
col: number; // 列号
row: number; // 行号
value: FieldData; // 格式化后的值
dataValue: FieldData; // 原始值
rect: RectProps; // 单元格矩形 { left, right, top, bottom, width, height }
table: BaseTableAPI; // 表格实例
cellHeaderPaths?: ICellHeaderPaths; // 表头路径
}7 种元素类型
text — 文本
{
type: 'text',
x: 10, y: 10,
text: '标题文字',
fill: '#333',
fontSize: 14,
fontFamily: 'PingFang SC',
fontWeight: 'bold',
textAlign: 'left',
textBaseline: 'top',
ellipsis: true, // 溢出省略
lineClamp: 2, // 最大行数
maxLineWidth: 100 // 最大行宽
}rect — 矩形
{
type: 'rect',
x: 0, y: 0,
width: 100, height: 30,
fill: '#e6f7ff',
stroke: '#1890ff',
lineWidth: 1,
radius: 4 // 圆角
}circle — 圆形
{
type: 'circle',
x: 20, y: 20,
radius: 10,
fill: '#52c41a',
stroke: '#389e0d',
lineWidth: 1
}arc — 弧形
{
type: 'arc',
x: 30, y: 30,
radius: 20,
startAngle: 0,
endAngle: Math.PI * 1.5,
fill: '#1890ff',
stroke: '#096dd9',
lineWidth: 2
}line — 线段
{
type: 'line',
points: [
{ x: 0, y: 20 },
{ x: 50, y: 10 },
{ x: 100, y: 30 }
],
stroke: '#1890ff',
lineWidth: 2
}icon — SVG 图标
{
type: 'icon',
x: 5, y: 5,
width: 20, height: 20,
svg: '<svg>...</svg>',
hover: {
width: 22, height: 22 // hover 时放大
}
}image — 图片
{
type: 'image',
x: 5, y: 5,
width: 40, height: 40,
src: 'https://example.com/avatar.png',
shape: 'circle', // 'circle' | 'square'
hover: {
width: 44, height: 44
}
}完整示例
{
field: 'user',
title: '用户',
width: 200,
customRender: (args) => {
const { rect, dataValue, table } = args;
const record = table.getCellOriginRecord(args.col, args.row);
return {
renderDefault: false,
elements: [
// 头像
{
type: 'image',
x: 10, y: 8,
width: 32, height: 32,
src: record.avatar,
shape: 'circle'
},
// 用户名
{
type: 'text',
x: 50, y: 10,
text: record.name,
fill: '#1f2329',
fontSize: 14,
fontWeight: 'bold'
},
// 描述
{
type: 'text',
x: 50, y: 28,
text: record.desc,
fill: '#86909c',
fontSize: 12
}
]
};
}
}---
二、Custom Layout — JSX 布局 API(推荐)
Custom Layout 使用 VRender 的场景图原语,支持 Flex 布局,开发体验更好。
使用方式
import { Group, Text, Image, Rect, Tag } from '@visactor/vtable';
{
field: 'user',
title: '用户',
width: 250,
customLayout: (args) => {
const { table, row, col, rect, value } = args;
const record = table.getCellOriginRecord(col, row);
const container = (
<Group display="flex" flexDirection="row" alignItems="center"
width={rect.width} height={rect.height} padding={[8, 12]}>
<Image
src={record.avatar}
width={36} height={36}
cornerRadius={18}
/>
<Group display="flex" flexDirection="column" padding={[0, 0, 0, 8]}>
<Text text={record.name} fontSize={14} fontWeight="bold" fill="#1f2329" />
<Text text={record.desc} fontSize={12} fill="#86909c" />
</Group>
</Group>
);
return {
rootContainer: container,
renderDefault: false
};
}
}可用 JSX 原语
| 原语 | 说明 | 关键属性 |
|---|---|---|
Group | 容器/布局 | display, flexDirection, alignItems, justifyContent, flexWrap, gap |
Text | 文本 | text, fontSize, fill, fontWeight, maxLineWidth, ellipsis |
Rect | 矩形 | width, height, fill, stroke, cornerRadius |
Circle | 圆形 | radius, fill, stroke |
Image | 图片 | src, width, height, cornerRadius |
Arc | 弧形 | radius, startAngle, endAngle, fill |
Tag | 标签 | text, fill, textStyle, padding, cornerRadius |
Line | 线段 | points, stroke, lineWidth |
Checkbox | 复选框 | checked, text |
Radio | 单选框 | checked, text |
Flex 布局属性
Group 支持完整的 Flex 布局:
<Group
display="flex"
flexDirection="row" // 'row' | 'column' | 'row-reverse' | 'column-reverse'
alignItems="center" // 'flex-start' | 'center' | 'flex-end' | 'stretch'
justifyContent="flex-start" // 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around'
flexWrap="wrap" // 'nowrap' | 'wrap'
gap={8} // 子元素间距
padding={[8, 12]} // 内边距
width={rect.width}
height={rect.height}
>
{/* 子元素 */}
</Group>事件绑定
JSX 元素支持事件绑定:
<Group
cursor="pointer"
onMouseEnter={(event) => { /* hover效果 */ }}
onMouseLeave={(event) => { /* 取消hover */ }}
onClick={(event) => {
console.log('点击了', args.row, args.col);
}}
>
<Text text="点击我" fill="#1890ff" />
</Group>响应式自定义样式
customLayout: (args) => {
const { rect, table, col, row } = args;
const record = table.getCellOriginRecord(col, row);
// 根据数据动态生成标签
const tags = record.tags?.map((tag, i) => (
<Tag
key={i}
text={tag}
fill="#e6f7ff"
textStyle={{ fill: '#1890ff', fontSize: 12 }}
padding={[2, 6]}
cornerRadius={3}
/>
)) ?? [];
return {
rootContainer: (
<Group display="flex" flexDirection="row" flexWrap="wrap" gap={4}
width={rect.width} height={rect.height} padding={[6, 8]}>
{tags}
</Group>
),
renderDefault: false
};
}---
三、全局自定义渲染
可在表格级别设置全局 customRender/customLayout:
const table = new ListTable({
customRender: (args) => {
// 对所有单元格生效
// ...
},
headerCustomRender: (args) => {
// 对所有表头单元格生效
// ...
},
customLayout: (args) => {
// 全局 JSX 布局
// ...
}
});列定义上的 customRender/customLayout 优先级高于全局配置。
---
四、两套方案对比
| 特性 | Custom Render | Custom Layout |
|---|---|---|
| 定位方式 | 绝对坐标 (x, y) | Flex 布局(自动排列) |
| 学习成本 | 低 | 中(需了解 Flex) |
| 布局能力 | 手动计算位置 | 自动 Flex 排列 |
| 事件支持 | 有限 | 完整事件系统 |
| 适用场景 | 简单图元组合 | 复杂卡片式布局 |
| 推荐程度 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
建议: 新项目优先使用 Custom Layout(JSX 方案),只在极简场景下使用 Custom Render。
API 方法速查
表格实例方法,通过 tableInstance.methodName() 调用。完整文档: docs/assets/api/zh/methods.md。
生命周期
| 方法 | 签名 | 说明 |
|---|---|---|
updateOption | (option: TableConstructorOptions) => void | 全量更新配置并重绘 |
renderWithRecreateCells | () => void | 重建单元格并重新渲染(批量配置更新后调用) |
release | () => void | 销毁表格实例(必须调用,释放资源) |
数据操作
| 方法 | 签名 | 说明 | 适用表格 |
|---|---|---|---|
setRecords | (records: any[], option?: { sortState }) => void | 设置/更新全量数据 | 全部 |
addRecord | (record: any, recordIndex?: number) => void | 添加单条数据 | ListTable |
addRecords | (records: any[], recordIndex?: number) => void | 添加多条数据 | ListTable |
deleteRecords | (recordIndexs: number[]) => void | 删除多条数据 | ListTable |
updateRecords | `(records: any[], recordIndexs: (number\ | number[])[]) => void` | 更新多条数据 |
changeCellValue | (col, row, value, workOnEditableCell?) => void | 更改单个单元格值 | 全部 |
changeCellValues | (col, row, values[][]) => void | 批量更改单元格值 | 全部 |
setRecordChildren | (children, recordIndex) => void | 树形结构插入子节点 | ListTable |
setTreeNodeChildren | (children, ...) => void | 透视表树形插入子节点 | PivotTable |
updateFilterRules | (filterRules) => void | 更新数据过滤规则 | ListTable |
getFilteredRecords | () => any[] | 获取过滤后的数据 | ListTable |
查询
| 方法 | 签名 | 说明 |
|---|---|---|
getCellValue | (col, row) => string | 获取单元格格式化后的展示值 |
getCellOriginValue | (col, row) => any | 获取单元格 format 前的值 |
getCellRawValue | (col, row) => any | 获取数据源最原始值(从 records 直取) |
getCellStyle | (col, row) => CellStyle | 获取单元格样式对象 |
getCellRange | (col, row) => CellRange | 获取单元格合并范围 |
getCellRect | (col, row) => RectProps | 获取单元格在表格中的位置 |
getCellRelativeRect | (col, row) => RectProps | 获取相对于左上角的位置(含滚动) |
getCellHeaderPaths | (col, row) => ICellHeaderPaths | 获取行列表头路径 |
getCellHeaderTreeNodes | (col, row) => object | 获取表头树节点(含自定义属性) |
getRecordByCell | (col, row) => any | 获取完整数据记录 |
getCellOriginRecord | (col, row) => any | 获取源数据项 |
getCellOverflowText | (col, row) => string | 获取省略文字全文 |
getCellAddress | (findRecord, field) => CellAddress | 根据数据查找地址 (ListTable) |
getCellAddressByHeaderPaths | (paths) => CellAddress | 根据表头路径查找地址 (PivotTable) |
getBodyColumnDefine | (col, row) => ColumnDefine | 获取列配置定义 |
getHeaderField | (col, row) => string | 获取 field / indicatorKey |
getAllCells | (range?) => CellInfo[] | 获取所有单元格信息 |
getAllBodyCells | (range?) => CellInfo[] | 获取 body 区域单元格 |
getAllColumnHeaderCells | () => CellInfo[] | 获取列表头单元格 |
getAllRowHeaderCells | () => CellInfo[] | 获取行表头单元格 |
索引转换
| 方法 | 说明 | 适用表格 |
|---|---|---|
getBodyIndexByTableIndex(col, row) | 表格行列号 → body 索引 | 全部 |
getTableIndexByBodyIndex(col, row) | body 索引 → 表格行列号 | 全部 |
getTableIndexByRecordIndex(recordIndex) | 数据索引 → 表格行列号 | ListTable |
getRecordIndexByCell(col, row) | 单元格 → 数据索引 | ListTable |
getBodyRowIndexByRecordIndex(index) | 数据索引 → body 行号 | ListTable |
getTableIndexByField(field) | 字段名 → 列号 | ListTable |
getRecordShowIndexByCell(col, row) | 单元格 → body 展示索引 | ListTable |
getCellAddrByFieldRecord(field, index) | 字段+数据索引 → 行列号 | ListTable |
选择
| 方法 | 签名 | 说明 |
|---|---|---|
selectCell | (col, row) => void | 选中单元格(传空清除) |
selectCells | (ranges: CellRange[]) => void | 选中多个区域 |
selectRow | (row, isShift?, isCtrl?) => void | 选中整行 |
selectCol | (col, isShift?, isCtrl?) => void | 选中整列 |
getSelectedCellInfos | () => CellInfo[][] | 获取选中单元格信息 |
clearSelected | () => void | 清除选中 |
getCopyValue | () => string | 获取选中区域复制内容 |
滚动
| 方法 | 签名 | 说明 |
|---|---|---|
getScrollTop | () => number | 获取竖向滚动位置 |
getScrollLeft | () => number | 获取横向滚动位置 |
setScrollTop | (top: number) => void | 设置竖向滚动位置 |
setScrollLeft | (left: number) => void | 设置横向滚动位置 |
scrollToCell | (cellAddr: { col?, row? }) => void | 滚动到指定单元格 |
disableScroll | () => void | 禁用滚动 |
enableScroll | () => void | 启用滚动 |
编辑
| 方法 | 签名 | 说明 |
|---|---|---|
startEditCell | (col?, row?, value?) => void | 开启单元格编辑 |
completeEditCell | () => void | 结束编辑(保存) |
cancelEditCell | () => void | 取消编辑(不保存) |
getEditor | (col, row) => IEditor | 获取单元格编辑器 |
树形操作
| 方法 | 说明 | 适用表格 |
|---|---|---|
toggleHierarchyState(col, row) | 切换展开/收起 | 全部 |
getHierarchyState(col, row) | 获取展开状态 | 全部 |
expandAllTreeNode() | 展开所有节点 | ListTable |
collapseAllTreeNode() | 折叠所有节点 | ListTable |
expandAllForRowTree() | 展开行表头所有节点 | PivotTable |
collapseAllForRowTree() | 折叠行表头所有节点 | PivotTable |
expandAllForColumnTree() | 展开列表头所有节点 | PivotTable |
collapseAllForColumnTree() | 折叠列表头所有节点 | PivotTable |
布局更新
| 方法 | 说明 | 适用表格 |
|---|---|---|
updateColumns(columns) | 更新列定义 | ListTable |
updatePagination(pagination) | 更新分页 | 全部 |
updateTheme(theme) | 更新主题 | 全部 |
updateSortState(sortState) | 更新排序状态 | ListTable |
updateSortRules(sortRules) | 全量更新排序规则 | PivotTable |
updatePivotSortState(state) | 更新透视排序(仅图标) | PivotTable |
setDropDownMenuHighlight(info) | 设置菜单选中高亮 | 全部 |
尺寸
| 方法 | 签名 | 说明 |
|---|---|---|
getColWidth | (col) => number | 获取列宽 |
getRowHeight | (row) => number | 获取行高 |
setColWidth | (col, width) => void | 设置列宽 |
setRowHeight | (row, height) => void | 设置行高 |
getAllRowsHeight | () => number | 获取所有行总高 |
getAllColsWidth | () => number | 获取所有列总宽 |
setCanvasSize | (w, h) => void | 设置画布尺寸 |
setPixelRatio | (ratio) => void | 设置像素比 |
导出
| 方法 | 签名 | 说明 |
|---|---|---|
exportImg | () => string | 导出可视区域为 base64 图片 |
exportCellImg | (col, row, options?) => string | 导出单个单元格图片 |
exportCellRangeImg | (range) => string | 导出单元格区域图片 |
事件
| 方法 | 签名 | 说明 |
|---|---|---|
on | (type, listener) => EventListenerId | 监听事件 |
off | (idOrType, listener?) => void | 移除监听 |
onVChartEvent | (type, query, listener) => void | 监听 VChart 事件 |
offVChartEvent | (type, listener) => void | 移除 VChart 事件 |
Checkbox / Radio / Switch 状态
| 方法 | 说明 |
|---|---|
getCheckboxState(field?) | 获取 checkbox 选中状态 |
getCellCheckboxState(col, row) | 获取单元格 checkbox 状态 |
setCellCheckboxState(col, row, checked) | 设置 checkbox 状态 |
getRadioState(field?) | 获取 radio 选中状态 |
getCellRadioState(col, row) | 获取单元格 radio 状态 |
setCellRadioState(col, row, index) | 设置 radio 选中 |
getSwitchState(field?) | 获取 switch 状态 |
getCellSwitchState(col, row) | 获取单元格 switch 状态 |
setCellSwitchState(col, row, open) | 设置 switch 状态 |
可视区域查询
| 方法 | 说明 |
|---|---|
getBodyVisibleCellRange() | 获取 body 可见单元格范围 |
getBodyVisibleColRange() | 获取 body 可见列范围 |
getBodyVisibleRowRange() | 获取 body 可见行范围 |
cellIsInVisualView(col, row) | 判断单元格是否完全可见 |
getCellAtRelativePosition(x, y) | 获取坐标处的单元格 |
其他
| 方法 | 说明 |
|---|---|
getDrawRange() | 获取实际绘制区域 |
showTooltip(col, row, tooltipOptions) | 显示提示框 |
showDropdownMenu(col, row, items) | 显示下拉菜单 |
setLegendSelected(selectedData) | 设置图例选中 |
getChartDatumPosition(...) | 获取图表图元位置 |
registerCustomCellStyle(id, style) | 注册自定义样式 |
arrangeCustomCellStyle(config) | 分配自定义样式 |
setSortedIndexMap(field, map) | 设置预排序索引 |
showMoverLine(col, row) | 显示移动标记线 |
hideMoverLine() | 隐藏移动标记线 |
updateCellContent(col, row) | 更新单元格内容 |
updateCellContentRange(range) | 更新区域内容 |
setTranslate(x, y) | 设置偏移量 |
setLoadingHierarchyState(col, row) | 设置加载中状态 |
getAggregateValuesByField(field) | 获取聚合值 |
isAggregation(col, row) | 判断是否聚合单元格 |
refreshAfterSourceChange() | 源数据变更后刷新 |
getLayoutRowTree() | 获取行表头树 (PivotTable) |
getLayoutColumnTree() | 获取列表头树 (PivotTable) |
API 属性速查
表格实例属性,通过 tableInstance.propertyName 访问。完整文档: docs/assets/api/zh/properties.md。
只读属性
| 属性 | 类型 | 说明 |
|---|---|---|
records | any[] | 当前表格数据数组(用 setRecords() 更新) |
options | TableConstructorOptions | 当前表格配置(用 updateOption() 更新) |
columns | ColumnsDefine | 当前列定义(ListTable,用 updateColumns() 更新) |
rowCount | number | 表格总行数(含表头) |
colCount | number | 表格总列数(含行表头) |
rowHeaderLevelCount | number | 行表头层级数 |
columnHeaderLevelCount | number | 列表头层级数 |
frozenColCount | number | 左侧冻结列数 |
frozenRowCount | number | 顶部冻结行数 |
rightFrozenColCount | number | 右侧冻结列数 |
bottomFrozenRowCount | number | 底部冻结行数 |
theme | ITableThemeDefine | 当前主题(用 updateTheme() 更新) |
pixelRatio | number | 画布像素比(用 setPixelRatio() 更新) |
rowHierarchyType | `'grid' \ | 'tree'` |
可读写属性
| 属性 | 类型 | 说明 | 注意 |
|---|---|---|---|
scrollLeft | number | 横向滚动位置 | 赋值后自动重绘 |
scrollTop | number | 纵向滚动位置 | 赋值后自动重绘 |
widthMode | `'standard' \ | 'adaptive' \ | 'autoWidth'` |
heightMode | `'standard' \ | 'adaptive' \ | 'autoHeight'` |
autoWrapText | boolean | 自动换行 | 赋值后需刷新 |
transpose | boolean | 转置显示 | 赋值后需重建 |
属性使用示例
const table = new ListTable(options);
// 读取数据
console.log(table.records.length); // 数据条数
console.log(table.rowCount); // 总行数
console.log(table.colCount); // 总列数
// 滚动控制
table.scrollTop = 100; // 滚动到 100px 位置
table.scrollLeft = 50;
// 修改模式(需手动刷新)
table.widthMode = 'adaptive';
table.renderWithRecreateCells();
// 获取冻结信息
console.log(`冻结 ${table.frozenColCount} 列, ${table.frozenRowCount} 行`);事件系统
使用方式
// 监听事件
const listenerId = tableInstance.on('click_cell', (args) => {
console.log('点击了', args.col, args.row);
});
// 移除监听(方式一:通过 ID)
tableInstance.off(listenerId);
// 移除监听(方式二:通过类型+函数)
tableInstance.off('click_cell', handler);事件分类速查
鼠标事件
| 事件名 | 参数类型 | 说明 |
|---|---|---|
click_cell | MousePointerCellEvent | 单击单元格 |
dblclick_cell | MousePointerCellEvent | 双击单元格 |
mouseenter_cell | MousePointerCellEvent | 鼠标进入单元格 |
mouseleave_cell | MousePointerCellEvent | 鼠标离开单元格 |
mousemove_cell | MousePointerCellEvent | 鼠标在单元格上移动 |
mousedown_cell | MousePointerCellEvent | 单元格鼠标按下 |
mouseup_cell | MousePointerCellEvent | 单元格鼠标松开 |
contextmenu_cell | MousePointerCellEvent | 单元格右键 |
mouseenter_table | — | 鼠标进入表格 |
mouseleave_table | — | 鼠标离开表格 |
mousedown_table | — | 鼠标在表格按下 |
contextmenu_canvas | — | 画布区域右键 |
MousePointerCellEvent 参数
{
col: number; // 列号
row: number; // 行号
value: FieldData; // 格式化后的值
dataValue: FieldData; // 原始值
cellRange: CellRange; // 单元格范围
cellType: string; // 单元格类型
title: string; // 列标题
field: FieldDef; // 字段名
cellLocation: CellLocation; // 'body' | 'rowHeader' | 'columnHeader' | 'cornerHeader'
originData: any; // 完整记录
event: MouseEvent; // 原生事件
target: any; // 场景图节点
targetCell: any; // 单元格节点
}选择事件
| 事件名 | 说明 |
|---|---|
selected_cell | 单元格选中状态改变 |
selected_clear | 清除所有选中 |
selected_changed | 选中范围改变(拖拽选择过程中持续触发) |
drag_select_end | 拖拽框选鼠标松开 |
copy_data | 复制内容 |
滚动事件
| 事件名 | 说明 |
|---|---|
scroll | 滚动事件(scrollLeft, scrollTop, scrollDirection) |
scroll_vertical_end | 竖向滚动到底部 |
scroll_horizontal_end | 横向滚动到最右 |
调整大小事件
| 事件名 | 说明 |
|---|---|
resize_column | 列宽调整中(持续触发) |
resize_column_end | 列宽调整完成 |
resize_row | 行高调整中 |
resize_row_end | 行高调整完成 |
拖拽排序事件
| 事件名 | 说明 |
|---|---|
change_header_position | 拖拽移动表头完成 |
change_header_position_start | 开始拖拽 |
changing_header_position | 拖拽中 |
change_header_position_fail | 拖拽失败 |
排序事件
| 事件名 | 说明 | 适用表格 |
|---|---|---|
sort_click | 点击排序图标 | ListTable |
after_sort | 排序完成 | ListTable |
pivot_sort_click | 透视表排序点击 | PivotTable |
菜单事件
| 事件名 | 说明 |
|---|---|
dropdown_menu_click | 下拉菜单项点击 |
dropdown_icon_click | 点击下拉图标 |
dropdown_menu_clear | 清空下拉菜单 |
show_menu | 显示菜单 |
hide_menu | 隐藏菜单 |
图标事件
| 事件名 | 说明 |
|---|---|
icon_click | 点击图标 |
drillmenu_click | 下钻按钮点击(PivotTable) |
键盘事件
| 事件名 | 说明 |
|---|---|
keydown | 键盘按下 |
树形展开事件
| 事件名 | 说明 |
|---|---|
tree_hierarchy_state_change | 树形节点展开/收起 |
表单控件事件
| 事件名 | 说明 |
|---|---|
checkbox_state_change | checkbox 状态变更 |
radio_state_change | radio 状态变更 |
switch_state_change | switch 状态变更 |
button_click | 按钮点击 |
数据变更事件
| 事件名 | 说明 |
|---|---|
change_cell_value | 更改单元格值 |
change_cell_values | 批量更改单元格值(聚合事件) |
paste_data | 粘贴数据 |
add_record | 添加记录 |
delete_record | 删除记录 |
update_record | 更新记录 |
图例事件
| 事件名 | 说明 |
|---|---|
legend_item_click | 图例项点击 |
legend_item_hover | 图例项 hover |
legend_item_unhover | 图例项取消 hover |
legend_change | 颜色/尺寸图例变更 |
坐标轴事件
| 事件名 | 说明 |
|---|---|
mouseenter_axis | 鼠标进入轴 |
mouseleave_axis | 鼠标离开轴 |
迷你图事件
| 事件名 | 说明 |
|---|---|
mouseover_chart_symbol | 鼠标经过迷你图标记 |
生命周期事件
| 事件名 | 说明 |
|---|---|
before_init | 初始化前 |
initialized | 初始化完成 |
after_render | 每次渲染完成 |
before_update_option | 更新配置前 |
before_set_size | 设置大小前 |
updated | 更新完成 |
空数据提示事件
| 事件名 | 说明 |
|---|---|
empty_tip_click | 空提示点击 |
empty_tip_dblclick | 空提示双击 |
常用事件示例
单元格点击
table.on('click_cell', (args) => {
const { col, row, cellLocation, originData } = args;
if (cellLocation === 'body') {
console.log('点击数据行:', originData);
}
});排序
table.on('sort_click', (args) => {
const { field, order } = args;
console.log(`排序: ${field} ${order}`);
// 自定义排序逻辑
});编辑完成
table.on('change_cell_value', (args) => {
const { col, row, value, oldValue } = args;
console.log(`单元格(${col},${row}) 从 ${oldValue} 改为 ${value}`);
});滚动加载
table.on('scroll_vertical_end', () => {
// 滚动到底部,加载更多数据
loadMoreData().then(newRecords => {
table.addRecords(newRecords);
});
});checkbox 状态
table.on('checkbox_state_change', (args) => {
const { col, row, checked } = args;
console.log(`checkbox(${col},${row}): ${checked}`);
});VChart 图表事件
// 监听嵌入 VChart 的事件(PivotChart 用)
table.onVChartEvent('click', { markName: 'bar' }, (args) => {
console.log('点击了柱形:', args.datum);
});透视表维度与指标
一、概念说明
透视表(PivotTable/PivotChart)通过 维度(Dimension)和 指标(Indicator)组织多维数据:
- 行维度 (
rows): 决定行表头结构,如"地区"、"城市" - 列维度 (
columns): 决定列表头结构,如"年份"、"季度" - 指标 (
indicators): 需要展示的度量值,如"销售额"、"利润"
┌── 列维度(category) ──┐
│ 办公用品 │ 家具 │
┌─ 行维度 ──┐ ├──────────┼───────────┤
│ 华东 │ │ ¥1,200 │ ¥3,400 │ ← 指标值(sales)
│ 华北 │ │ ¥800 │ ¥2,100 │
└───────────┘ └──────────┴───────────┘二、维度定义 IDimension
定义于 packages/vtable/src/ts-types/pivot-table/dimension/。
基础维度属性 (IBasicDimension)
| 属性 | 类型 | 说明 |
|---|---|---|
dimensionKey | `string \ | number` |
title | string | 维度标题(显示在角头或表头标题行) |
headerFormat | (value: any) => string | 表头值格式化 |
width | `number \ | string` |
minWidth | `number \ | string` |
maxWidth | `number \ | string` |
headerStyle | `ColumnStyleOption \ | Function` |
headerIcon | `string \ | ColumnIconOption \ |
headerCustomRender | ICustomRender | 表头自定义渲染 |
headerCustomLayout | ICustomLayout | 表头自定义布局 |
headerEditor | `string \ | IEditor` |
description | string | 维度描述(hover 提示) |
drillDown | boolean | 显示下钻按钮 |
drillUp | boolean | 显示上钻按钮 |
dropDownMenu | MenuListItem[] | 下拉菜单 |
showSort | boolean | 显示排序图标 |
sort | boolean | 开启排序功能 |
disableHeaderHover | boolean | 禁用 hover |
disableHeaderSelect | boolean | 禁用选中 |
dragHeader | boolean | 是否允许拖拽 |
维度类型变体
// 行维度:支持 width/minWidth/maxWidth
type IRowDimension = ILinkDimension | IImageDimension | ITextDimension;
// 列维度:不支持 width(由指标决定)
type IColumnDimension = Omit<IRowDimension, 'width' | 'minWidth' | 'maxWidth'>;
// 联合类型
type IDimension = IRowDimension | IColumnDimension;不同表头类型对应不同维度变体:
ITextDimension: 默认文本维度ILinkDimension: 链接维度(表头显示为链接)IImageDimension: 图片维度(表头显示为图片)
维度配置示例
rows: [
{
dimensionKey: 'region',
title: '地区',
width: 120,
headerStyle: { fontWeight: 'bold' },
drillDown: true, // 下钻
sort: true
},
{
dimensionKey: 'city',
title: '城市',
width: 100,
headerFormat: (value) => `📍 ${value}`
}
],
columns: [
{
dimensionKey: 'category',
title: '品类'
},
{
dimensionKey: 'year',
title: '年份'
}
]三、指标定义 IIndicator
定义于 packages/vtable/src/ts-types/pivot-table/indicator/。
基础指标属性
表头部分 (HeaderIndicator):
| 属性 | 类型 | 说明 |
|---|---|---|
indicatorKey | `string \ | number` |
title | string | 指标标题 |
headerIcon | `string \ | ColumnIconOption \ |
headerStyle | `ColumnStyleOption \ | Function` |
headerCustomRender | ICustomRender | 表头自定义渲染 |
headerCustomLayout | ICustomLayout | 表头自定义布局 |
sort | boolean | 排序配置 |
showSort | boolean | 显示排序图标 |
dropDownMenu | MenuListItem[] | 下拉菜单 |
hide | boolean | 隐藏该指标列 |
description | string | 描述 |
disableHeaderHover | boolean | 禁用 hover |
disableHeaderSelect | boolean | 禁用选中 |
Body 部分 (ColumnIndicator):
| 属性 | 类型 | 说明 |
|---|---|---|
width | `number \ | string` |
minWidth | `number \ | string` |
maxWidth | `number \ | string` |
format | (value, col, row, table) => string | 值格式化 |
cellType | ColumnTypeOption | 单元格类型 |
style | `ColumnStyleOption \ | Function` |
icon | `string \ | ColumnIconOption \ |
customRender | ICustomRender | 自定义渲染 |
customLayout | ICustomLayout | 自定义布局 |
editor | `string \ | IEditor` |
disableHover | boolean | 禁用 hover |
disableSelect | boolean | 禁用选中 |
IChartIndicator(透视图专用)
在 IIndicator 基础上增加图表配置:
| 属性 | 类型 | 说明 |
|---|---|---|
cellType | 'chart' | 固定为 chart |
chartModule | string | 图表模块名('vchart') |
chartSpec | any | VChart 图表规范配置 |
指标配置示例
indicators: [
{
indicatorKey: 'sales',
title: '销售额',
width: 120,
format: (value) => value ? `¥${Number(value).toLocaleString()}` : '-',
style: (args) => ({
bgColor: args.dataValue > 3000 ? '#f6ffed' : '#fff',
color: args.dataValue > 3000 ? '#52c41a' : '#333'
}),
sort: true
},
{
indicatorKey: 'profit',
title: '利润',
width: 100,
format: (value) => value ? `¥${Number(value).toLocaleString()}` : '-'
}
]四、角头配置 ICornerDefine
corner: {
titleOnDimension: 'row' | 'column' | 'all' | 'none'; // 标题显示位置
headerStyle: ColumnStyleOption; // 角头样式
customLayout: ICustomLayout; // 自定义布局
customRender: ICustomRender; // 自定义渲染
}五、自定义表头树 IHeaderTreeDefine
除了通过 rows/columns + records 自动生成表头,也可以手动定义表头树:
type IHeaderTreeDefine = IDimensionHeaderNode | IIndicatorHeaderNode;
// 维度节点
interface IDimensionHeaderNode {
dimensionKey: string | number; // 维度字段
value: string; // 维度值
children?: IHeaderTreeDefine[]; // 子节点
hierarchyState?: 'expand' | 'collapse';
virtual?: boolean; // 虚拟节点(忽略数据匹配)
levelSpan?: number; // 跨层级合并
}
// 指标节点
interface IIndicatorHeaderNode {
indicatorKey: string | number; // 指标 key
value?: string; // 显示值
children?: IHeaderTreeDefine[];
hide?: boolean; // 隐藏
}自定义表头树示例
const table = new PivotTable({
columnTree: [
{
dimensionKey: 'year',
value: '2023',
children: [
{ indicatorKey: 'sales', value: '销售额' },
{ indicatorKey: 'profit', value: '利润' }
]
},
{
dimensionKey: 'year',
value: '2024',
children: [
{ indicatorKey: 'sales', value: '销售额' },
{ indicatorKey: 'profit', value: '利润' }
]
}
],
rowTree: [
{
dimensionKey: 'region',
value: '华东',
children: [
{ dimensionKey: 'city', value: '上海' },
{ dimensionKey: 'city', value: '杭州' }
]
},
{ dimensionKey: 'region', value: '华北' }
]
});六、数据分析配置 dataConfig
dataConfig: {
// 聚合规则
aggregationRules: [
{
indicatorKey: 'sales',
field: 'sales',
aggregationType: VTable.AggregationType.SUM // SUM | COUNT | AVG | MAX | MIN | NONE
}
],
// 排序规则
sortRules: [
{
sortField: 'region',
sortType: VTable.SortType.ASC, // ASC | DESC | NONE
sortByIndicator: 'sales' // 按指标值排序
}
],
// 筛选规则
filterRules: [
{
filterFunc: (record) => record.sales > 100
}
],
// 小计/总计
totals: {
row: {
showGrandTotals: true, // 显示行总计
showSubTotals: true, // 显示行小计
grandTotalLabel: '总计',
subTotalLabel: '小计',
subTotalsDimensions: ['region'] // 对哪些维度显示小计
},
column: {
showGrandTotals: true,
grandTotalLabel: '总计'
}
},
// 派生字段
derivedFieldRules: [
{
fieldName: 'profitRate',
derivedFunc: (record) => {
return record.profit / record.sales;
}
}
]
}七、透视表行层级展示
// grid 模式(默认):每个维度独立一列
rowHierarchyType: 'grid'
// tree 模式:所有行维度在一列中树形展示
rowHierarchyType: 'tree'
// grid-tree 模式:grid 布局但支持展开收起
rowHierarchyType: 'grid-tree'表头标题行/列
// 在列表头顶部增加一行显示维度名称
columnHeaderTitle: {
title: true, // 或自定义字符串
headerStyle: { fontWeight: 'bold' }
}
// 在行表头左侧增加一列显示维度名称
rowHeaderTitle: {
title: true,
headerStyle: { fontWeight: 'bold' }
}数据绑定
一、ListTable 数据绑定
records — 直接传入数据
最常用的方式,将数组数据直接传入:
const table = new ListTable({
records: [
{ id: 1, name: 'Alice', age: 25, city: 'Beijing' },
{ id: 2, name: 'Bob', age: 30, city: 'Shanghai' }
],
columns: [
{ field: 'id', title: 'ID' },
{ field: 'name', title: '姓名' }
]
});
// 更新全量数据
table.setRecords(newRecords);
// 增删改
table.addRecord({ id: 3, name: 'Charlie', age: 28 });
table.addRecords([record1, record2], 0); // 在索引0处插入
table.deleteRecords([0, 2]); // 删除第0、2条
table.updateRecords([updatedRecord], [1]); // 更新第1条dataSource — 数据源对象(懒加载/大数据)
用于懒加载或自定义数据获取逻辑:
import { CachedDataSource, DataSource } from '@visactor/vtable';
// 创建数据源
const dataSource = new CachedDataSource({
get(index) {
// 根据索引获取数据(支持返回 Promise)
return records[index];
},
length: records.length,
getField(index, field) {
return records[index]?.[field];
}
});
const table = new ListTable({
dataSource,
columns: [...]
});field — 字段映射
field 支持多种形式:
// 1. 简单字段名
{
field: 'name';
}
// 2. 嵌套字段(点号分隔)
{
field: 'address.city';
}
// 3. 数组路径
{
field: ['address', 'city'];
}
// 4. 数字索引(数据为数组时)
{
field: 0;
}fieldFormat — 格式化显示
{
field: 'price',
title: '价格',
fieldFormat: (record, col, row, table) => {
return `¥${record.price?.toFixed(2)}`;
}
}注意: fieldFormat 影响的是展示值,getCellValue() 返回格式化后的值,getCellRawValue() 返回原始值。
排序
// 方式1:列定义上开启
{
field: 'age',
title: '年龄',
sort: true // 使用默认排序
}
// 方式2:自定义排序函数
{
field: 'name',
title: '姓名',
sort: (a, b, order) => {
return order === 'asc'
? a.localeCompare(b, 'zh')
: b.localeCompare(a, 'zh');
}
}
// 方式3:预排序(大数据性能优化)
table.setSortedIndexMap('age', sortedMap);
// 方式4:配置初始排序状态
const table = new ListTable({
sortState: { field: 'age', order: 'asc' }
});过滤
// 更新过滤规则
table.updateFilterRules([
{
filterKey: 'city',
filteredValues: ['Beijing', 'Shanghai']
}
]);
// 自定义过滤函数
table.updateFilterRules([
{
filterFunc: record => record.age > 20
}
]);
// 获取过滤后的数据
const filtered = table.getFilteredRecords();聚合
{
field: 'sales',
title: '销售额',
aggregation: [
{
aggregationType: VTable.AggregationType.SUM,
showOnTop: false, // 显示在底部
formatFun: (value) => `合计: ¥${value.toLocaleString()}`
}
]
}支持的聚合类型:SUM, COUNT, AVG, MAX, MIN, NONE, CUSTOM
分页
const table = new ListTable({
pagination: {
perPageCount: 20, // 每页 20 条
currentPage: 1 // 当前第 1 页
}
});
// 翻页
table.updatePagination({ currentPage: 2 });二、PivotTable 数据绑定
方式1:records + rows/columns/indicators(自动聚合)
最常用,由 VTable 内部完成数据分析:
const table = new PivotTable({
records: flatData, // 平坦的原始数据
rows: ['region', 'city'],
columns: ['category', 'subCategory'],
indicators: [
{
indicatorKey: 'sales',
title: '销售额'
}
],
dataConfig: {
aggregationRules: [
{
indicatorKey: 'sales',
field: 'sales',
aggregationType: VTable.AggregationType.SUM
}
]
}
});方式2:rowTree/columnTree + records(自定义表头)
手动定义表头结构:
const table = new PivotTable({
records: flatData,
rowTree: [
{ dimensionKey: 'region', value: '华东', children: [...] },
{ dimensionKey: 'region', value: '华北' }
],
columnTree: [
{ dimensionKey: 'year', value: '2023', children: [
{ indicatorKey: 'sales' },
{ indicatorKey: 'profit' }
]}
],
rows: [{ dimensionKey: 'region', title: '地区' }],
indicators: [
{ indicatorKey: 'sales', title: '销售额' }
]
});方式3:rowTree/columnTree 无 records(纯展示)
手动指定所有数据,不做聚合:
// 数据可以不传 records,在 tree 节点中带数据
// 此时必须设置 parseCustomTreeToMatchRecords三、PivotChart 数据绑定
const table = new PivotChart({
records: flatData,
rows: ['region'],
columns: ['category'],
indicators: [
{
indicatorKey: 'sales',
title: '销售额',
cellType: 'chart',
chartModule: 'vchart',
chartSpec: {
type: 'bar',
xField: ['category'],
yField: 'sales'
}
}
]
});注意:PivotChart 内部自动开启数据分析,不需要手动配置 dataConfig。
PivotChart records 对象格式(按指标分组)
当数据已经按指标分好组时,records 可以是对象格式(key 为指标分组名):
const table = new PivotChart({
indicators: [
{ indicatorKey: 'sales', cellType: 'chart', ... },
{ indicatorKey: 'profit', cellType: 'chart', ... }
],
// records 的 key 必须与 indicators[i].indicatorKey 严格一致
records: {
'sales': [ { region: '华东', sales: 1200, ... }, ... ],
'profit': [ { region: '华东', profit: 300, ... }, ... ]
}
});⚠️ 关键规则:records 对象格式时,key 必须与 `indicatorKey` 完全一致(大小写、下划线均须匹配)。
>
内部处理逻辑(参见dataset.tsprocessRecords):当records是对象时,每个 key 会作为assignedIndicatorKey传入,与indicators[i].indicatorKey进行严格字符串比较。key 不匹配时,该组数据被忽略,对应图表单元格显示为空白(只渲染轴,不渲染柱/线等图形元素)。
常见错误示例:
// ❌ 错误:key 与 indicatorKey 不一致
indicators: [{ indicatorKey: 'INDICATOR_KEY_0', ... }]
records: { 'INDICATORKEY0': [...] } // 下划线缺失 → 数据丢失
// ✅ 正确:key 与 indicatorKey 完全相同
indicators: [{ indicatorKey: 'INDICATOR_KEY_0', ... }]
records: { 'INDICATOR_KEY_0': [...] }注意:columnTree: []/rowTree: []为空数组是合法的,PivotChart 会在内部自动用指标节点补全,无需手动添加虚拟节点。
四、数据更新最佳实践
// ✅ 推荐:使用 API 更新
table.setRecords(newRecords); // 全量更新
table.addRecords(newRecords); // 增量添加
table.changeCellValue(col, row, value); // 单格修改
// ❌ 不推荐:直接修改 records 数组
table.records.push(newRecord); // 不会触发重新渲染!
// 如果必须直接修改数据源,需手动刷新
table.records[0].name = 'NewName';
table.refreshAfterSourceChange(); // ListTable 专有五、树形数据
const table = new ListTable({
records: [
{
name: '总部',
children: [{ name: '研发部', children: [{ name: '前端组' }, { name: '后端组' }] }, { name: '市场部' }]
}
],
columns: [
{ field: 'name', title: '部门', tree: true }, // tree: true 标记树形列
{ field: 'headcount', title: '人数' }
],
hierarchyIndent: 20, // 缩进像素
hierarchyExpandLevel: 2 // 默认展开2层
});
// 动态加载子节点
table.on('tree_hierarchy_state_change', args => {
if (args.hierarchyState === 'expand') {
loadChildren(args.originData).then(children => {
table.setRecordChildren(children, args.row);
});
}
});交互配置
一、选择
基本选择配置
const table = new ListTable({
select: {
disableSelect: false, // 是否禁用选择
disableHeaderSelect: false, // 是否禁用表头选择
// 选择高亮模式
highlightMode: 'cell', // 'cross' | 'column' | 'row' | 'cell'
// 点击表头的选择行为
headerSelectMode: 'inline', // 'inline' 选中整行/列 | 'cell' 只选表头
// 点击空白区域取消选中
blankAreaClickDeselect: true
}
});选择 API
// 选中单元格
table.selectCell(col, row);
// 选中区域
table.selectCells([
{ start: { col: 1, row: 1 }, end: { col: 3, row: 5 } }
]);
// 选中整行/列
table.selectRow(3);
table.selectCol(2);
// 清除选中
table.clearSelected();
// 获取选中信息
const selected = table.getSelectedCellInfos();选择事件
table.on('selected_cell', (args) => {
console.log('选中:', args.ranges);
});
table.on('selected_changed', (args) => {
// 拖拽选择过程中持续触发
});
table.on('selected_clear', () => {
console.log('取消选中');
});二、Hover 高亮
const table = new ListTable({
hover: {
// 高亮模式
highlightMode: 'cross', // 'cross' 十字花 | 'column' | 'row' | 'cell'
disableHover: false, // 禁用 hover
disableHeaderHover: false, // 禁用表头 hover
enableSingleCell: false // 只高亮单个单元格
}
});主题中配置 hover 样式:
theme: {
bodyStyle: {
hover: {
cellBgColor: '#e6f7ff', // hover 单元格背景色
inlineColumnBgColor: '#f0f5ff', // 十字花列背景色
inlineRowBgColor: '#f0f5ff' // 十字花行背景色
}
}
}三、编辑
配置编辑器
import { ListTable } from '@visactor/vtable';
import { InputEditor, DateInputEditor, ListEditor } from '@visactor/vtable-editors';
// 1. 注册编辑器
const inputEditor = new InputEditor();
const dateEditor = new DateInputEditor();
const listEditor = new ListEditor({ values: ['选项A', '选项B', '选项C'] });
VTable.register.editor('input', inputEditor);
VTable.register.editor('date', dateEditor);
VTable.register.editor('list', listEditor);
// 2. 在列定义中使用
const table = new ListTable({
columns: [
{ field: 'name', title: '姓名', editor: 'input' },
{ field: 'date', title: '日期', editor: 'date' },
{ field: 'status', title: '状态', editor: 'list' }
],
editCellTrigger: 'doubleclick' // 编辑触发方式
});编辑触发方式
editCellTrigger: 'doubleclick' // 双击(默认)
editCellTrigger: 'click' // 单击
editCellTrigger: 'api' // 仅通过 API
editCellTrigger: 'keydown' // 键盘按下
editCellTrigger: ['doubleclick', 'api'] // 多种方式编辑 API
// 开启编辑
table.startEditCell(col, row);
table.startEditCell(col, row, 'customValue'); // 自定义编辑初始值
// 结束编辑
table.completeEditCell(); // 保存
table.cancelEditCell(); // 取消
// 获取编辑器
const editor = table.getEditor(col, row);编辑事件
table.on('change_cell_value', (args) => {
const { col, row, value, oldValue } = args;
console.log('值改变:', oldValue, '->', value);
});动态编辑器
{
field: 'value',
title: '值',
editor: (args) => {
// 根据行数据动态返回编辑器
const record = args.table.getCellOriginRecord(args.col, args.row);
if (record.type === 'date') return 'date';
if (record.type === 'select') return 'list';
return 'input';
}
}四、排序
列定义排序
{
field: 'name',
title: '姓名',
sort: true, // 开启默认排序
showSort: true // 显示排序图标(默认 sort:true 时自动显示)
}
// 自定义排序
{
field: 'name',
sort: (a, b, order) => {
return order === 'asc'
? a.localeCompare(b, 'zh')
: b.localeCompare(a, 'zh');
}
}排序状态
// 初始排序
const table = new ListTable({
sortState: { field: 'age', order: 'asc' },
multipleSort: true // 支持多列排序
});
// 更新排序
table.updateSortState({
field: 'name',
order: 'desc'
});排序事件
table.on('sort_click', (args) => {
const { field, order } = args;
// 可以阻止默认排序,实现服务端排序
// args.preventDefault?.();
// fetchSortedData(field, order).then(data => table.setRecords(data));
});五、拖拽
拖拽调整列/行顺序
const table = new ListTable({
dragOrder: {
dragHeaderMode: 'column', // 'all' | 'none' | 'column' | 'row'
// 冻结列拖拽规则
frozenColDragHeaderMode: 'fixedFrozenCount'
// 'disabled': 禁止拖入/出冻结列
// 'adjustFrozenCount': 拖入冻结区增加冻结数
// 'fixedFrozenCount': 自由拖拽,冻结数不变
}
});拖拽事件
table.on('change_header_position', (args) => {
console.log(`列从 ${args.source} 移到 ${args.target}`);
});行拖拽排序
通过 rowSeriesNumber.dragOrder 启用行拖拽:
const table = new ListTable({
rowSeriesNumber: {
title: '#',
width: 50,
dragOrder: true // 启用行拖拽排序
}
});六、列宽/行高调整
const table = new ListTable({
resize: {
columnResizeMode: 'header', // 'all' | 'none' | 'header' | 'body'
rowResizeMode: 'none',
columnResizeWidth: 6 // 调整热区宽度
}
});列宽调整事件
table.on('resize_column_end', (args) => {
console.log(`列 ${args.col} 宽度调整为 ${args.colWidth}`);
});七、冻结列/行
const table = new ListTable({
frozenColCount: 2, // 左侧冻结2列
rightFrozenColCount: 1, // 右侧冻结1列
frozenRowCount: 1, // 顶部冻结(通常是表头)
bottomFrozenRowCount: 1 // 底部冻结(如汇总行)
});八、键盘交互
const table = new ListTable({
keyboardOptions: {
moveFocusCellOnTab: true, // Tab 键移动
editCellOnEnter: true, // Enter 进入编辑
selectAllOnCtrlA: true, // Ctrl+A 全选
copySelected: true, // Ctrl+C 复制
pasteValueToCell: true, // Ctrl+V 粘贴
moveSelectedCellOnArrowKeys: true, // 方向键移动
ctrlMultiSelect: true, // Ctrl 多选
shiftMultiSelect: true // Shift 范围选
}
});九、右键菜单
const table = new ListTable({
menu: {
contextMenuItems: [
{ text: '复制', menuKey: 'copy' },
{ text: '粘贴', menuKey: 'paste' },
{ text: '---' }, // 分割线
{ text: '删除行', menuKey: 'delete' }
]
}
});
table.on('dropdown_menu_click', (args) => {
switch (args.menuKey) {
case 'copy': table.getCopyValue(); break;
case 'delete': table.deleteRecords([args.row]); break;
}
});十、Tooltip 提示
const table = new ListTable({
tooltip: {
renderMode: 'html', // 'html' | 'canvas'
isShowOverflowTextTooltip: true, // 省略文字自动提示
confine: true, // 限制在表格内
overflowTextTooltipDisappearDelay: 1000 // 延迟消失(ms)
}
});
// 手动显示 tooltip
table.showTooltip(col, row, {
content: '自定义提示内容',
style: { bgColor: '#333', color: '#fff' }
});