不把 Worksheet 当数据库:用 SpreadJS 搭建结构化数据分析链路 很多 Web 表格项目一开始都会选择一种最直接的实现方式把接口返回的数据写进 Worksheet再在工作表里做筛选、排序、公式和透视分析。对于一张简单二维表这种方式很自然。用户熟悉开发也快。但当数据开始来自业务系统问题就会变复杂订单是一张表客户是一张表产品又是一张表订单里只有customerId和productId真正用于分析的客户名称、地区、客户等级、产品品类都在其他表里。如果把所有数据都先拼成一张大宽表再写入普通 Worksheet后续刷新、编辑、权限、同步和分析都会越来越重。这时更合适的思路不是让 Worksheet 继续承担更多职责而是把数据准备、业务视图和透视分析拆开。在 SpreadJS 中可以使用下面这条链路来组织结构化业务数据DataManager接入数据、定义字段、建立关系、生成 view ↓ TableSheet展示整理后的业务数据支持筛选、排序和填报 ↓ PivotTable基于 TableSheet 输出做透视分析这不是把 SpreadJS 简单等同于 Excel 的 Power Query 或 Power Pivot。更准确地说如果熟悉 Excel 中“数据准备”和“数据模型”的思路可以把 DataManager TableSheet PivotTable 理解为 SpreadJS 在 Web 表格应用中的一种分层实现方式。从一个订单分析场景说起假设我们要做一个订单分析页面数据来自三类实体Orders订单事实数据包括订单月份、客户 ID、产品 ID、渠道、数量、单价、折扣等。Customers客户维度数据包括客户名称、地区、等级、行业等。Products产品维度数据包括产品名称、品类、标价等。如果用普通 Worksheet 承载全部逻辑通常会先把这三张表 join 成一张扁平表然后写入单元格。这样做可以工作但会带来几个后续问题关系字段在写入前就要准备好数据冗余会增加。刷新和同步时需要重新处理拼接结果。代码容易围绕行列坐标组织而不是围绕业务对象组织。Worksheet 同时承担原始数据、关系模型、展示视图和分析数据源边界会变模糊。对于业务系统里的结构化数据更推荐先把数据放入 Workbook 级的数据层。用 DataManager 管理结构化数据DataManager 可以在 Workbook 中维护结构化 table并通过 schema、relationship 和 view 组织业务数据。下面示例将订单数据定义为一张具备字段类型、主键和计算字段的表const dataManager spread.dataManager(); const orderTable dataManager.addTable(Orders, { data: orders, schema: { columns: { orderId: { dataType: number, isPrimaryKey: true }, customerId: { dataType: number }, productId: { dataType: number }, orderMonth: { dataType: string }, quantity: { dataType: number }, unitPrice: { dataType: number }, discountRate: { dataType: number }, shipCost: { dataType: number }, netAmount: { dataType: formula, value: [quantity] * [unitPrice] * (1 - [discountRate]) [shipCost] } } } });客户表和产品表也可以作为独立 table 加入 DataManager。随后通过 relationship 表达订单与客户、订单与产品之间的关系dataManager.addRelationship( orderTable, customerId, customer, customerTable, customerId, orders ); dataManager.addRelationship( orderTable, productId, product, productTable, productId, orders );这样多表关系不需要依赖隐藏工作表、辅助列或预先拼好的扁平数组而是可以在数据模型中表达。用 View 生成面向分析的业务表有了关系之后下一步不是马上把所有原始数据铺进单元格而是先生成一个面向展示和分析的 view。在 demo 中analysisView从订单表出发同时取出客户和产品的关联字段const analysisView orderTable.addView(analysisView, [ { value: orderId, caption: OrderId, width: 90 }, { value: orderMonth, caption: Month, width: 88 }, { value: customer.regionName, caption: Region, width: 88 }, { value: customer.customerName, caption: Customer, width: 150 }, { value: customer.tier, caption: Tier, width: 76 }, { value: product.category, caption: Category, width: 112 }, { value: product.productName, caption: Product, width: 170 }, { value: channel, caption: Channel, width: 88 }, { value: quantity, caption: Quantity, width: 88 }, { value: netAmount, caption: NetAmount, width: 112 }, { value: status, caption: Status, width: 88 } ]); await analysisView.fetch();这个 view 就像一张已经准备好的业务分析表。它既包含订单自身字段也包含通过关系取出的地区、客户名称、客户等级、产品品类和产品名称。接下来把这个 view 绑定到 TableSheetconst tableSheet spread.addSheetTab( 1, PreparedOrders, GC.Spread.Sheets.SheetType.tableSheet ); tableSheet.setDataView(analysisView);用户看到的是一张可筛选、可排序、可交互的订单分析表开发侧维护的是 DataManager 中的结构化数据和关系模型。用 PivotTable 做汇总分析当 TableSheet 已经承载了整理后的业务视图PivotTable 就可以基于这张 TableSheet 做进一步汇总。demo 中的透视表以PreparedOrders作为数据源const pivotTable pivotSheet.pivotTables.add( SalesPivot, PreparedOrders, 4, 1, GC.Spread.Pivot.PivotTableLayoutType.tabular, GC.Spread.Pivot.PivotTableThemes.medium6 ); pivotTable.add(Region, 区域, GC.Spread.Pivot.PivotTableFieldType.rowField); pivotTable.add(Category, 品类, GC.Spread.Pivot.PivotTableFieldType.columnField); pivotTable.add(Channel, 渠道, GC.Spread.Pivot.PivotTableFieldType.filterField); pivotTable.add( NetAmount, 成交金额, GC.Spread.Pivot.PivotTableFieldType.valueField, GC.Pivot.SubtotalType.sum );这条链路的职责边界很清楚DataManager 负责数据接入、字段定义、计算字段和关系建模。TableSheet 负责展示整理后的业务数据并提供表格化交互。PivotTable 负责基于 TableSheet 输出做聚合分析。需要说明的是当前 demo 验证的是“PivotTable 以 TableSheet 作为数据源”这条路径而不是直接把 DataManager table 或 view 作为 PivotTable 数据源。为什么不直接写入普通 Worksheet普通 Worksheet 依然很重要。它适合自由表格、单元格级编辑、公式建模和轻量数据展示。但在结构化业务数据场景中普通 Worksheet 更适合作为对照而不是承载全部数据职责。demo 中提供了一个Raw Worksheet页使用同样的订单数据直接写入普通 Worksheetconst rows orders.map((item) [ item.orderId, item.orderMonth, item.customerId, item.productId, item.channel, item.quantity, item.unitPrice, item.discountRate, item.shipCost, item.status ]); sheet.setArray(3, 0, rows);这种方式直接有效但它只表达了单元格值。客户名称、地区、产品品类、计算金额和透视字段都需要在写入前准备好或者继续通过公式和辅助列组织。DataManager 方案则把这些内容放在数据层Orders.customerId - Customers.customerId Orders.productId - Products.productId Orders.netAmount - formula field analysisView - 面向 TableSheet / PivotTable 的业务字段集合所以这里的重点不是宣称某个 API 一定“性能更好”而是强调结构更清晰。SpreadJS 运行在浏览器环境中实际性能仍然取决于数据规模、字段数量、样式复杂度、公式数量、交互方式和服务端配合方式。更稳妥的判断是当数据具备实体、关系、刷新、填报和分析需求时把数据层前置到 DataManager通常会比把所有逻辑压进 Worksheet 更容易维护也更利于后续扩展。配套 demo本文配套 demo 使用 SpreadJS Designer 承载 Workbook演示了从 DataManager 建模、TableSheet 展示到 PivotTable 分析的完整链路。如需查看实现代码和截图生成方式可参考 配套 demo。适用场景与边界DataManager TableSheet PivotTable 更适合以下场景数据来自业务系统而不是用户临时录入的一小块表格。数据天然分为订单、客户、产品、地区等多个实体。需要关系字段、查询视图、筛选、排序、填报和透视分析。数据需要刷新、同步、复用或进一步对接远程服务。用户需要类 Excel 的表格交互体验但开发侧不希望所有逻辑都绑定在行列坐标上。以下场景则未必需要引入这条链路数据量较小且只有一张简单二维表。主要需求是自由编辑而不是结构化分析。数据没有关系模型也不需要复用。只是临时导入、查看和导出普通 Worksheet 已经足够。因此Worksheet 与 DataManager 并不是替代关系而是职责分层关系。普通 Worksheet 适合自由表格和单元格级操作DataManager 更适合承载结构化数据模型TableSheet 更适合展示和操作业务视图PivotTable 则负责聚合分析。小结在 Web 表格应用中Sheet 不一定要承担所有数据职责。当数据只是一张简单二维表时直接写入 Worksheet 很高效当数据来自业务系统并且具有多表关系、字段整理、刷新同步和透视分析需求时更合理的做法是把数据准备和关系建模前置。SpreadJS 中的 DataManager、TableSheet 和 PivotTable 可以组成一条清晰的结构化分析链路DataManager数据接入、schema、relationship、view TableSheet结构化展示、筛选、排序、填报 PivotTable透视分析这条路径的价值是让 Worksheet 回到展示、交互和分析层让数据模型由更合适的数据层承载。对于正在用 SpreadJS 构建业务系统、数据填报和在线分析能力的开发者这是一条值得优先考虑的工程路径。扩展链接SpreadJS AI Agent——智能对话表格