资讯动态

Univer实战指南:从选型到嵌入业务系统,手把手搭建在线表格

发布时间:2026/9/26 17:39:08 来源:尧图企业网站定制
做后台管理系统做久了你会得出一个结论凡是面向业务的数据类产品最后都绕不开表格这两个字。采购单要填、审批明细要导、Excel报表要在线预览每回都是拿一套类Excel组件硬撑撑到后面要么性能扛不住要么交互差一口气。前阵子我接手一个数据填报平台把市面上能用的在线表格方案又过了一遍最后选了 univer 这个开源项目。它和我之前用过的 Luckysheet 算是一脉相承但底层完全是重做TypeScript 编写、Canvas 渲染、插件化架构公式被拆成了独立的计算引擎。这篇文章把我从选型、搭建、接入业务数据到踩坑的全过程记录下来给正在做表格选型、或者准备在自己系统里嵌入 Univer 的朋友一份可以参考的实战笔记。1. univer 到底是个什么项目和 Luckysheet 一脉相承的新引擎1.1 从 Luckysheet 到 Univer不是升级是重建如果你做过前端表格功能多半听过 Luckysheet 的名字。那个项目当年在 GitHub 上很火浏览器里直接跑像模像样很多中后台系统拿它做在线 Excel 预览和简单编辑。但用过一段时间你就会发现问题它本质上是 DOM 渲染单元格一多滚动和输入就开始掉帧公式计算和界面渲染耦合在一起想单独在服务端算个公式几乎不可能真要往里面加深度业务功能改源码的成本相当高。Univer 的作者就是 Luckysheet 的作者。他后来的思路很明确不再修修补补而是用更现代的工程体系重新做一个。所以 Univer 不止是表格组件它是一套在线办公套件的基础设施电子表格目前最成熟文档、幻灯片这些模块也在慢慢推进。底层用 Canvas 做渲染数据层维护一套独立的工作簿模型交互层全部走命令系统功能用插件来组合。这个定位比又一个表格组件要重得多但它的价值也恰恰在这你可以在它上面长出真正属于自己的业务。1.2 拆开看模块Core、Render、Formula、UI 各自负责什么Univer 的仓库和 npm 包是按模块拆开的不是一个大包铺到底。最核心的几个包括univerjs/core领域模型。工作簿、工作表、单元格、样式、命令服务、事务机制都在这一层相当于整棵树的根。univerjs/engine-renderCanvas 渲染引擎。负责把工作簿模型画到浏览器上走的是自研渲染管线不依赖 DOM 表格。univerjs/engine-formula公式引擎。独立的词法解析、语法树构建、计算和依赖追踪理论上可以完全脱离 UI 单独跑。univerjs/sheets电子表格领域逻辑。包括行列操作、选区、单元格编辑、复制粘贴这些业务规则。univerjs/sheets-ui表格的 UI 层。工具栏、右键菜单、编辑框、浮动元素等等。univerjs/ui通用 UI 基建。和具体业务无关给上面这些模块提供统一的界面能力。这里最值得关注的是领域模型和渲染引擎的分离。传统前端表格方案里数据和 DOM 状态往往搅在一起你要么跟着 jQuery 那套走要么自己维护一套很脆的同步逻辑。Univer 把数据模型放得干干净净渲染引擎只负责把模型画出来UI 层只关心交互改一个不影响另外两个。这意味着你完全可以让 Univer 只做后台计算或者只做渲染再或者接入自己的交互层灵活度高出很多。1.3 版本坐标系0.x 和 1.x 几乎是两个世界这一节我觉得必须单独讲因为新手最容易在这里栽跟头。Univer 早期版本0.x和后期的 1.x 在 API 设计上有很大区别初始化代码、插件注册方式、命令 ID 都不一样。网上搜到的一些教程可能是 0.x 时代的你按它写跑起来大概率报错。我接手项目的时候官方已经推到 1.x所以我下面的示例都以 1.x 为准。但 1.x 本身迭代也快包名、注册顺序、参数结构都在微调。我的建议是三个字锁版本。在 package.json 里把univerjs相关依赖写成精确版本号不要用^让他随便浮动升级否则一个子包升级可能带崩整个表格功能。另外一个背景信息是协议。Univer 使用的是 Apache 2.0 协议对商业集成是友好的但自带的一些图标、字体资源要注意各自授权。技术选型时如果公司有法务审核环节提前把协议清单附上省得后面找补。2. 最小可用工程Vite Univer 手把手渲染一张表2.1 初始化项目和安装依赖我习惯用 pnpmVite 脚手架创建项目也干净。先初始化一个原生 TS 工程pnpm create vite univer-demo --template vanilla-ts cd univer-demo pnpm install然后装 Univer 相关的包。最少需要这些pnpm add univerjs/core univerjs/sheets univerjs/sheets-ui univerjs/ui pnpm add univerjs/engine-render univerjs/engine-formula pnpm add univerjs/sheets-formula引擎包需要单独装这一点容易被忽略。我在第一次搭建时只装了 core 和 sheets结果运行时直接报找不到 render 引擎。它不像有些开源项目那样把所有依赖都塞进一个入口包Univer 追求的是按需安装所以漏一个插件就少一个能力排查起来反而不太直观。2.2 初始化并渲染一个空工作簿在src/main.ts里写最基础的初始化逻辑import { LocaleType, Univer } from univerjs/core; import { UniverFormulaEnginePlugin } from univerjs/engine-formula; import { UniverRenderEnginePlugin } from univerjs/engine-render; import { UniverSheetsPlugin } from univerjs/sheets; import { UniverSheetsFormulaPlugin } from univerjs/sheets-formula; import { UniverSheetsUIPlugin } from univerjs/sheets-ui; import { UniverUIPlugin } from univerjs/ui; import univerjs/ui/lib/index.css; const univer new Univer({ locale: LocaleType.ZH_CN, defaultWorkbook: { id: workbook-01, sheets: [], }, }); univer.registerPlugin(UniverFormulaEnginePlugin); univer.registerPlugin(UniverRenderEnginePlugin); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverSheetsUIPlugin); univer.registerPlugin(UniverUIPlugin, { container: app, }); univer.registerPlugin(UniverSheetsFormulaPlugin);这里有个细节container对应的 DOM 元素必须在初始化之前已经存在而且要有明确的高度。我一开始写在window.onload之后挂载倒是没出问题但容器高度没设置时页面只露出一条灰色横线看起来像没渲染出来其实就是画布高度为 0。2.3 第一次跑起来的三个常见问题第一个漏掉样式文件。Univer 的样式和组件逻辑是分开的你不引入 CSS工具栏、菜单、编辑框全都布局错乱。至少要把对应包的样式入口引进来具体路径跟着你实际安装的版本走报错信息里一般会提示。第二个插件注册顺序。UniverUIPlugin要在UniverSheetsUIPlugin之后注册不然 UI 基建还没准备好表格插件挂不上界面。我试过倒过来写控制台会报类似的错误Cannot read property register of undefined属于典型的生命周期顺序问题。第三个一个页面尽量不要初始化多个 Univer 实例做测试。它的命令服务、上下文都是全局的你不知道哪个实例响应了哪个操作。业务上如果需要多个表格区域更合理的做法是一个实例管理多个工作簿而不是开多个实例。上面这些跑通之后你就能在浏览器里看到一个完整可编辑的电子表格工具栏、行号列号、单元格选区都有。到这一步Univer 基本算接进来了。3. 让表格活起来对象模型、命令系统和 UniverAPI3.1 工作簿、工作表、单元格这三层模型要心里有数Univer 的数据模型沿用了 Excel 的分层方式一个Workbook包含多个Worksheet每个工作表里面有行列和单元格单元格的值又有不同类型。初始化时可以通过defaultWorkbook传入已有的表结构。比如我要创建一个带一张空表的文件const univer new Univer({ locale: LocaleType.ZH_CN, defaultWorkbook: { id: wb-001, sheets: [ { id: sheet-001, name: 订单明细, rowCount: 500, colCount: 30, cellData: { A1: { v: 订单号, t: 2 }, B1: { v: 客户名称, t: 2 }, A2: { v: SO-2024001, t: 2 }, }, }, ], }, });这里v是单元格的值t是类型枚举。不同版本对类型的定义可能有差异但基本思路一致数据是数据样式是样式公式是公式。Univer 把f字段留给公式运行时它会根据依赖关系自动重算结果再回填到v上。我在做数据迁移的时候发现一个经验Univer 并不是必须先把数据全部塞进内存才能渲染。你完全可以在初始化时只建表结构等页面挂载完成后再通过后面的 UniverAPI 分批喂数据。这对于大表来说非常重要后面我专门说。3.2 为什么是 Command而不是直接 setData很多组件库的使用习惯是拿到对象直接改属性。Univer 不是这个思路。它把用户做了一个操作抽象成一个 Command比如插入行、修改单元格、删除工作表都是一个命令。Command 模式带来的直观好处有三个。一是可撤销和重做。因为每个操作都是可以被记录和反演的命令对象Univer 天然支持撤销栈。业务里如果要做强制校验后不允许撤销也可以在命令执行前拦截。二是协同编辑的底子。每个命令都可以序列化成 JSON通过网络同步给其他端各端按相同顺序执行就能保持状态一致。Univer 官方协同方案就是这么做的虽然接入起来要自己处理服务端和网络层但数据模型天然支持这一点。三是审计容易。谁在什么时候改了什么把命令日志打出来就行不需要额外埋点。这对 To B 系统来说是个很常见又很被低估的需求。所以我给你一个很实在的建议不要试图绕过命令系统直接改数据模型哪怕只是修改一个单元格。你用 API 操作的底层走的也是命令通道只是封装得更好看而已。合理利用命令系统后面做权限控制、操作审计、协同编辑都会省很多事。3.3 用 UniverAPI 把业务数据写进表格官方提供了一个相对友好的入口叫getUniverAPI()适合业务侧快速操作。比如我在填报平台里要动态创建一个新表并写入一批数据const univerAPI univer.getUniverAPI(); // 创建一个新工作表 univerAPI.createSheet(新增客资); // 拿到当前活动工作簿 const workbook univerAPI.getActiveWorkbook(); const sheet workbook.getActiveSheet(); // 在 A1:C3 范围内写入二维数据 sheet.getRange(0, 0, 3, 3).setValues([ [姓名, 手机号, 来源渠道], [张伟, 138****1234, 展会], [李娜, 139****5678, 官网], ]);这个 API 写起来有点像 SheetJS 的用法对业务开发来说很亲切。不过要记住一个细节拿到workbook和sheet对象后尽量在同一个同步流程里用完不要到处存引用。因为 Univer 内部的模型实例可能因为 undo、redo、导入导出等操作被替换缓存旧引用会出现操作了但界面没反应的诡异问题。业务数据接入时我还有一个小习惯所有表格数据操作集中在项目里封装一层 service比如TableService.writeRows()、TableService.insertRow()底层再去调 UniverAPI。这样哪天 Univer 升级导致 API 换了我只需要改一个文件而不是满项目找调用点。4. 公式引擎可以脱离 UI 单独使用给后端算个账4.1 公式编辑器不是唯一入口计算内核才是核心资产Univer 的公式引擎是我真正觉得它和其他前端表格项目拉开差距的地方。univerjs/engine-formula不依赖 Canvas不依赖 DOM它单纯负责公式的解析、依赖计算和更新。这意味着你可以在 Node.js 环境里直接拿它做服务端公式计算。我之前遇到一个需求客户上传一份带公式的 Excel系统需要在后端重算某些字段但是又不想把重算逻辑放在表格页面里。按传统做法你可能需要在前端偷偷开一个隐藏的表格组件去重算再把结果传回后端想想就很别扭。Univer 的方式是后端单独跑一个公式引擎实例接收前端传过来的公式和参数返回计算结果。这里有个容易忽略的问题公式是分语言的。SUM和SUM在中文、英文环境里都叫同一个函数但有些函数的本地化名称会变。后端计算时LocaleType要和你前端保持一致否则可能出现函数名无法识别的报错。4.2 在表格里写公式并监听计算结果前端业务里往单元格写入公式很简单核心就是给f字段赋值const sheet workbook.getActiveSheet(); // B1 单元格写入公式对 A1:A10 求和 sheet.getRange(1, 0, 1, 1).setFormula(SUM(A1:A10)); // C1 单元格写入跨表引用 sheet.getRange(2, 0, 1, 1).setFormula(订单明细!B2 100);写完公式后Univer 的公式引擎会自动计算并更新单元格的显示值。这里你可能会遇到一个常见困扰计算是异步的代码执行完立刻去读单元格值拿到的可能还是空或者旧值。需要在计算完成的事件里取结果或者用setTimeout延后读取。在业务里最稳的做法是监听 Univer 公式引擎的重算完成事件再做下一步操作不要人为加固定延时。4.3 自定义公式从一个业绩提成场景说起Univer 公式引擎支持注册自定义函数。我最初看到这个能力第一反应是终于不用在公式单元格前端 JS 里写一大串 if 了。举个例子我们的业务里有个提成计算规则销售额超过一万的部分提成 8%其余提成 5%。Excel 里你得写IF套MIN套MAX可读性很差。用自定义公式可以做成COMMISSION(A2)。思路是实现一个函数类注册到函数库里然后在任意单元格像用内置函数一样调用它。伪代码大概长这样import { registerFunction } from univerjs/engine-formula; registerFunction({ name: COMMISSION, type: number, description: 计算销售提成, calculate: (params) { const amount Number(params[0]); if (amount 10000) return amount * 0.05; return 10000 * 0.05 (amount - 10000) * 0.08; }, });不同版本的自定义公式类写法有差异有的版本要求继承BaseFunction有的版本用函数注册。但核心思想是一样的把复杂业务规则收敛到公式函数内部表格模型里只留一个干净的调用。这里我踩过一个坑自定义公式的注册时机。如果你在表格已经渲染完成后才注册某些已存在的公式单元格不会自动识别新函数必须重新触发一次重算才能生效。所以我的建议是自定义函数要在创建 Univer 实例之前、至少要在写入公式之前全部注册完。5. 业务接入实录导入导出、校验、条件格式与大数据量体验5.1 xlsx 导入导出不能只靠前端Univer 官方预设里带了 xlsx 导入导出的能力底层解析 Excel 文件后会转换成 Univer 自己的工作簿模型。前端结构大致是上传组件拿到 File 对象转成 ArrayBuffer再调用导入命令或 APIUniver 直接渲染出文件内容。但我在真实项目里建议你多一层心思导入导出尽量和后端服务配合不要让前端承担全部责任。原因有三个。第一Excel 的兼容性巨坑。日期格式、合并单元格、数据验证、图表、宏这些特性在开源解析库里经常出现精度丢失或样式错乱前端模型和 Excel 原始格式之间做不到 100% 无损。如果业务要求严格正确姿势是后端保留原始文件前端展示时导出一个快照用户导出时再走原始文件或模板生成。第二服务端校验更安全。用户上传 Excel 后你不能完全信任前端解析结果字段类型、唯一性、权限范围这些校验放在后端做更可控。第三大文件的导入性能。几十 MB 的 Excel 在前端解析会卡住主线程即使 Web Worker 也未必能覆盖所有逻辑。处理思路是限制上传文件大小同时把解析结果先给后端存一份前端只是展示精简后的视图。5.2 数据校验、条件格式、筛选这些插件要想清楚再上Univer 的插件体系里公式、数据校验、条件格式、筛选都是独立的。好处是按需引入坏处是很多新手不知道要装哪些包。我的建议是如果你做的是一个正经业务后台数据校验插件基本是必装的因为表格录入的数据绝大多数需要约束。场景很常见某列只能填数字某列不能重复某列必须在下拉列表内选择。用数据校验插件设置规则后用户在表格里输入非法值界面会直接提示不需要你额外写监听逻辑。条件格式则适合那种数据铺开后靠颜色识别异常的业务比如库存低于安全值时整行标红、销售额超过目标时单元格高亮。这些规则配置好之后数据一变颜色自动更新比自己在渲染层反复改样式高效得多。不过这里我要提醒一句这些插件都会增加初始化开销。如果页面只是只读预览报表公式、条件格式这些计算型插件不上也罢能让首屏渲染快不少。5.3 数据量变大后别急着骂性能先检查自己的代码我在项目里测过一张 2 万行、10 列的表格Univer 的 Canvas 渲染和虚拟滚动表现是不错的横向纵向滚动都很顺单元格编辑也没有明显延迟。但这有个前提不要一次性把 2 万行的 cellData 全量塞进初始化参数里。我自己第一次测试就是这么干的结果页面卡了差不多两秒才出来。后来改成初始化空表再按可视区域分批写入数据体验就完全不同了。Univer 的渲染是懒的它不会把看不到的区域都画出来但你如果提前把数据全部灌进模型模型本身的构建和依赖计算还是要花时间的。另外值得注意批量写入数据时优先用setValues这种数组接口不要写循环一个单元格一个单元格地setValue。循环调用意味着每个单元格都是一次命令事务性能开销是累积的哪怕最终效果一样耗时可能差几十倍。6. 我踩过的坑与最终选型结论什么项目适合用 Univer6.1 样式和周边环境的坑藏在细节里的麻烦Univer 的 UI 自带一套设计语言混进现有系统时很可能和你的组件库样式打架。常见问题是字体、按钮高度、弹窗层级和主题色不一致。解决思路不是去改 Univer 源码而是用 CSS 变量覆盖主题或者干脆只用UniverSheetsPlugin不注册 UI 插件把表格渲染到指定容器再用自己的工具栏包一层。后者虽然开发量更大但保住了系统视觉一致性后面维护也省心。另外一个很隐蔽的问题是 z-index。Univer 的卡片、菜单、下拉框都用了较大的层级值如果你的页面中有自己的悬浮面板出现被 Univer 弹出层盖住的情况不要慌调一下 z-index 容器就行。不过最好在你的业务容器上创建新的层叠上下文避免全局改动影响其他页面。6.2 协同编辑能力的基础已经在了但要落地还得自己搬砖协同编辑确实是 Univer 主打的卖点之一官方也有对应的协同方案。但你要清醒真正要在生产环境用协同网络拓扑、服务端状态同步、冲突合并、权限控制、操作速率限制这些东西不会因为是官方方案就自动变简单。Univer 做得好的是数据层面命令可序列化、可重放这给协同打下了一个干净的地基但地基之上该搬的砖一块都不会少。我的判断是如果项目只是内部系统几个管理员同时编辑一个表启动协同需求的概率不高可以暂时不做。如果产品面向 C 端用户多人同时编辑一张在线表格是核心场景那么需要把协同纳入项目计划里相当大的比例它不只是一个前端组件问题。6.3 到底哪些项目适合上 Univer我最近跑完这个项目心里的边界慢慢清晰了。如果你的场景是下面这些Univer 大概率适合你中后台系统里需要嵌入可编辑的类 Excel页面而不是单纯的表格展示。业务里存在复杂公式计算、跨表引用、服务端重算需求。需要解析和生成 xlsx 文件并且愿意搭配后端做兜底。后续可能要做多人协同编辑希望底层数据模型不用推倒重来。反过来如果你的需求只是把一批数据画成漂亮的表格那 Univer 就太重了。你按需引入它的渲染模块也可以做但更轻量的表格组件或直接后端生成图片/PDF可能是更划算的选择。最终我选 Univer 的核心原因其实不是某个单点功能而是它的扩展方式不破坏内核功能插件化。这意味着即使现在有些能力还不完善只要架构不变、社区活跃后面的功能会像搭积木一样补上来。作为一个长期运行的内部平台我更愿意为这个确定性买单。最后分享一个实用建议如果你决定上 Univer先别急着封装太多业务组件花一两天把官方仓库的示例项目完整跑一遍把它们已有的高级用法都看看。Univer 的源码相对清晰很多问题在源码里能找到比文档更准确的答案。接入只是开始真正值钱的是你对它模型和命令系统的理解深度。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑