最近被一个在线数据收集的需求折腾得够呛:几十个渠道、上百家门店要定期交表,格式要求完全一样,但对方又经常手滑改表头、删公式,最后汇总回来一团乱。最初想用在线文档共享,权限粗、人数一多就失控;换问卷系统,又没法复现那种“一行一条记录”的表格手感。最后我找到了univer,一个开源的在线办公套件,核心是用TypeScript写的Canvas渲染引擎,能复现类Excel的交互体验,同时把“区域权限”做成了很核心的能力——简单说,它可以让我定义好一张带表头、带公式的填报表单,只开放特定的单元格给用户编辑,其余格子全部锁定。这篇文章把我实际跑通这个需求的思路、踩过的坑和可以直接抄作业的流程都整理出来。
这篇内容适合三类人看:一是想做轻量级数据收集但又不想被问卷系统限制交互的开发者,二是想在自建系统里嵌入“可填写的表格组件”的前端工程师,三是被Excel模板管理逼疯的运营和产品同学。我会从架构认知、权限机制、实际复现到问题排查,一步一步把Univer这张牌打明白。
1. 为什么我选了univer而不是问卷系统
1.1 它解决的是真正让人头疼的问题
传统的表格收集流程,最麻烦的不是“收集数据”,而是“约束填法”。发一个Excel模板下去,有人把表头改了,有人把合计行删了,有人直接在旁边加了一列备注,等到回收汇总的时候,每一份文件的格式都是不一样的,清洗数据的成本远大于填数据的成本。
问卷系统能固定字段,但又把交互限制死了。问卷是“竖向一条条答”,而很多业务场景下用户脑子里想的是“横向一行行填”:比如门店上报当日销售额、供应商填每周库存、班级登记参赛名单,表格的多列、多行、横向滚动体验是问卷替代不了的。
Univer正好卡在这个空档上。它是一个开源的在线的Univer表格引擎,支持公式、样式、合并单元格、条件格式、数据验证这些Excel常见能力,而且整个界面就是类Excel的操作体验。更关键的是,它的权限体系能落到“区域”这一级:我可以把整张表设为只读,再单独放行指定的若干区域允许编辑。改完模板、锁好表头,剩下的活就是让用户在能填的格子里填数。
1.2 它的底子长什么样
简单说说Univer的架构,不是为了让大家都去读源码,而是知道它的边界在哪里,做技术选型才心里有底。
Univer整体分几层:核心层负责文档模型和命令系统,Sheet引擎负责工作簿、工作表、单元格的数据结构和计算逻辑,UI层提供工具栏、右键菜单、单元格编辑这些交互,再往上则是插件体系,比如协同编辑、权限、公式、图表、条件格式都是各自独立的插件。这种插件化结构带来的好处是,项目可以按需加载,不需要把整个办公套件都塞进一个包里。
渲染方面,Univer走的是Canvas渲染路线,而不是传统的DOM树逐单元格渲染。实测下来,几万行的表格滚动起来也很顺,卡顿感远低于同数据量的DOM表格。这一点对在线填表场景很重要,因为一旦有很多用户同时打开表格,浏览器性能直接决定体验。
还有一个现实情况需要提前说:Univer的版本迭代非常快,API变化也比较大。我这篇文章里涉及的接口和写法,对应的是最近这一两个大版本的使用方式,如果你装的是更新的版本,个别方法名有变化是正常的,但“锁定工作表+白名单式放行编辑区”的思路是稳定的,认准这个思路,换成任何版本都能很快找到对应API。
2. “只有指定单元格能填”这件事,Univer是怎么实现的
2.1 先拆解需求本质
“用户定义表格,然后让用户填写一些单元格,其他单元格用户无法修改”这句话换成表格术语,就是两件事:一是表格的“结构”是模板作者定义的,包括表头、公式、样式、数据验证;二是表格的“可编辑权”是按区域分发的,通常默认全部不可编辑,只放开指定范围。
你看一张纸质报销单就很好理解:表头和金额合计公式是印刷好的,谁也不会去改;需要填写的部分就只有日期、事由、金额几个空格。Univer的权限模型要解决的就是把这种物理约束变成数字约束。
这背后涉及三个角色定位:模板设计者(设置锁与开放规则)、填写者(在开放区域输入)、数据汇总者(只读查看,不被误改)。如果只有“全部可编辑”或“全部只读”两种状态,这个需求是没法玩的,好在Univer的权限模型天然就支持区域级控制。
2.2 权限保护的底层逻辑
Univer的权限模型可以理解成两级开关:
第一个层级是工作表级。对某个Sheet设置“编辑权限=关闭”之后,这张表整体就进入只读保护状态,任何单元格都无法直接修改。这个开关非常粗暴,但它是安全底线,保证用户不会在别的区域乱动。
第二个层级是区域级。在整体只读的前提下,给指定范围单独设置“允许编辑”,相当于开了一个白名单。白名单内的格子,用户正常输入、修改、删除;白名单外的格子,虽然能看到内容、能滚动、能选中,但没有编辑权,双击或直接输入会被拦截。
这套机制的关键在于“默认拒绝+显式放行”的思路。它不是用复杂的规则描述“哪里不能改”,而是先关掉一扇门,再把装了几个窗户的区域打开。对于几十个甚至上百个Sheet的模板批量生成场景,这种白名单模式比黑名单模式好维护得多,因为新增可编辑区不会跟原有规则冲突。
这里还有一个小细节值得注意:权限拦截不等于视觉禁用。实际操作中,锁定区域依然可以正常选中、复制、查看公式结果,只是不能编辑。用户通过光标形状变化(编辑状态下变成禁止符号)或者直接输入没反应,才能感知到不可编辑。这种设计其实很合理,因为它保留了“查看”体验,不会因为读保护把整张表变成一张死图。
2.3 为什么这个方案比自研表单靠谱
我自己动手写过简单的在线填表组件,走到后面都会碰到一个尴尬问题:数据校验和录入体验的结合很难做好。你要做一张带公式的合计行,SPA里单独实现计算逻辑;你要做下拉框联动,又得维护一堆状态;你要做合并单元格、冻结首行、条件格式,每一样都是不小的工程。
Univer把这些都封装好了,这是最大的省心点。用它的完整表格交互,用户不需要重新学习,Excel怎么操作在Univer里就怎么操作。填表人只需要点格子、输入、回车换行,完全符合直觉。而你通过权限机制把这些能力“裁剪”成刚刚好的形态,既保留了类Excel的体验,又限制了用户的操作边界。
当然也不是说Univer就是银弹。如果你的场景根本不需要表格形态,只是收集“姓名、电话、留言”这种三五个字段的短数据,问卷系统或普通表单组件更轻、更快。但一旦涉及行列结构、公式联动、批量录入,Univer就是那个卡位很准的工具。
3. 从零跑通Univer在线填表功能
3.1 搭一个最小可运行的前端工程
我这边是直接用Vite + TS起的React项目(Univer也有Vue3、原生JS的接入方式,原理差不多)。如果你的项目已有前端框架,可以直接把Univer作为依赖装进去。
npm create vite@latest univer-form -- --template react-ts cd univer-form npm install npm install @univerjs/presets注意,我装的是预设包(Preset),它会一次性带来当前推荐的Sheet核心、UI、公式等模块,最省事。Univer也有按需引入各个独立包的方式,但第一次跑通Demo不建议折腾,先用预设包把页面跑出来,再研究裁剪。
然后写一个最小初始化:
import { Univer, LocaleType } from '@univerjs/core'; import { UniverPreset } from '@univerjs/presets'; const univer = Univer.newUniver({ locale: LocaleType.ZH_CN, });具体入口文件的写法受当前版本影响很大,如果你的版本跟我写的不完全一致,直接参考官方文档的“Quick Start”部分复制示例即可。这里想强调的是,先把官方Demo跑通,确认页面能出现一个空白工作簿,再往下做。我见过不少人上来就啃API文档,花了半天连个表格都没渲染出来,所以先跑通再说。
3.2 在代码里定义模板表结构
跑通空白工作簿之后,下一步是生成模板。不需要手动在界面上慢慢画,代码里循环写入表头,效率高得多。我当时的模板大概是这样的:
- 第一行:序号、门店名称、负责人、本月销售额、本月退货额、备注
- 第二行及以下:留空,等待用户填写B~E列
- 最底部一行:合计,用公式SUM统计“本月销售额”和“本月退货额”
写表头的核心代码逻辑类似:
const config = { sheets: ['门店填报'], rowCount: 200, colCount: 10, }; // 通过 univer 创建或获取 Sheet 后,循环写入表头单元格 headers.forEach((header, index) => { sheet.getRange(0, index).setValue(header); sheet.getRange(0, index).setFontWeight('bold'); sheet.getRange(0, index).setBackgroundColor('#F2F2F2'); }); // 在最后一行写入合计公式 sheet.getRange(totalRow, 4).setFormula(`SUM(B2:B101)`);这里要注意的是,Univer的行列索引是0开始的,跟我们Excel里看到的第一行、第一列会差个一,调试的时候记着这个偏移,不然容易把表头写错行。模板里所有公共元素都定义好之后,剩下的工作就是“锁住它”。
3.3 锁定工作表并放行指定编辑区
这是整个需求最核心的一步。先整体锁定工作表,再给包装好的区域开放权限。
如果你只是临时配置一两张表,完全可以在UI界面操作:右键点击工作表标签,找到“保护工作表”相关的菜单,开启保护,然后在“允许编辑区域”里加规则,选择要放行的单元格范围即可。这种方式适合人工维护少量模板。
但如果你像我一样需要批量生成几十张区域模板,必须在代码里统一配置。Univer的权限API整体思路是设置工作表的默认权限为false,再对指定Range设置编辑权限为true。简化后的代码逻辑类似:
// 1. 关闭整张工作表的编辑权 univerAPI.sheet.getPermission().setWorksheetPermission({ permissionIds: ['edit'], allowed: false, }); // 2. 对 B2:E101 区域单独放行编辑权 univerAPI.sheet.getPermission().setRangePermission({ ranges: [{ startRow: 1, startColumn: 1, endRow: 100, endColumn: 4 }], allowed: true, });这段代码是示意写法,实际的API签名在不同版本里有差异。我强烈建议你在设置完权限之后,用一个只读访客身份打开页面测试一遍:点一下锁定区域的单元格,确认无法输入;再点一下开放区域的单元格,确认能正常编辑。只有实测过“能填的和不能填的都符合预期”,这个模板才算做完。
另外别忘了,权限配置最好在表格内容、数据验证等设置全部完成后统一执行一次,避免中途调试时反复修改带来的不一致。如果某个模板已经发出去被人填过数据,再改权限规则要更加谨慎,能追加允许区域就不要去动默认锁定的部分。
3.4 发布分享与多人填写
前端工程跑通后,部署其实很常规:build出静态资源,扔到任意静态托管服务或自家Nginx即可。任何人都能打开这个页面,看到你定义好的填报表单。因为权限保护是前端框架层面强制的(UI限制了输入),对普通用户来说它就是一张不能乱改的表。
如果你只是想收集数据,不要求实时协同,最简单的做法是让用户各自打开页面,把一行数据填进去,然后再通过后端接口把这一行的内容提交到数据库。我实际采用的是“用户填完当前行,点击提交按钮,前端把整行数据POST到后端接口”的模式。这种模式的好处是数据直接进库,根本不需要再解析合并Excel文件,省掉了最痛苦的汇总环节。
如果确实需要多人同时编辑同一份表格文件(类似在线文档那种实时看到别人打字的效果),那就不能只靠前端了,需要部署Univer的协同后端服务,通过WebSocket做双向同步。这个方案能力强但同时引入一套服务端复杂度,适合真的需要“同一张表多人同时填”的场景,如果只是各自填各自的行,用提交入库的方案反而更简单稳当。
4. 实操中遇到的高频问题与排查记录
4.1 权限设置了但用户还能改锁定单元格
这个问题我一开始也踩过,后来发现原因几乎总是同一个:工作表级锁定和区域级放行的作用顺序不对,或者把“样式上的只读视觉”当成了“真正的编辑拦截”。
在做表格UI的时候,很多开发者习惯用“设置单元格只读样式”来告诉用户这里不能改,但Univer的权限体系是不认样式标识的,它认的是权限点。如果你只是把锁定区域的背景色改成灰色、或者用编辑器把格子设置为不可编辑,而没有真正修改权限对象,那双击测试的时候照样能弹出输入框。
排查方法很简单,在权限配置完成后,打印一下当前工作表的权限对象,看看默认编辑权限是否为false,以及放行区域的ranges是否真的包含了要开放的区间。然后再到页面上实测,锁定区域点击后应该没有任何编辑光标出现,而不是仅仅看起来灰掉。
4.2 开放区域可以填,但用户不小心把公式拖拽覆盖了
这是保护规则的边界问题。你开放了B2:E101区域,用户完全可以在E101格子上拖拽自动填充,把下面的合计行公式破坏掉。因为我只限制了“编辑权”,没有限制“行列结构变更权”或“自动填充产生的覆盖”。
解决方案有两种:一是把合计行也放进保护范围,同时单独禁止整行插入/删除操作,这样用户无论怎么拖拽都动不了合计区域;二是把合计公式放到一个完全不开放的工作表里,填报表只留“数据区”,汇总统一在后台由程序计算。我个人更推荐第二种,因为把计算逻辑放在用户可见并且可感知的表格里,总免不了被各种操作碰坏。
4.3 数据验证下拉选项不生效
当你给开放区域设置下拉数据验证时,要注意数据验证规则和应用区域的关系。早期版本的Univer中,数据验证的规则受限于“一个区域一个规则”,如果你把数据验证加到整列,但权限区域只覆盖了其中一部分行,可能会导致验证不触发或者应用范围不对。
我的经验是先把所有开放区域范围明确出来,在同一个区域对象上一并设置好数据验证、背景色、输入提示,然后再去配权限。区域设置尽量保持一致,减少规则之间的交叉,排查起来也简单。
4.4 大数据量滚动卡顿
虽然Canvas渲染已经很快,但如果一个工作表里塞了几万行数据,同时每个单元格又有样式、批注、验证规则,初次渲染和滚动还是会有点吃力。遇到这种情况,建议只保留必要的行列数,不要动不动就建一个全工作表范围的数据区域。
另外,尽量不要在模板里一次性给超大范围(比如A1:Z10000)设置权限规则,权限引擎会按范围计算匹配,范围太大白白增加运算量。把范围限制在实际可能填写的行数附近,既安全又流畅。
4.5 常见问题速查
| 问题现象 | 可能原因 | 处理办法 |
|---|---|---|
| 界面显示灰色但仍可编辑 | 只做了视觉只读,没改权限点 | 用权限API设置编辑权限为false |
| 开放区域与锁定区域范围重叠 | 两个Range规则互相覆盖 | 检查规则优先级,或统一成单一开放范围 |
| 合计行被拖拽覆盖 | 没锁定合计区 | 将合计行纳入保护,或把公式挪到后端计算 |
| 下拉验证不弹 | 验证范围与开放范围不一致 | 按最终开放范围重新设置验证规则 |
| 权限API方法找不到 | 装的是不同版本 | 查官方文档对应版本API,核心思路不变 |
5. 再往前走一步:从“填表”到“数据后台”
5.1 常见应用场景
用Univer实现“不让乱改的填空表”之后,它的应用场景比想象中宽。最典型的是门店/渠道数据收集:总部定义好模板,各门店每周在固定区域填写销售额、库存、客户数,提交后由后端汇集。另一个常见场景是供应商报价,采购方把报价单模板锁定,只放开“单报价”列,供应商填完提交,采购方端看到的是统一格式的报价表。还有内部预算审批、教学练习表、招聘评分表等等,本质上都是“固定模板+部分区域开放填写”的套路。
5.2 配合数据验证做出更严谨的表单
权限只是第一步,真正让填报表单好用的是权限+数据验证的组合。比如“本月销售额”这一列可以设置数值范围,只能填0到999999;“退款原因”这一列设置下拉列表,只能选“质量问题、物流问题、无理由、其他”;“备注”列限制最多500字。Univer的数据验证配合区域保护,能让用户在框架内自由但不能乱来。
我的建议是先设计好每一列的填写规范,再一次性把表头、数据验证、权限保护全部配置完。表单体验做得越克制,反而越让人敢填,因为用户知道填什么、在哪填、填错了会提示,使用成本很低。
5.3 后续还可以这样扩展
如果项目继续往下走,可以考虑把Univer嵌进现有的中后台系统,做成一个可复用的“表格模板组件”。后端再预留一个接收行数据的接口,前端填完一行提交一行,数据直接落到数据库。更进一步,可以用Univer把数据库里的表反向渲染成可编辑表格,由管理员在后台调整开放规则,这样整个系统就从“表格收集数据”变成了“表格驱动业务”。
另外,移动端的触控操作适配也是值得关注的,Univer的渲染引擎在触屏上的滚动和缩放体验整体不错,但复杂的右键菜单和拖拽填充在手机上还是不如桌面端顺畅,如果用户群体里有大量手机填报需求,建议单独设计一个简化版的移动端模板,少开放一些复杂交互列。
5.4 一点真实的使用感受
我把这个方案跑通之后最大的感触是,Univer把“模板作者”和“表格消费者”之间的边界分得很干净。模板作者可以尽情用公式、条件格式、数据验证去做一张很专业的表,而填写者只能碰到该碰的地方。过去我需要靠发文档、盯版本、催格式来维护数据规范,现在整张表本身就把规范约束住了。
如果让我给后来者一个建议,那就是不要一上来就纠结底层API和架构细节,先照着官方Demo做完一个最小填表闭环,再逐步加入公式、验证、权限和提交逻辑。在这个项目里,权限机制的代码量其实很少,大部分时间花在打磨模板细节和交互体验上。等你把第一条业务线跑通,后面复制到新场景只是改改表头、调调范围的事,这套方案的价值就会持续放大。