Univer开源在线表格:白名单权限实现指定区域可填、其余只读 做B端系统的都知道凡是涉及到“多人填报”的业务最难的不是做出表格而是把表格的修改权限管住。我前阵子接到一个需求给财务部做一套报销单模板表格结构和样式由财务自己定义其他业务部门的同事打开后只能填“报销事由”“金额”“发票号”这些格子表头、说明、公式列一概不能动。这种场景用传统Excel来回传文件早就行不通了——你不是在等文件就是在等文件的最新版本。后来我们把整套表格引擎换成了Univer用Univer的在线表格能力做模板定义再用单元格权限把可填区域和只读区域彻底分开这个问题才算真正解决。Univer是一个开源的Web办公套件底层是一套TypeScript写的表格、文档、幻灯片引擎你可以把它理解成“前端界的Excel内核”能嵌入React、Vue或者纯TS项目。如果你正在做内部填报表单、协作台账、数据录入平台又不想从零造一套类Excel控件那这篇文章正好适合你。下面我会从需求分析、权限机制、完整实操、常见故障四个角度把“用户定义表格 → 用户填写指定单元格 → 其他单元格改不了”这条链路完整讲透。1. 先用业务场景想清楚到底怎么做“可填但改不了”1.1 业务方的原话拆解要的是“流程”不是“表格”财务部当时给我的原话是“我们要做一个固定格式的表格模板上级审核的部分不要让人动下级填写的地方要留出来。”这句话听起来简单拆开其实是三层需求表格的样式、列结构要允许业务方自己调整不能每次改格式都找开发改代码在模板发布之后普通用户只能往指定单元格里填内容除填写区域之外整个工作表的其他部分必须变成只读状态包括表头、说明文字、公式、合并单元格都不能被选中修改。这个需求本质上不是“做一个表格”而是“做一个有模板概念的填报流程”。表格只是载体背后还牵扯到字段校验、数据回收、版本管理。如果只做一个静态页面表单业务方改样式就得重新开发流程会卡死在需求变更上。Univer的在线表格引擎恰好能把“表格自由度”和“权限管控”这两件事同时扛下来。1.2 传统Excel协作的两个致命问题这类需求如果换到传统Excel方案最常见的管理手段是“保护工作表密码”。听起来挺合理实际执行起来全是坑。第一个坑是版本失控。业务方定义好模板后用邮件或者企业群发出去每个填报人保存的本地文件都是独立副本。等收集上来你会同时收到“报销单-最终版.xlsx”“报销单-最终版2.xlsx”“报销单-最新-改.xlsx”根本分不清哪份是最后状态。即使把文件锁了密码也挡不住有人按一下CtrlZ撤销或者直接复制粘贴参见区域把样式搞得一团糟。第二个坑是数据回收成本极高。哪怕文件形态统一最终数据还是要靠人工复制进系统。几十个人的报销表还能忍受几百张过来就容易出错漏。我当时用VBA插件去批量读取老实说能用但每次更新模板字段插件逻辑就得跟着改一遍维护成本不低。换成Univer之后模板和填报数据都变成线上JSON快照后端直接收数据模板更新只需要重新发布版本历史数据也不会丢。这和Excel时代是完全两种体验。1.3 Univer在这个场景里的三个支点为什么偏偏是Univer我用完一圈后的感受是它刚好踩在三个关键点上。纯Web在线表格不需要客户端打开浏览器输入地址就能编辑表单和报表共用一套数据引擎单元格级别权限控制可以对指定Range开放编辑权限其他区域锁死这是做“可填写但改不了”的基础能力公式和数据校验联动权限放开之后用户填的内容还能被数据校验约束让填进去的值“能填但不会乱填”。Univer还有协同编辑、条件格式、数据透视这类能力但在这个场景里最核心的三板斧就是上面这些。下面我来拆一下Univer的权限体系看看它到底是怎么做到“指定区域可填其他地方锁死”的。2. 核心机制拆解Univer的权限分层与两种保护路线2.1 权限分层工作簿、工作表、单元格区域Univer的权限模型你可以理解成一套“门禁系统”。编辑器里的每个动作比如插入文字、改单元格样式、合并单元格、增删行列都是一个命令Command。命令在真正执行前要先过一道权限校验校验不通过就直接打回。权限校验的范围分三个层级工作簿Workbook层决定用户能不能改工作簿结构比如新增Sheet、删除Sheet、重命名工作表Worksheet层决定用户能不能改动当前Sheet的结构比如插入行列、调整列宽、修改单元格属性单元格区域Range层决定用户能不能编辑某个具体范围的内容。Univer里面对应了三个可编辑权限点工作簿编辑权限、工作表编辑权限、区域编辑权限。普通用户打开一个模板后所有编辑命令都会先检查当前用户在目标区域里有没有对应权限。双击一个单元格时编辑器会根据是否拥有该区域的编辑权限决定要不要打开输入态。你可能会问这三层权限的覆盖关系谁优先实际操作下来Univer遵从一个原则细粒度优先也就是区域权限 工作表权限 工作簿权限。比如工作表层设置了不可编辑但是在某个Range上显式开启了编辑权限那这个区域内的单元格依然可以编辑。这正适合“整个表锁住只开放几个白名单区域”的模板填报模式。2.2 两种保护路线先锁后放 与 先放后收在Univer落地时保护策略通常有两种我管它们叫“先锁后放”和“先放后收”。先锁后放是先对整张工作表设置不可编辑权限再把允许填写的区域一个个加入到白名单里。优点是安全系数高防护思路清晰只要白名单漏了一个区域用户就填不了那个区域不会出现“忘记锁某个重要字段”的事故。缺点是需要把开放区域列清楚适合结构化的业务表单。先放后收是工作表默认全部开放编辑再对敏感区域单独设置不可编辑。这种模式适合大部分区域都要填、只有少数固定字段不能让用户碰的场景比如台账登记表。但问题也很明显——一旦业务方扩展了表格新增列默认是可编辑的很容易漏锁。我强烈建议模板填报类需求统一“先锁后放”。因为这个场景的安全边界是“不能允许用户修改不该改的地方”用白名单模式管理成本最低。你只需要维护一个“哪些区域开放”的列表其他所有编辑命令天然被拦截掉不用每条命令单独考虑要不要放行。Univer在更细的粒度上也提供类似Excel“保护工作表”与“允许用户编辑区域”的能力。你可以针对工作表设置保护项比如禁止调整列宽、禁止排序、禁止筛选也可以针对Range设置保护项只允许对指定区域写入内容。结合“先锁后放”的思路就是一句话工作表层禁止一切编辑区域层单独开放白名单。2.3 数据校验让用户“只能填对的东西”权限锁住了“能不能改”但没解决“改得对不对”。实际业务中这是两个问题。用户有权限填金额结果填了个“abc”你的报表一样没法看。所以Univer的数据校验Data Validation必须和权限配合使用。Univer内置的数据校验支持常见规则数值范围、日期格式、文本长度、自定义公式校验还支持下拉选择。你可以对开放区域设置“金额必须大于0且为数字”或者“日期必须符合yyyy-mm-dd格式”用户输入不合规内容时直接在线上被拦截。我举个具体例子设备领用登记表里“领用数量”这一列只开放给用户填但同时设置了一个数据校验只允许填写1到100之间的整数。用户手误填了个200单元格标红提示提交时再次校验不通过根本进不了后端。这种校验规则可以存在工作簿快照里随模板一起发布不需要额外写服务端逻辑。3. 实操全流程用Univer做一个“仅部分单元格可填”的在线表格3.1 安装与初始化按需引入还是全量PresetUniver目前的版本迭代很快如果你想跟着实操建议直接看官方文档里最新推荐的安装方式。以当前主流版本为例安装可以用官方Preset简化初始化的复杂度npm install univer univerjs/presets univerjs/design如果你的技术栈比较敏感不想引入整套Univer也可以按模块拆开装npm install univerjs/core univerjs/sheets univerjs/sheets-ui univerjs/ui模块化引入的好处是包体积可控但需要你自己去关联插件依赖配置成本稍高。Preset的方式就是开箱即用适合先把业务跑通。初始化一个Univer实例创建一个Sheet核心代码大概长这样import { Univer, UniverInstanceType } from univer; import { UniverSheetsCorePreset } from univerjs/presets; import univerjs/design/lib/index.css; const univer new Univer({ theme: default, presets: [UniverSheetsCorePreset()], }); const workbook univer.createUniverInstance(UniverInstanceType.UNIVER_SHEET, { id: asset-application, sheets: [ { id: sheet1, name: 设备领用登记表, rowCount: 50, columnCount: 12, cellData: { // 初始化表头等 } } ] });这段代码只要跑起来页面上就会出现一个可编辑的在线表格。接下来要做的就是围绕这个Sheet去操作权限。3.2 模板设计阶段让业务方自由编辑在设计阶段我们姑且把当前用户角色标记为“模板管理员”。管理员打开表格时不施加任何权限限制所有单元格都可编辑。这一步非常关键不能一上来就锁表否则业务方自己都没法设计模板。管理员在Univer的界面上可以直接做这些操作调整列宽行高、合并表头单元格、设置背景色、写公式、加上数据校验。这些全部发生在管理员登录态下前端根据当前账号的角色去决定是否需要给Univer注入权限。具体实现上我一般是在用户信息里加分角色字段。初始化Univer时如果角色是admin就跳过权限设置如果角色是filler就执行“先锁后放”的权限逻辑。这样同一个表格地址管理员和填报人看到的界面操作范围完全不同。模板发布时最好做两个动作一是把管理员编辑后的快照保存为“模板版本”二是把锁定/开放区域配置也保存下来。后面会讲到快照持久化。3.3 发布为填报模板核心权限命令重点来了怎么把表格变成“只能填指定单元格”的填报模板。我以Univer的权限服务为例核心思路是在初始化完成后先对工作表全体关闭编辑权限再对允许填写的Range打开白名单权限。下面是基于常见版本写法的核心代码示意具体API名可能随版本变化但思路一致import { FUniver, UniverInstanceType } from univer; import { UniverWorksheetEditablePermission, UniverRangeEditablePermission, } from univerjs/sheets; async function setupFillerPermissions(workbookId: string, sheetId: string) { const univerAPI FUniver.newAPI(univer); const permissionService univerAPI.getPermissionService(); // 1. 工作表全局禁止编辑 await permissionService.setWorksheetPermission({ unitId: workbookId, subUnitId: sheetId, permission: { [UniverWorksheetEditablePermission]: false, }, }); // 2. 开放白名单区域A列以及C2:E20 const allowRanges [ { startRow: 0, endRow: 49, startColumn: 0, endColumn: 0 }, // A列 { startRow: 1, endRow: 19, startColumn: 2, endColumn: 4 }, // C2:E20 ]; for (const range of allowRanges) { await permissionService.setRangePermission({ unitId: workbookId, subUnitId: sheetId, range, permission: { [UniverRangeEditablePermission]: true, }, }); } }这里有个细节容易踩坑Univer的Range是闭区间endRow和endColumn都包含在内写权限范围时别少算了一行。A列在0索引下是startColumn:0, endColumn:0如果业务上要开放到第10列就是endColumn:9。如果你同时要设置多个不连续区域用一个数组循环调setRangePermission就行代码量也不多。真正发布模板时我会把allowRanges这个数组作为模板配置一起存到后端下次加载模板时直接按配置重新设置权限不需要在业务层写死。3.4 快照持久化与恢复模板和数据如何存Univer提供工作簿快照Snapshot机制可以把工作簿的完整状态导出成一个JSON对象包括单元格数据、样式、合并单元格、数据校验、权限相关配置等。这是整个方案里最省心的一环。保存模板时我直接把当前workbook快照提交到后端const snapshot univer.getInstance(UniverInstanceType.UNIVER_SHEET, workbookId)?.getSnapshot(); await fetch(/api/template/save, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(snapshot), });用户打开填报页面时后端根据templateVersion返回对应的快照Univer用这个快照恢复出完整的表格状态const res await fetch(/api/template/load?version${version}); const snapshot await res.json(); const workbook univer.createUniverInstance(UniverInstanceType.UNIVER_SHEET, snapshot);还要提醒一点模板和数据要分开存。模板快照是业务方定义的“空白模板”用户填报完的数据快照属于“填报实例”。如果混在一起业务方重新发布模板时就可能把用户已经填好的数据覆盖掉。我会在数据表里加一个templateVersion字段每次发布模板版本号1填报记录绑定自己对应的模板版本。3.5 填报效果验证从发布到回收一条龙做完权限和持久化之后我本地起了个Demo模拟了整个流程。用管理员身份打开表格在H列临时加了一列“备注说明”合并了两个表头单元格又给“金额”列的数据校验设成了“数字且大于0”然后点击“发布模板”。接下来用普通填报账号重新打开同一个页面效果就很明显了整个Sheet可以滚动、可以选中单元格但双击表头区域时编辑器根本不激活鼠标挪到白名单区域C2:E20光标正常变成编辑状态输入数字、文本都没问题我在C2输入“8000”旁边的“合计”列立即通过公式SUM自动更新尝试往只读区域粘贴大块数据只有开放区域的单元格接受了内容其他区域原样保留。这个体验和Excel里“保护工作表允许用户编辑区域”的效果基本一致但Univer是纯Web在线不需要装任何客户端填报后的数据直接入库对后端来说收数据就是一次接口调用的事。4. 实战中踩过的坑权限不生效、样式被改、协同冲突4.1 权限命令不生效先查初始化时机和unitId我第一次调试时权限设置写在createUniverInstance后面同步执行结果页面上表格依然全部可编辑。排查了半天才发现Univer的工作簿初始化是异步的有些内部资源还没注册完成权限命令就被执行了等于打在了空门上。正确的做法是把权限设置放到Univer的onReady回调里或者用FUniver的API确保拿到的是初始化完成的实例。另一个高频坑是unitId不匹配。如果应用里同时创建了多个工作表getActiveWorkbook()取到的可能不是你正在操作的那个工作簿权限命令抛到别的workbook上界面上一看还是没锁住。我在多工作簿场景下习惯把workbookId在初始化时存下来权限操作一律显式传unitId绝不依赖“当前激活工作簿”。4.2 单元格锁了但样式还能改区分权限粒度只关闭内容编辑权限不代表用户不能改样式。这里要区分两个维度单元格内容编辑和单元格格式修改它们对应的命令和权限点不一样。有个真实案例我把某个区域设置了RangeEditablePermission: false双击进不去但用户居然还能通过右键修改填充色、加粗字体。原因是样式修改走了另外一条命令路由受的是工作表层样式相关权限点的约束而不是区域内容编辑权限。排查之后我把工作表层的样式修改权限也关掉或者干脆在UI层面对普通用户不展示格式工具栏问题才解决。这里也暴露了一个现实Univer的权限点非常多命名也细。做权限方案时不要只想着“能不能输入”要把插入行、删除行、调整列宽、合并单元格、条件格式、排序筛选这一票命令全部梳理一遍决定到底哪些按钮对填报用户可见。4.3 协同场景下权限校验不可只靠前端Univer在线版支持协同编辑多个用户同时打开同一份表格时操作会通过协同服务做合并处理。但权限校验在纯前端做只是方便用户体验不能作为安全边界。原因很简单懂技术的人可以绕过浏览器里的约束直接调协同服务接口向表格写入内容。我在对接后端时做了一个权限过滤中间件思路是服务端保存一份和模板快照一致的权限配置每次协同操作到达服务端时先校验操作者在目标区域是否有编辑权限没有权限就拒绝合并再返回给前端提示。这样前端锁权限解决体验问题后端锁权限解决安全问题两条腿都得走路。协同环境下还有一个体验问题要提前想好如果A用户对某个区域没有编辑权限B用户有那A的光标在这个区域里要么不允许选中要么选中后看不到任何编辑工具栏。Univer的交互反馈不算丰富建议产品设计时把“为什么不能编辑”的提示文案做明确纯用户直接双击不进去会误以为系统卡了。4.4 性能笔记包体积、渲染量与移动端表现Univer本质上是把一套完整办公套件搬进浏览器首屏资源不可能太轻。我实际接入时第一版全量引入后首屏脚本接近几兆内网测试还行外网就有点慢了。后来做了一些优化只注册Univer的Sheet相关插件不引入Doc和Slide模块把Univer的主包放进路由懒加载只有进入表格页面时才加载静态资源走CDN单独配置超长缓存表格数据量控制如果单个Sheet超过5000行带复杂样式初始化耗时明显增加对填报场景来说普通业务表单控制在1000行以内体验最流畅。移动端方面Univer在手机上能打开、能查看但真正输入体验比较一般小屏下单元格太小定点困难。如果你们的填报场景有一半用户是手机端我建议在移动端单独做一个轻量表单页或者在Univer外面套一层适配壳子不要强迫用户在小屏上操作高密度表格。5. 落地过程中的几条个人经验与选型判断5.1 判断标准什么场景才值得上Univer不是所有填表需求都应该用Univer。如果只是“姓名手机号备注”这种三五项的简单收集用普通表单组件更快杀鸡用牛刀反而拖慢开发节奏。但如果业务方反复提这些要求“能不能像Excel一样加列”“能不能自己调格式”“这里要加个公式自动算一下”“汇总到一块”那Univer这类在线表格引擎就是合适的底子。它最大的价值不是让你少写几个input组件而是给了业务方一个熟悉的Excel操作环境让产品需求从“开发每改一次样式就排期一次”变成“业务方自己拖拖拽拽就完成”这是管理成本的质变。5.2 我的惯例模板与填报分离、版本留痕在项目里我养成了一个惯例模板和填报数据一定分开存。模板表维护的是研发发布的表单定义数据表里每一行是一次填报实例。发布模板时把版本号1旧版本不删随时可以回溯。这样即使业务方改坏了模板也能回滚到上一个可用版本用户之前提交的填报任务不会受影响。关于权限白名单我还建议保留一份可读的配置说明。比如“A列开放”“C2:E20开放”这份配置既给前端做权限设置用也给后端做权限过滤用。我见过只在前端写了代码、后端完全不校验的案例后来有人直接调接口篡改了填报数据排查起来非常痛苦。5.3 后续可扩展填报任务、审批流与自动汇总如果只是做到“填表、锁表”Univer在这里还只发挥了三分之一的价值。后面我顺手做了两个扩展一是把模板实例化成填报任务每个任务有开始时间和截止时间过期之后自动切换成只读模式二是把填报结果统一拉回一张汇总Sheet用Univer自己的透视表和筛选功能做数据汇总财务再也不用人工CtrlC/V了。如果你们那边同样被Excel来回传文件折磨过我建议直接拿Univer先跑个Demo按我上面说的“先锁后放”思路10分钟就能看到效果。真去摸一下权限体系和快照API你会发现这套方案的上限比想象中高不少。