先把结论放在前面:如果你正在找一款能在网页里在线编辑表格、支持业务方自己定义填写区域、还能把其余单元格锁死不允许乱改的开源表格引擎,Univer 非常值得放进你的考察名单。
我最早关注 Univer,是因为团队内部一个反复踩坑的需求:业务同事每周要收集一批数据,之前一直是把 Excel 模板发到群里,大家下载、填写、再回传。一次两次还好,人一多就乱,有人改了不该改的公式列,有人用 WPS 打开后格式错乱,最后汇总还得人工对拍。后来我们决定做一个内部数据收集系统,核心要求就三条:模板由业务方自己定义;参与者只能在指定单元格填写;其余区域不能编辑。市面上的表格组件看了一圈,最后落在 Univer 上,就是因为它在“在线协作、单元格权限、Excel 兼容”这三块刚好都能覆盖,而且完全开源、可以私有化部署。
这篇文章我会从 Univer 的项目定位、权限模型、实际开发接入、常见坑这几个角度讲清楚,尤其重点拆解“开放一部分单元格、锁死其他区域”这个典型场景。如果你正在评估在线表格技术方案,或者马上要动手写一个可填充可锁定的报表系统,这篇应该能帮你省掉不少调研时间。
1. Univer 到底是什么,跟其他表格库差在哪
1.1 一个能在浏览器里跑的“Excel 引擎”
Univer 是一个基于 TypeScript 开发的开源办公套件,目前最成熟、最常用的是它的 Spreadsheet(电子表格)能力,同时也在向文档、幻灯片方向扩展。它不是一个“表格导出工具”,也不是只管渲染的静态组件,而是一整套从数据模型、公式引擎、渲染层、交互层到协作层的完整表格解决方案。你可以把它理解成“把一个迷你 Excel 塞进浏览器,再用 API 控制它的一举一动”。
它最大的特点是不依赖服务端渲染,纯前端运行,数据可以直接给到远程接口,也可以本地导出文件。这意味着它能像普通前端组件一样被嵌入到 React、Vue 项目中,同时又具备完整表格软件的操作体验:单元格编辑、拖拽填充、条件格式、数据校验、公式计算、筛选排序都有。值得注意的是,Univer 也支持后端协同能力,多人同时编辑一张表时能通过协同算法合并操作,这点在同类开源表格库里并不多见。
1.2 为什么大家都在选它:几条硬指标
单说“开源在线表格”,国内开发者以前接触比较多的是 Luckysheet、x-spreadsheet 那一类项目。Univer 之所以在社区里讨论度快速上升,核心是这几项能力凑在了一起:
一是插件化架构。Univer 核心包本身只负责基础框架和数据模型,公式、权限、协同、导入导出全部以插件形式挂载。需要什么功能就装什么插件,不需要的插件不引入,最终打包体积可以控制得很低。
二是Excel 兼容性。Univer 支持导入导出 xlsx 文件,格式还原度在开源项目里算是做得比较细致的。内部系统如果需要和外部交换报表,这一条几乎是刚需。
三是协同能力。它支持类似 Google Sheets 的多人协作模式,而且是把协同作为基础能力设计的,不是后加的补丁。
四是权限可编程。单元格、行、列、工作表都可以设置锁定与保护状态,并且支持通过代码动态控制。这就是我自己项目里用到的最核心能力,后面会重点展开。
拿它和几个常见方案对比一下,大概长这样:
| 能力维度 | Univer | Luckysheet | Handsontable |
|---|---|---|---|
| 开源协议 | 开源可商用 | 开源(社区版有一定限制) | 非商业需要授权 |
| 单元格权限锁定 | 支持且可编程 | 支持较弱 | 支持,偏商业版 |
| 协同编辑 | 内置方案,可接入 | 依赖二次开发 | 需要企业版 |
| Excel 导入导出 | xlsx 原生支持 | 支持,细节有损耗 | 支持,高级功能付费 |
| 前端框架适配 | React/Vue/原生均可用 | 依赖 jQuery 生态 | 框架适配较好 |
从表格能看出来,如果只是做一个展示级表格,很多库都能胜任;但如果要“在线协作 + 单元格权限 + 业务自定义填写”,Univer 几乎是唯一一个开源协议友好、能力路径完整的选择。
2. 核心场景拆解:开放填写单元格,锁死其他区域
2.1 先把需求讲清楚:哪些单元格可以动,哪些不行
很多初次接触 Univer 的开发者会混淆“单元格锁定(locked)”和“工作表保护(protect)”这两个概念。这里有必要先厘清:Univer 的权限逻辑继承自 Excel 的经典模型,单元格本身有“锁定”状态,但这个状态默认不生效;只有在整张工作表开启“保护”后,被标记为锁定的单元格才会真正不可编辑。反过来,如果某个单元格既处于保护工作表下,又被主动设置为“未锁定”,那它仍然可以填写。
这正好对应我们前面那个需求模型:整张表保护起来,默认所有单元格都不可编辑;再把需要参与者填写的单元格显式解锁。这样做的好处是,新插入的单元格默认也被保护住,不会出现“漏了一个地方没锁”的情况。哪怕业务同事后来往表格里加了一列,那新增单元格也天然处于锁定状态,必须显式开放才能填写。
这种模型在 Excel 里叫“保护工作表 + 取消锁定区域”,在 Univer 里本质上是同一套思路,只是把配置从桌面端操作搬到了代码里,可以动态下发到不同用户或不同表单。
2.2 “用户自定义表格”到底是怎么落地的
这个场景里的“用户定义表格”,核心含义是:业务同事不写代码,也能自己定义一张可填写的在线表格结构。具体在 Univer 里拆开,就是几件事的组合:
第一,定义表头与样式。业务方可以自己在界面上调整列名、列宽、背景色、边框,这些操作会实时反映到表格数据模型中。
第二,设置区域填写规则。哪些列是录入项、哪些列是公式列或合计列,业务方通过锁定/解锁操作就能表达。
第三,让参与者看到一张“干干净净”的表。别人打开表格时,只能看到模板结构,只能编辑被允许的区域,公式和底表数据看不到或者改不了。
第四,数据统一回收。参与者提交后,表格数据可以直接上报到服务端,不再需要人工合并回传文件。
这套东西如果从头自研,光是一个类 Excel 的界面渲染和单元格交互就要写很久,更别说公式计算、样式还原这些细节。用 Univer 等于把最重的底层都租来了,团队只需要聚焦在“业务规则”和“数据流”上。
2.3 解锁和锁定的两种常用配置方式
Univer 做单元格开放填写,实际操作上一般分两种路径。
一种是在界面上操作。表格开启保护后,选中要开放的单元格区域,右键或通过命令面板执行“取消锁定”操作。这种方式适合业务同事在模板定义阶段自己操作,不需要开发介入。
另一种是纯代码方式。初始化工作表时,通过样式循环或 API 直接标记单元格锁定状态。这种方式适合模板由后台生成、或者需要在运行时按用户动态计算的场景。
两种方式也可以混用:业务方先在线调整好模板,开发再把模板配置持久化到后端;用户打开时,Univer 加载模板配置,权限规则直接生效。这样既保留了业务方的灵活性,也保证了每一次打开都是一致的状态。
2.4 配合自定义工具栏,能让体验更贴近业务
我实际开发中的一个体会是:Univer 默认的右键菜单和工具栏对普通业务用户来说有点“过于专业”,但没关系,它提供了命令机制和 UI 插件扩展接口。你可以把不需要的菜单项去掉,只保留一个“填写说明”的浮动提示,甚至可以在顶部加一个自己的按钮,点击后校验必填项、然后提交数据。
这样做的好处是用户看到的不是一个“开发工具”,而是一个像问卷一样简单的填写表单。表格只是承载数据的壳,业务体验完全掌握在自己手里。很多人低估了这一点,总觉得“列表格就完事了”,实际上把界面上多余能力藏好,数据质量会明显上升。
3. 实操:从零跑通一个带单元格保护的 Univer 表格
3.1 环境准备与依赖引入
我以最常见的 React + Vite 项目为例,Univer 官方提供了 React 封装组件,接入会比较顺手。非 React 项目也不用担心,它本身是框架无关的,原生 JS、Vue 都可以使用,只是写法上略有差异。
初始化项目后,安装基础依赖:
npm install @univerjs/core @univerjs/ui @univerjs/sheets @univerjs/sheets-ui @univerjs/sheets-formula @univerjs/sheets-import-xlsx @univerjs/sheets-export-xlsx这里每个包用途不一样:core 是核心数据模型,ui 是框架界面,sheets 是表格引擎主体,sheets-ui 提供工具栏和交互,sheets-formula 处理公式计算,import/export 负责 xlsx 进出。如果不需要公式,最后一个公式包可以不加,体积会小一块。
安装完依赖之后,还需要保证项目支持 CSS 变量和现代浏览器特性,一般 Vite 默认配置直接够用。
3.2 初始化 Univer 并挂载表格实例
Univer 的初始化思路是“先注册插件,再创建实例”。直接看代码:
import { Univer } from '@univerjs/core'; import { UniverUIPlugin } from '@univerjs/ui'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; import { UniverFormulaEnginePlugin } from '@univerjs/sheets-formula'; const univer = new Univer(); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverFormulaEnginePlugin); univer.registerPlugin(UniverUIPlugin, { container: 'app', }); univer.registerPlugin(UniverSheetsUIPlugin); // 创建一张工作簿并添加工作表 const workbook = univer.createUniverSheet(); const worksheet = workbook.getActiveSheet();这段代码执行后,页面上就会渲染出一个完整的在线表格。需要注意,容器元素要有明确高度,否则表格虽然初始化成功,UI 区域可能被压缩成一条线。这个问题我第一次跑的时候也踩过,后来直接给容器设置了height: 100vh才正常显示。
3.3 设置工作表保护并开放填写区域
接下来是重头戏:默认锁死整张表,然后开放指定单元格区域。Univer 中启保护、设置锁定状态有一套操作命令,我先用“锁定全部单元格再解锁指定区域”的方式:
import { CommandType, ICommand } from '@univerjs/core'; // 1. 设置整张工作表为保护状态 worksheet.setProtection(true); // 2. 将需要填写的区域(例如 A2:C10)设置为未锁定 const range = worksheet.getRange('A2:C10'); range.setStyle({ locked: { lock: false }, });这里的关键点在于:保护是工作表级别的开关,锁定是单元格级别的属性。设置setProtection(true)之后,没有显式解锁的单元格全部变成只读;A2:C10被单独标记成未锁定,参与者只可以在这个范围内填写。
还有一个细节是样式设置里的locked对象,它包含一个lock布尔字段。有些开发者在接管 Univer 后会把注释填反,导致设置后行为完全反过来。建议每次配置保护之后,主动打开页面试一下能不能编辑。
如果你希望动态控制某个具体单元格,也很容易:
worksheet.getRange(1, 2, 1, 2).setStyle({ locked: { lock: false }, });参数含义分别是起始行、起始列、结束行、结束列,行列从 0 开始计数。这种方式适合后台根据不同用户身份下发不同的可编辑区域。
3.4 数据回收和导出:填完的表怎么收回来
单元格能填只是一个环节,更重要的是把填写结果收回来。Univer 支持监听单元格变更事件,典型的做法是把变更数据实时推给后端:
import { UniverInstanceService } from '@univerjs/core'; univer.onCommandExecuted((command) => { if (command.type === CommandType.OPERATION && command.id === 'sheet.operation.set-range-value') { // 拿到变更的区域和值,同步给后端 const params = command.params; console.log('用户改动了', params); } });监听命令流是通用姿势,因为用户既可能手工填写,也可能通过复制粘贴、拖拽填充等方式修改单元格,这些最终都会落到命令层,统一拦截不会漏。
如果提交方式偏向“整表提交”,也可以走导出路径,把当前表格导出为 xlsx:
import { UniverExportXlsxPlugin } from '@univerjs/sheets-export-xlsx'; const exportPlugin = univer.getPlugin(UniverExportXlsxPlugin); exportPlugin.exportXlsx();两种方式看业务需要。内部系统建议用监听命令实时上报,这样后台可以做必填校验、冲突检测,甚至实时汇总;如果只是定期归档,导出文件就够用。
4. 常见问题与排查技巧实录
4.1 设置了锁定单元格,别人还是能改,为什么
这是我被问得最多的问题。大多数情况不是因为代码写错,而是“保护”没开。可能你只做了setStyle({ locked: { lock: false } }),但忽略了worksheet.setProtection(true);也可能反过来,保护开了但锁定状态设置成了反逻辑。还有一个隐蔽点:在 Univer 中,如果工作表处于保护状态,默认情况下连选择单元格的视觉反馈都会受限,如果发现点击单元格没有选中框,一般就是保护生效但解锁区域没配置成功。
排查顺序建议是:先确认保护开关是否为 true;再确认目标单元格的 locked 是否明确设为 false;最后看是不是右键菜单被自定义 UI 插件覆盖了,导致界面用了另一套权限配置。
4.2 多人同时编辑,会不会有人绕过锁定
这里要区分一件事:Univer 的锁定是前端交互层的限制,并不是后端安全边界。也就是说,它保证了普通用户用鼠标点不进去,但不保证有人通过构造命令消息直接修改单元格数据。如果是内部系统、用户可信度高,这层限制足够;如果对数据安全要求极严格,正确姿势是后端也要有同样的权限校验规则,前端锁定只负责体验,后端权限才负责安全。
我自己的项目里,前端 Univer 锁好填写范围,后端在接收变更时再校验一次“该用户是否有权改这个单元格”,双保险。虽然前期多写一点代码,但后面出现权限纠纷时能省很多麻烦。
4.3 数据校验和单元格锁定的配合顺序
很多人在做日报填写场景时,既希望某些列不可编辑,又希望填写列校验格式,比如手机号必须是 11 位数字。Univer 中数据校验和锁定是两个独立维度,互不影响。数据校验需要单独注册规则:
import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation'; univer.registerPlugin(UniverSheetsDataValidationPlugin); // 给 A2:C10 添加“必填”或“数字”校验 worksheet.getRange('A2:C10').setDataValidation({ type: 'number', allowBlank: false, operator: 'between', formula1: 0, formula2: 999999, });配置数据校验后,填写区域依然是可编辑的,校验只对输入内容做约束。如果先后顺序搞反,比如先删了数据校验再取消锁定,表格会短暂出现空白校验状态,但不影响权限本身。建议在模板初始化时先注册校验规则,再设置锁定状态。
4.4 大表格内存与性能问题
Univer 毕竟是在浏览器里跑完整表格引擎,一方面功能强,另一方面也有性能成本。如果一张工作表有几万行数据,并且同时开启了多个插件,初次渲染和每次变更时的性能都会受到挑战。
实测下来,几个优化手段比较有效:一是去掉不用的插件,特别是公式引擎,如果表内没有公式,不注册公式插件能省掉明显的内存占用;二是关闭不必要的 UI 功能,比如剪贴板、查找替换等不常用工具栏能力;三是大量写入数据时使用批量更新接口,不要逐格setStyle,一次性构建数据模型再渲染。Univer 也提供类似虚拟渲染的机制,大数据量下表现会好很多,但你要设计数据模型时尽量别再串行执行大量细粒度命令。
实操建议汇总
这个项目我持续用了几个月,最大的感受是:Univer 的价值不是“做一个表格页面”,而是把表格变成业务系统的一个可交互数据组件。单元格锁定+开放填写的模型并不复杂,但它天然适合“模板由业务方定义、数据由参与者填写、结果被后台回收”的典型办公场景。
如果你现在还在用“发 Excel 文件、群里等回传”的流程,我建议直接把 Univer 嵌进内部系统里试试。先只做一张简单的日报表,把官方 demo 跑通,再把填写区域锁定,最后接上后端命令监听,整个链路很快就能完整走起来。等业务方尝到在线填写、实时汇总的甜头,后续扩展权限分角色、自动校验、跨表引用都会顺理成章。