业务团队希望自己搭建报表,最直观的方案是提供一个拖拽画布:选择表格或图表,填写接口与字段,再调整颜色和尺寸。演示版本很快就能完成,真正进入多个业务场景后,配置会迅速膨胀。
表格需要维度、指标、排序和汇总;折线图需要时间粒度、系列与缺失值策略;漏斗有自己的阶段语义;不同数据源又拥有不同查询能力。如果每个组件维护一套私有配置,编辑器、运行时和保存结构会被紧密绑定。
低代码 BI 的核心不是拖拽,而是设计一门足够稳定、又不过度承诺的描述语言。
图 1:编辑器保存的是领域 Spec,不是某个图表库的 option;运行时像编译器一样逐层验证和转换。
从业务问题建立中间模型
我们把一张报表拆成四类信息:数据查询描述“从哪里得到什么”;数据转换描述聚合、计算与格式;视觉编码描述字段如何映射到位置、长度、颜色;交互描述筛选、联动与下钻。
这四层不会完全独立,但明确区分后,很多能力可以跨组件复用。日期筛选不必在每种图表中重复实现,数据格式化也不需要嵌入视觉配置。
中间模型不能直接复制某个图表库的 option。图表库会升级或替换,产品概念却应该稳定。运行时通过适配器把领域配置转换为 G2Plot、ECharts 或表格组件需要的属性。
适配器也暴露能力矩阵。例如某类图支持双轴,另一类不支持;某数据源支持服务端聚合,另一数据源只能返回明细。编辑器根据矩阵限制可选项,避免生成运行时无法兑现的配置。
一个折线图的领域配置可以只有这些内容:
type ChartSpecV3 = {
version: 3;
source: { datasetId: string };
query: {
dimensions: Array<{ field: string; granularity?: "day" | "week" | "month" }>;
measures: Array<{ field: string; aggregate: "sum" | "avg" | "count" }>;
filters: FilterExpression[];
};
encoding: {
x: { field: string; type: "temporal" | "category" };
y: { field: string; format?: string };
color?: { field: string };
};
interaction: { tooltip: boolean; zoom: boolean };
};这里没有 grid.left、series.smooth 之类 ECharts 私有字段。表现细节由主题和适配器给默认值;确实形成产品能力的选项才进入 Schema。这个克制让保存数据不跟随图表库升级频繁迁移。
Schema 需要版本
配置一旦保存,就成为长期数据。新增字段容易,重命名、改变默认值或删除能力都会影响历史报表。
我们为 Schema 保留显式版本,并建立单向迁移。运行时加载旧配置时先转换到当前版本,迁移过程保持可测试。不能把兼容逻辑散落在每个组件里,否则几年后没有人知道某个判断在兼容哪个年代。
const migrations = {
1: (spec: ChartSpecV1): ChartSpecV2 => ({
...spec,
version: 2,
interaction: { tooltip: spec.tooltip ?? true },
}),
2: (spec: ChartSpecV2): ChartSpecV3 => ({
...spec,
version: 3,
source: { datasetId: spec.dataset },
}),
};
function migrate(input: AnyChartSpec): ChartSpecV3 {
let current = input;
while (current.version < 3) current = migrations[current.version](current as never);
return current as ChartSpecV3;
}迁移测试使用真实历史配置样本,并比较迁移前后的查询语义和最终编码,不只断言版本号变成 3。默认值变化尤其要有快照,否则旧报表会在没人编辑的情况下改变外观或计算。
默认值也必须写入语义。若一个字段未保存,当前版本与未来版本是否得到同样行为?为了压缩配置而省略默认值,可能让历史报表随着系统升级悄悄变化。
配置发布后还要可回滚。编辑态和运行态不能共享一份随时变化的数据,至少需要草稿、已发布版本与版本记录。用户在编辑器里的误操作不应立刻影响正在使用的看板。
编辑器不是配置表单集合
当配置项很多时,把它们全部展示出来只会把代码复杂度转移给业务用户。编辑器要围绕分析任务组织:先选数据,再确定想比较或观察什么,系统推荐合适图表,最后开放必要的表现调整。
字段选择需要理解类型和角色。数值可以作为指标,类别可以作为维度,时间拥有粒度和时区。拖拽行为不仅改变位置,还应该立即验证组合是否有效,并给出可以行动的反馈。
实时预览很重要,但不能每次输入都请求完整数据。编辑器可以使用采样数据、缓存查询结果,并对昂贵操作做节流。预览还要明确与生产数据的差异,避免用户把样本当作最终结果。
撤销与重做不适合通过保存整个页面快照无限堆积。更可控的方式是把编辑操作表达为命令,记录可逆变化。这样既能支持历史,也更容易分析用户如何构建报表。
运行时必须与编辑器解耦
编辑器关注创建体验,运行时关注加载速度、稳定性和兼容。两者共享 Schema 与渲染内核,但不应共享全部依赖。最终看板不需要拖拽库、属性面板和编辑历史。
运行时读取已发布配置,验证版本和完整性,按依赖加载数据,再渲染组件。单个图表失败不能拖垮整个看板,错误区域应保留位置并提供原因;公共筛选失败则需要明确影响范围。
运行链路被明确拆成编译与执行:
已发布 Spec
→ 版本迁移
→ Schema 与能力校验
→ 编译查询计划
→ 权限检查与数据请求
→ 数据转换
→ 编译视觉配置
→ 渲染器适配器每一步返回结构化错误。field_not_found 属于配置问题,dataset_forbidden 属于权限问题,query_timeout 属于运行问题。编辑器可以定位到具体字段,运行时则给看板使用者可行动反馈,二者不需要解析一段统一异常字符串。
每个组件都应该有生命周期:初始化、等待数据、渲染、更新、销毁。图表库常持有 Canvas、事件监听和大块数据,组件切换或页面退出时若不释放,会在长时间使用的后台中形成明显内存问题。
动态渲染也要限制能力。允许配置任意 JavaScript 表达式看似灵活,却带来安全、调试和版本兼容风险。我们更倾向提供受限公式与内置转换函数,让表达能力可以被静态分析和控制。
数据规模决定架构
低代码让创建图表更容易,也会让一个页面出现更多查询和更大数据。运行时必须把数据规模当作一等约束。
聚合尽量下推到服务端,浏览器只接收展示需要的数据。表格使用分页或虚拟化,图表对点数设置上限并提供采样策略。超过范围时,系统应解释为什么不能直接渲染,而不是让页面卡死。
多图表联动会产生请求风暴。筛选变化先形成统一查询上下文,再由页面级调度器合并、排序和取消任务。相同查询共享结果,旧上下文的响应不能覆盖新状态。这套调度不是顺手实现的细节,值得单独展开——我在多图表页面为什么需要请求调度里记录了并发限制、优先级老化与取消语义。
缓存键必须包含数据源、查询条件、权限与版本。BI 数据往往具有权限边界,错误缓存比没有缓存更危险。
可扩展不等于无限配置
低代码产品很容易陷入需求循环:每个客户都有例外,于是新增一个开关;开关组合产生新问题,再新增更多开关。最终没有人能解释全部组合。
我们会判断需求属于通用分析能力、行业模板还是单一场景。通用能力进入 Schema,稳定组合沉淀为模板,单一场景则考虑自定义组件或明确不支持。边界本身就是产品能力。
插件机制也需要契约。一个新图表组件必须声明支持的数据角色、配置 Schema、运行时依赖、序列化方式与版本兼容。只注册一个 React 或 Vue 组件远远不够。
测试的是语义,不只是组件
低代码系统的组合数量巨大,不可能穷举所有配置。测试重点应放在模型不变量和关键组合。
Schema 验证确保非法配置无法进入运行时;迁移测试保证历史样本升级后语义不变;适配器测试验证领域配置生成正确图表选项;关键模板做视觉回归;真实大数据样本用于性能基线。
每次线上错误都应沉淀为最小配置样本。配置数据比手工操作更适合复现,也能成为长期回归集。
最终交付的是受控表达力
低代码 BI 的价值,是让业务用户更接近问题与数据,而不是让所有人变成前端开发者。平台需要提供足够表达力,也要阻止无效、危险或无法维护的组合。
拖拽界面只是可见部分。真正决定系统寿命的,是中间模型、版本迁移、运行时隔离、数据边界和扩展契约。
一个成熟的配置系统不会承诺“什么都能做”。它会清楚说明什么能稳定完成,什么需要定制,以及新增能力将如何进入已有秩序。
这种克制最后落到一个很具体的要求:每份配置都能导出一张诊断单。诊断单包含 Schema 版本、迁移路径、数据集版本、查询计划、预计点数、渲染器能力命中和被降级选项。用户反馈“图不对”时,支持人员不必先索要整份敏感数据:
specVersion: 7 -> 9
queryPlan: server aggregation / 5m
estimatedPoints: 8,640
renderer: echarts-adapter@4
warnings: connectNulls disabled because missingRate=18%可解释的中间产物,是低代码平台长期可维护的部分。Schema 迁移的完整管线(单向迁移、语义校验、黄金样例、写回隔离)我在低代码配置升级里单独记录过,这里只保留了与引擎设计相关的边界。