1. 直面问题:业务人员只要“和原来一样”的表格
我们团队负责的内部系统中,有大量从 Excel 或 Word 迁徙过来的表单模板——例如设备巡检单、检测报告、各类审批表。当推动流程线上化时,最棘手的并非技术实现,而是终端用户的强烈抵触。
业务人员的诉求非常简单:“线上填的表格,必须和原来打印出来的纸质版一模一样。别让我重新学怎么填,也别改变任何排版。” 这意味着表格中的多级合并单元格、特定的边框粗细与颜色、部分区域的灰色底纹、动态变化的明细行,都必须原封不动地出现在网页上。
一开始我们尝试用常见的 UI 组件库来“拼”这些表。结果一个中等复杂度的记录表,光 CSS 和 <table> 结构就写了上千行。更被动的是,一旦业务调整模板(例如增加一列或修改合并规则),前端几乎要推倒重来。动态扩展行的需求也让代码变得异常脆弱,数据绑定的映射关系也很难维护。
我们意识到,如果继续这样纯手工复刻,每张表都将成为一个“代码债务炸弹”。
2. 偶遇解法:一款能还原原始文档的渲染引擎
在寻找解决方案的过程中,我们注意到一款名为 FlashTable 的表格渲染组件。它宣称可以直接导入 Excel/Word 源文件,并在浏览器里生成像素级一致的 Web 表单,同时提供动态行、公式计算和私有化部署支持。经过评估,它的工作逻辑恰好命中我们的痛点:
- 模板解析:上传 .xlsx 或 .docx 后,引擎会自动解析 OOXML 文档结构,生成包含每一个单元格的坐标、合并信息、样式以及可自定义数据路径的 JSON 描述。
- 高保真渲染:在页面中只需加载一个轻量运行时,传入上述 JSON,就能呈现与源文件完全一致的
<table>,样式细节、合并关系分毫不差。
对业务人员而言,这意味着他们看到的网页表单,和平时在 Excel 里填写的几乎没有任何区别。下面是我们基于该引擎的集成实践。
3. 我们的集成方案
3.1 从模板到拓扑数据
设计工具将 Excel 中的合并单元格、行高列宽、字体边框等转译为结构化数据。每个单元格的最终形态可抽象为:
interface CellTopology {
row: number;
col: number;
rowSpan: number;
colSpan: number;
style: Record<string, string>; // 引擎输出的样式键值对
field?: string; // 业务字段路径,如 "detail.sampleName"
value?: any;
}
业务人员上传模板后,我们只保存这份拓扑 JSON 至模板库,前端完全免去手写布局。
3.2 渲染:几行代码完成 1:1 呈现
在业务页面中引入运行时包,将模板数据和业务数据传入,即可完成绘制:
import {
TableRenderer } from 'table-renderer'; // FlashTable 提供的运行时
const container = document.getElementById('form-container');
const renderer = new TableRenderer({
container });
const {
cells, rowCount, colCount } = await loadTemplateSchema('tpl_02');
const formData = await loadRecordData('rec_456');
renderer.renderGrid({
rows: rowCount,
cols: colCount,
cells: cells.map(cell => ({
...cell,
value: formData[cell.field] ?? cell.value,
})),
});
运行之后,表格在浏览器中的显示效果与原始 Excel 完全一致——所有的合并单元格、边框样式、对齐方式都得到了精准还原。可编辑区域已自动挂载输入控件,并通过 field 属性与业务数据结构关联,无需编写额外样式。
3.3 动态行和外部数据联动
线下模板中经常包含需要按数据量重复的“明细区域”,例如样品清单或物料列表。引擎支持 # 数组展开语法,例如字段路径设为 "samples#testResult",便会将当前行识别为模板行,在运行时根据数据数组长度自动复制行,并维护好合并关系。
另一个常用功能是根据某单元格的值从外部系统拉取数据,自动填充相邻字段。借助引擎暴露的事件钩子可以轻松实现:
renderer.onCellChange(async (event) => {
if (event.field === 'sampleCode' && event.value) {
const res = await fetch(`/api/sample/${
event.value}`);
const json = await res.json();
renderer.updateCell('sampleName', json.name);
renderer.updateCell('spec', json.spec);
}
});
这种配置式联动既降低了前端开发量,又减少了人工录入错误。
3.4 多系统复用与安全隔离
我们旗下有几个不同技术栈的子系统(MES、OA、LIMS),都需要使用同一套复杂表单。渲染引擎支持以独立 iframe 方式运行,并通过 postMessage 与宿主系统通信。我们将它部署为公共服务,各系统仅需引入 iframe 并遵循通信协议即可。
宿主端的典型调用代码:
const iframe = document.getElementById('table-iframe') as HTMLIFrameElement;
// 通知加载模板
iframe.contentWindow?.postMessage({
type: 'LOAD_TEMPLATE',
payload: {
templateId: 'tpl_03' }
}, '*');
// 注入业务数据
iframe.contentWindow?.postMessage({
type: 'SET_DATA',
payload: {
fields: recordData }
}, '*');
// 监听提交事件
window.addEventListener('message', (event) => {
if (event.data?.type === 'SUBMIT') {
const {
fields } = event.data.payload;
persistRecord(fields);
}
});
这种沙箱化集成让复杂表格能力与各业务系统彻底解耦,一次开发即可多处复用,也满足了我们私有化部署和数据安全的要求。
4. 实践效果
引入该工具后,我们的表单线上化效率和质量有了明显提升:
- 真正的零失真体验:使用者在网页上看到的和日常在 Excel 中填写的分毫不差,培训成本几乎为零。
- 研发效率质变:新增一张复杂表单从数天缩短到几小时,主要工作变成配置字段映射,而非编写样式。
- 动态与联动能力:行扩展、外部数据获取等复杂需求由引擎原生支持,逻辑维护变得清晰。
- 一次部署,多系统共享:通过 iframe 隔离,MES、OA、LIMS 共用同一张表单,同时支持完整的私有化部署,数据不出内网。
回头来看,能否让业务人员顺利接受线上化,核心在于是否充分尊重他们的原有习惯。我们找到的这款渲染引擎恰好解决了最耗时的“排版复现”和“动态交互”问题,让我们能把精力投入到业务规则和系统整合上。对于同样面临“线下表格原样线上化”困境的团队,这种基于 OOXML 解析与专业引擎的思路,或许是一条值得尝试的路径。FlashTable 正是这一思路的典型实现。