
前两天有个朋友找我说他们要给几十家门店做一份月度数据填报模板表头、公式、汇总区都写死每个门店代表只需要在几个空白格子里填数字剩下的区域一个字符都不能动。以前在 Excel 里用“允许用户编辑区域”就能搞定现在业务要求在网页上完成还要手机也能打开。我第一反应是找现成的在线表格工具结果要么限制协作者数量、要么没法自定义填写范围要么公式能力弱得连 SUM 都得自己拼。折腾了一圈最后把目光落在Univer上——一个开源、可嵌入、用 TypeScript 写的办公套件。它最吸引我的不是“做得像 Excel”而是能精确控制“哪几格可以编辑其他格子打死不能动”。这篇记录就是我怎么用 Univer 把一张死模板变成可填写、不可乱改的在线表格以及这个过程中踩过的几个坑。1. 为什么是 Univer一张可填写的在线表格比想象中难做1.1 需求拆解填报模板到底需要什么很多人一听“在线表格”第一反应是做个电子表格软件。但实际上类似的业务场景往往简单得多管理员先定义一张表格模板普通用户打开后只能在指定单元格里填写内容剩下的区域既能看又不能动。我把需求拆成了四条管理员可以配置模板包括表头、行数列数、公式、合并单元格。普通用户只允许填写指定范围其他地方只读。填写内容要能校验比如必须填数字或者不能超过某个范围。最后能把数据汇总、导出最好不依赖桌面 Office。这四条听着不难真正做起来却处处是坑。普通的表格编辑器组件比如直接渲染一个table或者用文本框拼格子性能跟兼容性都会出问题如果自己用 Canvas 重写单元格渲染、编辑状态、选区管理那又是另一个量级的工程。所以一开始我就认定要用现成的表格引擎而不是从零造轮子。1.2 主流方案的对比开源表格库各有各的疼在敲定 Univer 之前我把常见的方案都梳理了一遍。纯读写的SheetJS / xlsx库负责解析 Excel 文件很称职但它根本不提供交互界面更别提编辑权限了。做过社区表格的Luckysheet虽然上手快可是前几年社区活跃度下降明显一些底层交互问题不好长期跟进。Handsontable功能强大但商业授权费用不低尤其公司项目里用合规成本要提前算。还有一类是调用云文档的开放 API省心是省心可数据都在人家手里定制空间也有限。Univer 在这几个维度里算是比较均衡的开源、TypeScript 编写、渲染层可以配合 Canvas 做到大表格不卡顿又保留了传统 DOM 交互的灵活性。更重要的是它从一开始就是插件化架构表格、文档、幻灯片是独立模块我只想要“表格 表单”能力就不用被迫上一整套 Office 全家桶。1.3 Univer 的定位不是仿 Excel是嵌到业务里的表格用 Univer 一段时间后我的体会是它最合适的定位不是“另一个 Excel”而是“业务系统里的表格基础设施”。它给你一块画布和一个数据模型至于工具栏放哪几个按钮、哪个区域能编辑、提交时怎么处理数据这些都由业务代码说了算。正因如此我才能很容易地把“管理员锁定表格用户填写指定单元格”这个场景实现出来。2. 保护与锁定理解 Univer 的权限模型先别急着写代码2.1 单元格 locked 只是标记工作表保护才是开关很多第一次接触表格权限的人会误以为只要设置单元格的locked属性为true用户就不能编辑了。这其实是 Excel 时代就遗留下来的惯性认知。locked 本身只是一面“告示牌”真正决定它有没有约束力的是工作表保护开关。两者配合才是完整机制先给所有单元格打上“默认锁定”的标签再开启保护让锁定生效然后对允许填写的那几个区域手动把locked标签撕掉。用大白话类比工作表保护是大门上的锁单元格的 locked 标记是房间里每扇门的门禁。你不锁大门房间里贴再多“禁止入内”都没用你把大门锁了但没单独给某间房开权限那间房照样进不去。Univer 也是同样的逻辑所以做配置时顺序很重要先全量锁定再定向解锁。2.2 命令系统编辑操作怎么被拦下来的Univer 的内部有一套命令系统用户界面上的每次修改本质上都是发给表格引擎的一条 Command。无论是输入字符、粘贴内容、拖动填充还是撤销重做最后都会汇聚到命令执行器。工作表保护的作用点就是这里——在命令真正改动单元格数据之前先检查目标范围是否允许被编辑一旦发现目标区域在锁定列表里命令就会被拒绝。这也解释了很多人的困惑为什么有的场景里复制粘贴能绕过锁定因为如果粘贴命令的检查逻辑只校验了选区而没有重新校验实际写入的目标范围理论上就会走到“看到选区允许就放了进去”的漏洞。所以我自己做验证的时候不会只测键盘输入还会专门测 CtrlV、双击填充、拖拽填充这些高频操作。2.3 用大白话理解权限分层在业务系统里我习惯把权限模型分三层看。第一层是 UI 层用户能不能在界面上看到这个格子可以编辑比如样式上把可编辑区域标蓝第二层是前端指令层Univer 的命令系统拦截非法写入第三层是后端数据层真正接收数据时再校验一次提交的单元格范围和数据格式。很多人只做了第一层也就是给单元格改了个蓝色背景但根本没动 locked 标记也没开保护那用户当然可以乱写。很多人做了前两层就觉得高枕无忧却忘了跑一个抓包脚本直接调接口提交脏数据。Univer 能帮你把第二层做得很扎实但第三层永远要靠自己的服务端兜底。3. 实操把一张模板装进 Univer并锁定不能填写的区域3.1 环境准备React Vite 的最小工程我这边用的是 React 配合 Vite 搭的前端工程Univer 的包体积不算小但用 Vite 做代码分割之后首屏压力可控。安装依赖的方式很简单用 npm 或 pnpm 把核心包拉下来就行具体包名以官方文档为准大致包含这几类npm install univerjs/core univerjs/sheet univerjs/sheet-ui univerjs/engine-render univerjs/engine-formula装完之后在组件里初始化一个 Univer 实例。由于不同版本 API 命名一直在微调我不会在这里贴一行完全照抄能跑的完整代码而是把关键流程讲清楚。核心思想就四步创建 Univer 实例、注册表格插件、传入表格数据结构、挂载到 DOM 容器。3.2 初始化工作簿并写入模板我的模板是一张“门店月度销售填报”表第一行是标题门店月度销售填报。第二行是表头门店名称、销售额、目标额、完成率、备注。完成率这一列用公式自动算等于销售额除以目标额。真正允许用户填写的只有 B3:D12 范围内的销售额、目标额、备注三列其他区域全部锁死。在 Univer 里创建这个工作簿时最关键的是先规划好单元格坐标。所有数据都可以先放进数据结构里再一次性渲染避免多次修改 UI 造成闪烁。比如用类似下面的结构组织初始数据具体字段写法要看当前版本的数据格式文档const workbookData { id: monthly-report, sheets: [ { name: 门店填报, rowCount: 30, colCount: 8, cellData: { // 第 1 行标题 0_0: { v: 门店月度销售填报仅可修改白色区域 }, // 第 2 行表头 1_0: { v: 门店名称 }, 1_1: { v: 销售额 }, 1_2: { v: 目标额 }, 1_3: { v: 完成率 }, 1_4: { v: 备注 }, }, }, ], };把这份数据放进 Univer 实例后表格会直接渲染出来。此时所有单元格默认应该处于可编辑状态因为还没做任何权限设置。下一步才是锁定。3.3 锁定全部单元格再解锁可编辑区域这里有一个执行顺序的讲究官方文档里不会特别强调但实际很容易翻车。我建议用“先锁后解锁”的顺序而不是“只对非编辑区域上锁”否则很容易漏掉新增的行列。第一步遍历当前工作表的所有单元格把locked字段设置为真。第二步对允许填写的范围比如 B3:D12再设置一次locked false。第三步开启工作表保护。开启保护时如果业务不需要密码就没有必要设置一个强制密码徒增用户负担。不过如果你想防一部分员工用非技术手段解除保护那设一个密码也能起到最低限度的阻挡作用。// 逻辑示意具体 API 请按官方文档调整 const worksheet workbook.getActiveSheet(); // 1. 全量锁定 setRangeLocked(worksheet, { startRow: 0, startColumn: 0, endRow: 29, endColumn: 7, }, true); // 2. 定向解锁可编辑区域 setRangeLocked(worksheet, { startRow: 2, startColumn: 1, endRow: 11, endColumn: 3, }, false); // 3. 开启工作表保护 worksheet.protect({ password: , allowSelectLockedCells: true, allowSelectUnlockedCells: true, });allowSelectLockedCells这个参数也值得说一句。很多产品要求的“锁定区域不能编辑但可以选择和复制”就靠它控制。如果你希望锁定区域连点选都做不到把它改成false即可。但我自己的经验是保持可选择可复制通常体验更好毕竟用户很多时候还是要参考汇总区里的数字。3.4 开启工作表保护与验证配置完成后务必把页面跑起来做一轮验证。我习惯按照这张表格逐项测试而不仅仅是肉眼看看测试项预期行为点击锁定区域无法输入状态栏有提示或光标被拒绝点击可编辑区域正常输入文字和数字在锁定区域按 CtrlV粘贴被拦截数据不写入双击可编辑区域后拖拽填充只能限制在解锁区域内扩展撤销重做即使撤销到历史步骤也不能改变锁定区域的保护状态重新加载页面模板结构和锁定配置保持不变这轮验证里最需要注意的是复制粘贴因为很多表格库在粘贴逻辑里会有“先全选目标区域再批量写入”的行为如果测试时没覆盖到后续上线就可能被用户钻空子。Univer 的保护机制在绝大多数场景下能挡住但版本更新频繁不能因为文档写了“支持保护”就直接信任实测一次才放心。4. 在线场景下的四个坑锁定失效往往不是 Univer 的问题4.1 只设样式不改 locked最经典的翻车现场我见过不少人用类似 Excel 的样式 API 给可编辑区域加了一个浅蓝色背景然后满心以为这就代表“可编辑区设置好了”。这在纯视觉上是合格的但权限上完全无效——样式只是让用户看到了“这里是用来填写的”真正拦截输入的是locked标记加工作表保护。所以让可编辑区域高亮没问题但高亮的同时一定要同步把那个范围的locked改成false。否则就会出现两种诡异情况要么整张表全被锁死要么整张表都能乱改。4.2 复制粘贴、撤销重做与协同编辑的边界Univer 的撤销重做机制非常强大但正因为强大它也会把“用户刚才的非法操作”恢复到某一个中间态。假如你完全依赖前端指令拦截而没有在服务端记录当前表格版本和已填写数据那么用户撤销到保护开启之前的某一轮操作时理论上可能重新获得一段可编辑的旧状态。这个问题在前端表格里普遍存在不是只有 Univer 才有。我的处理办法是把“保护配置”和“业务数据”分开存储。保护配置由管理员维护业务数据由用户提交后统一走接口落库页面上的表格只是交互容器不做持久化真相源。这样就算前端被玩出花来最终写入数据库的数据仍然要经过业务校验。另外在多用户协同编辑的场景中还要考虑两个用户几乎同时在改同一批解锁单元格的情况。此时 Univer 的协同底层会分发操作但最终的数据合并冲突策略取决于你的服务端实现。不要以为前端框架都帮你处理好了。4.3 前端保护不等于后端安全这是我最想强调的一条。任何跑在浏览器里的保护机制本质上都只是体验优化不是安全边界。用户只要打开浏览器开发者工具就能看到当前表格的数据结构、命令逻辑甚至手动调用内部 API 去修改单元格值。如果你把 Univer 当搜索按钮的前端组件那这种保护完全够用但如果你要的是“用户绝对不能改这个格子”这种强约束那一定要在后端接收数据时再做一次范围校验和格式校验。我之前的做法是前端提交时直接带上“我改了哪些单元格的坐标”和对应的值后端拿到后先校验这些坐标是否都在预先配置的可编辑白名单内再校验值类型最后才落库。就算有用户绕过前端攻击面也会被大大压缩。4.4 API 变动与版本升级的适配经验Univer 还是快速迭代阶段我试用期间就遇到了小版本升级后 API 签名变化的情况。最直观的例子是某些工具方法从核心包挪到了插件包还有渲染引擎的初始化方式调整。对于计划长期使用的团队我有三条建议。锁定大版本不要随手npm update追最新。把初始化 Univer 的代码封装成一个独立模块比如setupUniver()将来改 API 只动这一个文件。关注官方仓库的 release notes新版本出来后先跑一遍现有测试用例确认“锁定单元格”和“只读范围”仍然生效再升级。5. 把表单做得更完整数据校验、提交导出、移动端适配5.1 自定义工具栏与按钮只给用户一张表格是不够的业务上通常还需要“提交”按钮和“重新填写”按钮。Univer 的 UI 插件允许自定义工具栏把默认的菜单项收起来只留几个核心按钮这样普通用户看到的就是一个干干净净的填写界面。我的工具栏里最后只留下了这些保存点击后把当前解锁区域的数据提交到接口。重置把已经填写的数据清空恢复模板初始状态。帮助弹一个小提示告诉用户哪些区域可以填写。至于字体、边框、合并单元格、插入行之类的功能在我这个填报场景里统统隐藏。这样既降低用户学习成本也减少误操作导致模板结构被破坏的风险。5.2 数据校验与提交逻辑数据校验可以分两层。Univer 层面能做基础类型校验比如把某个范围的单元格设置为只能填数字或者限定日期格式。业务层面再校验一次比如销售额不能为负数、完成率不能大于某个阈值。我在实现时更喜欢把后端校验作为最终标准因为前端校验往往可以通过各种方式绕过而且不同浏览器对输入事件的兼容性也会影响校验体验。提交逻辑就相对简单了点击保存按钮时遍历所有可编辑区域拿到单元格的值组装成一个 JSON比如[ { row: 3, col: 1, value: 128000 }, { row: 3, col: 2, value: 100000 }, { row: 3, col: 4, value: 本月新店开业 } ]后端收到后再按门店维度落库。这里有个小技巧提交时不要把整张表的结构或样式数据一起传回去否则数据量会很大也没必要。5.3 导出 Excel 与移动端在线部署导出功能我直接用了 SheetJS 把前端数据转成.xlsx文件这样门店用户可以留底备查。注意导出的时候要把公式列的值也算好直接导出原始表格结构也行但用户打开后如果公式没有缓存值会看到一列空白。移动端方面Univer 的渲染基于 Canvas缩放和触摸滚动表现还可以但编辑体验相对桌面会弱一些。我的做法是移动端直接进入只读模式只允许查看汇总结果需要填写就去电脑浏览器操作。这样既省了移动端适配的精力也符合业务真实使用习惯。至于“在线”这件事其实 Univer 本质是一个纯前端库你只需要把打包后的静态文件部署到任意 Web 服务器用户打开浏览器就能用。如果想支持多人同时编辑、数据实时同步那需要自己接一套协作后端或者选择对应的商业方案。做表单填报这种场景我建议不要一开始就上实时协同先用“提交式”的方案后端把数据收上来再说。实时协同会引入大量冲突处理逻辑在需求并不明确时属于过度设计。最后分享几个我自己总结的实操体会Univer 在“自定义表格 限制编辑范围”这个方向上的完成度比我预期高不少。它最难能可贵的是把 Excel 那套保护机制搬到了前端同时又允许开发者通过命令系统介入拦截行为。但使用它的时候一定记住锁定与保护是两件事样式与权限是两件事前端与后端更是两件事。我个人的建议是拿到一个类似需求后先花半小时把“哪些区域可编辑、哪些不可编辑、锁定区域要不要允许选中复制、提交数据是否需要二次校验”这四件事定下来再去写代码。很多项目翻车并不是 Univer 能力不够而是需求里把“用户不能改某个格子”和“要做得像 Excel”混为一谈最后做成了一个又重又难的协作编辑器。如果让我重新选一次我还是会用 Univer。但我会更早地把“模板配置”和“用户填报”拆成两个独立入口管理员在配置页调整锁定范围填报人在另一个页面只看到可编辑区域。这个边界越清晰后面的代码就越省心。