做在线表格的开发者大概率遇到过这样的需求系统里要有一张“用户填写信息”的表格表格的模板由管理员定义用户只能往指定的单元格里填内容其余区域一个字符都碰不了。这类场景在报名系统、问卷收集、审批流、物料清单维护里非常常见。Univer正是为了这类在线表格场景而生的开源方案。它是一个完全开源的Web电子表格套件支持把表格嵌入到任意前端项目也可以基于它二次开发出类似飞书多维表格、腾讯文档这样的小型在线协作应用。最近社区里聊得最多的关键词就是Univer 用户定义表格 单元格只读保护说白了就是一套“让业务方自定义表头让用户只能填空白格”的交互模式。这篇文章没有什么高深的花架子我会从需求拆解、原理、实操到踩坑记录把Univer在“用户自定义填写表格”这件事上到底怎么落地讲清楚。如果你正在选型在线表格组件或者已经接入了Univer但搞不定单元格权限下面的内容应该能帮你省掉不少试错时间。1. Univer到底是什么为什么在线表格场景绕不开它1.1 前端表格套件和现成SaaS产品的区别很多人一听到“在线表格”第一反应是直接用腾讯文档、飞书表格或者买那种SaaS表单服务。但在真实业务里往往要求表格要嵌入到自己的后台系统中表头、填写规则、权限边界都需要按照业务逻辑动态生成。这个时候你再拿一个现成的SaaS表格去套基本是套不住的。Univer不是传统意义上你下载安装的Excel而是一个基于TypeScript开发的开源前端套件。它由几个核心模块组成核心数据模块负责工作簿、工作表、单元格的结构和状态管理表格渲染模块负责把数据画到Canvas上UI模块负责工具栏、右键菜单、筛选器等外围交互。你可以把它理解成一套“引擎加外壳”Univer提供的是底层能力而不是一个上线即用的封闭产品。这一点很关键。因为它意味着你可以像搭积木一样只取自己需要的部分比如只展示一个表格区禁用工具栏也可以完全重写UI让用户只能看到可填写的单元格周围的一切都变成你的业务输入面板。这种可控性是直接选用SaaS表单无法做到的。1.2 开发者最看重的三个点开放、性能、协同我在做选型的时候主要看三点开放程度、渲染性能、协同能力。Univer在这三方面都有自己的特色。开放程度方面Univer所有代码都开源提供了插件系统。你可以自己写一个插件在用户双击某个单元格的时候弹出你的业务选择器也可以拦截数据变更做自定义校验。对于需要深度定制“用户自定义表格”的产品而言这一点是决定性的。渲染性能方面Univer基于Canvas渲染而不是传统HtmlTable。几万行数据的表格滚动起来仍然能保持流畅这一点我在实际测试中比较满意。相比其他用DOM实现的组件它在大数据量场景下是真的能扛。协同能力方面Univer生态里提供了Collaboration模块可以做实时的多人编辑。不过这里有一个现实问题真实业务里“用户填写表格”和“多人像Excel一样同时编辑”是两个需求。多数填表场景根本不需要实时协同只需要保证各自的编辑区域不冲突。Univer给了你选择权——你可以只做数据保存和更新也可以上协同这比强制实时协同的SaaS更灵活。2. 搞懂“可编辑与锁定”的核心先渲染层再数据层2.1 用户在页面上看到的“不可修改”是怎么发生的“让用户只能填指定单元格其他单元格不能修改”这个诉求看起来简单但背后有两层逻辑。第一层是交互层的锁定。用户双击一个单元格时Univer的编辑状态会启动如果这个单元格被设置为只读或锁定编辑器就不会弹出来甚至键盘输入也会被拦截。这是用户最直观的体验能填的格子和不能填的格子一眼就能分辨出来。第二层是数据层的保护。Univer的数据模型里每个单元格除了有value之外还有样式和单元格级的状态标识。保护机制实际上是在整个工作表层面设一个“锁”然后再通过授权区域来“开锁”。默认情况下工作表的单元格都是锁定的只有你明确放开的区域才可以被编辑。放到现实生活里就是一个很好的类比整栋楼是锁着的每间办公室的门也都有锁但钥匙只发给你需要进入的那几间。别人知道你只能进这几间不代表其他房间的门是坏的而是锁还在只是你没钥匙。2.2 为什么说只靠前端保护是假的后端校验不能省我见过不少团队在页面上设置了只读区域就觉得数据安全了。但这里必须泼一盆冷水前端所有保护都只是交互层面的体验不是安全屏障。Univer的保护机制本质上是一个前端状态标记。用户如果懂一点前端开发完全可以通过调试工具、抓包工具伪造一条写数据请求绕过页面的锁定往不应该修改的单元格里塞数据。所以真正要保证“用户不能修改其他单元格”一定要在后端再做一次校验。后端最简单的方式是保存数据时你有两个数据集合。一个是表单模板定义了哪些单元格是允许用户填写的一个是用户提交的数据只保存允许范围内的单元格值。在保存接口里逐格对比用户提交的坐标是否在授权范围内。如果发现越界直接丢弃或者返回错误。这样前后端配合才能做到真正的不可修改。前端的锁定是给正常用户看的后端校验是给恶意用户和异常请求兜底的。两者缺一不可。2.3 用区域保护实现“模板表格”的思路讲完了原理说说如何把“用户定义表格”变成可落地的方案。核心思路是两段式设计模板编辑期和用户填写期。模板编辑期由管理员操作。管理员使用Univer渲染一个宽裕的空白表格任意编辑表头文字、合并单元格、设置列宽、添加数据校验下拉项。这些操作完成后我们把整个工作簿序列化成一个JSON模板存到数据库里。这个JSON中包含了所有单元格的样式、值、校验规则、合并区域等信息。用户填写期则是模板的实例化。当需要某个用户填写时系统读取这个模板JSON交给Univer渲染然后根据业务规则设置保护区域。一般做法是先把整张工作表设为全表锁定然后根据模板中约定的可填写区域把这些单元格从锁中释放出来。这样同一份模板可以被多个用户使用不同用户还可以有不同的可填写范围完全不需要重新开发一份表单页面。3. 手把手做一套“用户自定义填写”的在线表格3.1 安装Univer并初始化一个工作簿我用一个实际案例来讲假设要做一个供应商信息采集系统管理员定义了一张“月度台账”模板里面有固定的产品名称、规格、单价只读用户只需要填写“本月数量”和“备注”。步骤如下。先用包管理器安装Univer相关模块。我习惯使用pnpmnpm也一样npm i univerjs/core univerjs/sheets univerjs/ui univerjs/engine-render univerjs/design装完之后核心是创建一个Univer实例。初始化代码大概是下面这个样子import { Univer } from univerjs/core; import { defaultTheme, UniverDesign } from univerjs/design; import { UniverRenderEngine } from univerjs/engine-render; import { UniverSheets } from univerjs/sheets; import { UniverSheetsUI } from univerjs/sheets-ui; const univer new Univer({ theme: defaultTheme, locale: zhCN, }); univer.registerPlugin(UniverRenderEngine); univer.registerPlugin(UniverSheets); univer.registerPlugin(UniverSheetsUI);然后在页面上创建一个容器div idapp/div通过univer.createUnit渲染出第一个工作簿。这里的重点是Univer的“生命周期”概念任何工作簿、工作表的创建都通过统一的命令执行。建议把初始化代码封装在一个函数里后面加载模板JSON时反复调用即可。这样每次用户进入填表页面都能拿到一份干净的模板实例。3.2 定义表头区域和填写区域管理员编辑模板后我们的模板JSON里除了单元格数据还要定义“哪些区域可填”。如果模板本身是人编辑的怎么定义可填区域呢最简单的做法是约定可填写区域用特定背景色标记或者在模板里专门放一个配置Sheet。我个人常用的是背景色约定因为视觉上直观。管理员在模板里把需要填写的区域填充成浅黄色并且设置一个边框系统扫描这一份工作表读取单元格的样式凡是背景色等于约定值的区域自动记录为一个可编辑区域Range。这样做的好处是管理员不需要学习额外的配置界面直接使用表格本身就完成了自定义。采集端的逻辑再去解析这些区域统一设置为可编辑。举个例子const editableRanges getRangesByBackgroundColor(worksheet, #FFF2CC);拿到可编辑区域后接下来就是上锁。3.3 通过保护API锁定其余单元格Univer的protection API在不同版本里方法名会有一些变化但思路是一致的“先保护整张表再开放指定范围”。为了兼容性我通常这样操作先调用命令设置整个工作表的保护状态默认所有单元格锁定。然后遍历我们预先定义的editableRanges对每个Range执行一次“取消锁定”设置。伪代码示意// 伪代码设置全表保护 univer.getCommandService().executeCommand({ id: sheet.command.set-worksheet-protection, params: { unitId, sheetId, protection: { locked: true } }, }); // 伪代码开放指定区域 for (const range of editableRanges) { univer.getCommandService().executeCommand({ id: sheet.command.unlock-cell-range, params: { unitId, sheetId, range, locked: false }, }); }这里一定要强调顺序先锁全表再解锁指定区域。很多新手反着来结果一锁全表就把刚才开放的格子又锁上了最终表现就是哪里都不能填。除了单元格锁定还需要注意是否禁用插入行列、删除行列。在填表场景下普通用户不应该拥有这些结构级操作权限。所以保护工作表时还要把“允许插入行”“允许删除列”等选项关掉。否则用户虽然改不了格子内容却可以删掉一行数据照样破坏模板结构。3.4 保存模板、分发填写、回收数据的完整链路模板创建完成后建议把Univer的工作簿快照直接以JSON格式存储。可以看下面这个简易流程管理员端编辑表格保存时调用univer.getSnapshot()获取当前工作簿JSON。后台把快照存进数据库同时存储一个对应的editableRanges字段。用户端拉取列表点击“填写”后端返回模板快照和该用户的可编辑区域。前端初始化Univer加载快照再执行保护逻辑。用户填写已完成点击提交前端收集所有变更提交给后端。第5步有一个细节用户填写过程中Univer的数据变更会记录内存中。提交时你不必把整个工作簿快照传回来只需要把允许编辑的单元格值组装成一个对象然后调用你的保存接口。到了后端处理逻辑就很简单了。拿着请求里的坐标和值与editableRanges逐一比对一旦出现越界就拒绝保存。这样即使用户通过浏览器把其他单元格改成了内容后端也会拦下。4. 我踩过的坑保护失效、协同冲突、大表卡顿4.1 保护设置顺序导致的经典失效场景有一个非常常见的坑初始化Univer后还没等模板数据渲染完成就去执行保护命令结果发现没效果。原因在于工作簿的加载是异步过程。如果你在创建Univer实例后立刻执行命令数据层还没有就绪命令找不到对应的目标Sheet自然就失败了。解决方法是订阅一个“工作簿加载完成”的事件或者在使用createUnit之后在callback里执行保护逻辑。也可以加一个小的重试机制检测到Sheet存在后再设置。我当时在这个问题上折腾了大半天最后定位到是时序问题。另一个前置是“快捷键绕过保护”。Univer里很多操作走的是快捷键命令比如CtrlC、CtrlV。某些版本中粘贴操作会绕过单元格的锁校验。解决方法是同时监听粘贴事件或者使用插件系统拦截粘贴命令。如果你只是做填表场景干脆在用户端禁掉粘贴快捷键反而更省事。4.2 多人同时填一个格子怎么办填表场景最常见的冲突是同一个用户打开了两个标签页都填写同一个单元格后提交的覆盖前提交的。不要以为只有多人协同才会冲突单人多端也可能出问题。最简单的方案是加版本号或者更新时间戳。前端提交时带上数据的最后修改时间后端对比如果发现这个格子在用户打开期间已经被别人改过就返回冲突提示“页面数据已过期请刷新后再试”。如果你需要更严格的实时协同Univer官方有Collaboration模块底层使用CRDT做数据同步。但说实话对于“用户自定义填写表格”这个需求实时协同往往不是必须的。我建议第一版只做“保存时校验版本号”优先保证数据不丢、不覆盖。等真正需要多人同时在线编辑同一张表时再引入协同模块不迟。4.3 只读单元格里嵌了下拉校验结果不弹我在做“产品规格”列时遇到一个问题规格是只读的但我希望用户点击规格的时候可以悬浮看到完整名称所以给这个单元格加了下拉数据校验。结果页面渲染后明明校验配置在但点过去没反应。排查之后发现Univer的数据校验下拉和单元格的编辑状态是强相关的。单元格如果不能编辑就不会进入编辑模式那么下拉控件也就不会被触发。想要在只读状态下也能弹下拉需要单独设置单元格的交互属性或者在UI层自定义一个悬浮提示。这个问题的教训是不要把“查看辅助信息”和“编辑校验”混在同一个单元格里。更好的做法是在模板里增加一列“备注说明”或者在表格外部放一个信息面板。遇到类似需求先把展示和编辑解耦。4.4 性能排查一万行里的筛选和滚动模板表格如果放在后台管理端经常会一次性写入几千甚至上万行数据。这种量级下用户操作的流畅度会明显下降。Univer的Canvas渲染已经比DOM好很多但如果你加载的数据量极大仍然需要优化。我试过把一个一万行、二十列的表格直接塞进Univer首屏渲染大概需要一秒多滚动起来还算流畅但是进行全列筛选时会有明显卡顿。优化方法是分层渲染和懒加载大量数据只加载到前端可见区域滚动时再请求后续数据。还有一个容易被忽略的优化点降低频繁的样式计算。模板表格里不要给每一个单元格单独设置字体、边框、背景色。尽量使用列级样式或工作区默认样式否则样式解析会成为性能瓶颈。另外如果只是为了给用户填表表格中那些冻结窗格、公式链等复杂特性能不用就不用。保持模板结构简单性能会稳得多。5. 聊聊Univer的扩展空间和我的使用体会最后聊点实际的。Univer这套表格套件的扩展能力确实帮我解决了不少业务上的“非分之想”。比如“管理员自定义表格模板”这个需求最原始的版本可能就是一张固定表头、固定列的HTML表格。但随着业务复杂化客户开始要求自己添加列、调整列顺序、设置必填项。这些需求用传统表单组件迭代的话可能要折腾好几个版本。但基于Univer管理员端直接在一个真正的Excel环境里改前端只需要解析最终快照极大降低了开发成本。如果你考虑接入Univer我的建议是先花半天时间把官方API文档过一遍重点理解工作簿生命周期和命令系统。不要一上来就写业务代码因为Univer的API设计属于“一次理解长期受益”的类型。单元保护、样式扫描、快照加载这些概念打通之后你会发现“用户自定义表格”只是其中一个小场景后面做报表编辑、数据录入、动态模板都会顺手很多。有一点需要记住Univer只是前端能力它不给你的业务系统做身份认证也不决定谁能打开哪张表。真正把“可编辑范围”落到实处的还是你自己的后端逻辑和数据库模型。前端负责体验后端负责安全两者配合好了这个方案才能长期稳定跑下去。我个人在实际操作中的体会是这类表格类需求最怕的不是技术选型而是把“表格组件”当成“业务系统”来用。Univer给了你一个足够扎实的底盘但具体的填写流程、权限规则、数据回收仍然要你基于业务去一层层搭起来。想清楚这一点用它做出来的东西就远不止一个在线Excel那么简单了。
阅读完成 · 觉得有帮助?