资讯详情

资讯详情

Univer 在线表格:实现指定单元格可填写、其他区域锁定的完整方案

最近接了一个要做“在线表格填单”的需求。逻辑听起来很简单运营同事在系统后台自定义一张表格模板前端把表格渲染出来用户打开链接后只能填写其中指定的一部分单元格比如“手机号”“参与人数”“备注”这些字段其他单元格一律锁死不能选中、不能修改。这个场景在表格组件里其实很典型很多团队直接用 Excel 模板发下去再人工回收效率很低。我调研了一圈后最终选了开源表格组件 Univer 来做这套“Univer 在线表格”方案。如果你也在折腾 Univer 支持用户定义表格、指定单元格可填写、其他区域不可修改这类需求这篇实操笔记应该能帮你少走不少弯路。1. 为什么是 Univer找一个能嵌入网页的“活表格”1.1 这个需求真正要解决的不是“表格”而是“权限”一开始我以为这无非是做一个 HTML 表单放几个 input 就完事。但真和运营沟通完崩溃了他们要的是一张和 Excel 长得一模一样的大表包含列宽、行高、合并单元格、背景色、下拉选项、公式汇总甚至还有条件格式。用纯 HTML 重建这些每一次样式调整都是巨大工作量根本不现实。还有一条路是把 Excel 模板下载下去让用户填写再上传回收。这套流程也能跑但后果是版本满天飞、格式被改乱、手工汇总数据的时候想摔键盘。所以真正的核心需求是保持 Excel 的交互和排版能力同时做好“哪些单元格能填、哪些不能填”的权限控制。Univer 支持用户定义表格再把自定义好的表格放到网页上运行这正好切中痛点。Univer 本身就是一个可在浏览器中运行的表格内核渲染能力接近桌面表格软件也保留了完整的单元格级控制能力。你可以用代码精确控制任意单元格的可写状态再配数据校验和提交逻辑整体体验和 Excel 收表完全不是一个量级。1.2 对比过 Handsontable、Luckysheet 之后的取舍在定 Univer 之前我把市面上比较活跃的方案都过了一遍方案开源/授权单元格保护能力项目维护与在线体验Univer开源社区版可用支持表格保护和单元格锁定可精细控制模块化设计支持导入导出、公式、协同更新活跃Handsontable商业授权社区版功能受限保护能力较弱更多靠事件回调二次拦截很成熟但高级功能收费商用要评估授权Luckysheet开源有保护功能但文档和更新节奏不太稳定功能不少但对前端工程化体验一般我选 Univer 还有一个长远考虑它本身瞄准的是文档、表格、幻灯片一体的方向模块之间共用一套渲染内核。这次只做表格但后续如果要加多表格页签、协同编辑、复杂公式计算不用推倒重来。Univer 在线表格的场景今天能满足往后扩展空间也够。1.3 先建立最小认知Workbook、Worksheet、Cell 和“保护”Univer 的概念和 Excel 基本一致Workbook工作簿对应一个完整的表格文件。Worksheet工作表工作簿里的一个页签。Cell单元格最小的数据单元。Range连续单元格区域比如 “A1:C10”。保护工作表级别的开关开启后再结合单元格的 locked 属性来决定谁能编辑。理解这套模型很重要。Univer 里“锁定单元格”这个行为本质上是两层配合第一层是给整张工作表开启保护第二层是把允许编辑的单元格区域设置为 unlocked。这两层缺一不可顺序也要处理对。后面我会展开讲。2. 快速接入 Univer把一张自定义表格跑起来2.1 环境准备与依赖安装Univer 是标准的前端组件库对框架不挑剔。我的项目用的是 Vite React TypeScript你也可以用 Vue 或者原生 TS 接入核心流程差不多。安装核心依赖的命令大致如下npm install univerjs/core npm install univerjs/sheets npm install univerjs/ui如果你只是做原型验证Univer 也提供了更高层的预设包一次性把渲染、交互、公式等常用模块打包好。我建议小版本一定要锁住因为 Univer 的 API 还在快速演进不同版本之间的方法名和参数结构可能差得很多。装完先看一眼package.json里的版本号再对照官方文档或类型声明写代码能少踩很多坑。2.2 初始化一个在线表格实例最简单的初始化逻辑是创建 Univer 实例并注册表格相关插件。以官方文档里的预设方式为例代码看着大概是这样的import { createSheets } from univerjs/presets; const univer createSheets({ container: app, layout: { width: 100%, height: 600, }, });这段代码会往idapp的 DOM 容器里渲染出一个完整的在线表格界面。如果你希望更精细地控制插件可以手动注册核心插件import { Univer } from univerjs/core; import { UniverSheetsPlugin } from univerjs/sheets; const univer new Univer(); univer.registerPlugin(UniverSheetsPlugin);实际项目里我更喜欢手动注册因为可以只加载需要的模块减少首屏体积。做“填表”场景时很多用户不需要完整工具栏只展示固定区域即可。2.3 加载运营自定义的模板表格自定义模板通常有两种进入方式一种是直接把.xlsx文件导入进来Univer 支持 Excel 文件格式的读写另一种是通过 JSON 结构从后端拉取模板数据。我建议二选一或者两者结合。用 JSON 的好处是模板里的“锁定区域配置”可以一起管理后端返回一段数据就能同时决定表格长什么样、哪些格子能填。简化后的模板结构类似{ name: 项目周报模板, sheetOrder: [周报], sheets: { 周报: { rowCount: 24, columnCount: 8, cellData: { A1: { v: 项目周报 }, A3: { v: 负责人 }, B3: { v: }, A5: { v: 完成情况 }, B5: { v: } }, mergeData: [ { startRow: 0, endRow: 0, startColumn: 0, endColumn: 7 } ] } } }然后通过 Univer 的创建接口把这个模板装进去const workbook univer.createUnit( WORKBOOK, template );注意这段逻辑是简化后的方向性示例具体字段名要以你当前版本的 TypeScript 类型为准。重要的是思路模板结构来自后端前端负责渲染和权限控制不要把表格结构硬编码在前端。3. 核心让“指定单元格可填写其他全部锁死”3.1 先理解 Univer 的“锁定”底层逻辑在 Univer 里单元格的locked属性是样式的一部分默认情况下所有单元格都是“锁定”状态。但锁定了并不意味着不能编辑因为工作表还没开启保护。打个比方每个格子都装着一把锁但门没关锁不锁都没意义。所以要做“其他单元格不能修改”必须同时做两件事给工作表开启保护。把允许用户填写的区域显式设置为“不锁定”。只做第 1 步会导致所有格子都不能填连允许的区域也被锁死。只做第 2 步不开启保护用户仍然能到处乱改。这个顺序和组合几乎是我被咨询最多的一个问题。3.2 用 API 配置“保护工作表 解锁区域”我在项目里的做法是封装一个函数输入工作表实例和允许编辑的区域列表统一完成配置。伪代码如下function enableEditRanges(sheet, editableRanges: string[]) { // 第 1 步保护整个工作表 sheet.protect({ isProtected: true, options: { selectLockedCells: false, selectUnlockedCells: true, formatCells: false, formatRows: false, formatColumns: false, insertRows: false, insertColumns: false, deleteRows: false, deleteColumns: false, sort: false, filter: false, }, }); // 第 2 步把允许填写的区域设置为不锁定 editableRanges.forEach((rangeAddress) { const range sheet.getRange(rangeAddress); range.setStyle({ protection: { locked: false, }, }); }); }selectLockedCells: false这个配置很关键。做填表场景时用户选中一个锁定单元格不应该出现任何可编辑光标最好连选中框都不显示这样体验更接近表单。selectUnlockedCells: true则保证用户能正常点击允许填写的区域。如果允许填写的区域有很多块比如“B3:B10”“D3:D10”“F3:F10”直接传入一个数组即可。解锁之前最好先确认这些单元格没有被合并区域覆盖否则会有边界问题后面再细说。3.3 给编辑动作再加一道保险事件拦截工作表保护能拦住绝大多数正常操作但“粘贴”“拖拽”“双击填充”这类动作在部分版本里可能会绕过保护。为了万无一失我会在编辑类命令执行前加一道校验。思路是任何单元格内容变更先判断目标坐标是否在允许列表里不在就拒绝。下面这段是概念性示例具体命令名请以你的 Univer 版本为准univer.getCommandService().onCommandExecuted((command) { if (command.id sheet.mutation.editCell) { const { worksheetId, row, column } command.params; if (!isAllowedEditRange(worksheetId, row, column)) { // 撤销或阻止这次编辑 command.stopPropagation?.(); } } });我用这个方式拦截过很多异常路径。用户从小键盘直接输入、CtrlV 粘贴、拖动右下角填充这些操作的底层命令不同但只要统一在“单元格变更”这个层面做白名单校验就能保证“用户只能填写允许的区域”这个规则从上到下贯穿。3.4 用户实际填写的交互流程在 Univer 里做完权限控制后我习惯把允许填写的单元格加一层浅黄色底纹并在旁边写一段提示文字。用户打开链接后看到的效果是标题、说明、公式区域全部无法选中。允许填写的格子有明显背景色点击后直接输入。部分格子配置了数据校验比如只能选“是/否”。填完点击“提交”按钮前端收集数据发送给后端保存。这套交互比传统 Excel 收表清晰很多。Univer 支持用户定义表格最终在浏览器里给出的是一个受限的、可控的表单。对填表用户来说他们根本不需要理解 Excel只需要点格子、填内容、点提交。4. 实操中踩过的坑局部只读表格的排查清单4.1 最常见设置了保护为什么还能修改我在测试时最频繁踩到的坑是工作表保护看起来开了单元格锁属性也设置了但用户还是能编辑。排查下来大概率是下面几个原因现象可能原因解决办法保护开启后全部不可编辑没有给允许区域设置locked: false检查允许列表的单元格样式允许的区域仍然不可编辑selectUnlockedCells被设置成 false设置为 true同一次操作中锁区和可编辑区同时生效设置解锁样式时覆盖了原有样式用 mergeStyle 方式保留原样式模板加载后才做保护但 UI 没刷新表格渲染和权限设置顺序不对在模板加载完成后再调用保护函数有一点要特别提醒setStyle在某些版本里是整体替换单元格样式。如果你同时设置了背景色、边框和protection不小心就会把背景色覆盖掉。稳妥做法是先读取原样式再把protection合并进去。4.2 合并单元格、粘贴和拖拽填充分分钟绕过权限合并单元格是重灾区。Univer 里设置区域解锁时如果只对合并区域的左上角单元格设置locked: false用户点击合并区域时可能还是无法编辑或者状态不一致。解决方法是把合并区域覆盖到的所有单元格都批量设置成locked: false不要只写一个范围地址。粘贴是第二个坑。用户从外部复制一整片数据到表格里如果粘贴目标部分在锁定区、部分在可编辑区表现会很奇怪。我建议把粘贴动作也放进事件校验先算出粘贴的目标范围再和目标区域的权限做交集只允许粘贴到可编辑范围。如果粘贴范围超出了允许列表直接弹提示并回滚。拖拽填充和双击填充同理。比如用户双击某个可编辑单元格右下角公式或者内容可能会自动填到下方锁定区域。这个动作也需要在命令校验里拦截。总之不要只相信工作表保护一定要在前端命令层做二次校验。4.3 性能与体验优化几十张表、几千行的真实场景如果你的模板只有几十行那性能都不是问题。但我的项目里有一张数据收集表行数超过五千还带公式。这个时候要关注几个点不要在页面一开始就把所有协同能力打开单纯填表场景用单机模式够了。模板数据尽量用 JSON 传到前端不要在 Univer 里重复做复杂的 Excel 解析。可编辑区域列表不要用后端字符串拆分拼范围直接传结构化数组减少前端转换开销。给允许编辑的区域加上提示文字时尽量用单元格批注或额外说明列不要影响公式计算。从用户体验来说在线表格填单其实不应该给人“表格软件”的感觉而应该给人“智能表单”的感觉。所以我刻意隐藏了大多数工具栏入口只保留提交按钮和错误提示。Univer 在线表格的灵活性就在于你可以把不需要的能力收起来只露出业务需要的部分。5. 落地到线上的关键配置身份、模板、数据与部署5.1 用户身份与模板版本联动做真实项目时“谁能改表格结构”和“谁能填哪些单元格”往往是两拨人。运营管理员进后台时Univer 实例应该是可编辑的能看到完整工具栏普通用户进来时实例应该只读并且只能填指定区域。我的做法是登录后后端返回当前用户的角色和模板 ID前端根据角色渲染不同配置。管理员模板和用户模板用同一份 JSON 数据源但权限配置不同。普通用户拿到的模板里允许编辑区域就是动态接口给的那几个范围。运营调一下锁定范围前端重新拉配置就能生效不需要发版本。5.2 数据提交与后端校验用户填写完点击“提交”前端需要通过 Univer 的读值接口把允许编辑区域的数据提取出来。不要在前端拼好 Excel 再传后端我建议直接传结构化数据类似const result { templateId: weekly-report, values: { B3: 张三, B5: 已完成, F3: 2025-06-01, }, };后端保存前一定要再校验一次这些 key 是否都在模板允许编辑的范围内内容格式是否满足校验规则。因为前端代码可以被改锁定规则只起到引导作用服务端才是最后一道闸门。如果你有协同编辑或者多人同时提交的需求还要考虑版本冲突至少保证同一张模板下所有人提交的数据不会互相覆盖。5.3 部署时注意的打包体积和销毁问题Univer 功能很全因此打包体积不算小。生产环境里我会把 Univer 相关模块单独拆成一个异步 chunk用户打开填表页时才加载。这样首屏主页面不会因为表格组件拖慢很久。另外要注意 Univer 实例的生命周期。单页应用里路由切换后如果旧实例不销毁会残留监听器和内存占用。我在项目里会封装一个组件在useEffect的清理函数里主动调用实例的释放方法避免来回切换页面后内存只增不减。如果只是做“用户填写提交”这种低频操作建议每份提交独立初始化一张 Univer 表格用完即毁。这样既避免数据串场也让权限控制更清晰每个人拿到的都是自己的私有实例。最后分享一个小经验。我在这类项目里最看重的不是“哪个组件更像 Excel”而是“锁区配置能不能独立运营”。把模板结构、允许编辑范围、数据校验规则都放到后端配置前端只负责按配置渲染和收集数据这套思路在 Univer 上落地后运营改表真的不用再找开发改代码。还有一个小技巧开发阶段先把官方示例里的“工作表保护”完整跑一遍再叠加自己的样式和模板很多坑都不会踩到。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →