尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
用Univer实现在线表格单元格级只读与可编辑权限控制
做在线表格这个事有个需求被点名的频率比很多花哨功能都高能不能只让用户填指定的几个格剩下全锁死这句话我至少在不同项目里听过二十遍。Univer 这个开源在线表格项目恰好就是回答这个问题的最合适载体。它能嵌入 Web 应用提供接近原生 Excel 的体验同时又给了开发者足够的命令级控制能力用来做“用户自定义表格模板 指定单元格可填 其他单元格只读”的复杂业务场景。我把这些年在类似项目上踩过的坑、试过的方案、最后跑不动的套路都整理成这篇文章。内容会覆盖 Univer 的选型理由、权限模型的拆解、前端如何拦截编辑、后端如何做兜底校验以及哪些地方看似简单实则特别容易翻车。不管你是刚接触 Univer 的小白还是已经在做在线表格相关产品的老手这一篇都值得从头看完。1. 为什么选 Univer在线表格的需求真相与选型逻辑1.1 在线表格的核心场景不是“展示”而是“采集”大多数团队第一次想做在线表格心态是“我有 Excel 文件想放网页上让人填”。这听起来很简单但一旦落到产品里就会发现用户要的不是表格本身而是“一块受控的填写区域”。什么是受控的填写区域以运营部门常见的“设备巡检二维码”为例每台设备对应一条记录巡检人员扫码后看到台账样式表头是设备编号、设备名称、巡检项、是否正常、备注编号和名称是后台生成的巡检人员只能填写“巡检项”和“备注”这两个格子其它区域连点都点不进去。如果把整张表开放编辑数据很快就会被人为改坏如果只做一个纯表单界面又损失了表格的批量呈现能力。Univer 恰好夹在两者中间。它不是一个把 Excel 塞进浏览器的 iframe 套壳而是一个从零为 Web 设计的电子表格内核。从数据模型、公式引擎到单元格渲染和交互全都是可编程的。这种可编程性让我们可以把它改造成“长了表格外表的业务表单”。1.2 为什么不用现成的表格套件或云文档很多人在这个阶段会想直接用飞书文档、钉钉表格或者嵌一个 Google Sheets 不行吗不是不行是产品化空间太小。第三方表格产品解决的是“人跟人协作”的问题它默认所有登录者都是协作者权限控制也只能到“可编辑整个文档”或“可阅读整个文档”这种粗粒度。而我们需要的是具有业务语义的单元格级权限A 用户能填第二列到第五列B 用户能填第八列管理员能改所有列而且这套权限是由我们自己的后台动态下发的。第三方产品的另一个麻烦是定制成本。工作表的外观、字段下拉、行高列宽都要通过对方封闭的 API 去“够”一旦业务形态稍微偏一点就开始出现各种要绕过平台规则的别扭操作。自建表格内核绝大多数团队没有这个精力。Univer 是开源 MIT 协议可以直接拿来改数据在自己手里界面在自己手里权限规则也在自己手里这才是真正适合产品集成的路线。1.3 Univer 的核心能力清单结合我实际使用下来的感受Univer 最值得关注的几个能力如下完整表格交互单元格选择、编辑、批量填充、右键菜单、快捷键、行高列宽拖动基本日常办公会用到的操作都在。公式和函数内置了一套公式引擎可以在线计算、跨单元格引用数据处理能力不弱。导入导出兼容常见格式能实现和本地文件互传。命令架构所有用户操作都会派生成内部命令这给开发者提供了干预编辑行为的入口也是我们做单元格只读的关键。富文档能力除了表格Univer 还扩展支持文档和幻灯片。虽然本文只讨论表格但如果后续业务需要从表格跳到其他文档形态团队的学习成本是一致的。选型时我还会看它的社区活跃度和版本迭代速度。Univer 官方文档更新比较勤GitHub 上有不少示例代码遇到问题大多是能搜到解决方案的。如果某个开源项目半年不更新一次再完美的架构我也不敢往生产环境里放。2. 需求拆解用户自定义表格与单元格编辑权限2.1 产品上的真实场景一个典型的“登记表”业务把场景再具象一点。假设我们要给线下活动做一个线上签到和物资登记系统。主办方需要一张活动物资表表头由管理员在后台自定义品类、物资名称、负责人、需求数量、实际领用数量、备注。其中“品类”“物资名称”“负责人”是管理员填好的基础信息“需求数量”是申请人填写“实际领用数量”是仓库管理员填写“备注”大家都能填。仔细一拆这已经不是一张表而是三种角色在同一张表上的三个不同视图。用户自定义表格的需求也在这一刻成立管理员要能灵活定义表头和各列的填写规则普通用户看到模板后只能在自己的授权范围内填空。这种权限模型给前端的挑战在于Univer 本身是一个通用编辑器它默认“整个表都能编辑”。我们要做的是在它的编辑入口外面包一层权限闸门。2.2 权限模型单元格级白名单比角色判断更实用大家第一反应是做角色判断先判断当前登录用户是“申请人”还是“仓管员”再决定能不能编辑。但在真实业务里同一列可能出现“导入阶段可写、确认阶段不可写”“今天可写、明天只读”这样的动态变化。如果权限乱成一锅粥纯用角色判断会非常难以维护。我更推荐把权限描述成一张单元格白名单。后端给前端下发一个数据结构结构和“这个用户可以编辑哪些矩形区域”一一对应。比如interface UserCellPermission { regionId: string; // 区域标识 sheetId: string; // 工作表 ID startRow: number; // 起始行0 起 startCol: number; // 起始列 endRow: number; endCol: number; requireLogin: boolean; // 是否需要登录 status: editable | readonly; }前端拿到这张权限表之后再去 Univer 上做拦截。角色只是生成权限表的源数据真正在运行时发挥作用的永远是这个白名单。2.3 技术方案的三个层次谁先挡住谁设计实现方式时我把技术方案分成三个层次第一层是纯 UI 锁定。比如把只读单元格的背景变成灰色、把编辑框禁用让普通用户“看不出来能编辑”。这是最省事的做法但也是最经不起推敲的。用户按个 F12 打开开发者工具就能绕过前端界面直接改掉单元格内容就算前端没有漏洞一个复杂的复制粘贴操作也可能带着格式把内容冲进来。第二层是命令拦截。因为 Univer 会把所有编辑操作变成命令我们能监听命令、检查命令作用的范围、再决定是放行还是回滚。这种方案的拦截点在软件内部比纯 UI 锁定可靠得多能挡住绝大多数正常用户的误操作和少量恶意操作。第三层是后端兜底。无论前端做到什么程度最终的数据提交一律以后端校验为准。后端只接受白名单范围内的写入一旦发现越权字段直接拒绝或者丢弃。这一层才是数据安全的真正底线。三层方案不是三选一而是必须组合使用。我们实际项目的做法是用后端下发权限生成白名单用前端做交互反馈用后端拦截真脏数据。下面展示的就是这套组合里前端该如何落地。3. 实操搭建一个可编辑区域受控的 Univer 表格3.1 创建项目与安装依赖Univer 是 npm 包驱动的开源项目前端工程直接搭在一个 Vite Vue 或 Vite React 项目里都行。我下面以 Vite React 为例说明具体业务代码结构不影响思路。初始化项目npm create vitelatest univer-demo -- --template react-ts cd univer-demo npm install接下来安装 Univer 的相关依赖。Univer 的包名和版本在不同阶段有变化以官方最新文档为准。一般建议直接使用它提供的预设包这样不用手动维护一堆分散模块之间的版本兼容关系。npm install univerjs/core univerjs/sheets univerjs/sheets-ui univerjs/ui提示Univer 依赖比较多首次安装后一定仔细看启动日志把缺失的样式文件也引入。很多问题不是逻辑写错而是 CSS 没加载进去。3.2 初始化 Univer 工作簿Univer 实例初始化时可以接收一个 JSON这个 JSON 描述的就是一个完整的工作簿结构。我们可以自己构造一份也可以把管理员在后台设置好的模板直接发给前端。最简单的初始化代码长这样import { Univer, LocaleType } from univerjs/core; import { defaultTheme } from univerjs/design; import { sheetsPreset } from univerjs/sheets-preset; const univer new Univer({ locale: LocaleType.ZH_CN, theme: defaultTheme, presets: [sheetsPreset()], }); const workbookData { id: activity-material, name: 活动物资登记表, sheets: [ { id: sheet-1, name: 物资台账, rowCount: 100, colCount: 26, cellData: { // 1:1 是表头 1: { 1: { v: 品类 }, 2: { v: 物资名称 }, 3: { v: 负责人 }, 4: { v: 需求数量 }, 5: { v: 实际领用数量 }, 6: { v: 备注 }, }, }, }, ], }; const workbook univer.createWorkbook(workbookData);我习惯把表头和数据分开初始化。先通过新建工作簿把表头画出来再通过后续的 API 去填充基础数据这样页面不至于因为一次性塞入太多数据而卡顿。如果模板比较复杂也可以通过后台生成完整 JSON前端直接渲染。定位到可见单元格时我会用 Univer 提供的 F 命令或直接查 API 指定选区。初始化后先用一行代码选中 A1把用户的起点放在表格左上角体验会顺很多。3.3 单元格只读与可编辑的核心逻辑这里是最关键的部分也是很多新手最容易弄错的地方。不要试图让 Univer 原生支持一个“只读单元格”配置就一劳永逸它是一个表格编辑器不是表单生成器。我们要做的是干预它的事件流。Univer 的每次单元格修改基本都会落到一个叫sheet.set-range-values的命令上。这个命令带有 range 参数描述修改范围还带有 value 参数描述新值。我们只需要在自己的代码里监听所有命令查一下命令作用范围是否落在白名单外。核心思路如下维护一份editableRanges数组。监听命令执行事件。当收到sheet.set-range-values命令时把命令中的目标范围解析成行列区间。如果区间与editableRanges有交集放行否则找到被修改的旧值并回滚同时给出提示。下面是一个最小可用的拦截示例import { ICommandService } from univerjs/core; const editableRanges [ { sheetId: sheet-1, startRow: 2, startCol: 3, endRow: 20, endCol: 4 }, { sheetId: sheet-1, startRow: 2, startCol: 5, endRow: 20, endCol: 5 }, ]; function isCellInEditableRanges(sheetId: string, row: number, col: number) { return editableRanges.some((range) { if (range.sheetId ! sheetId) return false; return row range.startRow row range.endRow col range.startCol col range.endCol; }); }在 Univer 中注册监听const commandService univer.getCommandService(); commandService.onCommandExecuted((command) { if (command.id ! sheet.set-range-values) return; const params command.params || {}; const range params.range; const sheetId range?.sheetId || sheet-1; // 遍历单元格检查是否有越权修改 const startRow range.startRow; const endRow range.endRow; const startCol range.startCol; const endCol range.endCol; for (let row startRow; row endRow; row) { for (let col startCol; col endCol; col) { if (!isCellInEditableRanges(sheetId, row, col)) { // 恢复旧值或直接执行一次反向命令 restoreRange(sheetId, startRow, startCol, endRow, endCol); showReadOnlyTip(); return; } } } });实际业务中遍历整个 range 可能很浪费尤其是用户一次选中一整列时。性能更好的做法是先判断“保护范围和白名单范围是否有交集”没有交集再回滚。交界处有交集的部分如果同时包含可编辑和不可编辑单元格就逐格判断。这里给个简化判断函数function isRangeAllowed(sheetId: string, range) { for (const r of editableRanges) { if (r.sheetId ! sheetId) continue; // 判断两个矩形是否有重叠 const noOverlap range.startRow r.endRow || range.endRow r.startRow || range.startCol r.endCol || range.endCol r.startCol; if (!noOverlap) return true; } return false; }如果你只需要做“整个工作表只有某几个格子可填”那么翻转一下判断逻辑也不用管复杂矩阵了。注意回滚操作本身也是一个sheet.set-range-values命令如果不做标记会再次触发监听形成死循环。实际实现时我会在监听的 command 对象上加一个临时标记比如command._source rollback在 handler 里检测到就跳过处理。3.4 把权限和用户角色结合起来前端不能写死权限表。业务上线后不同用户登录看到的是不同的可填写区域。这时候权限表要从后台接口实时获取。推荐的数据流是这样用户登录 - 后端根据角色和当前业务状态返回权限表 - 前端存储权限表 - 构造 editableRanges - 初始化 Univer - 开始拦截接口返回的一个典型 JSON{ code: 0, data: { sheetId: sheet-1, editableRanges: [ { startRow: 2, startCol: 3, endRow: 20, endCol: 3 }, { startRow: 2, startCol: 5, endRow: 20, endCol: 5 } ] } }前端拿到这个 JSON 后直接把editableRanges重新赋值给拦截函数里引用的那个数组。这里要注意闭包陷阱如果用const保存数组拦截函数读到的还是旧引用。建议把权限数组放在一个可变的全局对象或者useRef里确保拦截函数每次执行都能读取最新权限。为了用户体验还可以把只读区域的底色改成浅灰同时给可编辑区域加一个明显边框。这些样式同样能通过 Univer 的单元格 API 设置不用自己用 CSS 硬调。3.5 后端兜底校验前端无论做得多完美都不能作为唯一防线。因为在线表格的保存动作最终会提交到服务器服务器如果照单全收那么一个临时伪造请求的人就能把任何单元格改掉。后端遇到批量提交的单元格数据时我建议用白名单收口检查请求携带的用户身份获取其可编辑区域。遍历请求中的每个单元格确认行列坐标是否落在白名单内。所有越权单元格直接忽略一个都不写入数据库。返回结果时明确告诉前端哪些字段被保留了哪些被拒绝了前端再给出提示。这里即使有前端拦截后端校验也一点不能省。不要相信任何来自浏览器的“我已经检查过了”。前端拦截的意义是提升体验后端拦截的意义才是保证数据正确。另外如果表格的保存动作不是一次性提交整块数据而是每次命令都实时同步那么后端就应该在命令进核心层之前做一个校验网关让非法命令根本到达不了数据层。前后的配合可以从“前端拦截、后端拒绝”变成“前端体验、后端裁决”的架构。4. 常见问题与避坑实录4.1 只读限制看起来失效了排查时第一件事就是确认“监听的是哪个命令”。Univer 内部有很多命令有些是 UI 层面的临时操作有些才是真正修改数据的。如果只监听了一个高层命令很容易出现“看着改了但没监听到”的情况。第二个常见原因是没有把命令参数里的sheetId、行列值正确解析。Univer 的行列起点是 0如果拿 Excel 的表格坐标直接减 1 减错整个判断都会错位。建议自己在拦截函数里打一条日志打印出每次修改的行列区间肉眼确认一遍。第三如果代码里同时用了 Univer 的快捷键、右键菜单和 API 写入这三条路径可能走的命令并不完全一样。我在项目里就把快捷键的粘贴、拖拽填充、直接赋值三种入口都测过最后统一都收敛到了sheet.set-range-values才敢确认拦截逻辑覆盖全面。4.2 复制粘贴和快捷键绕过拦截用户从外部复制一个 Excel 区域后直接粘贴到表格Univer 可能会触发多条命令甚至走批量粘贴专用通道。如果你的监听只覆盖普通单元格赋值命令这块区域就会成为漏网之鱼。处理方式有两种。第一种是在前端对“粘贴”事件做预处理解析剪贴板内容后自己过滤掉非白名单列再把允许的数据用 API 逐格写入。第二种是充分利用上文的回滚机制让批量粘贴走进同一套拦截流程一旦发现越权就整体回滚同时弹提示“该区域包含只读单元格”。从用户体验看我更推荐第一种。整体回滚对用户来说太粗暴一个不小心就把几分钟填的内容全清了。正确做法是粘贴时把可编辑列的数据留下来不可编辑列的数据丢弃这样既守住了规则也不至于让用户想骂人。4.3 并发编辑与保存冲突当多个用户同时填一张表时避免不了两人都往同一个单元格写入。如果后端只是简单地“最后提交者覆盖”数据错乱只是个时间问题。在实际项目中我的做法是给每次编辑生成一个版本号。前端拿到表格数据时后端返回当前版本提交时带上这个版本号后端发现版本不一致说明有人更新过就拒绝这次提交让前端重新拉取最新数据并提示用户“表格已被他人更新”。配合单元格白名单可以做得更进一步同一时间分别给多个用户分配不同的可编辑区域天然避免了写冲突。比如申请人填 4 到 5 列仓管员填 8 到 9 列两个区域没有交集并发量再高也不会撞车。这是单元格权限模型带来的额外好处。4.4 大数据量下的命令回滚性能如果你的表格有几百行而用户不小心全选后按了个删除键拦截到越权后要逐格恢复这个回滚操作会非常沉重。我曾经在演示环境里因为回滚逻辑写得粗糙卡了好几秒场面相当尴尬。性能优化建议如下不要把整张表的数据放在内存里做快照。只对保护区域里的单元格存旧值而且存一个稀疏 Map不是存二维数组。回滚时尽量合并范围。如果保护的是一整块矩形把相同旧值的单元格打包成一块区域一次性恢复。超大数据量下考虑把拦截逻辑放在命令进入核心层之前而不是执行后再补偿。花点时间理解 Univer 的“命令先判后行”机制性能收益比后面打补丁高得多。4.5 模板更新与老数据兼容管理员在后台改了表头删了几列新增了几列但老用户那边可能还开着旧模板。如果直接把新模板下发用户正在编辑的数据就会丢失。我建议把模板版本号一起下发前端加载时比较版本版本不同就弹窗提示“模板已更新请刷新页面”。同时模板更新不应该直接破坏旧单元格数据而是在后台做一列目标映射能迁的数据尽量迁到新位置迁不过去的数据单独归档。这样既支持用户自定义表格又不会因为自定义导致数据管理失控。关于 Univer 在线表格我最后想说的做完整套方案后我最深的体会是Univer 本身只是一把好用的“编辑器”真正让它变成业务产品的是开发者围绕它设计的规则系统——谁能在哪个格里写、写完之后怎么落库、冲突怎么解决。前端做体验后端做裁决。千万不要迷信单一技术点前端拦截做得再好没有后端兜底就是给用户留了一扇后门。如果你正在做类似的功能建议先用一个最小的闭环验证搭一个 Univer 页面搭一个简单的后端白名单接口把表格里最核心的两个单元格锁起来。跑通之后再往里面加模板管理、角色权限、数据联动。一开始就铺全功能大概率会被命令拦截的细节和版本问题拖垮。有一点我始终记得在一个通用编辑器之上做业务限制本质上是在跟“灵活性”对抗。把需求拆到位把权限边界画清楚剩下的交给代码。这套打法在朴素项目里已经足够硬了。
RELATED

相关推荐

回形针设计原理与工程应用:从弹性夹持到结构力学

回形针设计原理与工程应用:从弹性夹持到结构力学

1. 一个天天在桌上,却很少被正眼看的五金件说起来有点不好意思,我前阵子整理办公桌抽屉,倒出一堆回形针,数了数居然有七十多枚,铜色的、银色的、彩色镀膜的,有些都氧化发黑了。正准备全扔进垃圾桶&#xff…

📅 2026/11/2 18:23:06
CC攻击防护:识别、应急与长效解决方案

CC攻击防护:识别、应急与长效解决方案

1. CC攻击的本质与识别方法CC攻击(Challenge Collapsar Attack)是一种针对Web应用层的分布式拒绝服务攻击,它通过模拟真实用户行为向服务器发起大量看似合法的HTTP/HTTPS请求。与传统的DDoS攻击不同,CC攻击不依赖大流量冲击&#…

📅 2026/11/2 18:23:06
Univer开源在线表格框架:在线填报与单元格权限控制的完整实践

Univer开源在线表格框架:在线填报与单元格权限控制的完整实践

聊 Web 端表格需求时,绕不开一个名字:Univer。它是一款 TypeScript 写成的开源在线表格框架,目标是提供接近 Office 的编辑体验,同时把公式、数据透视、图表、协作这些能力都塞进浏览器里。我今年几个项目都在用 Univer 做“在线填…

📅 2026/11/2 18:23:06
MORE NEWS

更多资讯

📰

从MySQL LIKE到ES倒排索引:全文检索与混合检索实战

MySQL 的 LIKE 查询大概是每个后端开发都写过的东西,WHERE title LIKE %关键词%一敲,功能就跑通了。可当数据量从几万涨到几百万,这条语句的响应时间从 50ms 变成 5s 的时候,你就知道事情没那么简单了。这篇文章想聊的&#xff0c…

📰

Google开发客户15个方法:从搜索指令到客户画像的完整链路

做外贸和跨境业务的朋友大概都有过这种体验:展会跑断腿、平台询盘越来越贵、邮件发出去石沉大海,最后发现真正能持续带来高质量客户的渠道,反而是那个看起来最"普通"的搜索框。我做了十来年海外客户开发,团队里新人培训…

📰

H.266与神经编码:视频编码路线之争与工程落地实践

1. 视频编码的十字路口:从H.266到神经编码的路线之争视频编码这行有个很有意思的现象:每隔几年就会冒出一个“下一代标准”,宣称能比上一代省一半带宽,然后大家兴冲冲地跟进,结果发现编码复杂度翻了好几倍,…

📰

LAMMPS安装全攻略:Conda、源码与Git三条路线详解

分子动力学模拟这件事,门槛从来不在理论,而在第一步——把软件装起来。我见过太多人兴致勃勃打开终端,结果在编译报错里耗掉一整个下午,最后连个可执行文件都没跑出来。LAMMPS这个工具本身足够强大,跨平台、可扩展、支…

📰

回形针:从办公夹纸到手工改造与科学实验的万能小工具

1. 一个成熟的小铁丝:回形针的设计演进与材料门道提起 paperclip,很多人第一反应是"办公桌上那盒毫不起眼的小铁丝"。但如果你真的把它当成一个工业产品去研究,会发现回形针的寿命已经超过一百年,而且到今天仍然没有哪个…

📰

Python实现Q-Learning强化学习:5×5迷宫完整代码与调参避坑

简介:面向强化学习入门者与Python开发者的Q Learning学习材料,围绕部分可观测环境中通过交互逐步优化策略的问题展开,帮助读者把Q表、奖励、折扣因子、学习率与探索-利用平衡等概念落到可运行代码上。压缩包共5个文件,含Python实现…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬