ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Univer实战:构建指定单元格可编辑的在线表格权限方案

Univer实战:构建指定单元格可编辑的在线表格权限方案

1. 先说结论:Univer 是什么,为什么我推荐你关注它

Univer 这个开源在线表格项目,最近在技术圈的热度上升得挺快。如果你还没接触过,简单说,它是一套用 TypeScript 写的办公套件,核心是 Canvas 渲染的在线电子表格,能做到自托管、插件化、多人协同,还内置了完整的公式引擎。它的定位在体验上很像 Google Sheets 和飞书表格,但数据、服务、渲染全都可以放到自己手里,这对很多要处理敏感数据的公司来说是最关键的一点。

标题里那个热词场景——“支持用户定义表格,然后让用户去填写一些单元格,其他的单元格用户无法修改”,说得直白一点就是管理端定模板、填报端填格子。这在 OA 系统、审批流、数据收集、考试系统、内部工具的工单录入里太常见了。Univer 正好能覆盖这个场景,而且它不要求你放弃自己熟悉的开发栈,前端项目里直接以 SDK 的方式接入,后端可以自己做权限和存储,整体可控性非常高。

这篇文章适合三类人看:一是正在做内部数据系统、需要在线表格式录入界面的前端开发者;二是做技术选型的架构师,想弄清楚 Univer 和 Luckysheet、Handsontable 到底差在哪;三是产品负责人,想评估“模板表格 + 指定单元格可编辑”这种交互能不能低成本实现。我会把从零到能跑通一个“数据收集表”的过程拆开讲,包括权限控制怎么做,在线协同怎么配合,以及我在真实项目里踩过的几个坑。读完你至少能判断出 Univer 是不是你想要的,以及如果要用,第一步该干什么。

2. 为什么是 Univer:选型背后的核心逻辑

2.1 先横向对比一下主流方案

把 Univer 放在整个开源在线表格的生态里看,才能理解它为什么会出现在热词里。我整理了一张对比表,按真实选型时的关注点来列:

方案开源情况渲染方式协同能力二次开发成本典型场景
Google Sheets / Excel Online闭源自研非常强基本不可控个人或团队直接使用,数据不出网不可接受时就出局
Luckysheet开源(曾很火,维护节奏放缓)DOM 渲染一般,需自行接后端中等报表展示、轻量编辑
Handsontable核心功能非完全开源,商用收费DOM有插件方案但整体是组件思路较高专业表格组件嵌入后台
Univer开源,社区活跃Canvas 渲染有,提供协同配套思路中等,架构清爽自托管、权限控制、前后端分离的完整场景

这张表背后有一个很实际的问题:很多团队一开始觉得 Luckysheet 也挺好,但做着做着就会发现,一旦要做单元格级权限、多人实时协同、自定义命令撤销,DOM 渲染和不够清晰的命令层就会成为瓶颈。Univer 从一开始就把渲染和业务拆开设计,所有操作都走命令机制,这让权限控制和协同冲突处理有了统一的切入点。

2.2 命令机制:权限控制的关键支点

为什么“所有操作都走命令”对你做权限这么重要?你可以把它想象成银行柜台:所有存取款都必须经过柜员这条流程,你不能绕过柜台直接从金库拿钱。Univer 里的每一次赋值、合并单元格、修改样式,本质都是一个命令对象,先经过命令管线,再落到数据模型上。这意味着你要判断“这个用户能不能改这个格子”,只需要在命令执行前加一道拦截逻辑,而不用监听各种 DOM 事件、不用到处 patch 内置行为。

这是很多前端团队最容易忽略但最能影响后续开发效率的点。我见过有团队把 Univer 当成一个普通表格组件,在 onCellChange 里做校验,结果遇到协同编辑时发现“校验过了但是数据没被采纳”或者“几个用户同时改一个格子,状态乱了”。从命令层去做权限,而不是从 UI 事件层去做,是 Univer 逼着你学会的第一课。

2.3 插件化与自托管:不只是“换个皮肤”

Univer 的插件设计也不是摆设。工具栏按钮、菜单项、单元格渲染器、编辑器的协作行为,都可以通过插件注册进去。我这里讲的插件不是“界面上的一个小挂件”,而是贯穿渲染、交互、命令三层的能力扩展方式。很多业务需求,比如在某个只读单元格旁边悬浮显示审核状态、在填报时对输入做实时校验、给不同角色显示不同的列,用插件机制写起来比直接在项目里到处加 if else 干净得多。

自托管这一点也值得展开说。Google Sheets 体验确实好,但“数据不出企业网络”这条红线在很多公司是绕不过去的。Univer 可以作为前端资源直接托管在自己的服务器上,后端协同服务和数据存储都可以自己控制,等于你用开源成本换到了私有化部署的自主权。当然,天下没有免费的午餐,自托管意味着你要自己解决认证、数据库、文件存储、协同服务部署,我会在第四节讲到一套最小可跑的方案。

3. 核心场景拆解:怎么让“用户可填指定单元格,其他单元格改不了”

3.1 先理解 Excel 的“锁定单元格 + 保护工作表”

很多人第一次听到“用户可填单元格、其他单元格只读”的需求,第一反应是“这功能好像有点复杂”。其实这就是 Excel 里很老的机制:先把整张工作表的所有单元格默认设为“锁定”,然后只把你想要用户填写的 B2、C3 这些格子取消锁定,最后打开“保护工作表”开关。锁定属性 + 保护开关两个条件叠加,才决定了某个单元格最终是否可编辑。

只看锁定属性没用,因为默认所有单元格都是锁定状态;只用保护开关也没用,因为保护只针对“锁定单元格”生效。这个逻辑放到在线表格的语境里依然适用,只是实现方式从“Excel 菜单”变成了“接口调用”。你在方案设计阶段最好就直接沿用这个概念,不要自己发明一套“只读字段”之类的黑话,否则后续和技术、产品沟通都会多一层转译成本。

3.2 Univer 中的三级权限控制

在实际项目里,我通常把权限分成三个层级。第一是工作簿级:整个表格文件是否能被打开、能否新增工作表,这通常对接后端文件权限。第二是工作表级:某个 Sheet 是否可编辑,适用于“汇总页只读,录入页可编辑”这种结构。第三是区域级:在同一个 Sheet 内,哪些 range 可以被编辑,哪些 range 被锁定。标题里那个热词场景,就是典型的区域级控制。

这三个层级不是三选一,而是叠加使用。最简单的做法是:工作簿级靠后端鉴权拦截请求,工作表级和区域级在前端命令管线里做拦截和配置。Univer 官方 SDK 里关于工作簿保护和工作表保护的接口在各个版本有调整,但核心逻辑一致:先给某个 range 设置保护规则,再决定哪些操作者能绕过保护。由于版本差异比较明显,我建议你在接入时以官方文档的 workbook protection / range protection 相关章节为准,下面给的是概念性示意代码,核心思路是通用的。

// 概念性示意,具体 API 以你使用的 Univer 版本为准 // 1. 获取当前激活的工作簿和工作表 const workbook = univerAPI.getActiveWorkbook(); const sheet = workbook.getActiveSheet(); // 2. 把整个工作表设置为保护状态 // 默认所有单元格都不可编辑 sheet.getProtection().set(true, { allowSelectLockedCells: true, allowSelectUnlockedCells: true, allowEditObjects: false }); // 3. 把需要用户填写的区域解锁 // 比如只允许填写 A2:B10 sheet.unlockRange('A2:B10');

这段代码的意思很清楚:先开保护,再解禁可写区。值得注意的一条经验是:不要先解锁后开保护,因为开保护时如果你指定了全表保护,某些版本会在保护生效的瞬间把未锁定单元格的锁定状态一并重置,导致你前面辛苦解锁的区域依然被锁死。这是一个看起来不太可能、实际很容易踩的坑。

3.3 只靠保护还不够:配合数据验证和输入拦截

如果你只是做“模板表 + 指定区域可填”,保护机制已经够用。但真实业务里往往还要求“可填区域里也只能输入特定格式”,比如只能输入数字、只能选下拉值、不能填负数。这时候要用数据验证能力。Univer 提供了数据验证机制,可以对指定范围设置规则。但记住,数据验证和数据保护是两个独立能力,自定义数据验证规则的回调函数里,也可以做“这个单元格是否允许被编辑”的二次判断,这样就算保护环节被绕过,数据入口仍然有校验。

另外,编辑事件拦截也是一种兜底。即便你通过保护机制挡掉了正常 UI 的编辑操作,仍然建议在命令管线里再做一道“写操作白名单”校验。因为协同场景下,有些操作可能来自远端用户,有些操作可能来自程序化调用,保护规则不一定会拦截所有入口。把命令层校验当成最后一道保险,能避免很多诡异 bug。

4. 一个完整实操:从零搭建“数据收集表”

4.1 明确需求和最终效果

先假设一个具体场景:人力资源部的“月度绩效自评收集表”。HR 管理员定义模板,模板里包含员工姓名、部门、自评得分、自评说明这几列。员工打开链接后,只能填写自己那一行的“自评得分”和“自评说明”,姓名和部门由系统自动带入且不可改,其他区域完全锁定。

这个例子特别能反映热词里的需求,因为“用户定义表格 + 部分单元格可填写 + 其他单元格不允许修改”就是这里的核心。我把它拆成四步走:初始化项目、嵌入 Univer、设置模板和保护、联调在线协同。

4.2 第一步:初始化项目并嵌入 Univer

如果你已经有前端项目,直接按照官方文档把 Univer 的包安装上就行。我用 npm 做示例:

npm install @univerjs/preset-sheets npm install @univerjs/core

初始化一个 sheet 容器也不复杂,核心代码大致是这样的:

import { Univer } from '@univerjs/core'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverPresetSheet } from '@univerjs/preset-sheets'; const univer = new Univer({ // 这个配置项按官方 preset 示例来 // 具体参数看版本文档,重点是 UI、Sheet、公式等插件都要注册进来 ...UniverPresetSheet });

我不会在这里贴整个配置文件,因为版本不同差异很大,但有一个核心思路要强调:Univer 的接入不是一个组件套到底,而是通过插件注册机制把 core、sheets、ui、公式引擎、协同等能力分别加载。你越早点理解这一点,后面排查问题时思路越清晰。

4.3 第二步:生成模板并设置保护区域

模板数据我用一个简单的 JSON 来表达:

const template = [ ['员工姓名', '部门', '自评得分', '自评说明'], ['张三', '产品部', '', ''], ['李四', '研发部', '', ''], ['王五', '运营部', '', ''] ];

把模板写入工作表后,关键一步是设置保护。我在这里用概念性代码表达:

const sheet = workbook.getActiveSheet(); // 保护整张工作表 sheet.getProtection().set(true); // 只解锁 C2:D4,也就是“自评得分”和“自评说明”这两列的数据行 sheet.unlockRange('C2:D4');

这里有一点值得注意:实际权限控制里,员工姓名和部门的取值不一定来自表格里的静态文本,更合理的做法是由后端接口按登录人身份传值,前端负责渲染,并且把这些列也设置为不可写。这样即使有人打开浏览器的开发者工具,也无法轻易模拟出修改这些列的写命令,因为命令层已经拦掉了。

4.4 第三步:对接在线协同的最小化方案

“univer 在线”这个热词,背后包含的能力比表面看起来多:多人打开同一个表格、实时看到对方改动、网络抖动时还能继续编辑、冲突后能合并结果。Univer 官方提供了协同能力和配套实现思路,但自托管时你需要自己部署协同服务、准备数据库、做数据存储。

我的建议是:第一次跑通时先不要追求“生产级协同”。你可以在本地起一个 Univer 前端,再起一个简单的协同后端服务,先用两个浏览器窗口验证“一个人改了 C2,另一个人是否马上看到”。验证的重点不是花哨的光标颜色,而是数据一致性:两个人同时改同一个单元格时,最终结果是确定的,不会出现一个覆盖另一个但 UI 上毫无感知的情况。

协同这一块能把多少功能跑通,取决于你用的 Univer 版本和官方配套服务,不同版本的 API 差异很大。我的实操体会是:先只接“文档绑定 + 状态同步”的最小闭环,把权限命令层和协同命令层在代码上先分开,等真的做产品级方案时,再加冲突策略、版本回滚、操作日志这些重量级能力。

5. 在线协同与权限控制如何配合

5.1 协同的底层逻辑:不是“推送一个最终结果”

很多团队负责人觉得“在线协同”就是把一个人的操作广播给所有人。真不是这样。如果只是广播最终结果,那么用户 A 修改 C2 时,用户 B 正好也在改 C2,两个人会互相覆盖。规范的做法是把每个编辑动作拆成带时序的操作单元,通过协同协议让所有客户端按照相同顺序执行这组操作,保证最终状态一致。这也是我强调命令机制重要的原因:Univer 的操作天然是命令对象,协同同步的也是命令,而不是“改完后的整张表”。

这个区别直接影响到权限控制。如果你在协同里同步的是“最终结果”,那么权限校验只能发生在生成结果的瞬间,一旦结果进入同步通道,就不再可控。如果你同步的是命令,那每一个命令在进入通道前后都能被校验,这就给了你做安全边界的机会。所以在方案设计阶段,权限和协同就不是两件事,而是同一条命令链路的前后两段。

5.2 权限校验不能只在“发命令”时做

在协同场景下,权限控制通常要过两关。第一关,客户端命令发起时:用户在 UI 上按下编辑键,Univer 会尝试执行命令,你的保护规则在这里拦截非法编辑,给用户即时反馈,提示“这个格子受保护”。第二关,协同服务器接收到远端命令时:服务器侧必须再校验一次该用户是否有权执行这条命令。因为客户端永远可以被篡改,前端保护只是体验层面,真正的安全边界在服务端。

我在实际项目中踩过一个坑:只在前端做了保护,忽略了协同服务侧的命令鉴权。结果用户用脚本直接调客户端接口发命令,绕过了 UI,保护形同虚设。后来我们把“写操作命令校验”做成协同服务里的一个中间件,每个命令在落库前都要校验操作者身份和操作范围,这才把安全边界补上。

5.3 受保护区域的交互反馈

还有一个很细节、很影响体验的问题:受保护单元格在鼠标点击时,应该给什么反馈?直接禁用单元格会让用户觉得表格坏了;只读但可以选中,又会让用户困惑为什么不能输入。我的建议是:受保护区域保留“可选中、可复制、可查看”的交互,但在用户尝试输入时给出明确提示,比如状态栏显示“该单元格受保护,不可编辑”,或者通过 toast 提示。这样既不影响查看,也不会让用户以为浏览器卡了。

另外,对于可填区也应该有视觉引导。可以在模板加载完成时,给可编辑区域加一个浅色底纹,或者加一个输入框样式的边框。很多表单式录入场景之所以体验好,是因为用户一眼就能看出来哪些地方能填。这在在线表格里是很容易实现的一个小优化,却经常被忽略。

6. 常见问题与排查技巧实录

6.1 为什么我设置了保护,单元格还是能被编辑

这个问题的原因通常是:保护规则虽然设置了,但命令管线里还有一条编辑命令没有被标记为受保护范围拦截。检查思路就一条:在发起编辑命令之前,先打印命令链路上保护判断的结果。常见错误有三种:一是把保护设置放在 sheet 渲染完成之前,导致保护配置没挂到正确实例上;二是解锁区域使用了错误的 range 地址,比如把 A2:B10 写成了 A2:B9;三是协同模式下远端命令走了白名单,没走保护校验。

我给你的排查工具建议是:别只靠鼠标去点单元格测试,直接在代码里调用命令执行接口,逐步跟踪每一步的保护判断结果。这样你能在最短时间内确认是“保护没生效”还是“编辑命令没被拦截”,减少盲猜时间。

6.2 协同多人打开后,保护配置会互相覆盖吗

会有这种情况。假设 A、B 两个用户同时打开同一个模板,A 在负责改保护配置,B 正在表单里填写,如果 A 的保存操作把整张表的保护配置整体覆盖了,B 的修改就会撞上新的保护规则。我的建议是:保护配置只由后台服务端统一生成,前端只负责读取和渲染,别让前端用户直接用工具栏去改保护设置。如果你确实需要管理员在线调整保护范围,至少要把“编辑保护配置”的权限做成一个单独角色,并且使用事务化保存,避免并发写覆盖。

6.3 大表格操作卡顿怎么办

Univer 的 Canvas 渲染在多数场景下性能不错,但如果你把一张数据量很大的表全部挂进数据验证、条件格式、保护区域里,初始化时间会明显变长。我踩过的坑是:为了省事,给整个数据列都加了数据验证规则,结果用户输入一个字符都要解析一堆规则。排查后改成只对实际有数据的行设置规则,并且保护区域按真实业务范围收紧,卡顿立刻缓解。性能优化的经验就一句话:规则的范围要克制,永远不要用“整列”去表达“本列大部分行”。

6.4 数据校验提示不出现怎么办

数据验证触发失败,最常见的原因是触发时机设置成了“提交时”,而用户只是在填写过程中测试;其次是数据类型选择与单元格已有数据格式冲突。实际排查时,先把单元格格式统一,再检查校验规则的触发模式,基本都能定位。为了让你以后排查更快,我把这些常见问题整理成一个速查表:

现象常见原因处理建议
保护区域仍可编辑保护配置未挂载 / 命令绕过在命令管线打印拦截结果,检查 range 地址
协同时权限互相覆盖前端并发写保护配置保护配置由服务端统一下发
大表格卡顿规则范围过大收紧数据验证和保护范围
校验提示不出现触发时机或格式冲突统一单元格格式,检查触发模式

这个表里的每一条,我都亲手在处理客户问题或者内部项目时碰到过。尤其是“保护区域仍可编辑”那一类,最容易让人怀疑是不是框架 bug,实际上十次里有八次是自己配置时序不对。

7. 最后的个人体会

我在实际把 Univer 集成进一个内部填报系统之后,最大的感受就是:这个项目给了你很多高级能力,但它不是一个点开即用的 SaaS 工具,你需要为它设计好权限模型、协同边界和错误处理。第一周你可能都在解决环境问题和 API 版本差异,但一旦把命令层、保护层、协同层的逻辑理顺,后续加功能会非常快。

最后再分享一个小技巧:调试保护规则时,不要每次都用鼠标去点单元格测试,直接在命令行工具里调用命令执行接口,逐步跟踪每一步的保护判断结果。这样你能在最短时间内确认是“保护没生效”还是“编辑命令没被拦截”,省下来的时间足够你多写两个自定义插件了。如果你正准备在内部系统里引入“用户填指定格子、其他格子只读”的在线表格能力,我建议你完全可以用 Univer 先跑一个最小原型,时间不会白花。

返回列表