ARTICLE DETAIL

资讯详情

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

用Univer实现可填充表格模板:权限控制与数据验证实战

用Univer实现可填充表格模板:权限控制与数据验证实战 做数据填报类系统这两年我一直在找一个能直接嵌入网页、体验又接近原生Excel的表格组件。试过好几轮开源方案之后最近把主力切换到了univer上——这是个很新的在线表格引擎TypeScript写的底层用Canvas渲染最打动我的一个点是它能支撑“用户自定义表格模板然后让用户去填写指定单元格其他单元格完全锁死”的真实场景。如果你正在做类似的需求这篇文章既是记录也是参考。我会把初始化、权限控制、数据校验、读回数据这条路完整走一遍再把实际踩过的坑一起说出来。1. 为什么是这个场景可填写的表格模板远比看起来复杂1.1 从“让用户填表”这个朴素需求说起去年我接到的需求是给企业内部做一套项目工时填报系统。每个项目经理需要在系统里填写每周各任务的工时、下月计划这些数据是用户自己填的。但项目名称、WBS编号、负责人、部门这些字段必须由系统生成用户绝对不能改。更麻烦的是表头结构每个季度可能微调业务方希望管理员能自己改表格模板而不是每次让开发改代码。听起来不复杂真做起来全是细节。普通HTML表格方案需要自己处理焦点定位、合并单元格、复制粘贴、键盘方向键、单元格边框绘制做出来也就只能凑合看。用现成的表单页面方案字段一多就像在填传统页面跟Excel体验完全不是一回事。后来我意识到这类需求的本质是给用户一个“看起来是Excel”的在线表格但只有特定单元格可以编辑。这不是一个普通表单问题而是一个表格引擎的权限控制问题。1.2 univer能覆盖的能力矩阵univer是广州DreamNum团队开发的现代办公套件包含Sheet电子表格、Doc文档、Slide演示文稿三大模块现在开源且免费。对做表单类的开发者来说主要关注的是Univer Sheets。它的能力矩阵覆盖了文档模型工作簿/工作表/单元格三层结构支持合并单元格、样式、批注、筛选、条件格式。公式引擎内置了大量函数单元格内可以直接写公式。数据验证类似Excel的DataValidation能限制用户填什么类型、多大范围。权限保护工作表保护、工作簿保护、保护区间的设置能力。渲染交互Canvas渲染滚动和编辑的流畅度比DOM方案高。插件架构所有功能都是插件按需加载这对工程化很友好。其中“权限保护”加“数据验证”两个能力组合起来正好把“可填写指定单元格”这个需求吃透了。传统表单需要你从零实现的一套规则在univer里是原生概念。1.3 为什么不用老方案绕路我也评估过其他路线。给用户下载Excel模板再上传看起来省事但体验割裂用户在本地改完可能把公式删了、格式改了、结构弄乱了回到后端解析时全是烂数据。轻量级的表格组件虽然也能嵌入页面但很多对“锁定单元格”“按区域放行编辑”这类细粒度权限支持很弱要么没有要么只在付费版里。univer在线表格的核心模型和Excel的保护机制接近权限语义可以直接映射不用做太多曲线救国的操作。所以这篇内容的核心思路是把univer当作一个受控表格组件用保护模式加固模板用数据验证管理填写质量最后通过API把用户填的数据一次性读出来。按这个思路走开发和维护成本比自研低很多体验又足够接近Excel下面开始拆解具体步骤。2. 上手把univer跑进一个空项目2.1 安装与插件加载顺序univer采用插件架构不能只装一个包就完事。最基本的三件套是核心数据模型、电子表格能力、以及界面交互层。在我跑通的版本里安装命令大致如下npm install univerjs/core univerjs/sheets univerjs/sheets-ui univerjs/ui这三个包的定位要搞清楚core是引擎内核负责文档数据和操作分发的sheets提供电子表格的数据模型与命令系统sheets-ui是把表格渲染成可交互界面的一层。顺序上先注册UI基础再注册sheets能力否则界面层可能渲染不出来。还有一个容易被忽略的点univer的版本迭代非常快包与包之间有版本匹配要求。我的习惯是全部安装后用npm ls检查依赖树是否一致或者直接用统一的版本号安装。否则可能会出现Core的版本和UI插件的版本对不上运行时报一堆诡异错误。网上很多报错案例最后追根溯源都是版本错位。2.2 最小工作簿初始化初始化代码大致是下面这个样子核心是创建一个Univer实例然后往里注册插件import { Univer } from univerjs/core; import { UniverSheets } from univerjs/sheets; import { UniverSheetsUI } from univerjs/sheets-ui; import { UniverUI } from univerjs/ui; const univer new Univer({ locale: zhCN, theme: default, }); univer.addPlugin(UniverUI); univer.addPlugin(UniverSheets); univer.addPlugin(UniverSheetsUI);这一步跑通了页面上就会渲染出一个空白的电子表格外壳。注意我这里强烈建议一开始就把locale设置成zhCN因为默认英文环境下的菜单、右键项、数据验证提示在给国内用户使用时非常违和。切换中文环境之后再测试体验完全不一样。接下来创建一个工作簿并为它指定初始sheet。const workbook univer.createUniverSheet({ name: 项目工时填报模板, sheets: [ { id: sheet-001, name: 填报区, cellData: {}, }, ], }); const sheet workbook.getActiveSheet();cellData的格式是行索引和列索引组成的二维映射每个单元格对象里用v保存值用s保存样式。初始可以给空对象等模板生成时再填充。2.3 先把“待填写”的模板定义好模板设计是整个功能的根基。我建议在代码里维护一个模板数据结构而不是手动在页面上点出来这样以后模板调整方便也方便多环境下复用。一个简单的模板初始化可以这样写const templateCellData: Recordnumber, Recordnumber, ICellData { 0: { // 第一行是表头 0: { v: 项目编号, s: { bl: 1, bg: rgb(240,240,240) } }, 1: { v: 项目名称, s: { bl: 1, bg: rgb(240,240,240) } }, 2: { v: 负责人, s: { bl: 1, bg: rgb(240,240,240) } }, 3: { v: 本周工时, s: { bl: 1, bg: rgb(240,240,240) } }, 4: { v: 下周计划, s: { bl: 1, bg: rgb(240,240,240) } }, }, }; sheet.updateCellData(templateCellData);表头行给一个灰色背景加粗样式让用户一眼看出这部分是固定的。建议用rgb字符串做背景色因为univer的样式系统识别这种颜色的稳定性比较好比十六进制简写在某些主题下更可靠。这里有一个很关键的习惯模板设计和数据填写分离。模板配置集中放在一个函数里填写数据通过后面的API按行读出来两者不混在一起后续无论是多表头还是动态行都会轻松很多。3. 核心功能的落地让指定单元格可填其余锁定3.1 权限模型从Excel借鉴而来的三层控制在实现“锁定”之前先说清楚univer的权限模型它和Excel的保护机制是同一个思路默认情况下单元格是locked状态但只有开启了工作表保护锁定才生效。如果工作表没保护锁不锁都无所谓用户还是随便改。univer的保护分成三层工作簿保护控制能不能增删工作表、改变结构。工作表保护控制当前sheet里用户能不能做格式化、插入行、删除行、排序等操作。保护区Protected Range在整表受保护的前提下给特定区域开一个“优化”允许被授权的人编辑这些区域。我们需要的“指定单元格可填其他单元格不可修改”最稳妥的做法是整表开启工作表保护然后为核心填写区域添加保护区。换句话说默认全锁死按需开洞。这比“逐格设置unlocked”更安全因为以后新增行、新增列时默认还是锁死的不会出现漏网之鱼。3.2 实际操作锁定全表放行填报区域先开启工作表保护sheet.setWorksheetProtection({ selectLockedCells: false, selectUnlockedCells: true, formatCells: false, formatColumns: false, formatRows: false, insertRows: false, insertColumns: false, deleteRows: false, deleteColumns: false, sort: false, filter: false, editObjects: false, });这段配置的意思是用户无法格式化单元格无法插入删除行列无法排序筛选但可以选中可编辑区域。字段名随着版本可能略有调整但概念和Excel的保护选项是对应的。然后添加保护区允许用户编辑工时填写区域sheet.addProtectedRange({ title: 工时填写区, ranges: [ { startRow: 1, startColumn: 3, endRow: 50, endColumn: 4, }, ], });这一行代码的含义非常直白在整个工作表保护模式下第1行到第50行的C列和D列区间是允许编辑的。其他区域例如A列的“项目编号”、B列的“项目名称”即便看起来颜色不同用户双击也无法输入。我实际测试过处于保护区之外的单元格用户点击后会收到明显的不可编辑反馈效果和Excel里的“单元格受保护”提示类似。这一点非常关键能避免用户误操作后还一头雾水。3.3 校验规则数据验证给填写内容兜底光能填写还不够用户可能填出“下周计划-10”这种数据。univer的数据验证能力能把脏数据挡在门外。给工时填写区加上数字范围校验sheet.updateDataValidation({ rules: [ { type: number, ranges: [ { startRow: 1, startColumn: 3, endRow: 50, endColumn: 3, }, ], formula1: 0, formula2: 168, }, ], });type: number表示只能填数字formula1和formula2是上下限。例如“本周工时”就限制在0到168之间超范围直接给用户提示。除了数字范围还可以做列表下拉选择、日期范围、文本长度校验。这类规则组合起来能让填写的自由度受控但又不窒息。数据验证还有一个额外好处其他单元格依然锁定但用户至少能看到、能选中视觉上是在一张完整表格里操作而不是面对几个孤零零的输入框。这种信心的建立对表单类产品非常重要。3.4 收集用户数据从sheet读回结果用户填完之后数据要能拿回来。最直接的方式是按单元格读取const rowData: Recordnumber, string | number {}; for (let row 1; row 50; row) { const projectId sheet.getCellData(row, 0)?.[v]; const workHours sheet.getCellData(row, 3)?.[v]; const planHours sheet.getCellData(row, 4)?.[v]; if (projectId || workHours) { rowData[row] { projectId, workHours, planHours, }; } }这里的思路是读取时只遍历我们允许填写的行范围跳过表头行。这样做既能避免处理无意义的数据也能天然忽略锁定区域的内容。实测下来读取几万行数据耗时在毫秒级性能完全不需要担心。如果业务需要实时监测用户填写也可以订阅单元格变更事件。但表单类场景我建议用“提交时统一读取”的策略因为用户可能在填写过程中临时改来改去实时提交反而会产生一堆中间数据。把校验分散在填写阶段、把收集集中在提交阶段是最省心的。4. 真实使用中会遇到的坑与解法4.1 保护必须在用户看到表格之前生效这是我踩过最痛的一次坑。第一版我把保护放在异步取数之后动态设置结果页面加载有一瞬间表格是完全可编辑的手快的用户立刻改了表头。修复方法很简单把保护设置放在创建workbook同步流程里确保表格渲染出来时保护已经生效。如果你所在的项目里模板需要从服务器拉取一定要保证先取模板数据、再初始化workbook而不是先显示空白sheet再填充。宁可页面白屏等片刻也不要给用户“可趁之机”。4.2 复制粘贴能不能绕过保护这是用户最常碰到的边界。在原生Excel里复制内容粘贴到锁定单元格会被阻止univer基本遵循同样的语义但具体表现依赖浏览器和版本。我在测试中发现部分版本在CtrlV粘贴到保护区之外时虽然没有修改成功但也不会给出明显提示用户会觉得“好像粘贴了但又没反应”。对策有两个一是适当放大保护区范围让用户粘贴时有正确反馈二是在UI层监听粘贴事件对保护区之外的粘贴行为弹出自定义Toast提示。我个人倾向于后者因为给用户一个明确提示远好过让他在表格里摸黑试错。4.3 canvas渲染下的交互反馈要自己调univer走的是Canvas渲染这带来流畅度但也意味着你不能再像操作DOM那样给某个单元格加一个普通div做悬浮提示。用户期望的“我该填哪里”这类问题需要用样式和颜色主动引导。我的做法是在保护区内的单元格统一加一个淡黄色背景或浅蓝色边框同时第一行表头保持灰色这样用户打开表格的第一眼就明白白色/黄色区域是可填写的灰色区域看就行。如果需要更强的引导可以额外用条件格式或自定义主题样式去强化视觉分区。4.4 导出后的格式兼容性在表单场景里导出Excel通常是刚需。univer本身支持导出xlsx但要注意保护区、工作表保护这类权限信息在导出后是否被完整保留取决于导出库的实现。我在导出后拿Excel打开发现保护设置大部分能保留但个别复杂的样式会丢掉。因此我的习惯是导出的Excel主要给用户做“只读存档”而不是给用户继续编辑的“源文件”。真正要复用模板走接口重新拉数据生成。这样哪怕导出过程丢了一些边缘样式也不影响核心业务。坑点表现解法保护未及时生效用户短暂可编辑初始化同步设置保护复制粘贴无反馈粘贴静默失败监听粘贴事件提示可填区域不醒目用户找不到填写位置样式区分条件格式引导导出兼容性部分权限丢失只读存档模板复用走接口5. 能力和边界选型前的冷静判断5.1 把几个主流表格组件放在一起看选型时我把几个主流开源方案都过了一遍感受如下组件维护状态细粒度权限体验适用univer活跃社区热支持保护保护区接近ExcelCanvas流畅中大型在线表格Luckysheet更新放缓部分支持曾很流行但生态停滞简单在线编辑x-data-spreadsheet低频率较弱轻量但功能有限极简场景Handsontable商业授权强但收费成熟稳定商用付费项目如果你的核心诉求就是“受控表单式填写”univer的亮点在于开源产品里它把保护模式、保护区、数据验证这几个关键能力都做成了原生能力不用造轮子。其他几个方案不是不能做而是要自己叠一层权限逻辑维护成本不同。5.2 适合承载的场景我实际跑通的场景包括项目工时周报填报管理员自定义表头员工只填工时数字。工序流转单生产部门按工序填数量、质检结果其他信息和计算列锁定。年度预算收集各区域负责人填预算数字科目、表头、汇总行锁死。审批表前置收集HR或行政预先设定模板让多个部门团队分别填写对应列。共同点是数据有结构、填写有边界、校验有规则。这种产品用univer做承载层体验比网页表单好很多而且管理员还能在线自定义表格不用每次找开发。5.3 现阶段不建议用的场景也有几个场景我明确不会选univer极简嵌入式看板只展示三五行数据没必要引入表格引擎。大型实时行情终端几十个sheet、上万单元格持续高频刷新渲染压力大需要更精细的定制。需要深度定制编辑器UIuniver的界面菜单已经比较完整但如果你想完全重写交互改造插件的工作量不小。另外提醒一点如果只是给用户填一两个字段别硬塞表格引擎HTML表单就够了。表格引擎的威力在于字段多、行数不定、有Excel操作习惯用户群这些条件缺一两个都不太划算。我在实际项目里最受益的一条经验是把“模板设计”和“填写提交”彻底分开。模板是代码管的结构填写是用户管的数据两者之间只通过受保护的画布发生关系。这也符合univer的设计理念——它不是一个填表的小工具而是一个能长成业务系统的表格底座。如果你准备上手建议先从本节里的初始化流程开始跑通后再一层一层加保护、加校验、加导出。前两步不要急着做得花哨把“默认锁定指定区域放行”这个核心模型理解透后面的功能基本就是在这个模型上长出来的。
返回列表