☰
Univer在线表格引擎:全表只读加白名单,实现指定单元格可填写
2026/10/1 3:57:19 网站建设 项目流程

做内部报名表那年,我第一次被“共享Excel文件”坑到怀疑人生。表格发到群里,两小时后回收,标题被改了两版、说明文字被删掉一截、还有同学的填表内容直接覆盖了别人的数据。后来我换成了Univer,把表格嵌进自己的系统里,用权限控制锁死模板区域,只让填写者在指定单元格里输入,才彻底根治了这类问题。

Univer是一个开源的在线文档引擎,准确说是以电子表格为核心、同时支持文档和幻灯片的Web端方案。它在表格场景里解决的核心问题就是:你不需要从零写一个Canvas渲染器,也不需要在网页里嵌入一个不好控制的iframe版Excel,而是直接用TypeScript SDK把一张“真正的表格”铺到自己的页面上,并且通过权限模型决定谁能改、能改哪里。这篇文章我会先讲清楚它解决了什么,再带你跑通最小示例,然后重点拆解“表格模板锁定、只留指定单元格可填写”的实现链路,最后聊一聊上线后会遇到的坑和它的更多玩法。

1. 从“表格被改乱”到认识Univer:一个填表需求能逼出多少麻烦

1.1 那段共享Excel搞采集的日子

当时我要做的是一个员工活动报名登记表,大概二十列,包含部门、姓名、工号、参加场次、饮食禁忌、备注这些字段。第一版方案很朴素:把做好的Excel模板发到工作群,让大家下载填完再回传。结果收到的文件五花八门:有人直接在原文件上改,把表头格式弄乱了;有人把前面的说明行删除了;最头疼的是两个同事同时填写,后保存的人把先保存的人的内容整个覆盖掉了。

我后来尝试了Excel自带的“保护工作表”功能,勾选某些单元格可编辑,再通过共享链接分发给同事。想法是好的,实操却一地鸡毛:很多同事的手机端打开后各种兼容问题,电脑上需要选择“启用编辑”,权限设置稍微复杂一点业务上级自己就处理不了。让我印象最深的是,有个同事为了改一处被锁住的字段,直接把整个工作表取消保护重新保存了。那一刻我意识到,靠Excel本身控制填写区域,对于非技术用户来说根本不现实。

再往后我又看了“金数据、问卷星”这类表单工具。它们确实能收集数据,但界面是传统的“字段表单项”,不是用户习惯的表格填报界面。业务方想要的效果很明确:打开之后就是一张熟悉的Excel表格,表头、公式、说明都已经摆好,用户只需要在指定的白色区域里填内容,其他区域碰都碰不了。这种介于“表单”和“表格”之间的需求,传统表单工具实现不了。

1.2 市面上的现成方案为什么都不够顺

这个问题拖了一阵子,我逼着自己去对比了市面上能嵌入Web系统的前端表格方案。本来也考虑过自己用Canvas写一套表格编辑器,但很快就放弃了:不说无穷无尽的单元格交互细节,光是一个撤销重做、复制粘贴、列宽拖拽、公式联动就够一个团队忙半年。但用开源的成果,选项也没有想象中多。

我列过一张对比表,把几个主流方案放在一起看:

方案嵌入自有系统单元格保护/白名单公式引擎数据回传方式备注
Handsontable可以支持limited edit弱事件监听更像数据网格,不是完整Excel
Luckysheet可以较弱较强快照社区热度下降,更新较慢
嵌入Google Sheets iframe受限支持强受限样式和权限不能自定义
Univer可以原生支持强快照/命令监听/协同React/Vue/Vanilla都能接

对比之后就比较清楚了:Handsontable本质是“数据网格”,用户编辑体验和Excel差距很大;Luckysheet在中文社区很火,但它核心能力和API的现代性已经落后了;Google Sheets嵌入是省事,可权限、域名和样式都无法贴近业务。Univer的优势在于它是用TypeScript从零重写的完整表格引擎,渲染层用Canvas,公式引擎独立设计,还专门做了权限/锁定机制,可以精细控制每个单元格能不能编辑,这正好命中我的核心需求。

1.3 Univer的定位:可嵌入系统的在线表格内核

Univer本身不是一个“做好的在线Excel网站”,而是一套可以嵌入你业务的文档引擎。它分几层:核心包负责表格数据模型;渲染引擎负责往Canvas上画格子;公式引擎负责计算;UI插件提供工具栏、右键菜单、状态栏这些界面;再往上还有协同后端的能力。这意味着你可以只取你需要的部分,不需要工具栏就干脆不注册UI插件,用一个纯只读表格展示数据也完全没问题。

对我当时的需求来说,Univer最吸引我的有两点。第一,它不需要iframe,直接把一张表格渲染在业务DOM节点里,样式和行为都归我控制;第二,它的权限保护模型能从“整个工作表只读”到“指定范围允许编辑”进行组合,这正好等于“管理员定义表格,填写者只能填指定单元格”。下面我会先带你把最小环境跑起来,然后再进入这个核心功能。

2. 先跑起来:Univer的最小可运行示例

2.1 准备环境与装包

Univer的SDK是标准的npm包,只要你的前端项目是Webpack、Vite、或者React/Vue这类常规工程,都能直接接入。我当时的项目是Vite + React,Node版本用的18。需要注意Univer的新版本迭代很快,当前常用的大版本号是0.x,不同小版本之间的API有变化,但核心思想一直没变。在你动手之前,先打开docs.univer.ai看一眼当前版本对应的包名和初始化方式,整篇文章的代码很多地方都要跟你实际安装的版本对应上。

安装命令很简单:

npm install @univerjs/core @univerjs/sheets @univerjs/sheets-ui @univerjs/ui @univerjs/engine-render

新版官方还提供了一组打包好的预设包,用起来更省心。但我个人建议刚开始不要直接用预设,因为隐藏细节太多,出了问题反而难排查。我更喜欢分别引入核心包,把每一层都看得明明白白。如果你的项目是用的某一个稳定版本,建议把版本号固定在package.json里,不要放任npm把依赖升级到小版本里的最新版。这一步后面我会再专门讲,因为版本错位是Univer项目最大的坑之一。

2.2 初始化第一个在线表格

装好包以后,初始化逻辑在纯JavaScript里就能跑。最小示例大概是这样的:

import { Univer, LocaleType, defaultTheme } from '@univerjs/core'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; const univer = new Univer({ locale: LocaleType.ZH_CN, theme: defaultTheme, }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverSheetsUIPlugin, { container: 'app', });

HTML那边只需要准备一个id为app的div,并且给它一个明确的高度:

<div id="app" style="width: 100%; height: 600px;"></div>

刚上手最容易犯的错就是容器只给了宽度没给高度,结果页面上一片空白,还以为没跑起来。Univer的渲染引擎会在容器尺寸变化时自动重算,但如果初始高度是0,它从开始就没法正确布局。

跑起来之后你能看到完整的表格工作区,包括行号列号、单元格网格、左上角全选按钮,以及底部的工作表页签。你可以直接双击单元格输入内容、拖选区域、调整列宽,和用Office打开一张普通sheet的体验非常接近。

2.3 把业务想要的表结构灌进去

跑通空白表格之后,下一步就是把“管理员定义好的表格模板”塞进去。Univer的底层数据模型是“工作簿快照”,简单理解就是整个workbook的JSON描述,里面包含了每个sheet的单元格值、合并单元格、行高列宽、工作表属性、甚至公式。你可以通过API在内存里创建一张新表并写入值,也可以直接把一张设计好的Excel文件导出成快照再加载。

我当时更喜欢的做法是让管理员在Univer界面里手工设计表头、说明行、合并单元格和必要的公式,设计完以后由前端把这套快照保存到后端。用户打开页面时,后端把这份快照返回,Univer再把它加载进内存。这样“用户定义表格”这个需求就落到了:管理员负责定义,系统负责保存和分发,填写者只负责往白名单区域里填内容。

加载快照的思路级代码长这样:

const snapshot = await fetch('/api/template/activity-signup').then(r => r.json()); const workbook = univer.getCurrentWorkbook(); await workbook.loadSnapshot(snapshot);

这不是完整的官方API,但方向是对的。如果你用低版本API,可能要操作univer实例本身而非workbook对象,所以这里的关键不是背书,而是理解Univer以工作簿为单位管理数据,页面刷新后所有内容都可以从快照恢复。

2.4 怎么判断已经跑通了

跑通的标准不是“看到网格”,而是几个细节同时成立:能看到底部Sheet页签且能切换;单元格支持选择、编辑和撤销;工具栏上的字号、加粗、颜色能用;窗口缩放时表格跟着自适应。如果以上都正常,说明你的集成环境没问题,可以进入下一步做权限控制了。

3. 让用户只能填指定单元格:权限保护实战

3.1 需求拆解:管理员编辑、填写者只读、填报区可写

回到最初那个报名表需求,实际存在两种角色。管理员是表格的设计者,负责定义字段、调整模板、在特殊情况下修改数据;填写者是终端用户,只能看到表格,并且在特定的可编辑区域里填入自己的信息,表格的其他部分对他们来说跟一张图片没有区别。

如果用一句话描述需求,就是“对整个工作表默认只读,指定小范围放开编辑”。这跟在Excel里先锁定全部单元格、再单独解锁几个区域的做法完全一致。Univer把这种能力原生暴露出来了,你需要做的只是调用对应的保护和例外接口。

3.2 设计思路:全表锁死再开白名单

当时我第一眼看到权限模型时,下意识的想法是“把不能改的区域一个个标记为只读”。但后来我想通了,正确的思路应该是反过来:先把整张表锁死,再把能改的区域逐个加进白名单。为什么?因为表格模板里大部分区域都该被保护,可编辑的填报区域是少数。如果默认所有人都能改,那你必须保证每个不该被动的单元格都被你想到、标记到;一旦漏标一个,用户就能把表头或者公式改掉。

全表锁死再开白名单的好处是安全边界非常清晰:我只需要把“哪些区域允许填写”配好,剩下的自动全部只读。哪怕后来表格模板里新增了列,新增的区域也被默认保护,不会出现意外。这个思路建议你务必沿用,别一开始就顺着Excel时代的“锁定个别单元格”的旧习惯走。

3.3 思路级实现代码与解释

由于Univer版本迭代速度很快,我在下面给出的是思路级代码,你可以按当前版本的API查找具体方法名替换。整个过程分为三步:拿到当前sheet、把整表编辑权限设置为false、把允许填写的区域加进保护例外。

// 1. 获取当前工作表 const sheet = univer.getActiveSheet(); // 2. 整表只读:关闭编辑权限 sheet.getPermission().setSheetPermission({ edit: false, }); // 3. 把可编辑区域加入白名单 // 假设报名表里 C5:C100 是需要填写的区域 sheet.addRangeProtection({ range: { startRow: 4, startColumn: 2, endRow: 99, endColumn: 2 }, permission: { edit: true }, }); // 如果有多块分散区域,逐个add即可

整个操作要放在后端返回模板快照、表格加载完成之后执行。执行完之后,你可以再通过UI把这个sheet标成“已保护”,让所有用户看到的都是一张锁定的模板。

有一类细节很容易被忽略:Univer里的“保护”默认是对用户交互层的限制,比如点击只读单元格不会进入编辑状态、快捷键输入会被拦截。但不同版本对复制粘贴、拖动填充这类操作的处理不一致,所以在配置完权限后,一定要用真实浏览器场景自测一遍,重点测“选中只读区域后粘贴是否还能把值写进去”。如果发现粘贴还能侵入,就需要额外在粘贴事件前置拦截。

3.4 填写者实际看到的体验

权限配置完成后,我打开另一个浏览器窗口模拟填写者视角,体验是符合预期的。整张表只有一个区域是白色可编辑状态,其余单元格点击时没有光标,双击也不会进入编辑;输入文字时只在白名单单元格里生效,选区一旦拖到受保护区域会出现不允许编辑的提示。配合了冻结首行和把可填区域底色设成浅黄色之后,用户一眼就能明白该往哪儿填。

这里我强烈建议你在开发阶段多做两件事。第一,把白名单区域的边框和底色做得比只读区域更显眼,这是减少业务方投诉的最好办法;第二,为可编辑区域加一些输入规范提示,比如“本次报名每人限报一场,请在C列填写场次编号”,用批注或者额外说明行写在模板里。用户体验上的细节往往比权限代码更重要。

3.5 把填写的数据安全地收回来

表格是让人填的,填完以后数据必须回传到业务系统。Univer不强制你走某一种数据通道,但最朴素的做法是监听命令事件。用户每次编辑单元格,Univer内部都会派发一个变更命令,前端截获以后可以把变更内容增量发给后端。

思路代码大致是这样:

univer.getCommandService().onCommandExecuted((command) => { if (command.id === 'sheet.mutation.set-range-values') { // 组装变更信息,推给后端保存接口 saveCellChanges(command.params); } });

更进一步,如果你不想记录每一条变更,也可以在用户点击“提交”按钮时直接把整个工作簿的快照或者指定区域的值一次性发给后端。两种方式各有适用场景:增量变更适合草稿自动保存,快照提交适合表单收尾。我在实际项目里用了“增量监听 + 提交按钮做最终校验”,二者配合,数据基本不会丢。

不过我必须强调一件事:前端保护根本挡不住一个懂技术的人。用户可以通过DevTools直接改内存数据,或者绕过界面调用接口往只读区域写值。所以后端收到单元格数据时,必须再次根据模板对应的单元格行列号和工号做白名单校验,只接受可编辑区域里的数据。前端的锁定是防止误操作,后端的校验才是安全底线。

4. 权限保护背后的设计逻辑

4.1 工作表保护与范围保护:本质是“默认允许+例外”

Univer的权限模型核心可以概括成一句话:默认状态是允许编辑,保护操作把整张表“收口”,范围保护又在收口的基础上开出一个个“窗口”。这种“默认允许、按层收窄、再按范围例外”的设计,跟操作系统里“默认拒绝所有端口、再按需放行”的安全策略是同一个思路,比在千千万万个单元格里逐一打标记可靠得多。

它的组合方式非常灵活。你可以做“整表只读+三块可编辑区域”,也可以做“整表可编辑+某块敏感区域只读”,甚至可以做“列A可编辑、列B只读、列C受公式保护”。我建议你在设计模板时先画一张角色权限矩阵:谁、能看什么、能改什么、能改哪几行哪几列。这张矩阵一旦理清,落到Univer API上只是几分钟的事。

4.2 保护做在哪里:前端交互层、命令层与后端校验

理解权限还需要明白它落在哪一层。Univer的前端至少在三个层面处理保护:交互层拦截鼠标和键盘的编辑意图;命令层阻止非法的变更命令被执行;数据层保证快照和协同同步时不会把非法写入扩散。这三层加在一起,能让一个正常用户在界面上毫无漏洞地遵守规则。

但正如前面所说,这些全是“前端规则”。Univer只是浏览器里的一段JavaScript,所有逻辑都在用户手里,只要想绕,总能绕开。所以在做架构设计时,一定要分清职责:前端保护负责体验和防误操作,业务系统后端负责最终的数据校验和权限审计。我见过一些团队迷信前端锁定,结果被人改了数据都不知道,这个教训很深刻。

4.3 协同编辑下的权限组合

如果你需要多人同时填写同一张表,比如一个班级的多个学生同时填同一份报名表,那就要考虑协同冲突了。Univer本身有协同编辑的基础能力,可以通过后端服务把多个客户端的变更合并到同一份工作簿快照上。但大部分表单采集场景其实不需要毫秒级协同,一篇报名表被两个人同时修改同一格的可能性也很低。

更务实的做法是:按用户维度分配不同的表格区域,或者干脆一个人一个sheet,最后汇总。我那位业务负责人后来接受了“每人填写自己的一个sheet”的方案,整体稳定性和开发量都明显更可控。这也算一个经验:Univer能协同,不代表所有场景都该用协同。权限保护解决的是“能不能改”,行级隔离解决的是“改了会不会冲突”,两个问题最好不要混在一起。

5. 从Demo到上线:我踩过的坑和解决办法

5.1 版本迭代太快,依赖对齐要谨慎

如果只让我说一条Univer项目最重要的教训,那就是版本。Univer现在还处在快速迭代期,官方文档上同一个功能在不同版本里的包名和API都不一样。最典型的情况是:直接用npm install latest装完,初始化代码却总是报错,因为主包和插件包之间版本不一致。

我当时的做法是打开官网示例仓库,把其中package.json里的版本号原样抄下来,然后锁死。升级版本时绝不零散升级,而是整套升级后跑一遍全量功能测试。另外Univer的某些插件依赖Web Worker,在Vite构建时可能遇到worker加载路径问题,内存和网络请求里会报404之类的错误,这时候检查构建配置里对worker资源的处理是第一步。

5.2 容器、主题和本地化这些“小事”

前面提到容器必须给高度,这算第一坑。第二个常见坑是主题和本地化。Univer默认是英文界面,创建实例时不配locale,工具栏和右键菜单就是英文的。中文环境务必配置LocaleType.ZH_CN,还要注意字体资源是否加载完整,否则某些中文输入场景的渲染会有偏移。

还有一个观感上的细节:Univer UI插件自带的工具栏很完整,但如果你不想让用户看到一堆不需要的按钮,可以通过UI扩展隐藏菜单项,或者干脆注册更精简的插件配置。我最后保留了常用编辑按钮,去掉了宏、脚本相关的入口,界面干净很多。这类配置在文档里都有,但很容易被忽略,导致你交付的是一套“功能过于完整”的表格,业务方反而不知道哪些可以用。

5.3 性能表现:大表格与公式重算

Univer用Canvas渲染,性能底子不差。我拿一个一万行、三十列的报名数据sheet做过测试,滚动和缩放都很流畅。但要注意的是:性能和“操作复杂度”不是一回事。如果表里塞了很多合并单元格、复杂条件格式、大公式数组,交互响应会明显下降。特别是当你一次性set几千个单元格的值时,正确的做法是先组装一个批量变更命令,而不是循环逐格设置,否则前面的格子还没渲染完,用户已经觉得卡了。

如果数据量确实大,建议按需加载:先用模板和首屏数据渲染,等用户滚动到接近底部时再异步加载后续行。Univer是前端内存模型,一次性灌入几十万行会让初始化和内存占用都变得难看。

5.4 遇到报错的排查思路

跑Univer项目遇到的报错,很多不是业务代码的问题,而是资源加载和版本问题。我总结了一套排查顺序:先看network面板有没有404的静态资源;接着确认当前导入的包版本是否一致;然后用官方示例仓库的配置对比你的初始化参数;最后才怀疑是自己的业务逻辑写错了。如果你在控制台看到某个插件未注册之类错误,八九成是registerPlugin的调用顺序或参数配置问题。

另外,Univer的渲染和交互深度依赖Canvas,所以浏览器版本太旧的话,可能出现光标错位、选区闪烁这些奇怪问题。这类问题通常无解,直接建议用户升级浏览器,比在代码里打补丁强多了。

6. 一个表格引擎还能延伸出哪些玩法

一个能在线渲染表格、支持公式、又能精细控制权限的引擎,能做的远不止报名登记。我在这套能力之上陆续做了几个不同的业务场景,都挺顺利。

第一个是周期性数据填报,比如每周的业务报表。管理员在月初定义好报表模板,员工每周在指定区域填数字,月底自动汇总。这个场景和报名表本质一样,只是数据量大、需要历史版本留痕。用Univer天然保留快照,每次提交都存一个版本,业务方随时可以回看上周填了什么。

第二个是评分表,比如面试评分、学生成绩录入。考官打开表格只能看到自己负责的考生列,其他列全部只读,分数域可以配置公式自动求平均。这里权限保护和公式引擎是核心,体验比纸版表格强一个量级。

第三个是审批流里的明细填充。比如采购申请单,申请人填商品明细,审批人只读查看并在指定位置填写审批意见。用表格比表单更灵活的地方在于,明细行数不固定,用户可以按需插入新行,而审批区域始终被锁定,不会被人误改。

还有一个方向是“轻量BI”。Univer支持读取数据快照、展示图表和数据透视表,把后端统计数据灌进去后,可以直接在一个页面上给业务方呈现可交互的报表面板。它不会替代专业BI工具,但在内部系统里做个快速数据查看页面完全够用。

从技术框架角度,Univer还有文档和幻灯片能力,意味着你可以在同一个前端工程里统一表格、文档、PPT三种内容形态,未来做一个在线Office套件也不是没可能。但对大多数项目来说,当前最能落地的还是表格场景。我的一位前同事甚至把它嵌进了他们公司的低代码平台,让用户可以拖拽生成自定义报表模板,然后用类似“管理员定义、终端填写”的模式开放给客户,算是一个很有意思的玩法。

我个人实际使用下来,最大的感受是Univer把“在线表格能力”这一块硬骨头啃下来了,剩下的关键是你如何设计权限和数据流。它还在快速迭代,很多文档细节也不是那么完善,所以动手前把版本锁好、官方的示例跑一遍、再按自己的场景慢慢扩展,是最稳的路径。如果你的系统缺一个“能让用户填表、又不让他乱改”的在线表格,不妨从这个小而明确的切入点开始试试。

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

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

立即咨询