ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Univer在线表格:用开源方案实现指定区域可编辑的数据填报

Univer在线表格:用开源方案实现指定区域可编辑的数据填报 如果你平时经常处理“网页表单收集”“在线填表”这类需求大概率遇到过一句让人头疼的话我想要一张网页表格我自己定义好表头、样式和需要填的字段然后发给同事、客户或者群里的用户他们只能在指定的几个格子里填其他区域一律不能改。早几年我可能会建议直接去买一套在线Office但自从把开源的 Univer 引入了几个项目之后这个场景变得非常可控。Univer 是一个开源在线表格引擎社区一般说的“univer在线”就是指把 Univer 部署在浏览器端、随时打开随时用。这篇文章我就从实际落地的角度把 Univer 的核心能力、方案选型思路以及“定义表格后只允许用户编辑部分单元格”的完整做法拆开讲清楚适合正在做在线文档、数据填报、协同表格或者企业内部收集工具的人参考。1. Univer到底是什么它解决什么场景1.1 一分钟认识UniverUniver 本质上是一套“跑在浏览器里的表格内核”用 Canvas 做渲染用 TypeScript 编写项目开源主协议是 MIT。它可以让你把一个类似 Excel 的电子表格页面嵌入到自己的 Web 应用里不需要额外安装 Office 软件也不依赖商业组件授权。和普通的前端表格组件相比Univer 更像一个完整的表格软件支持公式、条件格式、筛选、排序、冻结窗格、数据透视表、协同编辑还支持自定义单元格渲染和函数扩展。我第一次接触 Univer 的时候第一感受是“这东西的定位不是做个好看的表格展示而是直接把一个表格产品塞进你的业务系统”。它不只是输出几行几列的数据而是具备完整的表格数据模型内部有工作簿、工作表、单元格、区域、命名字段这些概念。也就是说你可以在页面上做出一张真正可以“用”的表格用户可以像操作 Excel 一样去编辑、计算、填写同时还留给你足够的 API 去约束这些操作。1.2 真正解决的是“回收和汇总”的难题绝大多数业务系统里的表格需求并不是要做数据分析工具而是要完成一个动作把散落在各处的数据收集回来再统一汇总。传统做法是把 Excel 模板发到群里让大家下载、填写、回传然后专人一个个手工合并。这个过程时间成本高而且格式五花八门填错列、改错区域都是常有的事。Univer 能把这个动作变成一个在线协作流程管理员在网页里定义好列名、格式、校验规则把需要用户填写的格子开放出来其余单元格全部锁定。用户拿到链接后直接在浏览器里填填完提交数据进到后台前端可以统一读取、展示甚至导出成新的 Excel 文件。这个流程里“定义表格”和“限定填写范围”是核心诉求Univer 正好在这两点上提供了足够的底层能力。1.3 “Univer在线”到底怎么理解“univer在线”这个词在最近的需求里出现过很多次。它其实指的是两种形态一是 Univer 渲染出来的网页表格天然就是在线操作不需要下载软件二是 Univer 在协同方面有服务端方案多个用户同时打开同一张表时可以做到基于操作的协同编辑。前者是开箱即用的后者一般需要自己部署协同服务或者集成到现有的后端架构里。如果你只是想快速把表格嵌入页面那么只用前端部分就够了。我后面的例子会围绕“前端表格 受保护区域”展开这也是大部分数据收集场景里最稳的一种起步方式。2. 方案选型为什么我会用Univer而不是其他表格方案2.1 常见表格方案的真实对比在做在线表格选型之前我先列一下市面上常见的几条路线每条路线我都实际接触过先看清楚边界再选型能省掉很多返工。方案编辑能力单元格保护协同编辑成本二次开发难度普通HTML表格弱需要手写无低低但需求会越做越多前端数据表格库AG Grid等中部分支持无中等中Luckysheet中有保护能力有旧版开源中高SpreadJS强完善有商业授权费高中Univer强完善有服务端方案开源中高但值得在“单元格保护”这个需求上思路其实分两种一种是表格自身提供锁定和解锁区域的能力另一种是开发者在编辑事件里拦截数据变更。表格自身提供保护能力会更可靠因为锁定状态是跟随数据模型走的不管用户怎么点击、复制、粘贴、拖拽最终写入前都会经过控制逻辑。Univer 很早就把工作表的保护能力和区域权限作为一等公民做到内核里这一点比自己在表格组件上写拦截要省力得多。2.2 让我站队Univer的三个理由第一个理由纯前端渲染和具体业务框架无关。Univer 有框架无关的核心层官方也提供了对 React、Vue3 等框架的适配。我在不同项目里分别用过 Vue3 和 React 接入改动量不大它不会把你的页面结构绑架成某个框架的专属组件。第二个理由插件化架构能力可以按需加载。Univer 的核心包只包含数据模型和计算逻辑UI、协同、导入导出、公式编辑这些能力都是插件。项目里如果只需要填表和派发数据可以不加载多余的模块这样首屏体积和加载速度都在可控范围。第三个理由表格数据的控制粒度细。从单元格、区域到工作表、工作簿都有对应的数据结构和操作入口。你要做到“哪些格子能填哪些不能填”不需要 hack 渲染层只要操作保护和权限控制相关的数据即可干净利落。2.3 什么时候不建议用UniverUniver 并不适合所有场景。如果只是做一个只读的报表页面引入 Univer 反而会显得笨重服务端渲染一个 HTML 表格就够用。如果你的核心场景是 Word 文档编辑页Univer 虽然也在扩展文档内核但目前更成熟、更常用的是它的表格能力。还有一个必须诚实说的问题Univer 的深度定制有一定学习成本版本升级时 API 有变动团队里如果没有人愿意啃 TypeScript 类型定义遇到问题会比较吃力。不过针对“univer 支持用户定义表格然后让用户填写部分单元格”这类需求Univer 的能力是恰到好处的。下面这部分我就把核心实现机制摊开讲。3. 核心机制拆解如何做到“只有指定单元格能改”3.1 单元格保护的本质先锁门再给钥匙理解 Univer 的保护机制可以用一个生活场景来类比一栋楼里有很多房间默认所有房间都锁着。管理员手里有一把总钥匙他指定哪些房间是开放的别人进入后只能在开放房间里活动其他地方进不去。Univer 的表格模型也是一样工作表中的单元格默认是“锁定”状态当开启保护后所有锁定单元格都不允许用户修改只有被加入“可编辑区域”的格子会被解锁用户才能填写和修改。具体到 Univer 的数据结构里工作表会有一份保护配置比如是否允许格式化、是否允许插入行列、是否允许筛选排序等等。这些开关控制的是“用户能不能动表格结构”。针对单元格本身则需要配合可编辑区域来实现。如果整个工作表都开启保护但没有任何可编辑区域那这张表对用户来说就是一张纯只读表。反过来如果你想开放某些列给用户填写就把这些列的范围放到可编辑区域列表里。3.2 初始化表格时配置保护区域把保护能力落到实际项目里第一种做法是在初始化工作簿时直接传入带保护配置的表格数据。Univer 的工作簿数据结构是一个 JSONsheets 数组里每个 sheet 都有自己的配置。下面这段代码是这种思路的示意具体字段名要以你安装的包版本对应的类型定义为准但整体结构是一致的。const workbookData { id: workbook-001, sheets: [ { id: sheet-001, name: 员工信息收集表, cellData: { A1: { v: 姓名 }, B1: { v: 手机号 }, C1: { v: 部门 }, A2: { v: }, B2: { v: }, C2: { v: }, }, sheetProtection: { formatCells: false, formatRows: false, formatColumns: false, insertRows: false, insertColumns: false, deleteRows: false, deleteColumns: false, sort: false, filter: false, }, unlockedRanges: [ { startRow: 1, startColumn: 0, endRow: 100, endColumn: 0 }, { startRow: 1, startColumn: 1, endRow: 100, endColumn: 1 }, { startRow: 1, startColumn: 2, endRow: 100, endColumn: 2 }, ], }, ], };这段配置表达的意思是A1 到 C1 是表头默认锁定A2 到 C100、B2 到 C100 这些区域被标记为可编辑用户可以填写。当用户试图修改表头或其他空白区域时Univer 会因为保护机制直接拒绝操作不会让脏数据落到模型里。3.3 运行时动态开放可编辑区域初始化配置适合模板比较固定的场景。但真实业务里管理员可能希望先看到表格再在界面上勾选“这次开放这几列”这时候就需要运行时动态修改保护区域。Univer 的实例提供了获取活动工作表的 API你可以先拿到当前 workbook 对应的 sheet再调用和区域保护相关的方法把用户勾选的区间加入编辑白名单。这个能力和配置项初始化是等价的区别只是时机不同。我自己的习惯是模板仍然由后端在初始化时下发但是在模板基础上需要临时开放的救援列、补充说明列就放到运行时动态处理避免为了改一个区域重新建整个工作簿。3.4 配合权限体系前端保护不是唯一的闸门这里必须说一句大实话浏览器里的纯前端保护只能拦住正常用户的操作拦不住故意抓接口的人。Univer 帮助你避免了误操作和普通用户乱改数据但如果场景是多人协同、数据敏感必须要让后端参与校验。每一次数据变更写入前服务端根据用户身份校验他是否有权限修改对应单元格。这个原则在“univer 在线表格 数据填报”场景里尤其重要。所以我在项目里的做法是双通道前端用 Univer 的单元格保护让界面不可改后端在接收提交数据时再一次检查单元格坐标和用户权限。前端是体验后端是底线。两者结合才能保证“用户改了不该改的格子”这种事故不发生。4. 从零到一做一个可复用的“只读可填”表格示例4.1 准备项目环境为了让你能快速跑起来接 Univer 最省事的组合是 Vite TypeScript框架可以用 Vue3 或 React。这里我用 Vue3 举例但核心逻辑放到 React 里也完全可以。先创建项目npm create vitelatest univer-demo -- --template vue-ts cd univer-demo npm install然后安装 Univer 相关包。Univer 的包数量不少按需安装即可npm install univerjs/core univerjs/design univerjs/ui univerjs/sheets univerjs/sheets-ui univerjs/sheets-formula univerjs/sheets-conditional-formatting如果你下了比较新的版本可能会发现官方推荐用 preset 方式统一注册插件用法会更简洁。不管哪种方式核心思路是一样的创建 Univer 实例、注册插件、挂载容器、创建工作簿。4.2 在Vue组件里挂载Univer表格在src/components/CollectSheet.vue里放一个容器节点。注意 Univer 内部要管理大量 DOM 事件挂载容器不要和页面上的其他高频更新区域混在一起最好给它一个独立宽度和高度。template div refcontainerRef classsheet-container/div /template script setup langts import { onMounted, ref } from vue; import { Univer } from univerjs/core; import { defaultTheme } from univerjs/design; import { UniverSheetsPlugin } from univerjs/sheets; import { UniverUIPlugin } from univerjs/ui; import { UniverSheetsUIPlugin } from univerjs/sheets-ui; const containerRef refHTMLDivElement(); let univer: Univer; onMounted(() { if (!containerRef.value) return; univer new Univer({ theme: defaultTheme, }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverUIPlugin, { container: containerRef.value, }); univer.registerPlugin(UniverSheetsUIPlugin); const workbookData { id: workbook-001, sheets: [ { id: sheet-001, name: 访客登记表, cellData: { A1: { v: 姓名 }, B1: { v: 联系电话 }, C1: { v: 到访事由 }, A2: { v: }, B2: { v: }, C2: { v: }, }, sheetProtection: { formatCells: false, formatRows: false, formatColumns: false, insertRows: false, insertColumns: false, deleteRows: false, deleteColumns: false, sort: false, filter: false, }, unlockedRanges: [ { startRow: 1, startColumn: 0, endRow: 50, endColumn: 0 }, { startRow: 1, startColumn: 1, endRow: 50, endColumn: 1 }, { startRow: 1, startColumn: 2, endRow: 50, endColumn: 2 }, ], }, ], }; // 旧版本使用 createUniverSheet新版本可改用 createUnit univer.createUniverSheet(workbookData); }); /script style scoped .sheet-container { width: 100%; height: 480px; border: 1px solid #E2E8F0; border-radius: 8px; overflow: hidden; } /style这里你其实已经得到了一个在线表格。用户打开页面后只能填写 A2 到 C50 这个范围内的格子表头和其他区域全部锁定。如果需要改字段管理员只要修改cellData里的结构业务侧完全不用动。4.3 增加一个“提交”按钮把填好的数据收回来在线表格光可以填还不够填完之后得能把数据取出来不然等于白做。Univer 提供了 API 读取单元格内容我们可以遍历填表区域组装成 JSON 发给后端。import { UniverInstanceType } from univerjs/core; function collectData() { const univerAPI univer.getAPI(); const workbook univerAPI.getActiveWorkbook(); const sheet workbook.getActiveSheet(); const rows 50; const result []; for (let row 1; row rows; row) { const name sheet.getCellValue(row, 0); const phone sheet.getCellValue(row, 1); const reason sheet.getCellValue(row, 2); if (!name !phone !reason) { continue; } result.push({ name: name ?? , phone: phone ?? , reason: reason ?? , }); } // 这里直接交给后端接口 // fetch(/api/collect, { method: POST, body: JSON.stringify(result) }) console.log(result); }有一点要注意用户的表格数据可能非常多遍历单元格的时候建议只遍历你开放的填写区间不要从头到尾扫整张表否则大表格场景下会有性能浪费。另外Univer 的getCellValue返回的对象里可能同时有原始值和显示值如果是数字、日期这类格式化字段建议在读取时做好类型转换。4.4 让表格更接近真实业务下拉、校验和默认值一个可用的填报表单往往还需要数据校验。Univer 支持条件格式和数据校验相关的能力我在实际中经常用三种组合单元格下拉列表、必填校验、提交前的前端校验。下拉列表适合“部门”“到访事由”这种枚举字段能极大降低用户输入成本。使用上可以在初始化时给单元格配置 dataValidation 相关字段也可以在运行时通过 API 操作。不过要记住数据校验把多数错误拦截在客户端已经很好了但服务端仍需做完整性校验。任何前端校验都可能被绕过最终以服务端结果为准。5. 常见问题与排查记录5.1 配置了保护区域表格还是能随便改遇到这个情况第一反应先检查工作表保护开关有没有真正开启。我见过不少初始化配置里只放了unlockedRanges但没配sheetProtection或者配了保护但把formatCells这类开关留成了默认值。实际上工作表保护是一个整体开关默认不开启只有保护开启后锁定区域的限制才生效。另外不同版本的 Univer 对字段名有调整如果初始化后没有效果最好打开浏览器控制台打印工作簿数据看看请求的配置有没有被真正合并进模型。5.2 协同场景下保护被绕过Univer 支持协同编辑多端同时操作时每个客户端的本地模型都有自己的保护状态。如果 A 用户开启了保护B 用户本地可能还保留着旧的未保护配置这时 B 尝试改锁定的格子可能不会被立刻拦截。解决思路是不要让保护状态只存在本地把工作簿的快照和用户权限统一交给服务端服务端在合并操作时再做一次校验。前端保护是做体验服务端校验才是做边界。5.3 大数据量时卡顿明显Univer 的 Canvas 渲染性能在同类工具里已经算不错但如果你一次性把十万行数据放进表格任何前端都会吃力。实际项目里填报表单更推荐“少量列 控制可填写行数”的做法。我一般会把开放区域控制在 200 行以内超过这个量就分页或者分批提交。另外尽量关闭不用的插件比如不需要公式引擎就不要加载公式插件不需要条件格式就不要引入条件格式模块缩小包体也能减少初始化压力。5.4 升级Univer版本后初始化代码报错这是开源控件最常见的坑。Univer 在 0.x 到 1.x 之间有几次比较大的 API 调整插件注册方式和createUniverSheet这类方法都有变化。我的习惯是先把当前版本的 TypeScript 类型定义文件翻出来重点看IWorkbookData、ISheetProtection、Univer构造函数的定义再动手改代码。不要一上来就照抄旧版文章很多二十个月前的代码放在新版已经跑不起来了。5.5 中文输入法选字时表格焦点丢失这个不是 Univer 独有所有浏览器端 Canvas 表格都会遇到输入法弹窗和焦点交接的兼容性问题。如果你发现用户反馈中文输入不稳定先确认浏览器版本然后看 Univer 的版本更新记录。这类问题往往在新内核版本里有修复。遇到顽固问题可以退一步把输入处理放在覆盖层上由浮层接管输入失焦后写回单元格但这样开发成本较高一般建议先升级到新版本试试。6. 一点个人体会在实际交付过几个基于 Univer 的填表项目之后我最大的感受是不要急着做很深的自定义功能先把“模板下发、锁定区域、用户填写、数据回收”这条最基础的主链路跑通。一条干净的主链路比花哨的样式和复杂权限更有价值。还有一个小经验如果用户原本就是拿 Excel 维护模板的最好先导出模板 Excel再通过 Univer 的导入能力把模板转成在线表格。这样做的好处是用户的心理成本很低他们不需要重新学一套操作打开网页看到的还是熟悉的表格样子。等基础链路稳定以后再逐步加上校验、协同、操作日志这些看起来“高级”的能力。项目越往后拖你会发现“能稳定填表回收数据”才是客户真正愿意买单的核心其他都是增量。
返回列表