☰
Univer开源表格引擎实战:从自定义模板到单元格级权限控制
2026/10/1 4:33:55 网站建设 项目流程

做在线表格这种事情,我前前后后折腾过好几轮。市面上的库不少,但要么是纯展示型,要么是把某个商业产品的 Web 端抽出来,想加一点个性化的权限控制都无从下手。最近项目里正好有一个需求:要做一个“用户先自行定义表格结构,再邀请别人按规则填写”的小工具,我认真试了试 Univer,发现它正好卡在一个很舒服的位置。现在搜索 univer 这个关键词,能看到大量和在线表格相关的讨论,它本质上是一个基于 TypeScript 的开源在线表格引擎,核心能力包括 Canvas 渲染、公式计算、协同编辑、权限控制,以及一套非常完整的命令系统。它既能帮你摆脱从零写表格的噩梦,也能让你在单元格级别去控制用户能不能点、能不能改。

如果你也面临“用户自己定义表格,别人只能填写指定单元格”这类需求,这篇内容就是我基于实际项目踩坑和验证后的经验总结。Univer 的学习曲线并不是没有,但把它用对地方之后,你会明显感觉到在线表格从一个“功能组件”变成了一套可以承载业务的“基础设施”。这里我不会只讲 API 怎么调,更多会聊清楚每个设计选择背后的原因,以及那些官方文档里没有明说的坑。

1. 先说清楚 Univer 到底解决什么问题

1.1 不是又一个在线 Excel,而是表格基础设施

很多人第一次看到 Univer,第一反应是“又一个网页版 Excel”。实际上,Univer 的定位更接近一个“不绑定 UI 的表格引擎”。它默认没有把完整工具栏、菜单、状态栏都塞给你,而是把表格所需的底层能力拆成了插件:Sheet 插件负责多工作表管理,Formula 插件负责计算,协同插件负责实时同步,渲染引擎负责把单元格画到 Canvas 上。这样你在业务系统里嵌入表格时,可以只挑自己真正需要的能力,而不是被迫引入一整头“大象”。

这个思路和 SheetJS 这类文件处理库是完全不同的层级。SheetJS 专注于解析和生成 Excel 文件,它不负责界面,也不负责交互。Univer 则是一个真正可交互的表格实体:用户点击单元格、输入内容、拖动选区、触发公式,这些操作都发生在它自己的渲染层和交互层里。如果你之前用过 Luckysheet,也会发现 Univer 在设计上更克制,它的状态管理、命令模式、插件机制做得更系统化,为后续的多人和权限隔离打下了很好的底子。

我第一次把 Univer 初始化起来时,最直观的感受是:滚动一万行数据不卡,甚至公式联动也能顺畅刷新。这种流畅不是单纯靠懒加载刷出来的“伪性能”,而是 Canvas 分区渲染带来的真实体验。对我这种需要在业务系统里嵌入表格的人来说,这个点直接决定了产品到底能不能往生产环境推。

1.2 为什么自托管方案会优先考虑 Univer

当时需求方提了两条硬条件:第一,表格必须能在公司内网运行;第二,用户填写的数据不能长期存放在第三方服务器上。这就意味着云文档产品的开放 SDK 用不了,必须找一套可以私有化部署的开源方案。Univer 是 MIT 协议开源项目,数据可以存在你自己的后端,页面通过 npm 包引入,甚至可以嵌入到现有的 Vue 或 React 应用中。你不会被某个在线表格平台绑架,也不会因为外部服务的账号体系而被迫调整自己的用户模型。

不过开源不等于零成本。Univer 的项目结构里交错着 Core、Sheets、UI、Formula 等多个包,链条比普通表格组件要长。如果只是想在页面上展示一张静态表格,用它确实是杀鸡用牛刀。可一旦你有“让用户填表但必须锁死部分区域”这类精细需求,它的插件化架构就能让你在不改 UI 框架的前提下,把权限、校验、协同都挂载进去。这个特征,恰恰是“univer 支持用户定义表格,然后让用户去填写一些单元格”这类场景的标准答案。

1.3 值得关注的四大核心能力

既然要拿 Univer 做业务,就必须对它的能力边界有数。我梳理了四个高频使用的核心能力,分别对应不同业务场景。

第一是公式引擎。Univer 有自己的公式计算引擎,支持常用数学函数、文本函数、日期函数,还支持跨工作表引用。我在模板编辑器里生成公式后,用户填写时公式会实时重算,这比单纯存一个字符串有意义得多。第二是数据校验。你可以给单元格配置下拉选项、数值范围、正则表达式等校验规则,用户输入非法内容时,页面会直接拦截。第三是权限与保护。这是热词里“其他单元格无法修改”的直接答案,工作簿、工作表、范围三级保护模型可以精确到格子。第四是协同编辑。Univer 的协同方案基于操作转换,能在多个客户端之间同步增量操作,避免整表覆盖。

把这四个能力串起来看,Univer 不再是一张“死表格”,而是一个可编程的在线数据操作界面。业务系统里的填报表、审批表、资产登记表,本质上都可以用这套底座去搭建。

2. 核心需求拆解:让用户只改该改的格子

2.1 从“整表可编辑”到“指定区域可编辑”的权限模型

很多从零手写表格的同学,第一版权限控制就是给每个输入框加一个 disabled。这种方法在几十行数据时还凑合,一旦表格变大、用户协作变多,就显得非常粗糙。Univer 的权限控制是层级化的:工作簿一层,工作表一层,具体范围一层。你可以先把整张工作表设置为“锁定”状态,然后再单独把某个范围标记为“可编辑区域”。这种模型的思路和 Excel 的“保护工作表”很接近,但实现是建立在命令系统之上的,每次权限改变都会生成一条可追踪的命令,意味着你可以接入服务端审计,也可以实现用户离开后自动撤销权限。

实际落地时,我把“锁定”和“可编辑”拆成了两个状态:一个用于控制渲染层的交互(能不能点、能不能输入),另一个用于控制数据层的写入(提交时由后端校验)。Univer 负责前者,你的后端接口负责后者。把这两个状态混为一谈,是很多人后来排查权限失效问题的根源。你要明确一点:前端锁死永远不是安全手段,它只是用户体验层面的“护栏”,真正的数据防护一定要在后端再做一次。

2.2 数据校验:让用户填进去的不是乱数据

单纯的“能编辑/不能编辑”其实只解决了一半问题。比如一个用于收集员工信息的表格,身份证号、手机号、部门名称各有各的格式要求。如果只控制了可编辑区域,用户还是可能在手机号那格填一个“abc”。Univer 的数据校验能力可以让你在单元格级别定义规则,包括下拉选择、数值范围、日期、文本长度等。配合自定义公式,你甚至能做跨单元格校验,例如“B 列填写了日期,C 列必须晚于 B 列”。

我第一次做这个需求时不以为意,后来测试同事拿一串两百位的文本粘进手机号格子,才发现校验不是“锦上添花”,而是表格填写的底线。Univer 的校验规则一般是在工作表配置里定义,也可以在命令中动态追加。对于“用户自己定义表格”的场景,你需要把这些规则做成模板的一部分:用户设计表格时,可选择“文本”“数字”“日期”“下拉选择”等类型,Univer 会自动生成对应的校验器,保存后发给填写方时才能自动生效。

2.3 多人填同一张表时的冲突处理

需求里提到“用户去填写一些单元格”,这背后隐藏着多人填写问题。Univer 官方协同能力更多是基于 WebSocket 的增量同步。简单理解就是:每个用户的编辑动作会转成原子操作,例如“把 A1 从 1 改成 2”,广播给其他用户,然后每个客户端按顺序重放这些操作。这样既避免了整表覆盖,也让用户在同一张表的不同区域里同时编辑成为可能。

但协同不是开了插件就万事大吉。你需要自己决定客户端和服务端之间哪些操作要合并、哪些操作要加版本号、冲突了以谁为准。我在项目里让后端为每个单元格保存一个版本号,当用户提交时,如果版本号不一致就拒绝写入,让用户重新加载。Univer 的协同插件把“操作同步”做掉了,但“业务上该不该允许这次写入”仍然要你亲自把关。

2.4 用户自建表格的模板化思路

既然是“用户定义表格”,就涉及到模板化设计。用户画完表格后,我们不能只保存一个 Excel 文件,因为后续还要把权限规则、校验规则、填写状态挂上去。更好的方式是保存一份 Univer workbook 配置 JSON,里面包含单元格样式、合并区域、行高列宽、公式、保护范围。填写者打开页面时,后端把这份 JSON 下发,前端重新创建 Univer 实例。

这种模板化思路把“表格结构”和“用户填写的数据”分开存储,结构是模板,数据是独立的记录。好处是同一张模板可以生成上千条填写记录,每次填写互不影响;反过来,你修改模板结构后也不会破坏已经提交的数据。这个设计在低代码平台里非常常见,Univer 天然支持这样的存储方式,因为它的整个工作簿状态就是可序列化的。

3. 实操过程与核心环节实现

3.1 项目初始化:用 npm 把 Univer 接入 Vue/React

我这边项目基于 React,但 Univer 在设计上不依赖任何框架,在 Vue 里使用流程基本一样。先安装一组核心依赖,命令大致是:

npm install @univerjs/core @univerjs/sheets @univerjs/ui @univerjs/preset-sheets

注意一点,Univer 的版本迭代速度并不慢,不同版本的包名和初始化方式会有差异。早期版本常用一个UniverSheetPlugin的写法,后来的版本更强调通过plugins数组装配。我建议你以官方文档中对应安装版本的那页为准,不要拿一篇老教程硬套。初始化一个最简工作簿的代码风格大致如下:

import { Univer } from '@univerjs/core' import { UniverSheetsPlugin } from '@univerjs/sheets' import { UniverUiPlugin } from '@univerjs/ui' const univer = new Univer({ plugins: [ UniverSheetsPlugin(), UniverUiPlugin() ] }) univer.createUnit(/* WorkBookConfig 或 WorkSheetConfig */)

第一次跑起来后,你应该能在页面上看到一个可以输入公式、拖动填充的表格。如果页面空白,先去检查 npm 包里是否有对应的样式文件没有引入。这个坑我会在“常见问题”里详细讲。

3.2 定义表格模板:让用户自己设计结构

“用户自己定义表格”这一步,本质上是在后台先创建一个空的工作表,然后把行高、列宽、合并单元格、表头样式这些信息保存成配置文件。Univer 允许你通过一个配置对象直接创建工作簿,这个对象里的styles、rowData、columnData、cellData、merge都是结构定义来源。你可以把它理解成一张“元数据表”,用户在设计器里调整好后,我们把对象序列化保存到后端。

比如设置前两行为表头并合并 A1:D1,在配置里会有类似merge: { '0_0': { startRow: 0, startColumn: 0, endRow: 0, endColumn: 3 } }的数据。这里我不展开全部字段,因为不同版本字段名略有调整。但思路是确定的:先做一个“模板编辑器”页面,让用户通过 Univer 的 UI 调整样式和公式,保存时把 workbook 的完整配置 JSON 传到服务端;填写者进入时,再把这个 JSON 拉下来,重新 createUnit。

如果你觉得这种方式太重,也有轻量做法:在后端用一份简化 JSON 维护行列结构,前端初始化时动态生成配置。我使用前者,因为用户设计表格的过程中本来就需要看到所见即所得的效果,直接编辑 Univer 实例再导出配置,是最不容易出错的方式。

3.3 锁定非填写区域:核心权限配置

这是热词里“其他单元格用户无法修改”的直接实现。我通常分三步走。

第一步,先给整张工作表加一个保护状态。在 Univer 的配置里,工作表对象上有与protection相关的字段,或者可以通过命令系统的对应 Mutation 设置。这一步的语义是“默认全表只读”。

第二步,把要开放的区域从保护中摘出来。下面的代码是一个简化示意,目的是帮你理解思路,实际调用以你安装版本的 API 为准:

const sheet = univer.getActiveWorkbook().getActiveSheet() // 锁定整表 sheet.protect() // 放开 A1:C10 区域的编辑权限 sheet.unprotectRange('A1:C10')

如果你希望更精确一点,可以按业务规则动态开放多个不连续的格子,比如只开放 B2、B4、C5。我建议不要硬编码单元格坐标,而是把这些坐标也放进模板配置里,由后端统一维护。用户设计表格时,勾选“允许填写”的格子,勾选结果就会追加到可编辑区域列表。

第三步,在填写者打开页面时,把可编辑格子用浅黄色背景高亮出来。这不是权限功能,但我强烈建议做这一步体验优化。用户并不知道哪些格子能填,高亮之后整个页面的可用性会提升一个层级。你可以通过单元格样式设置 background 颜色,写入配置,或者在初始化后用 API 批量设置样式。

3.4 把填写结果保存并导出

Univer 提供了导出 XLSX 这类命令,可以把当前工作簿转换成 Excel 文件。但我在实际项目里不会直接让前端导出作为唯一持久化手段。前端的主要职责是收集改动:监听单元格更新事件,读取版本号和值,然后提交给后端接口。后端保存的仍然是模板配置 JSON 加上用户填写的数据对象。真正的 Excel 导放在后端,或者生成时把数据塞进模板里,这样既保证前后端状态一致,也避免了浏览器生成大文件时的卡顿。

这里最想强调的一点:不要把整张表全量回传。每一处修改都应以“原子操作”的形式提交,后端只需要知道“谁在什么时间把哪个坐标的值改成了什么”。Univer 的协作协议在这方面已经做了不少封装,你顺着它的思路设计自己的 API,后面写审计日志都会轻松很多。

3.5 公式、条件格式与下拉选项的联动

只做“锁定和填写”的话,表格还很单薄。实际业务中往往还要配合公式自动计算,比如“单价乘以数量等于总价”,以及用条件格式把超标的单元格标红。Univer 支持这些能力的配置化定义,你可以把公式写进单元格配置,也可以把条件格式规则放在工作表级别。

我和设计同事讨论时,他们问能不能让用户在填表页面看到“实时合计”。答案是可以。公式引擎会跟随可编辑区域的值变化自动重算,不需要用户手动点击“计算”。但你要注意性能:如果整张表有几千条公式,每次输入都可能触发大量重算。我后来把表格拆成了多个工作表子域,只让核心区域联动,避免一改就全量算。这个优化思路在“性能调优”部分会再提到。

4. 常见问题与排查技巧实录

4.1 页面白屏或表格不渲染

Univer 的渲染建立在 Canvas 之上,但它的 UI 层脱离不了一些基础 DOM 节点。白屏最常见的原因是样式文件没有引全。我第一次初始化时,表格区域完全空白,控制台也没有报错,后来才发现少引了@univerjs/ui附带的 CSS。另一个原因是在同一页面上多次创建 Univer 实例,却没有销毁旧实例。你可以通过univer.dispose()释放资源,否则两个实例会同时抢占同一个容器节点,导致渲染错乱。

排查顺序建议是:先确认 npm 包版本是否一致,再看样式是否引入,最后看容器是否设置了明确宽度和高度。很多白屏不是逻辑错误,而是 CSS 布局给容器一个 0 高度。

4.2 权限设置不生效,用户还是能乱改格子

这种情况十有八九是保护层级用错了。Univer 里有“工作簿保护”和“工作表保护”两层概念。如果只保护工作簿,但没对具体工作表设置保护规则,那用户依然可以编辑当前工作表。反过来,你锁定了工作表,却忘了放开可编辑范围,那所有格子都动不了。排查时先打印一下工作表对象的保护状态。

另外要注意,Univer 的 UI 层会有自己的交互事件流。比如有些版本支持“拖动填充柄”操作,这个操作可能绕过保护逻辑,把用户输入覆盖到不该编辑的单元格里。遇到这种情况,可以关闭对应的拖拽填充插件,或者在业务层拦截拖拽结束事件,恢复被覆盖的数据。

4.3 保护区域里的公式不自动重算

把某些单元格设置为只读之后,公式往往还会引用它们。如果你的配置里没有打开自动计算,或者只读区域的引用值通过非正常渠道变更,会出现公式结果不更新。常见原因是:你在模板编辑器里设置了公式,但填写者打开时初始化顺序不对,导致公式引擎启动晚于首次渲染。解决办法是把公式配置放在初始化参数里,而不是在页面加载完成后再用命令补写。

另一个坑是公式引用跨工作表,而两个工作表的保护状态不一致。此时 Univer 的公式计算会遵守“可用的引用范围”,有可能把被保护区域的单元格当作 0 处理。建议在测试阶段就用一份带公式的表格去专门验证保护后的计算结果,不要等上线再查。

4.4 多人同时填写时数据丢失

协同模式下,最典型的故障是两个人同时改同一个格子,后提交的人覆盖了前一个人的值。Univer 的增量同步可以缓解冲突,但业务层必须有自己的版本控制。我的做法是在单元格元数据里加一个rev字段,每次修改rev + 1。提交时携带旧rev,后端发现不匹配就返回冲突,前端弹窗让用户决定用哪个版本。

还有一个容易被忽略的点:WebSocket 连接断线重连之后,增量操作可能因为服务端状态重建而丢失。需要在服务端保存每个会话的操作日志,重连时把缺失的操作补发过去。如果只是把 Univer 的协同插件拉起来,却没有做操作日志持久化,那么一次网络抖动就有可能导致数据回溯。

4.5 大数据量表格的滚动卡顿

我之前压测了一份两万行、二十列的表格,虽然 Univer 的 Canvas 渲染比 DOM 表格好很多,但如果一次性给每个单元格都填充独立样式对象,内存占用依然会快速上升。优化方向有两点:一是让表格配置尽量复用样式,而不是每行都生成新样式对象;二是开启虚拟滚动相关的配置,只渲染可视区域附近的单元格。这样大表格的滚动和输入延迟都会有明显改善。

如果你发现滚动流畅但“新增行”变慢,可能是在为整行设置样式时触发了大范围重绘。建议把对大片区域的操作合并成一次批量命令,而不是循环逐单元格调用 API。Univer 的命令系统天然支持批量变更,利用好这个能力,事半功倍。

5. 应用场景扩展:从一张填报表到一套业务平台

5.1 用 Univer 做数据收集与审批工具

热词里“univer 支持用户定义表格,然后让用户去填写一些单元格”这个场景,最常见的应用就是数据收集。比如公司内部收集预算,财务先定义一张预算表,锁死科目名称和计算公式,只放开各月份填写格。填报人进入后,看到的是一张已经排好版、可填写但不可乱动的表格。Univer 正好把“模板设计”和“填写控制”合并到了一起。

还有审批场景。审批人在表格里填“同意”或“不同意”时,其他区域都不应被随意修改。这时候保护范围可以精确到审批意见那一列。用户提交后,后端把整张表连同审批意见一起归档。这个流程以前要靠邮件传 Excel 实现,现在用 Univer 做出来以后,版本混乱问题几乎消失了。

5.2 在低代码平台里嵌入 Univer

低代码平台最头疼的一个组件就是“可动态配置的表格”。如果你用列表组件做,只适合展示;如果用原生表格做,公式、合并单元格、权限控制又得自己写一遍。Univer 直接补齐了这块能力:平台把 workbook 配置作为 DSL 存储,用户通过拖拽调整列宽和字段类型,保存后变成表单模板。运行时前端加载配置,后端按模板接收数据,完美对接到低代码的“数据模型”中。

我在一个内部工具里就是这么用的:用户上传 Excel,解析成 Univer 工作簿,在线调整后再保存为模板。整个过程不用写一行 Excel 解析代码,Univer 已经帮你把导入和导出接好了。对于做低代码平台或者内部管理系统的人来说,这能节省大量重复劳动。

5.3 性能调优与私有化部署建议

如果要把 Univer 放进正式生产环境,我建议一开始就从架构上想清楚。渲染和前端包可以走 CDN 或内网静态资源服务,但协同和保存必须走你自己的后端。不要把版本控制逻辑写在客户端,因为客户端永远不可信。后端负责保存操作日志、维护版本号、控制并发,前端只是“交互层”。

部署方式上,Univer 前端是无状态静态资源,后端只提供配置、保存、导出三个接口即可。最简单的方式是用 Nginx 托管静态页面,后端用 Node.js 或 Java 提供 JSON 接口。由于 Univer 的数据模型是可序列化的,你甚至可以把工作簿配置直接存进 MongoDB 或 PostgreSQL 的 JSON 字段里。我项目里就是这样存的,查询和恢复都非常方便。

5.4 后续扩展:把 Univer 接上图表和仪表盘

Univer 的能力边界远不止填表,它还可以承载图表、条件格式、列表筛选等功能。如果你已经用 Univer 做完了数据录入层,下一步可以把它升级成数据展示层:读取后端数据后生成柱状图、折线图,或者做汇总仪表盘。这样用户既能在 Univer 里录入数据,也能在同一种技术栈下看到可视化结果,技术维护成本会低很多。

我记得当时把一张报销记录表接上图表后,财务同事直接用它替代了原来的多套报表工具。Univer 的插件生态虽然还在成长期,但对于一个能自控权限和数据的团队来说,它已经完全够用。

最后再分享一点个人经验

在我实际的项目里,Univer 最让我满意的不是某个炫酷插件,而是它把表格的“操作”全部抽象成了命令。我们做权限控制的时候,不用直接去改 DOM、改状态,只需要往命令系统里注册规则,剩下的焦点事件、撤销重做、协同同步,它自己就能配合起来。这个设计风格,让一个三人的前端小组也能维护住一个在线表格产品。

如果你已经准备调研 univer,我建议先别想着集成所有功能,挑一个最小场景从零跑通。比如先做一张只有一个工作簿、一个可编辑区域、一个只读区域的表格,然后把权限和后端保存接起来。这个里程碑完成后,你再去看协同、公式、导入导出,会觉得整个世界顺很多。我不会说它是零成本的,因为它的 API 和文档离“开箱即用”还有一点距离,但你如果需要一套能私有化部署、能控制到单元格权限的在线表格底座,Univer 确实值得花两周时间做一次技术验证。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询