
最近要做一个在线登记表需求方描述得特别直白“表格我们早就画好了用户只能填我们要他填的那几个格子其余格子碰都不能碰。”这句话翻译成技术方案就是在线表格的“受控填写”。我最后选了Univer这个开源在线表格项目来落地原因很简单它既保留了类Excel的完整编辑体验又能把单元格级权限控制做得足够细。你完全可以通过它实现“用户只能填B2到E12其他区域全部锁定”的效果公式、表头、说明文字一概改不动。这篇文章我会把当时的选型思路、保护机制原理、实操步骤和踩坑记录完整写一遍。Univer不是某个商业产品而是一个开源的前端表格引擎所有渲染都在Canvas上完成支持公式、样式、合并单元格、协同编辑。如果你正在做内部系统、数据收集工具、低代码平台或者单纯想研究现代前端表格引擎怎么做这篇内容应该能帮你省不少弯路。1. Univer在线表格项目它到底在解决什么问题1.1 被问过无数遍的需求画好格子的表单式表格在真实业务系统里纯表单和纯Excel都不够用。纯表单适合字段少的问卷一旦字段多了、布局复杂了用户就会觉得“还是在Excel里直接填舒服”。可真正把一个Excel文件发给用户几个经典问题立刻就会出现格式被改乱框线、合并单元格、字体颜色全没了公式被覆盖合计列、占比列被手滑删掉数据填错位多一行少一行对不上版本混乱微信传过来传过去最后没人知道哪个是最新版本。Univer这类在线表格工具核心价值就是“把Excel的编辑体验搬进浏览器同时让开发者定义游戏规则”。这里的游戏规则包括谁能编辑、哪个区域能编辑、编辑后怎么提交、能不能多人同时编辑。这篇博文重点讲前两项也就是“用户定义表格划定可填写区域其余区域锁定只读”。要特别注意“受控填写”不等于给整张表加一个readonly属性。readonly是整张表都不能点用户根本没有填写的入口真正需要的是“局部开放、局部只读”而且最好能灵活调整开放区域。Univer对单元格有独立的锁定状态配合工作表级别的保护开关才能实现精确到格的权限控制。1.2 同类开源方案对比为什么我选了Univer前两年做这类需求大家会先想到Luckysheet或者Handsontable。我也把几个主流方案放在一起对比过结论比较明确项目渲染方式协同能力保护粒度扩展性LuckysheetCanvas DOM 混合有但依赖服务端配合基础一般社区活跃度已下滑HandsontableDOM商业版才支持有偏网格组件一般需要自己处理Excel兼容UniverCanvas命令驱动协同架构原生支持工作簿/工作表/区域三级插件化扩展点丰富我选Univer的主要原因有三点。第一底层渲染全部走Canvas数据量大一点也扛得住。我在测试环境里导入了3万行、20列的数据滚动和缩放基本流畅没有DOM方案那种明显的掉帧感。对内部系统来说这个体验差距是决定性的。第二Univer的命令系统把所有操作都统一成命令消息。无论用户是输入数据、删除行、还是合并单元格都会经过命令链路。这意味着开发者可以在命令层做拦截和校验——这也是实现“只能填指定区域”的关键架构基础。没有这一层想做细粒度控制就只能靠UI层遮罩迟早会被绕过。第三Univer整体走插件化路线Sheet、Doc、Slide分层清晰。团队可以按需打包只引入表格能力不想要的模块直接去掉前端体积可控。1.3 适合谁来用、能用在什么场景从我接触到的实际项目来看以下几类场景和Univer的匹配度最高内部管理系统需要嵌入表格编辑能力比如ERP里的单据录入、后台配置表数据收集场景用户只需要填写指定单元格数据直接落库省去人工汇总低代码平台给非技术用户提供“像Excel一样的业务表格”同时保留权限控制想学习前端表格引擎原理的开发者Univer源码结构清晰配合官方文档可以顺滑入门。换句话说只要你的产品里有一条“表格模板 用户填写”的业务链路Univer就值得纳入技术选型。2. 核心需求拆解让用户只能填写指定单元格2.1 保护机制的本质默认锁定按需解锁理解Univer的单元格保护机制最快捷的方式是先理解Excel的那套语义。Excel里每个单元格都有一个“锁定”属性但它不是独立生效的必须配合“保护工作表”这个总开关。当保护开关打开时所有lockedtrue的单元格都不能编辑而lockedfalse的单元格即使保护开启也依然可以编辑。Univer在这一点上对齐了Excel的模型因为团队从一开始就把“Excel能做到的事情Univer都要做到”当作目标。所以我们的配置思路也完全清晰把所有单元格默认锁定新建表格时默认就是locked状态把允许用户填写的区域显式设置成lockedfalse打开工作表保护开关设置密码或者权限策略。顺序建议固定为“先全表锁定再批量解锁开放区”否则逐格操作时容易漏尤其是区域重叠的时候。我当时第一次配置就因为先解锁后锁定把整个B列都给锁死了折腾了半小时才反应过来。2.2 落到Univer里保护对象与可编辑范围的映射在Univer里需要理解三级保护粒度工作簿级、工作表级、区域级。绝大多数业务场景用工作表级保护就够了。如果一个表格需要开放多个不连续的区域可以把这些Range统一放在“可编辑区域集合”里。我把当时的配置思路整理成了下面这段示意配置。需要注意Univer不同版本的字段名变化比较大这里的结构只是表达“保护开关 默认锁定 开放范围”三层逻辑// 示意配置字段名请以当前版本官方类型为准 { protectionEnabled: true, protectionPassword: template2024, defaultCellLocked: true, unlockRanges: [ { sheetId: supplier, range: B2:E9 }, { sheetId: supplier, range: G2:G9 }, ], }不要把这个当成可直接粘贴的配置它描述的是数据模型。实际落地时我更建议把这类元数据存到后端配置表里而不是硬编码在代码中。线上调整开放区域时改一条数据库记录比重新发版靠谱一百倍。用户侧的体验其实很直观开放区域可以做成浅黄色底色锁定区域保持白底。鼠标移到可填区域时光标是空心十字移到锁定区域时光标变成禁止符号。用户不需要任何说明自己就懂哪里能填、哪里不能动。2.3 一个实证案例供应商信息登记表的字段设计做模板之前我强烈建议先画一张“什么可填、什么不可填”的映射表这个动作能减少一大半沟通成本。我用供应商信息登记表举个例子单元格区域内容是否可编辑A1:G1标题供应商信息登记表否A2:G2填表说明文字否B4:D4供应商名称 / 税号 / 联系人是B5:D5电话 / 邮箱 / 地址是F4:G6历史交易数据由公式计算否B8:G10补充备注是这里有几个值得强调的细节。第一历史交易数据区域不仅锁定而且依赖公式自动计算。用户不用填也改不动省得他们不小心把公式覆盖了。第二备注区开放是因为业务上确实需要灵活补充信息。第三一旦映射表和业务方确认完毕后续开发基本不会返工。很多项目死在“需求变来变去”就是因为一开始没把可编辑边界定下来。字段设计时还有一个额外考虑单元格的保护状态要和校验规则配套。比如手机号字段开放填写但必须校验11位数字邮箱字段开放但必须符合邮箱格式。我在下一节会讲到怎么把校验规则挂到开放区域上。3. 实操搭一个“只能填指定格子”的Univer在线表3.1 两种落地路径模板文件优先代码配置兜底真正做项目时我不建议从零用代码画表格模板。业务方手里的Excel模板可能已经用了好几年表头、框线、合并单元格、条件格式都是现成的。用代码重建纯属浪费时间而且后续每次改样式都要重新发版。我的推荐是“模板文件优先”路线业务方在Excel或WPS里画好模板包括表头、说明、格式交付一份xlsx文件开发把它作为模板资产存到对象存储或数据库Univer启动时加载这个模板再用代码或配置把开放区域解锁打开保护用户每次打开表格都是基于模板生成的一个新副本。这样做的好处非常明显模板的样式细节完全交给业务方把控前端代码只负责“加载模板 配置规则”。后续改表头只需要换模板文件完全不用动代码逻辑。如果模板需要根据用户身份动态生成或者开放区域因人而异那就再叠加一层代码配置。两个方案可以混用核心原则是“模板归模板规则归规则”。3.2 初始化Univer并加载模板我接入时的版本是0.2x系列官方API迭代速度很快所以这里重点讲流程代码里只保留关键骨架。先安装依赖npm install univerjs/preset-sheets univerjs/core然后初始化Univer实例import { Univer, LocaleType } from univerjs/preset-sheets; import { UniverSheetsCorePlugin } from univerjs/preset-sheets/plugins; const univer new Univer({ locale: LocaleType.ZH_CN, plugins: [ UniverSheetsCorePlugin(), ], });不同版本的preset包名和插件导出方式可能不同但只要打开官方Playground看一眼几分钟就能对齐。初始化完成之后需要把模板xlsx加载进工作簿。我当时的做法是后端把模板xlsx作为静态资源对外提供前端fetch文件转成ArrayBuffer通过Univer的导入能力把文件内容写入当前工作簿导入完成后再执行保护配置。Univer对xlsx的导入支持比较完整合并单元格和大部分样式都能保留这也是我敢于走“模板优先”路线的底气。3.3 配置保护规则核心的锁定与解锁操作加载模板后就需要对开放区域做unlock然后打开保护。这里用示意代码演示核心流程还是那句老话不同版本API差异极大请以官方类型定义为准。const sheet getMainSheet(univer); // 把开放区域设为“未锁定” sheet.getRange(B4:D9).setLocked(false); sheet.getRange(B11:G11).setLocked(false); // 打开工作表保护 sheet.enableProtection(your-password);这种写法能跑通但它高度依赖API封装。在我接触Univer的过程中更稳的方案其实是直接用命令系统做拦截。Univer所有单元格值变更都会经过命令链路我们可以注册一个自定义监听器判断本次变更的Range是否在授权列表里如果不在就直接拒绝。我也理解这种链路拦截初看有点抽象但它确实更适合生产环境。因为它不依赖某个版本的具体API而是和Univer的核心架构绑定。哪怕官方哪天把enableProtection改名了你的拦截逻辑依然有效。3.4 数据提交与校验填完的内容怎么拿回来用户填完表格之后需要点一个“提交”按钮。这个按钮不需要塞进Univer内部放在自己的业务页面上就行。点击提交后我们需要遍历所有开放区域读取单元格值做必填和格式校验再组装成JSON发给后端。我当时的数据读取思路是维护一份开放区域配置和模板元数据放在一起用Univer的取值API遍历每个开放区域校验规则单独维护比如手机号正则、邮箱正则前端先做一次校验后端接口再完整验证一遍双重保险。对少量字段直接逐格取值没有问题。但对几百行明细数据逐格读值的性能就不划算。更高效的做法是整块Range一次性取出再按坐标映射到业务字段。下一节我会详细讲合并单元格和坐标映射的坑。3.5 用户侧的完整体验流用户打开链接 → 看到模板表格 → 浅黄色区域可编辑其余区域点击无反应 → 填写后点提交 → 数据写入后端。整个流程不需要任何手动保存动作表单天然具备结构。管理端要做的也很简单模板每次基于原始文件生成新副本避免上一个用户填的数据串到下一个人的单子上。用户提交后管理员在后台列表里查看或导出汇总。这个模型很朴素但非常稳定。4. 落地过程踩过的坑与排查技巧实录4.1 保护不生效先按这三个方向排查我在调试过程中遇到最多的问题就是“明明设置了锁定用户居然还能编辑”。排查下来原因基本逃不出这三类第一没有真正打开保护总开关。只设置单元格lockedfalse或true但没有启用工作表保护锁定属性是不生效的。很多新手会把“设置锁定”和“启用保护”当成一件事。第二锁定状态被样式优先级覆盖。某些主题或者格式操作会重置单元格的保护状态。如果你发现某个区域怎么设都锁不住先查是不是在设完锁定之后又执行过批量样式操作。第三模板被二次保存。管理端预览模板时如果意外触发了保存可能会把开放区域重新锁回去。这个问题非常隐蔽我们当时花了整整一天才发现是同事在预览时点了保存。4.2 Canvas渲染的性能优势以及两个代价Univer用Canvas渲染滚动、缩放比DOM方案明显流畅这是它的核心优势。我在本地实测导入了3万行数据之后滚动依然不掉帧公式计算在Web Worker里执行主线程不阻塞。但代价也要说清楚。Canvas图形界面下文本输入法候选框、富文本展示、无障碍支持都依赖额外适配。我们测试时发现搜狗输入法在某些Windows浏览器版本上候选框偶尔会悬浮位置异常。这个问题在发版前必须全流程实机测试不要只看Chrome macOS的完美表现。另外一个代价是如果要做DOM overlay比如自定义悬浮按钮需要处理好层级关系。Canvas有自己的渲染层普通DOM元素浮在它上面时容易产生坐标偏移。4.3 多人同时填写简单填报表就别上协同Univer本身具备协同编辑能力但对于“受控填报表”这个场景我强烈建议不要一开始就上协同。原因很简单两个人同时编辑同一个开放单元格产生冲突的概率特别高。而填报表业务本来就不需要多人同时操作同一份副本。我的方案是“副本隔离”每个用户打开模板时生成独立副本谁的数据都不覆盖谁提交时统一写入主数据表。只有企业内部多人协同编辑同一份计划表、同一个销售漏斗这类场景才值得启用协同。如果非要启用协同至少要配合锁定机制当一个用户开始编辑某个Range时服务端给这个Range加上占用锁其他用户只能只读。这个逻辑要在Univer之外实现因为它本质上是业务权限而不是表格渲染问题。4.4 合并单元格与数据导出坐标映射的坑模板里几乎必用合并单元格读数据时如果还按“每个格子读一次”会把合并区域里非左上角的单元格读成null。正确的做法是读取范围后做一次“主格映射”合并区域只取左上角的值。我当时维护了一个合并单元格的解析函数流程是获取当前Range的合并区域信息遍历开放区域时如果发现当前坐标落在某个合并区域内直接返回该区域左上角单元格的值把值按业务字段映射到JSON结构里。如果不做这一步用户明明在合并单元格里填了内容提交后却拿到一堆null体验直接归零。4.5 给新手的避坑清单锁定Univer版本不要随手upgrade。这个项目迭代快小版本之间API都可能在变建议在package.json里锁死版本号升级前单独用分支验证。中文locale资源要完整引入。否则右键菜单和工具栏会显示英文对内部系统来说是“看起来就没做完”。大数据表格建议关闭自动公式重算需要计算时手动触发。否则每次改动都触发全表重算操作会明显卡顿。不要把工作表保护密码放在前端逻辑里当安全凭证。保护功能只是防手滑防不了懂技术的人直接调API改数据。真正的权限判断必须放在后端接口前端控制只是体验层的事。最后聊一点个人体会做受控填写表格真正难的一直不是“怎样让某个格子只读”而是把“哪些格子可以填、为什么可以填、填完之后怎么收”想清楚。Univer把底层的单元格保护、渲染、协同都做扎实了开发者的精力就应该花在模板管理和权限规则上。我后来的做法是把模板结构、开放区域、校验规则统一放到一张配置表里业务变了就改配置而不是改代码。这一招在需求频繁变动的环境下非常管用。如果你也在折腾在线表格我建议就从“局部可填”这个小场景开始它会逼着你把Univer的架构从表层功能一直看到命令系统和渲染内核比单纯刷文档有用得多。