传说中的表中表:VTable 主从表功能正式上线
VTable 主从表插件
本文作者:
薯片(github.com/Violet2314) 广东财经大学
导读
VTable 主从表插件(MasterDetailPlugin)是基于 VisActor VTable 开发的企业级数据可视化组件,专门解决复杂业务场景中的层次化数据展示需求。该插件突破了传统表格的平面化限制,实现了在主表行内嵌入完整子表格的创新交互模式,为用户提供了直观、高效的数据钻取和详情查看体验
核心能力
- 支持在主表行内嵌入完整的子表格,让复杂数据结构一目了然
- 支持静态配置和动态函数配置,满足各种业务场景
- 支持懒加载机制,优化大数据量场景下的性能表现
典型业务场景
| 业务场景 | 主表数据 | 子表数据 |
|---|---|---|
| 订单管理 | 订单基本信息 | 商品清单、物流详情 |
| 项目管理 | 项目概览 | 任务列表、成员分工 |
| 财务管理 | 汇总数据 | 明细账目、凭证信息 |
| 库存管理 | 产品类别 | SKU详情、库存动态 |
| 客户管理 | 客户基本信息 | 联系记录、交易历史 |
快速上手
获取NPM包
首先,你需要在项目根目录下使用以下命令安装:
# 使用 npm 安装
npm install @visactor/vtable @visactor/vtable-plugins
# 使用 yarn 安装
yarn add @visactor/vtable @visactor/vtable-plugins
引入主从表插件
通过 NPM 包引入
import * as VTable from '@visactor/vtable';
import { MasterDetailPlugin } from '@visactor/vtable-plugins';
function generateData(count) {
const depts = ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance'];
return Array.from({ length: count }).map(( _ , i) => ({
id: i + 1,
rowNo: i + 1,
name: `姓名 ${i + 1}`,
department: depts[i % depts.length],
score: Math.floor(Math.random() * 100),
amount: Math.floor(Math.random() * 10000) / 100,
children:
i % 4 === 0
? [
{ task: `子任务 A-${i + 1}`, status: 'open' },
{ task: `子任务 B-${i + 1}`, status: 'done' }
]
: undefined
}));
}
const records = generateData(11);
const masterDetailPlugin = new VTablePlugins.MasterDetailPlugin({
id: 'master-detail-static-3',
detailTableOptions: {
columns: [
{ field: 'task', title: '任务名', width: 220 },
{ field: 'status', title: '状态', width: 120 }
],
defaultRowHeight: 30,
defaultHeaderRowHeight: 30,
style: { margin: 12, height: 160 },
theme: VTable.themes.BRIGHT
}
});
const columns = [
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'rowNo', title: '#', width: 60, headerType: 'text', cellType: 'text' },
{ field: 'name', title: '姓名', width: 140, sort: true },
{ field: 'department', title: '部门', width: 140, sort: true },
{ field: 'score', title: '分数', width: 100, sort: true },
{
field: 'amount',
title: '金额',
width: 120,
sort: true,
fieldFormat: (v) => {
if (typeof v === 'number' && !isNaN(v)) {
return `$${v.toFixed(2)}`;
}
return v === undefined || v === null ? '' : String(v);
}
}
];
const option = {
container: document.getElementById(CONTAINER_ID),
columns,
records,
autoFillWidth: true,
hierarchyTextStartAlignment: true,
plugins: [masterDetailPlugin]
};
const tableInstance = new VTable.ListTable(option);
运行后效果:
![]()
参数配置
| 参数名称 | 类型 | 默认值 | 功能说明 |
|---|---|---|---|
id |
string | master-detail-${timestamp} |
插件实例的全局唯一标识符,用于区分多个插件实例 |
enableCheckboxCascade |
boolean | true |
是否启用主从表之间的checkbox级联功能,主表中的复选框选择会自动与相应的子表同步 |
detailTableOptions |
DetailTableOptions | Function | 子表配置选项,支持静态对象配置或基于数据的动态配置函数 |
动态配置示例
const masterDetailPlugin = new MasterDetailPlugin({
id: 'employee-detail-plugin',
detailTableOptions: ({ data, bodyRowIndex }) => {
if (bodyRowIndex === 0) {
return {
columns: [
//......
],
theme: VTable.themes.BRIGHT,
style: {
margin: 20,
height: 300
}
};
}
return {
columns: [
//......
],
theme: VTable.themes.DARK,
style: {
margin: 20,
height: 300
}
};
}
});
主从表插件的主要能力
展开行和渲染子表
当用户点击表格行的展开图标时,系统会在该行下方动态创建一个完整的子表格实例。子表格具备独立的配置、数据源和交互能力,与主表形成层次化的数据展示结构。
![]()
当用户点击展开图标时,会触发以下处理链:
用户点击展开图标
↓
EventManager.handleIconClick()
↓
MasterDetailPlugin.expandRow(rowIndex, colIndex)
↓
检查行是否已展开 (isRowExpanded)
↓
获取记录数据 (getRecordByRowIndex)
↓
MasterDetailPlugin.getChildren()
↓
ConfigManager.getDetailConfigForRecord()
↓
updateRowHeightForExpand()
↓
updateContainerHeight()
↓
SubTableManager.renderSubTable()
↓
recalculateAllSubTablePositions(bodyRowIndex + 1)
↓
drawUnderlineForRow()
↓
refreshRowIcon()
技术特性
- 独立 实例: 每个子表都是完整的VTable实例,支持所有表格功能
- 动态行高: 主表行高自动适配子表内容,支持自适应和固定高度
- 位置同步: 主表滚动时子表位置实时跟随,保持视觉连贯性
- 内存管理: 收起时自动销毁子表实例,展开时重新创建
生命周期管理
创建阶段: 主表行展开 → 解析配置 → 实例化子表
运行阶段: 位置同步 → 事件处理 → 数据更新
销毁阶段: 行收起 → 清理事件 → 销毁实例 → 回收内存
滚动同步机制
主表滚动时,所有子表会实时跟随滚动,保持视觉上的一体化效果。同时优化了滚动事件的触发机制,并且还有主子表的滚动分离机制,即当鼠标在子表内滚动时并且没有滚动到上下边界的时候,我滚动的是子表的内容,主表是不会滚动的,主表滚动的时候子表是不会滚动的
![]()
技术特性
- scrollEventAlwaysTrigger: 自动设置为true,确保边界滚动也能触发事件
- 批量更新: 一次滚动事件批量更新所有子表位置
主从表checkbox联动功能
主从表checkbox联动实现了主表与子表之间选择状态的智能同步。当主表行被选中时,其对应的子表中所有行自动选中;当子表中部分行被选中/取消选中时,主表行的选择状态会相应更新状态。使用参数enableCheckboxCascade来决定是否开启,默认是true
主表checkbox点击 → 检测子表存在 → 遍历子表所有行 → 同步选择状态 → 触发联动回调
子表checkbox点击 → 统计子表选中数量 → 计算主表状态 → 更新主表checkbox → 触发联动回调
![]()
技术实现
- 事件监听: 监听主表和子表的checkbox变化事件
- 状态同步: 通过内部状态管理器维护选择状态映射关系
- 回调通知: 选择状态变化时触发相应回调函数,便于业务处理
这种联动机制大大提升了复杂数据结构的操作效率,用户可以通过简单的点击实现批量选择操作。
懒加载功能
当父行记录的 children 字段为 true 时,表示这是一个懒加载节点,需要通过监听 VTable TREE_HIERARCHY_STATE_CHANGE 事件来实现异步数据获取
当用户点击包含懒加载节点的展开图标时,会触发以下调用链:
用户点击展开图标
↓
table.toggleHierarchyState()
↓
触发 TREE_HIERARCHY_STATE_CHANGE 事件
↓
用户事件处理器接收事件参数
↓
tableInstance.setLoadingHierarchyState(col, row) 显示loading图标
↓
异步数据获取
↓
plugin.setRecordChildren(detailData, col, row) 设置数据并展开
↓
渲染子表并完成展开
| 方法 | 说明 | 参数 |
|---|---|---|
plugin.setLoadingHierarchyState(col, row) |
显示loading图标 | col: 列索引, row: 行索引 |
plugin.setRecordChildren(children, col, row) |
设置子数据并展开 | children: 子数据数组, col: 列索引, row: 行索引 |
![]()
用户配置示例
const masterData = [
{ id: 1, name: '订单001', children: true }, // 懒加载标识
{ id: 2, name: '订单002', children: [...] } // 静态数据
];
const plugin = new MasterDetailPlugin({
detailTableOptions: {
// 子表配置
}
});
const tableInstance = new VTable.ListTable(options);
// 监听主从表层次状态变化事件
const { MASTER_DETAIL_HIERARCHY_STATE_CHANGE } = VTable.ListTable.EVENT_TYPE;
tableInstance.on(MASTER_DETAIL_HIERARCHY_STATE_CHANGE, async (args) => {
if (args.hierarchyState === VTable.TYPES.HierarchyState.expand &&
args.originData?.children === true) {
// 显示loading状态
plugin.setLoadingHierarchyState(args.col, args.row);
try {
// 异步数据获取
const detailData = await fetchDataFromAPI(args.originData.id);
// 设置子数据并自动展开
plugin.setRecordChildren(detailData, args.col, args.row);
} catch (error) {
console.error('Failed to load detail data:', error);
}
}
});
智能缓存机制
-
一次加载: 数据加载成功后,
children: true自动转换为实际数据数组 - 永久缓存: 再次点击时识别为静态数据,直接展开无需重新加载
这种设计实现了透明的按需加载,用户只需配置 onLazyLoad 回调,其余状态管理完全由插件自动处理。
小结
VTable 主从表插件是一个正在成长的开源数据展示插件,我们致力于为Web应用提供优秀的层次化数据展示解决方案。
它将复杂的主从数据关系展示能力带到了Web端,同时充分发挥了现代Web技术的交互优势。无论您是要构建订单管理系统、项目管理平台、财务报表系统,还是任何需要主从表功能的Web应用,VTable主从表插件都能为你提供一个良好的选择
在线demo和教程
demo: visactor.com/vtable/demo… 教程: visactor.com/vtable/guid…
欢迎交流
最后,我们诚挚的欢迎所有对数据可视化感兴趣的朋友参与进来,参与 VisActor 的开源建设:
VTable:VTable 官网、VTable Github(欢迎 Star)
VisActor 官方网站:www.visactor.io/ 或 www.viactor.com
Discord:discord.gg/3wPyxVyH6m
飞书群(外网):打开链接扫码
![]()
微信公众号:打开链接扫码
![]()
github:github.com/VisActor