最近在做一个企业内部的“数据填报系统”需求听起来平平无奇主管先创建一张表格把自己想让员工填写的格子标出来其他单元格谁都别想动员工打开页面后只能在指定的可编辑单元格里录入数据表头、说明、公式区域全部只读。这个需求放在网页端做第一反应是直接用Excel文件上传后端解析就完了。但产品加了一个限制员工必须在线看到原始表格填完还能复核、导出Excel。Excel上传方案只能做单向下发做不了基于单元格粒度的编辑权限控制。于是我把目标转向纯前端表格内核最后选定了开源的Univer整体跑下来效果符合预期。这篇文章算是一份从选型到落地、再到踩坑的完整记录如果你也在做类似的台账录入、在线填报、静态模板收集工具应该能少走不少弯路。1. 为什么用Univer而不是SpreadJS或Luckysheet1.1 业务场景拆解先把这个需求掰开看它本质上是“带权限约束的在线表格填写工具”而不是一个多人协作文档。主管在管理端负责定义模板包括表头、列宽、公式、默认值、下拉菜单然后框选一块区域作为“可填写区”其余地方都是“只读区”。员工侧打开的页面需求就三条第一能看清整张表格的结构第二只能在指定单元格里输入内容第三录入后能保存、能导出。表面上是表格组件的能力实际上考验的是“编辑控制”的精细程度毕竟多数在线表格组件默认允许用户随便点随便改。结合这个场景我当时给方案定了几个硬性指标必须是前端渲染的类Excel交互体验而不是一张“看起来像表格”的HTML表。支持列宽、合并单元格、样式、公式因为主管设计的模板经常会用到这些能力。能控制单元格的可编辑性最好可以拦截具体的编辑操作。与现有前端框架的集成成本不能太高团队是React技术栈。开源优先能自己改代码不依赖黑盒授权。1.2 SpreadJS、Luckysheet与Univer的横向对比市面上的前端表格方案其实不少真正纳入过评估的只有三个SpreadJS、Luckysheet、Univer。SpreadJS是商业组件功能确实全面数据透视、图表、打印、导入导出都做得很成熟。团队试用过稳定性也让人放心但授权费用摆在那里。对于一个内部填报工具来说这个成本不值得背而且商业组件的扩展边界即使再宽也总有“咨询工单式”的等待成本。Luckysheet是较早的开源方案Excel互导顺手UI也接近Office风格。但它的代码维护节奏一直不太稳定而且协同能力被拆成了另一个产品插件体系相对封闭。拿来做常规展示可以要在上面做“单元格编辑权限”这种深水区改造心里没底。最终敲定的是Univer。它把表格、文档、幻灯片放在一套统一内核里UI层和数据层分离扩展上确实灵活。更关键的是它的命令驱动架构界面上每一次操作最终都会变成一条Command在命令系统里跑。既然是命令就能在它执行之前拦下来做权限判断。这对于“部分单元格只读”这种需求来说属于天然对口的技术底座。1.3 Univer的架构优势命令驱动带来的可能性Univer的核心概念并不复杂整个表格数据存储在Workbook里每个Sheet由CellData、样式、行列信息等组成而所有对数据的修改都要经由命令系统派发。这个设计带来一个好处你要控制用户能不能改某个单元格不一定要去改UI层的事件绑定只需要在命令入口处做判断。用户按了Delete、手动输入、粘贴数据走到最后都会触发对应的编辑命令拦截到这一层权限控制就变得可控且统一。Univer还提供了一套Facade API面向业务开发者的封装很多常用操作不需要直接碰命令层代码。比如要设置某个区域的值标准写法是拿活动工作表然后getRange(row, col, rowCount, colCount)后调用setValue非常直观。官方推荐的集成方式也简单支持React、Vue、Angular等主流框架容器挂载的方式和普通DOM组件差别不大。2. 工程接入5分钟让Univer跑起来2.1 依赖安装与Preset方案当前版本Univer推荐使用Preset方式进行安装它会把创建Univer实例所需的模块打包好免去手动组装一套Service的繁琐过程。以Sheet场景为例安装命令如下npm install univerjs/presets然后就可以创建Univer实例import { FUniver } from univerjs/core/facade; import { UniverPresetSheet } from univerjs/presets/lib/sheets; const container document.getElementById(univer-container)!; const univer FUniver.newUniver({ locale: zhCN, presets: [ UniverPresetSheet({ container }) ] });容器是一个普通的div注意需要给这个容器一个明确宽度和高度否则画布能渲染出来但交互区尺寸会是0。我在项目里统一用的#univer-container { width: 100%; height: 640px; border: 1px solid #ddd; border-radius: 4px; }Univer的版本迭代非常快不同小版本之间API名称和参数结构会有调整。我下面给的代码都基于当时使用的0.3.x版本如果你安装的版本更新碰到方法名对不上就去node_modules/univerjs/**/lib/facade里翻类型定义这个习惯在Univer社区里几乎是必须的。2.2 创建Workbook并写入初始数据实例创建完成后可以直接在工作表上做初始化。我们要做的模板会先在管理端预填好表头、公式、样式然后由员工端加载。const activeSheet univer.getActiveSheet(); // 直接给一块区域写入二维数组 activeSheet .getRange(0, 0, 2, 4) .setValue([ [指标名称, 上期值, 本期填写值, 环比变化], [营收(万元), 120, null, C2-B2] ]);写入的数据支持数字、字符串、公式字符串公式会由Univer内置计算引擎计算。注意我在“本期填写值”那里填了null表示这是一个空单元格留给员工填写后端返回模板数据时空单元格保持为空即可。如果工作薄里需要多个Sheet可以在创建时配置好也可以用后续的命令动态添加。项目里我们的模板是按Sheet组织的一个模板一个Sheet加载完数据后通过sheet.setName()设置成指标名称。2.3 通过Facade操作Sheet公式、样式、行列配置除了写数据模板还需要设置列宽、合并单元格、背景色。这些能力全都可以通过Facade API完成// 设置第1列宽度为120像素 activeSheet.setColumnWidth(0, 120); // 合并A1:A2 activeSheet.mergeCells(0, 0, 2, 1); // 给表头区域加灰色背景 activeSheet .getRange(0, 0, 1, 4) .setStyle({ backColor: #F2F2F2, fontWeight: bold, horizontalAlign: center });这些操作在界面上会有立即可见的反馈也可以全部在初始化阶段执行。我们甚至给管理端做过一个“模板设计预览”就是复用同一套Facade代码只是把输入源从后端配置换成管理员在页面上框选的结果。3. 核心需求指定单元格可编辑其余只读3.1 将“可编辑区域”抽象成配置“允许用户填写一定区域、其他单元格不能修改”这个需求实现前先要设计数据模型。不能把可编辑状态散落在各个单元格上否则模板配置、权限判断、后端校验都会很难维护。我采用的方式是定义一份区域配置精确到行、列、行数、列数{ templateId: template-001, editableRanges: [ { row: 1, col: 2, rowSpan: 5, colSpan: 1 }, { row: 1, col: 3, rowSpan: 5, colSpan: 1 } ], protectedRanges: [] }意思很明确整张表的默认状态是“只读”只有两个区域允许编辑。一来方便管理端通过框选自动生成二来员工端加载模板时直接套用这套配置三来后端在接收提交数据时也能根据同一个配置做二次校验避免有人绕过前端直接造接口请求。3.2 方案A工作表保护与单元格锁定Univer本身有工作表保护的概念思路和Excel一致先把允许编辑的单元格标记为“不锁定”再开启工作表的保护模式这样被保护的单元格就不可编辑了。const editableRanges [ { row: 1, col: 2, rowSpan: 5, colSpan: 1 }, { row: 1, col: 3, rowSpan: 5, colSpan: 1 } ]; // 先给所有单元格上锁 activeSheet .getRange(0, 0, activeSheet.getConfig().rowCount, activeSheet.getConfig().columnCount) .setStyle({ protection: { lock: true } }); // 再把允许编辑的区域解锁 for (const range of editableRanges) { activeSheet .getRange(range.row, range.col, range.rowSpan, range.colSpan) .setStyle({ protection: { lock: false } }); } // 开启工作表保护 activeSheet.setProtection({ protect: true, password: });这段代码对Excel场景非常自然导出后如果用户把文件下载到本地保护信息也能保留。但Univer在不同版本里对保护能力的实现深度有差异尤其是拖拽填充、双击编辑这类操作不一定完全被保护逻辑拦住。我在测试中就碰到过“单元格锁住了但下拉填充还能把数据拖进锁定区”的情况所以不能只依赖这一层。3.3 方案B命令拦截实现软只读更稳妥的方式是在命令系统层面做拦截。Univer的大部分编辑行为最终都会派发编辑类命令比如BeforeActiveCellEditCommand这类前置命令。我们可以在命令进入执行管线前拿到当前操作的目标单元格坐标然后判断这个坐标是否落在可编辑区域内不在就直接阻止。这部分的代码是核心但不同版本命令ID略有差异我给出的是经过验证的思路import { ISetRangeValuesCommandParams } from univerjs/sheets; import { SetRangeValuesCommand } from univerjs/sheets; const editableRanges [ { row: 1, col: 2, rowSpan: 5, colSpan: 1 } ]; function isEditable(row: number, col: number) { for (const range of editableRanges) { if ( row range.row row range.row range.rowSpan col range.col col range.col range.colSpan ) { return true; } } return false; } univer.onBeforeCommandExecute((command) { if (command.id SetRangeValuesCommand.id) { const params command.params as ISetRangeValuesCommandParams; // 解析出目标区域 const { range } params; // 遍历区域的每个单元格只要有不可编辑的格子就整体阻止 for (let row range.startRow; row range.endRow; row) { for (let col range.startColumn; col range.endColumn; col) { if (!isEditable(row, col)) { // 阻止命令执行弹提示或忽略 return false; } } } } });命令被阻止后界面上的表现是“输入无效”但用户没有反馈会觉得很奇怪。所以我在拦截的同时会调用业务层的Toast组件提示“当前单元格为只读区域请在可填写范围内录入”。这个方案不影响公式计算、不影响界面渲染只是把编辑入口关掉。缺点是它只对在线交互有效如果用户导出Excel后本地修改那就管不到了。3.4 双轨方案与取舍建议这两个方案不是二选一我最终在线上版本里是组合使用的。交互层用命令拦截保证员工在网页上没法误触只读单元格也不会有奇怪的拖拽填充行为。导出层用工作表保护保证下载到本地的Excel自带锁定规则二次分发后也不会被随意修改。如果只是做内部工具单用方案B其实也够如果要交付给外部供应商或跨部门使用Excel导出的保护信息就是刚需。建议还是两个都做成本不高收益是权限链路完整。4. 进阶应用用户自定义模板、填写、回收与校验4.1 管理端如何设计模板既然热词里提到了“支持用户定义表格”那就必然涉及管理端如何把模板“设计”出来。Univer整个界面是Web应用可以直接在管理端页面嵌入一个Univer实例让管理员像操作Excel一样编辑模板。我在项目里给管理端做了一个“模板编辑模式”管理员打开编辑器像操作Excel一样填写表头、设置样式、插入公式。编辑完成后切换到一个“标记可编辑区域”的状态这个状态下可以直接在页面上框选一块区域框选后点击“标记为可填写”系统会把区域坐标追加到editableRanges里。点击保存后前端把当前工作表的配置、样式、数据、公式连同editableRanges一起提交到后端存成一套模板JSON。保存模板的核心数据可以这样拿const workbook univer.getActiveWorkbook(); const sheet workbook!.getActiveSheet(); const config sheet.getConfig(); // 把工作簿、公式、样式保存为模板 const templatePayload { name: sheet.getName(), cellData: config.cellData, rowData: config.rowData, colData: config.colData, // 来自管理员的框选结果 editableRanges };这份JSON就是模板的完整定义。员工端加载时只要拿到它用同样的一套Univer API重建即可。4.2 用户填写后的数据提取员工填写完成后最核心的问题是如何把数据保存到后端我通常不用setValue反向读取全部单元格。更合理的做法是只遍历editableRanges里的区域因为这些才是用户真正被允许修改的地方其他单元格无论是什么数据都不应该被提交。const result: Recordstring, unknown[] []; for (const range of editableRanges) { const values activeSheet .getRange(range.row, range.col, range.rowSpan, range.colSpan) .getValue(); result.push({ range: ${range.row}:${range.col}, values }); } // 提交给后端 api.post(/submit, { templateId: template-001, data: result });这里有个小坑如果可编辑区域里混有公式单元格getValue()拿到的可能是计算后的值而不是公式本身。如果业务需要在服务端重新计算建议前端提交时把公式也顺带一起提交或者只允许在纯输入单元格上做可编辑标记别把公式单元格开放出来。一般模板设计规范里就该禁止这个操作。4.3 数据校验与公式联动填报表单光能输入还不够需要有一层校验。Univer自带的数据校验能力可以用于下拉选择、数值范围控制但如果我们希望校验逻辑完全由后端统一维护更可控的方式还是在命令拦截层做自定义校验。举个例子某个指标要求填写的数值不能小于0univer.onBeforeCommandExecute((command) { if (command.id SetRangeValuesCommand.id) { // 拿到value后做校验 const newValues params.value; for (let i 0; i newValues.length; i) { for (let j 0; j newValues[i].length; j) { if (typeof newValues[i][j] number newValues[i][j] 0) { showError(数值不能小于0); return false; } } } } });这样做的好处是即使员工输入了非法值也会在单元格层面直接被拦下不会污染数据。配合后端的提交校验数据质量可控。公式联动方面我建议把公式放在只读区而不是可填写区。比如“环比变化”那一列管理员在模板里预先写好公式C2-B2员工只需填“本期填写值”环比自动算出来。由于公式列不在可编辑范围内员工也没法覆盖公式这个设计天然保护了计算逻辑。5. 避坑实录Univer落地过程中的常见问题5.1 只读控制被工具栏绕过只拦截编辑命令并不代表高枕无忧。Univer自带的工具栏里还有“清除内容”、“删除单元格”、“排序”等操作这些操作会绕过用户输入层面的拦截直接改掉单元格数据。我在一开始就吃过这个亏员工按了全选然后点“清除内容”整张表的内容全没了。后来处理方式是把工具栏和右键菜单裁剪掉UniverPresetSheet({ container, toolbar: false, rightMenu: false });对于纯填写场景工具栏对用户来说本来就是多余的。保留单元格选择和筛选就有足够好的体验工具栏关掉后误操作大幅减少。如果确实需要部分按钮Univer支持自定义工具栏配置按需保留即可。5.2 保护状态下的公式编辑冲突使用工作表保护后公式单元格和锁定的样式单元格虽然不能手动编辑了但Univer内部某些命令或者拖拽自动填充还是可能触达这些区域。我在测试中复现过选中一个可编辑单元格拖拽填充柄往下拉Univer会把可编辑区域的公式或值延续到下面几行即使这些行不在解锁范围内。排查下来发现这是自动填充逻辑没有实时校验锁定状态导致的。我的处理方式是在模板初始化阶段就把表格设置为只读默认态避免“先全部可编辑再标记只读”造成的保护盲区。命令拦截层把“拖拽填充”这一类命令也纳入判断只要目标区域包含非可编辑单元格就整体拒绝。如果你在线上也遇到类似问题建议在onBeforeCommandExecute里把能想到的命令都打印出来然后逐个检查哪些命令操作了非可编辑区域。5.3 大数据量下渲染与性能调优表格数据到几千行时Univer渲染和交互会有一定的卡顿这是Web表格的通病。我们的填报模板一般控制在一百行以内暂未遇到大的性能瓶颈。但如果你的模板确实要承载大量数据有几个调整方向关闭网格线渲染Univer设置里能省一部分绘制开销。减少复杂合并单元格的使用合并会给计算带来额外开销。初始化数据时避免逐格写入尽量用区域批量写入也就是getRange(...).setValue(二维数组)的方式。如果模板只读可以设置后不启用编辑服务轻量模式下性能提升明显。5.4 版本升级导致的API不兼容Univer到现在依然处于快速迭代期差不多半年内API就可能发生不兼容变化。我们曾因为升级小版本导致setProtection的参数结构变了编译不通过排查花了不少时间。我的建议是锁定主版本不要随便升级小版本除非有明确的功能修复。升级前先在本地跑一遍核心的模板初始化、只读控制、数据提取自动化测试。关注官方升级日志尤其留意Facade层的变更说明。这类项目里稳定比新特性重要得多。5.5 撤销栈把只读逻辑绕过去最后一个坑比较隐蔽。Univer默认有撤销/重做功能用户先执行了一个允许的操作然后再通过撤销命令把表格恢复到某个中间状态这个恢复操作可能会把只读区域的数据也一起改动。处理方式是看场景填报工具原则上不允许撤销恢复覆盖其他区域我把撤销栈范围做了限制在命令拦截层禁止撤销命令影响非可编辑区域或者干脆在员工端关闭撤销功能。内部工具里填写准确比操作便利更重要。最后想分享的一个心得这个项目最让我满意的不是Univer能渲染出多像Excel而是它的命令机制让权限控制变得足够干净。你要负责的是“定义规则”而不是“对抗UI”把一份模板的可编辑区域配好拦截命令校验数据这套链路跑通后员工真的很难通过常规操作破坏表格。后续如果想再接协同编辑、接入AI生成公式模板Univer的架构也能撑得住。至少再让我选一次我还会用这套方案。
企业数字化 ERP 产品动态
相关推荐
【无套路自取】群晖洗白算号器:一键补齐 NAS 序列号与 MAC 地址 【无套路自取】群晖洗白算号器:一键补齐 NAS 序列号与 MAC 地址 【免费下载链接】群晖全能全系列洗白算号器 本仓库提供了一个资源文件,名为“群晖全能全系列洗白算号器”。该工具旨在帮助用户生成群晖NAS设备的序列号和MAC地址,以便进行洗白… · 2026/11/13 19:52:35
borsapy CLI命令行完全手册:25+命令让你在终端秒查股价、财报与信号 【免费下载链接】borsapy Python library for Turkish financial markets - BIST stocks, forex, crypto, funds, inflation 项目地址: https://gitcode.com/gh_mirrors/bo/borsapy 点击查看 免费下载 borsapy 是一个面向土耳其金融市场的 Python 数据库࿰… · 2026/11/6 3:04:46
【纯免费分享】IPOP IP绑定工具 4.1 完整指南:一套工具整合 10 项网络功能 【纯免费分享】IPOP IP绑定工具 4.1 完整指南:一套工具整合 10 项网络功能 【免费下载链接】华为IP工具IPOPV4.1 **IPOP软件** 是一款由华为推出的强大网络管理工具集,其核心功能始于IP地址的动态绑定,历经多次迭代更新,如今已经发… · 2026/11/2 14:14:36
新会电工证查询怕考不过白交培训费?考前押题帮你稳过 新会电工证查询怕考不过白交培训费?考前押题帮你稳过 怕考不过白交培训费?你不是一个人。新会电工证查询已经成了不少学员心中的“卡点”,尤其在考前没有针对性复习,结果一上考场就懵了。但其实,掌握好考前押题技巧,结合官方题库和实操经验,你也能轻松… · 2026/11/16 3:39:54
培训机构给考电工证吗 值不值得考 一个工地电工的真实经验 培训机构给考电工证吗 值不值得考 一个工地电工的真实经验 工地太忙根本没时间复习考试,这几乎是所有一线工人都会遇到的问题。尤其是新乡的应届工程类毕业生,刚毕业就进入工地,每天从早干到晚,哪还有心思去复习那些考试题?但现实是, 电工证… · 2026/11/16 3:39:54
低压电工证复审简单吗保姆级教程:工地太忙根本没时间复习考试 低压电工证复审简单吗保姆级教程:工地太忙根本没时间复习考试 工地太忙根本没时间复习考试,这几乎是每个低压电工的共同痛点。尤其是面对低压电工证复审时,不仅要准备理论考试,还要面对实操考核,时间紧、任务重、压力大。很多人开始怀疑:… · 2026/11/16 3:39:54
安阳考电工证的学校怎么选?内部消息教你避坑 安阳考电工证的学校怎么选?内部消息教你避坑 之前考的证在外省能不能转过来?这个问题困扰了不少在河南打工的小伙伴,特别是那些已经在外地考了电工证,但想回安阳发展的朋友。别急,今天我就给你讲讲 安阳考电工证的学校 怎么选, 内部消息… · 2026/11/16 3:39:54
电工证延期怎么搞?别被中介坑了,含金量得靠自己 电工证延期怎么搞?别被中介坑了,含金量得靠自己 你是不是也遇到过这种情况,电工证快到期了,不知道去哪儿办延期,怕被中介坑钱、拿假证,或者干脆被忽悠着办了个没用的证?别急,今天我就从洛阳涧西区市政公用工程一线操作员的角度,来给你说清楚电工证延… · 2026/11/16 3:39:54
电工证贵阳在哪里报名千万别踩坑,工地太忙根本没时间复习考试 电工证贵阳在哪里报名千万别踩坑,工地太忙根本没时间复习考试 工地太忙根本没时间复习考试,这是很多一线电工的真实写照,尤其是像洛阳这样的城市,市政工程多,项目周期紧,很多人在赶工期的同时,还要应对电工证的考试和复审,稍不注意就容易踩坑。… · 2026/11/16 3:38:53
长春初级电工证怎么考?通过率揭秘全在这里 长春初级电工证怎么考?通过率揭秘全在这里 之前考的证在外省能不能转过来?这几乎是每个想从事电工工作的人都会问的问题。特别是那些在外地打拼多年、手里握着一张“外地电工证”的人,到了长春想继续干这一行,第一反应就是“我的证能用吗?”。今天我们就… · 2026/11/16 3:21:55
国内电工证报考条件与考试时间 低压电工证怎么考?学历不高也能报名,官方报名入口全在这 你是不是经常听说“电工证是入行门槛”,但又担心自己学历不够、经验不足,根本报不了名?别急,今天就带你从零了解【国内电工证】的报考流程、考试内容,还有怎么在 官方报名入口… · 2026/11/16 3:01:18
电工作业时如何断电?电子证书到底能不能全国通用? 电工作业时如何断电?电子证书到底能不能全国通用? 实操考试心里没底怕挂科,特别是电工作业时如何断电这一操作,不仅关系到你能不能拿到证书,更关系到以后能不能顺利上岗。很多人觉得这不过就是“断个电”,但其实这里面的门道可不少,稍有不慎就可能出错… · 2026/11/15 0:00:50
电工证培训的通知在哪考?别再被中介骗了 电工证培训的通知在哪考?别再被中介骗了 你是不是还在为 电工证培训的通知在哪考 发愁?网上搜了一堆信息,结果要么是挂羊头卖狗肉的培训机构,要么就是打着“包过”旗号的骗子?别急,今天我来给你讲讲, 电工证培训的通知… · 2026/11/16 0:00:34
电工证去哪个部门考核值不值得考 信阳转岗人真实经历分享 电工证去哪个部门考核值不值得考 信阳转岗人真实经历分享 怕考不过白交培训费?你不是一个人,信阳的李师傅去年刚转岗做电工,就因为没搞清楚电工证去哪个部门考核,差点交了8000块的培训费。别急,下面这份实操考试全攻略,告诉你电工证到底值不值得考… · 2026/11/16 0:00:34
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 1:51:20
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 4:02:19
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 3:02:05