ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Univer在线表格实践:如何实现部分单元格可编辑其余只读

2026/10/2 5:50:21 拓冰建站 浏览量
Univer在线表格实践:如何实现部分单元格可编辑其余只读 做表格类业务尤其是“在线填报、权限受限但又要协作”的场景最近绕不开一个名字Univer。这不是又一个网页版Excel的Demo而是一套基于TypeScript的开源办公套件核心是高性能Canvas渲染的电子表格引擎同时还带文档和幻灯片能力。我最早是被“用户定义表格只开放某些单元格让人填写其余单元格不可修改”这个需求拖进坑的把Univer彻底翻了一遍之后发现它在这一类需求上的完成度确实比很多老牌开源表格项目都高。这篇文章就围绕这个场景把我从选型、架构调研、实际集成到踩坑排错的经验一次性讲清楚。Univer能做的事一句话概括就是把一个体验接近原生Excel的在线表格通过SDK方式嵌入到你的Web项目里并且可以对单元格做细粒度的权限控制、数据校验、公式联动、协同编辑甚至自绘UI。它适合有表格能力的团队使用也适合需要在复杂业务中定制“半开放填报表”的前端开发者。下面我从为什么选它、核心设计逻辑、实操落地步骤、常见坑四个维度展开。1. 为什么是Univer在线表格引擎的选型逻辑1.1 Univer 到底是什么能解决什么问题Univer 从底层看是一个渲染引擎加上一套可插拔的业务模块。市面上大多数开源表格项目都在用DOM表格或者Canvas拼拼凑凑而Univer从一开始就走Canvas渲染路线内部维护一份完整的Range、Row、Col、Cell模型所以在十万行数据、大量合并单元格、复杂样式混排的场景下滚动和写入都不会直接卡死浏览器。我把它理解成“一个自带业务框架的表格内核”。它不只是给你一张能画网格的画布还内置了公式引擎、条件格式、数据校验、跨表格引用、协同编辑协议、单元格锁定/保护等能力。对于“类似Excel但又要限制输入范围”的线上表格需求这些能力是几个独立库加起来很难对齐的。在实际项目里最典型的使用方式有两种一种是整个页面就是一个Univer实例用户打开后只看到一张类似Excel的工作表另一种是把Univer嵌入现有系统作为某个流程里的“单据录入区”比如排班表、项目计划表、仓库盘点单、问卷收集表。后者正是“部分单元格可修改、其他单元格只读”需求的高发区。Univer解决的根本问题不是“画一个表格”而是“让表格成为业务系统里的一个可靠编辑组件”。你需要开放A列让人填姓名锁定B列的公式不让人碰C列只允许选下拉项——这些在Univer里都有对应的能力模型而不是靠外部监听键盘事件去“拦截”输入。1.2 与其他表格解决方案的横向对比选型阶段我实际对比过四类方案Luckysheet、x-spreadsheet、Handsontable、Univer。这里列一张对照表方便你快速判断。方案渲染方式开源协议单元格保护能力公式引擎协同编辑生态活跃度适合场景LuckysheetCanvasMIT较弱需自写逻辑有基础公式依赖第三方服务器实现已长期不活跃轻度在线表格展示x-spreadsheetCanvasMIT无完整权限体系基础公式无一般简单表格编辑HandsontableDOM非商业需付费有行/列/单元格保护可接入公式插件企业版支持活跃但商用受限数据网格录入UniverCanvas开源免费核心模块基于Workbook/Sheet/Range的保护机制较完善公式引擎官方协同方案很活跃业务系统深度集成最初我倾向Luckysheet因为网上资料多Demo效果也好看。但落地时发现Luckysheet的定位更接近“预览Excel效果的在线表格”要把它改造成“只允许填写指定区域”的填报组件你需要自己拦截editor事件、自己维护每块区域的锁状态、自己处理行列增删造成的边界遗漏这些工作和自研一个表格编辑器已经没什么区别了。Handsontable的交互体验确实好也在部分项目里被广泛使用社区版却无法商用高级权限功能集中在商业版里一旦业务后续要加协同、公式重算二次开发成本会非常高。我后来选择Univer核心原因有三个第一开源免费基于核心协议可以自由扩展定制第二它的架构是模块化的权限带、数据校验带、公式引擎、协同服务是分开注入的我只需要启用需要的模块第三它自身就是一套带业务属性的办公套件不是只有网格渲染而是把“填写、校验、锁定、计算、提交”这一整条链路都考虑进去了。1.3 为什么“部分单元格可编辑、部分只读”如此常见先理解需求本身的逻辑。几乎所有线上表格都会包含两类信息一类是需要人工录入的原始数据另一类是由原始数据计算出来的派生数据或者是业务上不允许随意修改的统计字段。举个例子一张门店排班表门店员工只能填写自己名字对应的“到岗状态”而“出勤率”“违规扣分”这类字段用公式自动计算如果开放编辑一个手滑就会把整张表的计算结果弄乱。再比如考勤补卡申请申请人只能填“日期”“补卡原因”审批人只能改“审批意见”而“员工编号”“部门”必须从系统读取后锁定靠人工手填既容易填错也容易被恶意篡改。这种需求如果直接放到普通在线表格里任何人都能点进任何单元格输入系统就失去了可信度。Univer提供的单元格保护能力本质上就是给表格加了一套“角色化权限边界”你可以声明哪些范围只读、哪些范围允许编辑、哪些范围按用户状态动态决定。在我看来这比很多自研系统里“保存后再校验一遍”的做法更有价值因为它在源头就限制了用户的输入路径用户从一开始就只会看到自己能填的区域。2. 核心设计拆解如何实现“用户可填、其他不可改”2.1 Univer的权限与保护模型Univer的权限体系与Excel的“保护工作表”思路相似但不是简单的全表加锁。它支持三个层级的控制Workbook级、Sheet级、Range级。你可以先锁定整张工作表然后额外开放某些区域也可以默认全部开放单独锁定某个区域。这两种模式分别对应“严格填报场景”和“局部敏感字段保护场景”。我实际用得最多的是“先锁后放”策略。新建一个工作表后默认对它做保护所有单元格均不可选中编辑然后通过worker获取指定工作表的控制权把允许填写的区域加入到开放名单里。这样即使后续有人通过外部接口插入了一行新数据新行也默认处于锁定状态不会漏出未保护的编辑漏洞。另一个常用能力是隐藏公式和隐藏单元格。对于用户不可修改且不想让他看到的计算逻辑可以只保留计算结果不让前端拿到公式串。注意这里的“隐藏”是在Univer配置层面生效的配合后端接口才能做到真正的数据隔离前端隐藏只是第一道防线。Univer还为Range提供了按用户维度做配置的入口。你可以结合登录态在渲染表格之前先判断当前用户是“填写人”还是“管理员”动态生成不同的保护配置并渲染到同一个表格结构上。这样同一个模板不同角色看到的就是不同的可编辑区域体验上和普通表单系统几乎一致。2.2 可编辑单元格范围的配置方式配置思路不复杂核心是把“范围声明”和“锁定状态”挂到表格上。下面给出一段基于常见版本思路的示意代码实际API名称以你部署的版本为准重点是理解流程。// 获取当前活动工作簿 const workbook univer.getActiveWorkbook(); const sheet workbook.getActiveSheet(); // 第一步默认锁定整张表 sheet.protectTable({ lockAll: true, // 所有单元格不可编辑 }); // 第二步把允许填写的区域加入开放名单 sheet.addUnprotectRange( { row: 1, col: 3, rowCount: 20, colCount: 1, }, { rule: editable, // 本区域允许输入 } ); // 第三步可选给开放区域设置数据校验 sheet.setDataValidation({ row: 1, col: 3, rowCount: 20, colCount: 1, type: list, operator: in, formula1: 白班,夜班,休息, });这段代码体现了一个关键思路表格默认是关闭编辑的只有显式声明的区域才允许用户触碰。这种“默认拒绝”比“默认允许再排除敏感区”安全得多因为你对所有没处理到的意外区域都保持了锁定状态。不过要注意保护区域和开放区域可以在不同图层叠加。如果既不指定row也不指定col而是把rowCount、colCount设为全量那就是整行或整列的开放。当业务表有动态行数时我更推荐使用按行范围配置比如“第2行到第500行开放第1行锁定作为表头”这样即使数据量超出预期也不会突然多出可编辑区。2.3 表单模式与数据校验的配合单纯“锁定一部分、开放一部分”只是解决了“哪些地方不能写”的问题还不能保证“写进去的合格”。Univer的数据校验模块可以和保护机制独立使用两者叠加后才算把填报体验补齐。典型组合是这样对允许填写的单元格设置校验规则比如日期范围、数字区间、下拉选项、必填项。当用户在开放区域输入了不符合规则的数据Univer会给出单元格角标提示并拒绝写入错误格式的内容。这点体验比传统Excel的“数据有效性”提示更直观因为校验结果直接反馈在表格上。如果项目还需要做“提交”动作可以在Univer外层增加一个按钮读取工作表的受保护区域之外的开头值组装成JSON提交到后端。提交前再统一触发一次sheet.getValidations()把校验失败的单元格标红拿到错误列表后再提示用户。这套流程比把表格当成纯展示视图可靠得多。注意Univer的校验是面向单元格内容的内存模型它不会自动帮你和业务数据库做同步。提交阶段仍然需要后端做二次校验前端校验只是提升用户体验的辅助手段不能作为数据安全的唯一保障。3. 从零搭建Univer在线表格的实操记录3.1 快速初始化一个Univer表格项目我习惯用npm初始化一个纯前端项目这里以Vite环境为例。Univer官方提供了预设包安装方式各家版本略有差异但大致思路是引入核心引擎和需要的模块然后挂到一个容器div上。下面是一段非常精简的初始化示意npm create vitelatest my-univer-app -- --template vanilla-ts cd my-univer-app npm install npm install univerjs/presets univerjs/preset-sheet实际执行时Univer的插件体系会有多个包名。建议以当前版本的官方文档为准把核心包、预设包、UI包都装齐。第一次跑起Univer时最直观的体验是它的编辑器交互是沉浸式的CTRLC/V、合并单元格、行列拖拽、缩放比例这些基本操作都不会让你觉得在用一个“阉割版Excel”。初始化代码大致长这样import { Univer } from univerjs/presets; import { UniverSheetPlugin } from univerjs/preset-sheet; const univer new Univer({ plugins: [new UniverSheetPlugin()], }); univer.createSheet({ name: 排班填报, rowCount: 200, colCount: 20, defaultData: getTemplateData(), // 加载模板数据 });跑起来之后浏览器里会出现一个完整的Sheet编辑区。此时先别急着做权限配置先把数据模板加载好确认列宽、合并单元格、表头样式都正确再继续。3.2 用模板数据搭建填报结构大部分填报类的表格都不是从空白表开始的而是先定义好模板再让用户填写其中一部分。我通常把模板在Univer里通过代码生成或者先用Univer手动搭好再导出JSON结构。模板数据可以包含样式、合并单元格、固定行高、冻结窗格。网格的冻结窗格很关键比如表头在顶部用户往下滚动时表头要一直显示这是通过设置冻结行的方式做到的。在实际代码中你可以通过workbook的配置项或者操作命令来完成命令式操作更贴近动态场景。生成模板时有个经验把复杂的说明文字放在单元格批注里而不是直接写在单元格内部。比如“此处请填写工号格式为十位数字”这样一段提示如果写在单元格里录入时就容易和真正的业务数据混在一起。Univer支持给单元格挂备注或批注信息既不干扰填写区域也能给用户提供明确指引。另外模板中凡是公式字段建议在加载模板后由Univer自动计算一次把结果缓存起来。后续一旦用户修改了相关前置单元格公式会响应更新。这条链路如果在初始模板阶段没调试好会在线上出现“公式没动”的卡顿感很影响体验。3.3 核心配置锁定全部再放开指定区域下面这段是整篇里最重要的实操环节。目标是第1行表头锁定第2到第100行开放B、D、E三列用于填写其他列一律只读。const workbook univer.getActiveWorkbook(); const sheet workbook.getActiveSheet(); // 锁定所有单元格 sheet.protectTable({ lockAll: true, }); // 开放B列第2行到第100行 sheet.addUnprotectRange({ row: 2, rowCount: 99, col: 1, colCount: 1, }); // 开放D、E两列 sheet.addUnprotectRange({ row: 2, rowCount: 99, col: 3, colCount: 2, }); // 设置“姓名”列数据校验非空 sheet.setDataValidation({ row: 2, col: 1, rowCount: 99, colCount: 1, allowBlank: false, });配置完成后最好在界面上手动试一遍。点一个锁定区域的单元格应该没有任何编辑光标出现点开放区域则可以正常输入。这里有一个很重要的细节单元格的“锁定”和“选中”是两回事。用户仍然可以看到锁定区域的内容也可能选中锁定区域只是不能修改内容。如果你的业务要求锁定区域连选中都不行需要额外开关单元格选择行为Univer里也有对应控制点只是要单独设置。3.4 将填写结果导出和提交表格做得再好最终还是要落到业务系统里。我采用的做法是页面上提供一个“提交”按钮点击后读取所有开放区域的单元格内容生成JSON发给后端。代码示意如下const sheetData []; const ranges sheet.getUnprotectRanges(); ranges.forEach((range) { const value sheet.getCell({ row: range.row, col: range.col, }); sheetData.push({ row: range.row, col: range.col, value: value?.v ?? , }); }); fetch(/api/table/submit, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ sheetId: xxxx, data: sheetData }), });提交前建议再触发一次校验把校验失败的区域高亮出来而不是直接让用户提交。后端接受到数据后也不能完全信任前端传来的内容需要重新校验格式和范围防止绕过前端直接构造请求。还有一点别忽略开放区域可能被用户清空。如果业务上该字段是必填项提交前要遍历所有开放区域检查空值。Univer允许你在自定义逻辑里读取单元格内容做统一校验这些检查逻辑放在前端可以让用户当场修正放在后端只能等提交后统一驳回体验差很多。4. 实战中踩过的坑与排查技巧4.1 单元格保护在插入行列后失效这是最隐蔽也最容易踩的坑。如果用户在操作时新增了一行新行的单元格默认并不一定继承当前工作表的保护状态可能导致本来应该锁定的区域出现可编辑的新行。排查思路很简单每次渲染完成后检查那些动态生成的行/列是否继承了锁定状态。Univer提供的保护机制在一般情况下能处理真正的行列插入但要严格依赖你底层版本对插入命令的权限判定是否包含保护继承。我在实际项目里遇到过某个版本中插入行会绕过范围锁定后来通过在插入事件回调里重新执行protectTable解决。建议不要把保护配置写死在初始化代码里而是在工作簿结构变化事件触发后重新应用一次。实现时可以在事件监听里再次调用保护相关的API保证无论用户怎么操作表格结构最终都会回到“全表锁定开放指定区域”的状态。4.2 复制粘贴绕过锁定用户从外部Excel复制一片数据粘贴到Univer的开放区域时如果粘贴范围比开放区域大可能会把内容覆盖到周围的锁定单元格。这个问题在纯前端权限模型里非常容易忽略因为保护模型管的是“用户主动编辑”而粘贴是一个批量写入操作。解决方式有两种一种是在粘贴事件里做范围判断超出开放范围的直接拒绝并提示另一种是设置粘贴行为统一粘贴为纯文本避免把外部表格的样式、公式带入到受保护区。更稳妥的方案是预判在用户选中粘贴目标区域时检查该区域是否全部属于开放范围只要有一个单元格不在开放名单内就阻止整个粘贴动作。这个逻辑虽然代码量不大但用户体验影响明显值得多花点时间打磨。4.3 性能问题大数据量下的渲染与交互Univer的Canvas渲染已经比DOM方案高效很多但也不是没有性能瓶颈。主要包括大范围锁定区域的数据量过大、合并单元格过多、同时开启太多数据校验规则。我碰到的典型情况是200列、3000行左右的报表初始渲染时浏览器会卡顿一会儿。解决办法是做“懒加载”不要在初始化时把所有数据都塞进去先渲染可视区域等用户滚动加载后续数据。Univer本身的渲染机制已经做了视口裁剪但如果业务数据本身加载得太多性能依然会受影响。另一个性能优化点是保护区域的配置方式。如果开放范围非常零碎比如几十个小块区域不要逐块添加保护范围。尽量先把大范围合并成连续区域再一次性配置否则会产生大量的Range对象增加计算负担。4.4 协同场景下的保护冲突Univer支持协同编辑如果同一个文档被多个用户同时打开A把某个单元格锁定了B正在进行编辑这时后台的协同服务需要判断本次提交是否合法。前端保护只是界面上的约束协同服务端的权限校验才是真正的阀门。我在接入协同功能时遇到过的体验问题是A用户合并单元格后B用户对同一个范围内的单元格填写内容导致两边同时改了同一个区域最终结果互相覆盖。后来通过把保护范围同步到协同服务端并在合并单元格时检测到目标范围有未释放的编辑锁才彻底解决。如果你暂时不接入协同只是在单机场景使用这个坑可以忽略。但只要开放多人编辑保护逻辑一定要在服务端再实现一遍只靠前端设定保护范围是不足够可信的。4.5 常见问题速查表问题现象可能原因快速排查建议点击锁定单元格还能输入保护配置未真正生效检查是否调用了protectTable确认lockAll是否为true新增行后新行可编辑插入命令未继承保护状态监听行插入事件重新执行一次保护逻辑复制粘贴覆盖了锁定区域粘贴动作绕过了编辑锁在粘贴事件里校验目标范围超限就直接拒绝公式计算值没有自动更新公式引擎未注册或缓存未刷新确认公式模块已启用修改前置单元格并触发重算大量校验规则导致输入卡顿校验规则过多且范围过大将连续范围合并缩小校验区域多人编辑互相覆盖协同服务端未做范围锁需要把前端保护范围同步到服务端以上每条都是我在实际项目里遇到过的。表格类组件最特别的地方在于它像一把“瑞士军刀”看着很强大但具体到业务场景里细节问题几乎需要手把手调。Univer的灵活性给了很多解决问题的空间却也意味着你必须真正理解它的模型而不是拿它当一个黑盒。我个人习惯是把所有保护、校验、填充规则都集中写在一个初始化模块里页面其他部分只负责传数据和接收结果。这样排查问题时不用满项目找配置也方便后续升级版本时统一调整API调用。最后再分享一个小技巧每次升级Univer版本前先把现有项目里和单元格保护、数据校验相关的测试用例跑一遍这两个功能是版本迭代中变动最多的区域很多新版号虽然功能更多但如果旧逻辑没跟上线上表格分分钟就会变成一张“观看表”。