DirectSurface UIDirectSurface UI
开始使用
组件/数据组件

数据组件 / COMPONENT

ReportView

报表系统采用 v3 Cell-first 模板。完整设计体验使用 RenderReportV3Workspace;只展示已物化结果时使用 RenderReportV3RuntimeSurface;需要直接渲染通用虚拟表格时使用 RenderReportView 和 ReportVirtualModel。

文档 READY示例 1
PUBLIC APIRenderReportView

FUNCTION EXPLORER

可运行示例与完整源码。

这里始终保留组件的主运行入口;文档中的示例用于补充具体功能说明。

LIVE EXAMPLE REPORT VIEW
全屏
正在启动 DirectSurface UI 运行时…

ReportView 与 Report Workspace

通用布局能力:本页中的可布局 Render* 视图和工作区实例统一支持 widthheight、min/max、margin 和槽位对齐;继承 RenderStackPanel 的工作区还支持容器 padding。详见组件通用布局属性

报表系统采用 v3 Cell-first 模板。完整设计体验使用 RenderReportV3Workspace;只展示已物化结果时使用 RenderReportV3RuntimeSurface;需要直接渲染通用虚拟表格时使用 RenderReportViewReportVirtualModel

旧的 Region-first 设计器与工作台已停止公开,不应在新代码中使用。

LIVE CANVAS可运行组件示例
ON-DEMAND RUNTIME交互式示例将在进入视区时启动避免文档首屏同时初始化多个 Canvas Runtime
LIVE CANVAS可运行组件示例
ON-DEMAND RUNTIME交互式示例将在进入视区时启动避免文档首屏同时初始化多个 Canvas Runtime

API 总览

API 类型 用途
RenderReportV3Workspace class 设计、预览、数据集、属性和模板保存工作区。
RenderReportV3RuntimeSurface class 只读运行预览和正式报表表面。
RenderReportView class 基于 ReportVirtualModel 的通用虚拟报表视图。
compileReportTemplateV3() function 校验并编译 v3 Cell/Relation 模板。
materializeReportTemplateV3() function 把模板和数据集物化为运行文档。
prepareReportTemplateV3ForPersistence() function 执行保存前压缩、校验和 JSON 输出。
copyVirtualReportRangeToTsv() function 复制指定报表区域。
exportVirtualReportToTsv() function 导出整个虚拟报表。

所有 API 均从 ds-ui 包入口导入;设计器内部状态和旧版 Region-first 对象不属于公共 API。

快速开始

import {  RenderReportV3Workspace,  type ReportDatasetMap,  type ReportTemplateV3,} from 'ds-ui' const template: ReportTemplateV3 = {  schemaVersion: 3,  rows: [{ height: 30 }, { height: 30 }],  columns: [{ width: 140 }, { width: 100 }],  cells: [    {      id: 'item',      address: 'A2',      row: 1,      column: 0,      value: { type: 'field', dataset: 'orders', field: 'item' },      binding: { dataset: 'orders', mode: 'group', groupBy: ['item'] },      expansion: { direction: 'down' },    },    {      id: 'amount',      address: 'B2',      row: 1,      column: 1,      value: { type: 'field', dataset: 'orders', field: 'amount' },      binding: { dataset: 'orders', mode: 'value', field: 'amount' },    },  ],  relations: [    { type: 'structure', from: 'item', to: 'amount', kind: 'child' },    { type: 'layout', from: 'item', to: 'amount', kind: 'rightOf' },    { type: 'scope', from: 'item', to: 'amount', kind: 'inherit' },  ],  datasets: [{    id: 'orders',    fields: [      { id: 'item', type: 'text', role: 'dimension' },      { id: 'amount', type: 'number', role: 'measure' },    ],  }],} const datasets: ReportDatasetMap = {  orders: [    { item: '门诊', amount: 120 },    { item: '住院', amount: 260 },  ],} const workspace = new RenderReportV3Workspace({  template,  datasets,  onTemplateChange: next => { void next },  onTemplateExport: json => { void json },})

v3 模板模型

ReportTemplateV3 由 Cell 和 Relation 组成:

  • Cell 保存地址、值、数据绑定、展开方向、格式和样式;
  • structure relation 表达父子语义;
  • layout relation 表达 belowrightOf 或对齐关系;
  • scope relation 表达继承、独立、连接、聚合或普通 Cell 运行上下文;
  • axiscross relation 表达交叉报表的行轴、列轴和指标;
  • dataset 与 parameter 定义属于模板资源,但运行数据通过 ReportDatasetMap 传入。

Dataset 字段的 typerole 是两个独立维度。type 描述实际值类型并约束聚合函数;role 只是设计期的维度/度量推荐,不限制字段用途。文本、日期、布尔和数字字段都可以用于 Group、Row Axis 或 Column Axis,因此年份、季度、数字编码即使标记为 role: 'measure' 也仍可直接分组。COUNT 接受任意字段;当前 SUMAVGMINMAX 只接受 type: 'number'

使用 parseReportTemplateV3Json() 读取外部 JSON。持久化前调用 prepareReportTemplateV3ForPersistence();它会压缩设计模板、执行 JSON round-trip 和 compiler 校验,仅在没有 error 时返回可保存的 JSON。不要通过 JSON.stringify() 或类型断言绕过保存门禁。

Cell value 只支持 textfieldaggregateexpression。小计与总计由 aggregate binding 配合 layout/scope relation 表达,不是独立 value 类型。Cell 与 Dataset 字段格式统一使用 ReportValueFormatV3 typed formatter,例如:

聚合 relation 使用显式父格上下文,不再使用 aggregateScopescopeRefs

const aggregateCells = [{  id: 'reportTotal',  row: 3,  column: 2,  binding: { dataset: 'main', mode: 'aggregate', aggregate: { func: 'sum', field: 'amount' } },}] const aggregateRelations = [  // 每个国家实例聚合一次,Dataset 继承自国家格  { type: 'scope', to: 'countryTotal', kind: 'aggregate', rowParentCellId: 'country' },  // 国家与月份运行实例的交集  {    type: 'scope',    to: 'countryMonthAmount',    kind: 'aggregate',    rowParentCellId: 'country',    columnParentCellId: 'month',  },  // 无父格时 Dataset 显式保存在 Aggregate Cell binding 上  { type: 'scope', to: 'reportTotal', kind: 'aggregate' },]

存在 Row 或 Column Parent 时 Cell Dataset 必须为空并从父格继承;无父格时 Cell 必须显式声明 Dataset。两个父格必须解析到同一 Dataset。Aggregate scope relation 本身不保存 Dataset。旧的 aggregateScope/scopeRefs 模板属于已移除协议,JSON 读取会直接报错。

设计端的 Aggregate Cell 直接配置 Row Context Parent CellColumn Context Parent Cell,不再提供 Aggregate Scope 枚举。有任一父格时,Dataset 显示为只读的 Inherited: <dataset>;移除最后一个父格会清空 Dataset,但保留 Field、Aggregate Function 和格式,用户随后必须显式选择 Dataset 才能通过保存校验。

Parent 决定数据上下文和运行实例数;Row Parent AlignmentColumn Parent Alignment 分别决定 Cell 如何映射到父格展开后的运行区间。两个方向互不影响,默认均为 End

  • Start:保持相对父格起点的设计偏移,Span 不变。
  • End:保持相对父格终点的设计偏移,Span 不变。
  • Stretch:起点跟随父格起点、终点跟随父格终点,动态扩展 rowSpancolumnSpan。只有 Cell 与对应父格的设计投影重叠时才合法。

例如年份父格在设计态占 C 列,运行时因季度展开为 C~F:同样位于 C 列的上下文 Cell 使用 Start 时落在 C,使用默认 End 时落在 F,使用 Stretch 时合并覆盖 C~F;设计在 D 列并使用 End 的年度聚合 Cell 会落在 G。父格本身采用 Merge 或 Repeat 只影响显示,不改变上述区间映射。

Summary 的逻辑实例唯一由 Cell + Row Parent实例 + Column Parent实例 确定。Row/Column Parent 同时存在时取两边实例的笛卡尔积,数据作用域为两个父上下文数据行的交集。小计行、小计列或它们的交点只是布局结果,不会额外生成第二个逻辑实例。父格运行内容区间包含其明细、下级分组和下级小计,但不包含当前父级自己的边界槽,因此默认 End 不会循环包含自身或多偏移一格。Cell 同时位于行、列父格的末端时,会自然落在小计行与小计列的交点。

  • rowParentCellId: 'country' + columnParentCellId: 'year' 表示每个国家、每个年度一个小计;将 Cell 设计在季度交叉值右侧,会在每个年度的季度展开后出现。
  • rowParentCellId: 'country' + columnParentCellId: undefined 表示每个国家跨全部年度的总计;将 Cell 设计在整个交叉区域右侧,会被横向展开自然推到行尾,不会生成额外小计行。
  • rowParentCellId: undefined + columnParentCellId: 'year' 表示每个年度跨全部国家的总计;将 Cell 设计在交叉区域下方,会被纵向展开自然推到列底。
  • Row/Column Parent 都为空时表示整个 Dataset 的总计,Dataset 必须由 Aggregate Cell 显式声明。它同样保留设计网格中的行列、间距和合并关系;多个总计 Cell 设计在同一行时,运行时仍保持同行,不会改成对角线排列。
  • 需要把上下文 Cell 保留在父格首端时显式选择 Start;需要覆盖整个父格运行跨度时选择 Stretch。真正需要每个下级实例都生成一次时,应改为绑定下级 Parent,而不是使用 Stretch。
  • 同一父上下文下的 Cell 只有在设计网格同一行时才会组成同一个 Summary Fragment;设计在不同行的小计会保持独立行并按原顺序重复。

这些都是普通 Aggregate Cell,不需要额外的“行尾总计”、“列底总计”或 axisEnd 类型。

普通静态文本 Cell 也可以通过 scope.context 使用相同的 Row/Column Parent 运行上下文,而不绑定任何 Dataset 字段。它的出现次数由父格实例决定,位置继续受行列扩展推动,文本、样式和合并 Span 原样保留。这样可让合并的“小计”标签与相邻聚合格共同重复:

const subtotalCells = [  { id: 'subtotalLabel', row: 2, column: 0, columnSpan: 2, value: { type: 'text', text: '小计' } },  { id: 'subtotalAmount', row: 2, column: 2, binding: { mode: 'aggregate', aggregate: { func: 'sum', field: 'amount' } } },] const subtotalRelations = [  { type: 'scope', to: 'subtotalLabel', kind: 'context', rowParentCellId: 'country' },  { type: 'scope', to: 'subtotalAmount', kind: 'aggregate', rowParentCellId: 'country' },]

属性面板对普通文本和 Aggregate Cell 均显示 Row Context Parent CellColumn Context Parent Cell;设置父格后还会显示对应的 Row Parent AlignmentColumn Parent Alignmentcontext 只控制运行实例、继承上下文和父格区间映射,不会把静态文本改成字段或聚合绑定。

const moneyFormat = {  formatter: {    type: 'currency' as const,    currency: 'CNY',    minimumFractionDigits: 2,    maximumFractionDigits: 2,  },}

保存示例:

import {  prepareReportTemplateV3ForPersistence,  type ReportTemplateV3,} from 'ds-ui' declare const template: ReportTemplateV3declare const saveTemplate: (json: string) => Promise<void> const persistence = prepareReportTemplateV3ForPersistence(template)if (!persistence.valid || !persistence.json) {  throw new Error(persistence.diagnostics.map(item => item.message).join('; '))}await saveTemplate(persistence.json)

编译与物化

import {  compileReportTemplateV3,  materializeReportTemplateV3,  type ReportTemplateV3,} from 'ds-ui' const template: ReportTemplateV3 = {  schemaVersion: 3,  rows: [{}],  columns: [{}],  cells: [],  relations: [],}const compiled = compileReportTemplateV3(template)const document = materializeReportTemplateV3({ compiled, datasets: {} })

compileReportTemplateV3() 生成 Cell/Relation 图结构与诊断;materializeReportTemplateV3() 生成运行文档和 ReportVirtualModel。业务侧需要自定义工作台时,可以组合这些无 UI API,而不必复制 Workspace 内部状态。

RenderReportV3Workspace

RenderReportV3Workspace 提供设计、预览、数据集字段、属性、数据预览和校验区域。Preview 中 Cell 属性面板保持可见但完全只读,字段放置、Cell 内容与语义、关系、合并、冻结、粘贴和 Duplicate 等模板修改入口只在 Designer 生效。常用选项包括:

  • templatedatasetsparameterValuesroot
  • mode: 'design' | 'preview'
  • previewDataStatepreviewStateMessage,用于在业务数据加载期间显示 Preview Loading 或 Error;
  • showToolbarshowStatusBarshowResourceTools
  • onTemplateChangeonTemplateExportonModeChange
  • onGridStructureActionsChange,用于将行列插入、删除命令接入业务工具栏;
  • onFreezeActionsChange,用于将冻结行列命令接入业务工具栏;
  • onDrillthrough,用于接收 Preview 中“查看明细”命令的完整运行时上下文;
  • onGridInsertActionsChange 保留为只包含插入动作的兼容回调。

Workspace 内置 Toolbar 在工作区生命周期内保持同一个 RenderToolbar 实例,模板刷新、模式切换和命令状态更新只同步 Toolbar groups,不会反复注册焦点对象。showToolbar: false 时不会创建隐藏的 Toolbar,也不会向焦点域注册不可见命令入口。

业务端同时更新 Dataset、查询参数和 Root Context 时,应使用一次 setRuntimeContext() 批量提交,避免连续 setter 触发多次 Preview 物化:

import type {  RenderReportV3Workspace,  ReportDatasetMap,  ReportParameterValues,} from 'ds-ui' declare const workspace: RenderReportV3Workspacedeclare const loadDatasets: () => Promise<ReportDatasetMap>declare const parameterValues: ReportParameterValuesdeclare const root: Record<string, unknown> workspace.setRuntimeContext({  dataState: 'loading',  stateMessage: '正在加载预览数据…',}) const datasets = await loadDatasets()workspace.setRuntimeContext({  datasets,  parameterValues,  root,  dataState: 'ready',  stateMessage: '',})

setRuntimeContext() 会在运行上下文未变时保留已物化的 Preview;从 Designer 再次进入 Preview 不会重复计算。是否重新请求 SQL/API 数据由业务端负责,设计预览通常应缓存已加载 Dataset,只在用户明确刷新时重新请求。

gridStructureActions 根据当前单元格或矩形选区生成插入、删除动作;applyGridStructureAction() 执行动作。删除矩形选区时会一次删除覆盖的行或列,并把选区重定位到最近的有效网格位置。Ctrl/Cmd 形成的非连续 Cell 多选不会被隐式扩大为外接矩形;存在行或列空档时,对应的批量删除动作会禁用。报表始终至少保留一行和一列。在合并 Cell 的 Span 内部插入行列时会扩展对应 Span,确保原合并区域的结构范围不被截断。旧的 gridInsertActionsapplyGridInsertAction() 和四个插入便捷方法继续可用。

mergeActionsapplyMergeAction()mergeSelection()unmergeSelection() 提供矩形合并操作。合并采用数据安全规则:选区必须是至少两个格子的连续矩形,不能越界或切穿已有合并单元格。多个普通静态文本 Cell 可以合并,文本按从上到下、每行从左到右的顺序直接拼接,空白 Cell 会被忽略,合并结果使用左上锚点的样式。选区只有一个数据绑定、表达式、扩展或关系 Cell 且其余位置为空时也允许合并,并保留该语义 Cell 的 id、内容、格式、样式和关系。多个语义 Cell,或语义 Cell 与非空静态文本混合时,动作会禁用并返回诊断,不会覆盖数据。

取消合并只移除锚点的 rowSpancolumnSpan,保留锚点内容与关系,其余位置恢复为空网格槽。静态文本合并后,取消合并会把拼接结果保留在左上角,不会自动拆回原来的多个文本。onMergeActionsChange 可用于同步业务工具栏的启用状态和冲突提示。

Designer 的行号和列标支持单击或拖动选择整行、整列以及连续多行、多列。freezeActionsapplyFreezeAction() 将表头选区转换为模板 freeze 设置:冻结行必须选择从第 1 行开始的 Top N 连续整行,冻结列必须选择从 A 列开始的 Left N 连续整列;只选择中间行列时动作会禁用并返回边界原因。冻结行、冻结列可以分别取消,也可以一次取消全部冻结。freeze.rightColumns 仍由模板 API 支持,当前 Toolbar 只编辑顶部行和左侧列。

冻结属于数据查看行为。Designer 只编辑并持久化 template.freeze,设计网格自身始终按未冻结状态滚动,但会在冻结行列边界绘制深色指示线;指示线随普通网格内容滚动,只用于帮助设计人员感知冻结范围。设计预览、运行预览和正式 Runtime 才执行真实冻结。Preview 中所有新增、取消冻结动作均为只读禁用状态。左右冻结列的总数不能超过模板列数。运行视图会在冻结区域边界持续绘制深色分隔线和渐变阴影,滚动内容进入冻结区域下方或右侧时阴影会增强。行列结构命令会同步维护冻结边界:在 Top N 或 Left N 冻结区域内部插入会扩大冻结数量,在边界及普通区域插入不会改变冻结数量;删除冻结区域内的行列会按重叠数量缩小冻结范围。右侧冻结列也遵循相同的边界维护规则。

设计器通过 cellClipboardActionscopySelectedCells()pasteCopiedCells()duplicateSelectedCells()applyCellClipboardAction() 提供结构化 Cell 复制。内部剪贴板保存源矩形、Cell 相对坐标、内容、绑定、格式、样式、条件和 Span;粘贴时生成新 Cell id,并重映射选区内部关系和 group order 的 Cell 引用。指向选区外部的关系不会复制。复制多个 Cell 时必须使用连续矩形选区;Ctrl/Cmd 形成的不规则离散多选不会按外接矩形隐式带入未选 Cell,Copy 和 Duplicate 会直接禁用并返回原因。动作状态使用只读检查,只有真正复制时才构造剪贴板;Duplicate 使用按模板缓存的占用区间,按钮状态会准确反映是否存在空目标区域。

粘贴目标取当前 Cell、矩形选区左上角或空网格槽。第一版采用非覆盖策略:越界、切穿合并 Cell 或目标存在有效 Cell 时禁用粘贴。Duplicate 优先尝试选区右侧,其次下方,再查找首个连续空区域。Designer 支持 Ctrl/Cmd+CCtrl/Cmd+VCtrl/Cmd+D;这些快捷键操作模板内部剪贴板,Preview 的 TSV 复制行为不变。Backspace 清空当前 Cell 或矩形选区中的所有 Cell 内容;批量清空按语义依赖从子 Cell 到父 Cell 执行,任意一个 Cell 仍被选区外语义关系引用时整批拒绝,不会只清除一部分。onCellClipboardActionsChange 可用于同步业务工具栏。

单元格条件样式在 Cell PropertyGrid 的 Conditional Style / Rules 属性中统一管理。该属性显示规则数量,激活后打开结构化编辑弹窗,可新增、删除和逐条编辑数据来源、比较运算符、比较值以及匹配后的文字和颜色样式;编辑期间只修改草稿,点击 Apply 后才一次写回 Cell 的 conditions

单元格边框

ReportCellStyle.bordertoprightbottomleft 四边保存边框,每条边可独立设置 stylecolorwidth。线型支持 nonesoliddasheddotted。未声明的边继承报表默认样式;style: 'none' 表示显式关闭该边。旧模板中的 borderColorborderWidth 仍可读取,但新的设计操作只写四边模型。

const style = {  border: {    top: { style: 'solid', width: 2, color: { r: 60, g: 70, b: 80, a: 1 } },    bottom: { style: 'dashed', width: 1, color: { r: 120, g: 130, b: 140, a: 1 } },    left: { style: 'none' },  },}

Workspace Toolbar 的 Borders 下拉菜单和 PropertyGrid 的 Style / Borders 属性都支持 Top、Bottom、Left、Right、All、Outer、Inner、Inner Horizontal、Inner Vertical 和 No Borders。边框命令作用于当前矩形选区;需要绘制边框的空白网格槽会创建明确标记的装饰 Cell,未被当前预设影响的槽位不会创建占位数据。装饰 Cell 不参与结构布局、不推动其他 Cell,但会按设计位置投影到动态扩展后的运行时坐标。装饰边框始终作为独立图层保留,不会覆盖数据 Cell 的内容,也不会被提升为运行时合并 Cell 的整体边框。No Borders 会删除纯边框装饰 Cell,并在有内容或数据语义的 Cell 上写入显式 none;它不会为从未设置过边框的空白区域批量创建 Cell。选区切穿已有合并 Cell 时命令会拒绝,必须完整选择该合并区域。

运行时先绘制所有 Cell 的背景和内容,再跨滚动区、冻结行和冻结列统一绘制边框,因此粗边框和虚线不会被相邻 Cell 背景覆盖。相邻 Cell 的共享边按最小网格边段只绘制一次;合并 Cell 的长边会与相邻普通 Cell 的各段分别决议。显式边框优先于继承边框,显式 none 可关闭共享边,其他冲突按宽度和线型稳定决议。PropertyGrid 修改 Border Color 或 Border Width 时只修改对应属性,不会抹平各边已有的线型;宽度设为 0 后再恢复正数仍使用原线型。后续 Toolbar 边框预设使用最近一次有效的边框线型、颜色和宽度。条件样式按边深合并:只覆盖 bottom 不会丢失另外三边。导出快照和工作簿中间模型会在 borderDecorations 中保留运行时坐标及四边样式,并深复制边框颜色与线型配置。

使用 REPORT_V3_WORKSPACE_ITEM_IDSReportV3WorkspaceItemId 引用稳定工作区 id。关系图和 JSON 诊断默认属于 debug 能力;普通业务页面应以 Cell、字段、属性和预览为主要入口。

运行预览

import {  RenderReportV3RuntimeSurface,  compileReportTemplateV3,  materializeReportTemplateV3,  type ReportTemplateV3,} from 'ds-ui' const template: ReportTemplateV3 = {  schemaVersion: 3,  rows: [{}],  columns: [{}],  cells: [],  relations: [],}const compiled = compileReportTemplateV3(template)const document = materializeReportTemplateV3({ compiled, datasets: {} })const preview = new RenderReportV3RuntimeSurface({  document,  showGridHeaders: true,  minHeight: 320,  onRowHeightChange: (row, height) => { void row; void height },  onColumnWidthChange: (column, width) => { void column; void width },})

运行面接收 v3 materializer 文档。启用网格头后,用户可以调整来源模板行高和列宽;重复展开的运行行列会映射回对应模板行列。

通用 RenderReportView

RenderReportView 是共享 Canvas 虚拟表格表面,通过 virtualModel 接收已经物化的数据:

import {  RenderReportView,  compileReportTemplateV3,  materializeReportTemplateV3,  type ReportTemplateV3,} from 'ds-ui' const template: ReportTemplateV3 = {  schemaVersion: 3,  rows: [{}],  columns: [{}],  cells: [],  relations: [],}const compiled = compileReportTemplateV3(template)const model = materializeReportTemplateV3({ compiled, datasets: {} }).virtualModelconst view = new RenderReportView({  virtualModel: model,  dataState: 'ready',  visualGuideMode: 'preview',  onSelectionChange: selection => { void selection },  onScrollChange: (scrollX, scrollY) => { void scrollX; void scrollY },  onDrillthrough: context => {    // 根据 context.datasetName、filters、rowScopes 和 columnScopes 打开业务明细。    void context  },}) view.scrollTo(120, 240)view.revealCell(10, 3)view.setSelection(  { startRow: 10, startCol: 3, endRow: 10, endCol: 3 },  { scroll: true },)

共享选项由 ReportViewSharedOptions 描述,共享调试结果由 ReportViewDebugState 描述。调试结果包含 scrollXscrollYReportDrillContextReportDrillScopeEntry 可用于读取 materializer 提供的钻取元数据;ReportDrillthroughHandler 是“查看明细”命令的公开处理器类型。只有配置 onDrillthrough 且目标 Cell 具有可用运行时上下文时,默认右键菜单才显示“查看明细”,因此不会出现没有执行目标的空命令。自定义 contextMenuProvider 仍可替换默认菜单。

View 支持:

  • 冻结行、左侧列和右侧列;
  • 虚拟 viewport 拉取与大数据滚动;
  • scrollTo()revealCell()onScrollChange 二维滚动控制;
  • 单元格选择、键盘移动和复制;
  • 多单元格选区使用统一的区域背景和外框,当前活动格使用较弱的内框;
  • 行高、列宽调整;
  • 自定义右键菜单和显式钻取回调;
  • 单行文本真实溢出时显示完整内容 Tooltip;
  • loading、empty 和 error 状态。

选择状态与键盘焦点相互独立:失去焦点不会清除已选择区域,但焦点框和键盘移动只在 View 实际获得焦点后生效。dataState 不是 ready 时,View 会保留可供恢复的选择坐标,同时关闭菜单、清除 hover/resize/drag/Tooltip 等瞬态状态,并阻止复制、键盘、指针、滚轮和右键菜单访问旧模型。业务端因此可以安全地在 Loading 或 Error 表面继续持有上一份 virtualModel

报表交互 Chrome(网格头、选择、焦点、hover、resize、冻结边界和设计徽标)从当前 ThemeData 派生,支持深浅主题。模板 Cell 自身的背景、边框、字体和颜色仍以物化后的 Cell style 为准,不会被交互主题覆盖。

正式布局会根据最新内容范围裁剪滚动位置,measure 测量布局不会修改真实滚动状态。替换为新的非空 virtualModel 时,现有选择会尽量按行列坐标保留,并裁剪到新模型范围;将模型设为 null 时会清空选择。

如果自定义 ReportVirtualModel 保持同一个对象引用、但内部行高或列宽发生变化,可调用 invalidateVirtualModel() 重新计算布局,不需要先把 virtualModel 设为 null

复制与导出

copyVirtualReportRangeToTsv()exportVirtualReportToTsv() 只依赖 ReportVirtualModel,可用于 v3、业务自定义模型和大数据模型。createReportWorkbookExport() 用于生成工作簿导出结构。

import {  copyVirtualReportRangeToTsv,  exportVirtualReportToTsv,  compileReportTemplateV3,  materializeReportTemplateV3,  type ReportTemplateV3,} from 'ds-ui' const template: ReportTemplateV3 = {  schemaVersion: 3,  rows: [{}],  columns: [{}],  cells: [],  relations: [],}const compiled = compileReportTemplateV3(template)const model = materializeReportTemplateV3({ compiled, datasets: {} }).virtualModelconst selected = copyVirtualReportRangeToTsv(model, {  startRow: 0,  startCol: 0,  endRow: 9,  endCol: 3,}) const all = exportVirtualReportToTsv(model)

性能建议

  • 大数据模型应延迟实现 getCell()getViewport(),避免预先创建完整矩阵;
  • 只在 viewport 返回可见 Cell,并保证 row/column span 锚点稳定;
  • 数据变化时替换 materialized document 或 virtual model,不要直接修改 View 内部缓存;
  • 格式化、聚合和关系计算放在 v3 materializer 或业务模型中,Canvas View 只负责绘制与交互。