Univer开源在线表格:锁定工作表,只允许编辑指定单元格
发布时间:2026/11/2 14:24:41 锦皓数字建站

Univer 这名字这两年在前端圈子里越来越常见了。它是一个开源的在线表格基础设施你可以把它理解成一套可以直接嵌入到自己系统中的“Web Excel 引擎”。更直白一点说如果公司要做数据填报、预算管理、订单录入这类需要多人在线编辑表格的场景与其从零写一个表格不如把 Univer 拉进来快速搞定一个带公式计算、数据校验、权限锁定、协同编辑的在线表格页面。很多人搜“univer在线”可能想找一个现成的 SaaS 表格产品但 Univer 的定位不是那个——它是一套让你自己造出“在线表格”的开源底座。这篇文章我会从项目架构、权限模型、实操配置和避坑经验几个方面把 Univer 的核心玩法拆开讲清楚重点解决一个最典型的需求怎么让用户只填写指定的几个单元格而其他区域完全不可修改。1. Univer是什么一个开源的“在线Excel”基础设施1.1 它的定位不是“一个Excel产品”而是“表格能力工厂”先说清楚一个容易混淆的点Univer 不是一个像腾讯文档、飞书表格那样开箱即用的在线 Excel 产品它是一个开源的表格基础设施也就是一套功能齐全的“表格能力工厂”。它基于 TypeScript 开发核心模块是电子表格Sheet同时也在覆盖文档、幻灯片等更多场景。你通过安装它的预设插件把表格的渲染引擎、公式引擎、协同协议、权限系统这些能力集成进自己的应用最终呈现出来的产品是由你自己定义的。这个方向其实是近年来的大趋势。以往企业做信息化系统如果需要在线表格最原始的办法是让用户下载 Excel 模板填好了再上传回来复杂一点的做法是自己写表格控件但那几乎等于跳进了无底洞。Univer 的价值在于它把表格底层那些极其耗费精力的模块——Canvas 渲染、公式计算、单元格样式、撤销重做、协同冲突解决——全部封装好了业务方只需要关注自己的需求边界。1.2 为什么“底部渲染”和“公式计算”是自研表格的两座大山如果你尝试过从零开发在线表格就会理解为什么大多数团队最终选择拥抱开源方案。第一座大山是渲染。一个表格可能有几十万行数据如果每个单元格都用一个 DOM 节点来渲染浏览器早就卡死了。所以现代在线表格普遍使用 Canvas 或类似 WebGL 的方案来做底层绘制只渲染可视区域内的格子滚动时按需重绘。Univer 的渲染引擎走的就是这条路线这也是它在处理大表格时还能保持流畅的关键。第二座大山是公式引擎。Excel 里一个简单的SUM函数背后牵扯到单元格依赖分析、算链调度、循环引用处理、错误值传递等多个环节。自己做一套能兼容日常使用场景的公式引擎没有几个人的团队很难谈成功。Univer 提供了内置的公式引擎支持常用的数学、统计、逻辑、查找函数并且把复杂的计算放到了 Web Worker 中避免阻塞主线程的界面操作。剩下的还有协同编辑、权限控制、数据校验、导入导出这些能力每一项单拎出来都是一个大工程。所以从我的角度来说在一个业务系统里需要嵌入表格时评估清楚哪些能力是核心壁垒哪些可以依赖开源基础设施本身就是一个很关键的架构决策。1.3 开源协议与社区状态Univer 使用的是 Apache License 2.0 协议这意味着不管是个人项目还是商业项目都可以免费使用、二次修改、闭源发布只需要保留版权声明。对于大多数企业内部系统来说这个协议非常友好不会有太多授权上的顾虑。项目在 GitHub 上有完整的源代码、文档和示例项目社区也提供了中英文资料。由于项目仍在快速迭代API 在部分小版本之间会有些调整这一点后面实操部分我会再提醒。2. 核心需求拆解如何实现“用户只能填指定单元格”2.1 “可编辑/只读”背后的真实业务场景我在沟通很多团队时发现大家提到“用户自定义表格”的需求其实并不是真的想让用户自由设计整个表格。绝大多数情况是这样一个场景业务管理员先设计好一张表然后在表里划出一块区域让普通用户去填写里面的一些单元格其他区域由系统根据规则自动计算、由管理员维护或直接锁死。举几个很典型的例子。第一个是月度数据填报财务在表头写好科目名称在汇总行写好公式普通业务员只能填入自己负责的几个数据单元格但表头、历史月份数据、汇总公式区域必须全部锁定否则一个误操作就会把整张表弄乱。第二个是外部访客登记运营做一张活动报名表姓名、手机号、邮箱三个单元格可编辑其他单元格隐藏或锁定防止访问者改动活动规则。第三个是供应链报价供应商只被允许在价格列里填写自己的报价其余列的信息由采购部门维护双方互不干扰。这类需求用 Excel 的话对应的就是“保护工作表”功能。Univer 也实现了这一套保护模型而且比传统桌面 Excel 更进一步的是它把这个能力暴露成了开发接口开发者可以在程序里动态控制甚至可以做到不同用户、不同角色打开同一张表格时可编辑的区域不一样。2.2 Univer的权限模型工作表保护与单元格锁定要理解 Univer 这个功能先记住两个关键词保护Protection和锁定Locked。这两个词很容易混淆但它们的组合关系其实很简单。“锁定”是单元格级别的一个属性。你可以把每个单元格想象成一个买回来的房子它默认有一个“锁”。如果只有一把锁没有把门关上这个锁是不生效的。“保护”是工作表级别的一个总开关相当于把整栋楼的门统一关上了。保护开启之后所有默认锁定状态的单元格都不可编辑而只有那些被明确设置为“未锁定”的格子才会在保护状态下继续允许用户输入。这个模型的好处是设计上的灵活。如果你希望整个工作表绝大部分都锁定只开放一小块区域给用户填写那么操作路径就是两步第一步开启工作表保护第二步把允许填写的那个区域设置成未锁定状态。反过来如果你想默认全部开放只限制某一个特定区域也可以先取消所有单元格的锁定然后对目标区域点亮“锁定”标志。Univer 把这套能力做成了 UI 操作和 API 两种方式前者适合手动配置后者适合在后端或前端逻辑里动态下发。2.3 用全局API配置“锁定工作表解锁指定范围”Univer 的运行环境里暴露了一个全局的univerAPI对象我在示例项目里最常用到的就是在初始化之后通过它拿到当前工作簿、当前工作表进而做保护配置。下面这段代码演示的效果是把当前工作表保护起来同时允许第 1 到第 2 列、前 10 行的单元格区域也就是类似A1:B10的范围保持可编辑。const workbook univerAPI.getActiveWorkbook(); const sheet workbook.getActiveSheet(); const protectionParams { protection: { // 开启所有单元格的默认锁定 locked: true, // 是否允许用户选中被锁定的单元格 selectLockedCells: false, // 是否允许用户选中未锁定的单元格 selectUnlockedCells: true, // 允许编辑的区域 ranges: [ { range: { row: 0, column: 0, rowEnd: 9, columnEnd: 1, }, locked: false, }, ], }, }; sheet.setProtection(protectionParams);这里要注意几个细节。第一row和column的索引从 0 开始也就是说row: 0, column: 0对应的其实是 Excel 里的A1。第二保护范围虽然是在前端配置的但更严谨的做法是当你的系统有后端时由后端根据登录用户的身份动态生成这套配置再下发到前端。因为前端的配置理论上是可以被懂技术的人修改的真正的安全管控要依赖服务端校验但我后面会讲对一般的业务使用场景来说先通过锁定来控制误操作和规范填写已经能解决掉 90% 的问题了。2.4 “Univer在线”的另一个含义灵活部署能力为什么很多人搜“univer在线”而不是“univer开源”我推测一部分原因是他们想找一个能在浏览器里直接用起来、不用部署的表格工具。Univer 本身是一个前端库它通过 npm 包发布官方给出了 CDN 引入和构建工具引入两种方式。如果你想快速体验可以在本地起一个工程用pnpm create初始化一个大前端的项目然后安装univerjs/presets这一类的预设包再按照官方示例把表格渲染出来。它天然就是跑在浏览器里的不需要任何服务端组件也能完成基础的表格编辑功能。但如果你要做成多人协作、权限隔离、数据保存那就需要一个后端来帮忙保存表格数据、维护权限、广播协同事件。Univer 的定位是“前端基础设施”它没有强制绑定特定后端你可以用 Node.js、Java、Python 任何一种后端语言来存储和转发它的数据结构。这一点对于那些想把表单做成 SaaS 产品的人来说特别关键——前端拿到的是能力底座后端自己控制业务。3. 实操演示从零搭一个“填报表”3.1 第一步初始化Univer并渲染出表格先说一个最重要的提醒Univer 目前处于快速迭代期不同小版本的包名和初始化写法可能存在差异。所以下面的代码我建议你对照官方文档里的“快速开始”示例来核对核心逻辑是一致的只是顶层装配方式可能会有微调。我自己在项目里用的是类似这样的初始化方式。先用包管理器创建一个基础工程然后安装 Univer 相关的依赖包。通常你需要的是主框架包、表格核心包、表格 UI 包和公式包。pnpm add univerjs/presets pnpm add univerjs/sheets pnpm add univerjs/sheets-ui pnpm add univerjs/sheets-formula然后在入口文件里初始化import { createUniver } from univerjs/presets; import { UniverLocaleEnUS, UniverLocaleZhCN } from univerjs/presets; import { UniverPresetSheetsCore } from univerjs/presets; // 具体预设包的导出名称请以上一级版本文档为准 const univer createUniver({ locale: zhCN, plugins: [ UniverPresetSheetsCore, // 如果还需要公式、UI就再装配对应的预设 // UniverPresetSheetsFormula, // UniverPresetSheetsUI, ], });初始化完成之后Univer会把表格画到页面上指定的容器里。因为渲染是基于 Canvas 的所以你只需要给容器设置一个明确的宽高例如#univer-container { width: 100%; height: 600px; border: 1px solid #e0e0e0; }这里我强烈建议把Container的尺寸设置为不小于 500px 的可视高度太小的画布会严重影响用户操作感受尤其是单元格弹编辑器和下拉选项时很容易被遮挡。3.2 第二步搭建表头与业务列结构有了表格之后下一步是往里面写入业务结构。假设我们做一张“销售数据填报”表表头是这样的A1 是“日期”B1 是“产品名称”C1 是“销售额”D1 是“备注”。我们希望用户只能填写 C2 到 C20 的销售额其他区域全部锁定。先把单元格数据写进工作表。Univer 里最常见的方式是操作univerAPI获取当前活动工作簿再通过范围写入数据const sheet univerAPI.getActiveWorkbook().getActiveSheet(); // 写入表头 sheet.getRange(A1).setValue(日期); sheet.getRange(B1).setValue(产品名称); sheet.getRange(C1).setValue(销售额); sheet.getRange(D1).setValue(备注);如果你想更高效一点一次性给一批单元格批量赋值const headers [ [日期, 产品名称, 销售额, 备注], ]; sheet.getRange(0, 0, 0, 3).setValues(headers);批量赋值的效率要远高于逐个单元格的setValue尤其是当表格结构复杂、初始化数据比较多的时候。每次调用setValue都会触发一次数据变更事件大量调用会带来不必要的性能开销。3.3 第三步锁定单元格并开放指定填写区现在到了核心环节保护这张工作表同时只让 C2 到 C20 这一块区域可编辑。按照我们前面讲过的保护模型代码是这样组织的const sheet univerAPI.getActiveWorkbook().getActiveSheet(); // 对整表开启保护 sheet.setProtection({ protection: { locked: true, selectLockedCells: false, selectUnlockedCells: true, // 明确指定允许编辑的范围C2:C20 ranges: [ { range: { row: 1, column: 2, rowEnd: 19, columnEnd: 2, }, locked: false, }, ], }, });执行这一段之后你会发现表格是“活的”但普通用户只能点击 C2 到 C20 的单元格进行编辑。点击表头或者其他区域单元格会处于选中状态但没有可编辑的外观键盘输入也不会生效。这其实很好地模拟了 Excel 里“保护工作表”的感觉。如果你的业务更复杂一点允许多个不连续的区域填写那么ranges数组里就可以写多个区间。比如ranges: [ { range: { row: 1, column: 2, rowEnd: 19, columnEnd: 2 }, locked: false, }, { range: { row: 25, column: 0, rowEnd: 27, columnEnd: 1 }, locked: false, }, ]这样用户既能在销售额列填报也能在下方某一块区域补充说明。其他区域依然是只读的。3.4 第四步给填写区加上数据校验规则光有可编辑范围还不够。用户可能在里面填上各种乱七八糟的内容或者根本不按规范的格式来。数据校验在这里就派上了用场。最常见的需求是销售额列只允许填数字不允许填文本备注列只允许从预设选项里选一个。Univer 支持数据校验插件。给范围设置下拉列表校验的代码大致是这样的const sheet univerAPI.getActiveWorkbook().getActiveSheet(); sheet.getRange(C2:C20).setDataValidation({ type: decimal, allowBlank: false, require: { min: 0, }, errorMessage: 销售额必须为大于等于0的数字, });如果你想让用户只能在预设选项中选择比如备注列只能选择“已完成”“进行中”“未开始”可以配置列表校验sheet.getRange(D2:D20).setDataValidation({ type: list, allowBlank: true, formula1: 已完成,进行中,未开始, showDropDown: true, });配置完数据校验之后前端界面上会出现下拉箭头用户如果输入了不符合规则的内容会得到提示。这在业务采集场景里几乎是个必选项因为单靠“锁定单元格”只能避免用户乱改结构但没法约束用户填正确的内容。3.5 把表格嵌入现有业务系统Univer 既然是前端库嵌入到任何现代前端框架里都没有本质障碍。不管你是用 React、Vue还是直接用原生页面只要保证初始化代码在页面挂载后执行并且提供了一个固定尺寸的挂载节点它就能正常工作。实际操作中要注意几个点。第一不要在canvas容器之外再叠加大量的高层级元素遮挡它Canvas 表格的交互是基于事件监听实现的被遮挡后容易出现“点不动”的假象。第二如果你在单页应用里切换路由离开页面时要销毁 Univer 实例避免重复创建多个画布导致内存增长。第三初始化时尽量做按需装配只装你需要的插件不要一上来就把所有功能全部引入因为打包体积对应用启动速度影响很大。3.6 关于“用户定义表格”的更完整解法热词里“univer 支持用户定义表格”这句话其实还可以再延伸一层含义。有些团队想做的不是“管理员定义好表格用户照着填”而是让普通用户自己也能设计表格结构比如自己增加列、拖拽行高、设置颜色、插入公式。这种自由度更高的需求Univer 也能支持因为它本身就具备完整的表格编辑能力。但这里隐含着一个产品设计上的平衡问题如果你把所有编辑能力全部开放那就意味着系统的表格结构可能被用户改得乱七八糟如果全部锁定又失去了在线表格的意义。我自己的经验是把权限模型做得更细一点在角色层面区分“设计者”和“填写者”。设计者拥有完整的结构编辑权限填写者只有单元格内容的填写权限。这个在 Univer 里通过保护配置的差异下发就能实现关键是在你的业务后端保存好角色与权限的映射关系而不是在前端写死。4. 常见问题与排查技巧实录4.1 表格“看起来锁不住”改动却能生效这个现象我遇到不止一次。配置了保护之后用户还是能修改某些单元格的内容排查下来常见原因有三个。第一个原因是保护配置在初始化之后被覆盖了。比如你先调用了setProtection但后续某个插件或者某段业务代码又对工作表做了一次重置把保护状态清掉了。排查方法是在操作后立刻读取一下当前的保护配置确认是否还处于保护状态。第二个原因是用户通过复制粘贴绕过了锁定。这里面有个细节锁定单元格只限制键盘输入和编辑但某些情况下用户从外部复制一片区域粘贴进来可能会触发单元格内容的批量覆盖。如果这个问题在你的产品里也出现了需要把粘贴行为的权限也收紧或者同时设置单元格格式保护禁止修改单元格的格式属性。第三个原因是保护和锁定的组合理解颠倒。注意看你的配置如果你设置了locked: true但ranges里没有给任何范围解锁那么整张表都不可编辑如果你设置了locked: false那等于所有格子都不锁只有了解和明确设置locked: true且开启保护的行列才不会被修改。4.2 大数据量下滚动卡顿有一类表格场景很特殊数据量极大比如一个 sheet 里放了三万行数据还带着几十列公式。这时候即使 Canvas 渲染已经很高效仍然可能遇到滚动时的帧率下降和公式计算阻塞。我的解决思路是这样。第一能不放在同一个 sheet 里的数据尽量拆分到多个 sheet每个 sheet 的数据量控制在合理范围内。第二公式频繁引用的区域如果不需要动态计算可以改成静态值减少每次数据变更后的计算链路。第三对用户端和数据端做一个交互上的分页在线表格只展示最近的数据历史数据通过查询条件按需加载。第四如果你有充裕的工程资源可以把大计算量的工作放到服务端前端只做结果渲染。4.3 公式不自动重算汇总结果空白Univer 的公式引擎是独立插件如果你在初始化时没有装配公式预设那么单元格里输入公式后可能只显示文本而不是计算结果。解决方法是确认你的插件列表里有公式预设。还有一个常见问题是公式运算在新增行列场景下的引用范围不正确。比如你允许用户新增了一行但汇总公式的范围还是旧的行数结果就会漏掉新数据。解决这类问题我习惯在业务逻辑里对汇总公式的引用区域做动态生成用代码去强制更新公式范围。4.4 不同角色看到不同的可编辑区域这套玩法如果只在配置里写死那就太浪费了。我在实际项目中经常把“保护配置”和“用户身份”绑定起来。举个例子员工登录系统后前端拿到当前的用户角色管理员就下发一套全开放的配置普通员工就下发一套只有填写区域开放的配置。在 Univer 里你可以通过univerAPI.getActiveWorkbook().getActiveSheet().setProtection(...)动态设置保护状态所以完全可以在用户打开表格之后根据接口返回的权限数据重新设置。注意不要把这个逻辑只放在初始化之前因为异步获取用户状态时表格可能已经渲染完了你需要等到权限数据返回后再执行保护配置的下发。5. 常见问题速查表我把这几个月开发生态里最容易被问到的问题整理成一张速查表你在自测或评审时可以直接对照。问题典型原因解决建议单元格锁不住还能改保护未开启 / 锁定状态配置错误检查setProtection是否执行成功确认locked默认值与ranges中的例外区域设置整张表都锁死了用户什么都做不了ranges没有配置任何可编辑区域在保护配置里给填写区设置locked: false表单下面拉菜单不显示数据校验插件未安装或showDropDown未开启安装校验插件配置showDropDown: true输入非法内容没提示数据校验规则中未开启提示配置errorMessage和警告策略粘贴内容绕过锁定部分操作路径未被保护限制粘贴操作或在粘贴后恢复保护状态数据量大滚动卡顿单 sheet 数据过多、公式链过长拆分 sheet、按需加载、服务端计算公式显示为文本公式插件未装配检查初始化插件列表确认公式预设已启用初始化未设置容器尺寸表格不显示Canvas 高度为 0给容器设置固定高度建议不小于 500px单页应用切换路由后内存持续增长Univer 实例未销毁在组件卸载时销毁实例及时释放事件监听用户编辑后数据不保存缺少后端保存逻辑根据业务需要自行实现表格数据序列化与持久化这张表里的问题我在给团队做内部培训时几乎每条都现场演示过尤其前三条是新手上线最容易踩的坑。最后再分享一个我在实际项目里验证过的技巧。如果你要把 Univer 做成一个真正可交付的“用户填表”系统可以把保护配置抽象成后端返回的数据结构前端只是消费方。比如后端返回这样的信息{ protected: true, editableRanges: [ { row: 1, column: 2, rowEnd: 19, columnEnd: 2 } ] }前端拿到之后再把这个结构绑定到setProtection的参数上。这样做的好处是当业务方以后需要调整哪些列让用户填、哪些列锁死时后台运维直接改配置数据就能完成不需要重新发版前端代码。我个人的经验是这种动态下发的架构比把权限写死在前端代码里要稳健得多也让开发和产品之间省去了很多来回沟通的成本。Univer 作为开源基础设施给了我们一把很顺手的钥匙但真正把业务做顺还是要靠工程侧把这些配置和数据流设计得足够清晰。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。