ARTICLE DETAIL

资讯详情

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

Univer SDK:可编程文档引擎与单元格级权限控制实战

Univer SDK:可编程文档引擎与单元格级权限控制实战 1. 项目概述Univer 是什么它解决的不是“又一个表格工具”而是协同场景下的权限控制与可编程文档底座Univer 这个名字乍一听像某个开源项目代号但结合热搜词里反复出现的spreadsheets、documents、presentations、SDK再叠加“支持用户定义表格然后让用户去填写一些单元格其他的单元格用户无法修改”这一关键描述基本可以锁定这不是 Excel 的平替也不是 WPS 的网页版而是一个面向开发者、以 SDK 为核心交付形态的可嵌入式、可编程、细粒度权限控制的富文档引擎。我第一次在客户现场看到 Univer 的实际部署是在一家制造业企业的生产调度系统里。他们没用它做报表而是把一张标准的《每日设备点检表》作为模板嵌入到内部工单系统中——工程师登录后系统自动根据其所属产线加载对应设备列表只开放“状态”“异常描述”“签名”三列可编辑其余如设备编号、标准参数、校验公式、历史记录全部锁定。提交后数据实时回传至 MES且所有修改留痕。整个过程没有跳转到外部页面没有弹出 Excel 插件就是原生 Web 页面里一个“看起来像 Excel”的组件但背后是完整的权限策略、数据绑定和事件响应链。这就是 Univer 的真实定位它不直接面向终端用户卖 License而是作为企业级应用的“文档能力模块”被集成进去。它的核心价值不在界面有多像 Excel而在于你能在代码里精确声明“第3行到第10行、B列到D列允许编辑E列仅读取F列由公式自动计算且不可覆盖G列需通过 API 校验后才允许提交”。这种能力传统 Office 套件靠宏或插件勉强实现但稳定性差、调试难、升级易断裂而纯前端表格库如 Handsontable、AG Grid又缺乏文档级语义比如样式继承、跨页分页、打印布局、公式引擎更别说内置的协作冲突处理和版本快照。关键词里反复出现的 “SDK” 不是虚词。Univer 提供的是 TypeScript 原生 SDK不是一堆 JS 文件打包下载完就完事。它包含完整的类型定义、模块化架构univer/core、univer/sheets、univer/docs、univer/slides、可插拔的插件系统Plugin System以及一套基于 RxJS 的响应式状态管理机制。这意味着你不是在“调用一个 API”而是在“构建一个文档应用”——你可以替换默认的 UI 组件、注入自定义的公式函数、拦截单元格编辑事件、接管文件保存逻辑、甚至重写整个渲染管线。阿里云认证 SDK、Android SDK 安装这些热词混杂其中恰恰说明开发者正在尝试把它接入各种异构环境既有 Web 端的中后台系统也有 Electron 桌面客户端甚至有团队在探索用它重构移动端 H5 表单的交互体验。所以如果你正面临这些场景Univer 值得你花两小时认真看懂你需要在自有系统里嵌入一个“能填数据、能算公式、能带样式的表格”但又不能让用户随便删列、改标题、破坏结构你的业务表单有强校验规则比如“结束时间必须晚于开始时间且间隔不超过24小时”需要在前端实时反馈而非等提交后报错你希望不同角色审核员、操作员、管理员看到同一份文档时编辑区域完全不同且权限变更后无需重新发布页面你厌倦了维护一堆 jQuery Bootstrap 的手工表单想用一套统一的文档模型驱动前后端你正在评估是否值得投入资源自研文档引擎而 Univer 的开源协议Apache 2.0和活跃社区GitHub Star 数月均增长超 300提供了足够底气。它不是万能胶不适合做 PPT 演示动画或复杂 Word 排版但它确实是目前中文技术圈里最接近“Excel 引擎内核 Google Docs 协作协议 VS Code 可扩展性”的三位一体方案。接下来我们就从底层设计开始一层层剥开它如何把“用户只能填几个格子”这件事做成可工程化落地的能力。2. 架构设计与核心思路为什么选择“可编程文档底座”而非“封装好的组件”2.1 传统方案的三大死结封装越深定制越痛在 Univer 出现之前企业级表格需求通常有三条技术路径每条都带着明显硬伤第一类Office Online Server / OnlyOffice 集成这是最“省事”的方案——直接套壳。但代价是你完全失去对 UI 的控制权。想把“保存按钮”挪到右上角不行那是人家的 DOM 结构。想让某列输入框自动聚焦得研究他们晦涩的私有 API 文档且每次升级都可能失效。更致命的是权限粒度OnlyOffice 能设“整张表只读”但做不到“A1:A10 可编辑B1:B10 只读C1:C10 由服务端动态生成且禁用复制”。它的权限模型是文档级或工作表级而真实业务需要的是单元格级Cell-level 规则级Rule-level的组合。第二类AG Grid / Handsontable 等高级表格库这类库自由度高API 清晰但本质仍是“数据表格”不是“文档”。它们擅长处理百万行数据渲染却无法原生支持合并单元格后的样式继承比如合并 A1:C1 后设置背景色D1 是否受影响公式跨工作表引用Sheet1!A1 Sheet2!B2打印时自动分页、页眉页脚、缩放适配导出 PDF 时保留字体嵌入和矢量图形。你不得不自己补全这些能力结果是一个“表格组件”项目最终演变成一个微型 Office 套件开发人力成本远超预期。第三类自研轻量级表格引擎见过太多团队掉进这个坑。初期用 Canvas 渲染性能不错但很快遇到瓶颈公式引擎要兼容 Excel 函数SUMIF、VLOOKUP、TEXTJOIN光函数语法解析器就得写两周协作编辑需要 OTOperational Transformation算法调试一个光标同步 bug 花掉整个迭代周期移动端触摸交互双指缩放、长按复制和桌面端键盘操作F2 编辑、CtrlZ 撤销逻辑完全不同维护两套事件系统最后发现80% 的代码都在复刻 Excel 已有的行为而不是解决业务问题。Univer 的破局点就在于它不试图替代 Excel而是把 Excel 的核心能力拆解成可编程的积木。它的架构图不是“一个大黑盒”而是一组松耦合、高内聚的模块┌─────────────────────────────────────────────────────────────┐ │ univer/core │ ← 核心运行时命令中心、状态管理、插件生命周期 ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ univer/sheets │ │ univer/docs │ │ univer/slides │ │ ← 三大文档类型实现各自负责渲染、编辑、导出 │ └─────────────┘ └──────────────┘ └──────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ univer/ui (UI 组件层) │ │ ← 提供 React/Vue/Angular 绑定但非强制依赖 │ └─────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ univer/plugin-* (插件生态) │ │ ← 权限插件、公式插件、协作插件、导出插件... │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘这个设计的关键在于所有业务逻辑包括你关心的“哪些格子能改”都不在 UI 层而在 Core 和 Plugin 层。UI 组件只是“皮肤”你可以用 Univer 默认的 React 组件也可以用 Vue 封装一套甚至用纯 HTML Web Components 替换——只要它能向 Core 发送SetRangeValuesCommand这样的标准化指令。2.2 “用户只能填几个格子”的底层实现权限模型不是开关而是规则引擎回到标题里那个具体需求“支持用户定义表格然后让用户去填写一些单元格其他的单元格用户无法修改”。如果用传统思路你会想到加 CSSpointer-events: none或 JSoninput拦截。但 Univer 的做法更彻底它把权限判断下沉到命令执行前的拦截层。当你点击一个单元格准备编辑时前端流程是UI 层捕获点击事件 → 触发SelectCellCommandCore 层收到命令 → 查询当前激活工作表的WorksheetModelWorksheetModel调用PermissionService检查该单元格坐标row, col是否在当前用户权限白名单内如果否PermissionService返回false整个命令链终止UI 层连编辑框都不会弹出如果是继续执行StartEditCommand进入编辑状态。这个PermissionService不是简单的布尔判断。它支持三种权限模式权限模式配置方式适用场景实际效果静态范围[{ range: A1:C10, editable: true }]固定表单区域A1:C10 内任意单元格可编辑其余全锁动态规则[{ condition: (cell) cell.row % 2 0, editable: true }]奇偶行差异化所有偶数行可编辑奇数行锁定服务端校验[{ api: /api/permission/check, method: POST }]复杂业务逻辑每次编辑前调用后端接口返回{ allowed: true, reason: 已审核 }我实测过动态规则模式。在一份销售日报模板里我们配置了const permissionRules [ // 第1行标题栏绝对禁止编辑 { range: 1:1, editable: false }, // B列销售额仅销售经理及以上角色可编辑 { range: B:B, editable: (ctx) ctx.user.role manager || ctx.user.role admin }, // C列完成率自动计算禁止手动输入 { range: C:C, editable: false, formula: B2/A2 } ];效果是普通销售员打开表格B列显示为灰色背景禁用状态C列显示计算结果但无法选中经理登录后B列恢复可编辑且输入数字后 C 列实时刷新。整个过程无需刷新页面权限变更即时生效。提示权限规则不是一次性加载的。Univer 支持热更新——你可以在运行时调用permissionService.updateRules(newRules)动态切换策略。我们在灰度发布时先对 5% 用户启用新规则监控错误日志确认无误后再全量推送。2.3 SDK 的真正价值不是“调用 API”而是“参与引擎构建”很多开发者初看 Univer SDK 文档会误以为它是类似axios的请求库。其实不然。Univer SDK 的本质是提供一套与引擎深度对话的契约。你不是在“使用”它而是在“塑造”它。举个典型例子如何让 Univer 支持一个自定义函数比如GET_USER_INFO(email)传统方案写个 JS 函数挂到window下Excel 公式里调用。但这样无法享受 Univer 的公式依赖追踪、异步计算队列、错误提示统一风格等能力。正确做法是注册一个 Plugin// custom-function.plugin.ts import { Plugin, PluginType, ICommandService } from univer/core; import { FormulaDataModel } from univer/sheets-formula; export class CustomFunctionPlugin extends Plugin { static override type PluginType.CUSTOM; constructor(private readonly _commandService: ICommandService) { super(); } override onMounted(): void { // 注册函数到公式引擎 const formulaDataModel this._injector.get(FormulaDataModel); formulaDataModel.registerFunction(GET_USER_INFO, async (email: string) { try { const res await fetch(/api/user?email${email}); return (await res.json()).name; } catch (e) { return #ERROR!; } }); } }然后在主应用中安装const univer new Univer({ plugins: [ new SheetsPlugin(), new CustomFunctionPlugin(commandService), // 注入 CommandService ], });这个函数现在具备了 Univer 全栈能力输入GET_USER_INFO(A1)后A1 单元格成为它的依赖项A1 改变时自动重算如果网络请求超时会显示#WAITING...而不是卡死错误时统一显示红色#ERROR!且点击单元格能看到详细错误堆栈支持 CtrlZ 撤销整个函数调用而非只撤销返回值。这才是 SDK 的威力它让你把业务逻辑无缝编织进文档引擎的血液里而不是在引擎外打补丁。后续章节我们会深入这个插件系统的实操细节。3. 核心功能实现详解从零开始配置一个“只允许填写指定单元格”的表单3.1 环境准备与最小可行 Demo5 分钟跑通基础表格别急着写权限逻辑先确保本地环境能跑起来。Univer 的官方 Quick Start 文档写得清晰但有几个实操中容易踩的坑我帮你提前避掉。第一步创建项目# 推荐使用 pnpm比 npm/yarn 更快且依赖解析更准 pnpm create vitelatest my-univer-app -- --template react-swc cd my-univer-app pnpm install注意不要用create-react-app因为 Univer 的 ESM 模块依赖在 CRA 的 Webpack 5 配置下会有兼容问题。Vite 是官方推荐的构建工具。第二步安装核心依赖pnpm add univer/core univer/sheets univer/sheets-ui univer/sheets-formula # 注意univer/sheets-ui 是 React 绑定层如果你用 Vue要装 univer/sheets-ui-vue这里有个关键点univer/sheets-formula必须显式安装。很多人漏掉它结果公式全显示#NAME?查半天才发现是公式引擎没加载。第三步初始化 Univer 实例在src/main.tsx中import React from react; import ReactDOM from react-dom/client; import { Univer, UniverInstanceType } from univer/core; import { SheetsPlugin } from univer/sheets; import { SheetsUIPlugin } from univer/sheets-ui; import { SheetsFormulaPlugin } from univer/sheets-formula; // 创建 Univer 实例 const univer new Univer({ locale: zh-CN, // 中文界面 unit: sheet-1, // 默认工作表 ID }); // 注册插件顺序很重要UI 插件必须在 SheetsPlugin 之后 univer.installPlugin(new SheetsPlugin()); univer.installPlugin(new SheetsFormulaPlugin()); // 公式插件必须在 UI 插件前 univer.installPlugin(new SheetsUIPlugin()); // 渲染到 DOM ReactDOM.createRoot(document.getElementById(root)!).render( React.StrictMode div iduniver-root style{{ width: 100vw, height: 100vh }} / /React.StrictMode ); // 启动 Univer这一步不能少 univer.start();注意univer.start()必须在所有插件安装完毕后调用。我曾因把它放在installPlugin前导致 UI 插件找不到 SheetsPlugin 的服务报错Cannot find service for Sheets. 这个错误信息很模糊实际就是启动时机错了。第四步加载一个空表格在src/App.tsx中import { useUniverInstance } from univer/sheets-ui; function App() { const univerInstance useUniverInstance(); // 加载一个空白工作簿 if (univerInstance !univerInstance.getAllUnits().length) { univerInstance.createUnit(UniverInstanceType.UNIVER_SHEET, { title: 我的表单, sheet: { rowCount: 100, columnCount: 26, } }); } return ( div classNameApp div iduniver-root style{{ width: 100%, height: 80vh }} / /div ); } export default App;运行pnpm dev你应该能看到一个干净的、类似 Excel 的界面可以自由编辑、插入公式、调整列宽。这是验证 SDK 集成成功的黄金标准。3.2 定义“可编辑区域”两种方式适用不同场景现在进入正题如何让某些单元格可编辑其他锁定Univer 提供两种官方支持的方式我建议根据项目阶段选择方式一静态范围锁定适合 MVP 阶段快速验证这是最简单直接的方法通过WorksheetModel的setProtection方法实现。它相当于给整张表加一把锁然后指定哪些区域是“钥匙孔”。// 在表格加载后执行 const workbook univerInstance.getUnitWorkbook(unitId); const worksheet workbook.getActiveSheet(); // 1. 先锁定整张表默认所有单元格不可编辑 worksheet.setProtection(true); // 2. 解锁指定区域A1:D10 worksheet.setProtection(false, { ranges: [A1:D10], }); // 3. 可选设置保护密码防止用户手动取消保护 worksheet.setProtectionPassword(your-secret-password);效果是A1:D10 区域可自由编辑其他区域右键菜单消失双击无反应F2 键无效。这个方法的优点是零学习成本5 行代码搞定缺点是无法动态变化——一旦设置除非调用setProtection(false)解锁整表否则不能新增解锁区域。实操心得setProtection的ranges参数支持 Excel 标准格式包括A1单单元格A1:C10矩形区域A:A,C:C,E1:E100多区域用英文逗号分隔1:1,10:10整行我们曾用B:B,D:D,F:F锁定所有奇数列只开放偶数列给用户填写非常高效。方式二动态权限插件适合生产环境推荐当你的业务需要根据用户角色、表单状态、甚至时间条件动态控制编辑权限时就必须上插件方案。Univer 官方提供了univer/permission插件但它的默认实现较简单我们需要扩展。步骤 1创建权限插件// plugins/CellPermissionPlugin.ts import { Plugin, PluginType, ICommandService, Inject, Injector } from univer/core; import { IPermissionService, PermissionService } from univer/permission; import { IUniverSheetsPluginConfig } from univer/sheets; import { IWorkbookData } from univer/core; export interface ICellPermissionConfig { // 静态规则{ range: A1:C10, editable: true } staticRules?: Array{ range: string; editable: boolean }; // 动态规则函数返回布尔值 dynamicRules?: Array(cell: { row: number; col: number; value: any }) boolean; } export class CellPermissionPlugin extends Plugin { static override type PluginType.CUSTOM; constructor( private readonly _config: ICellPermissionConfig, Inject(Injector) private readonly _injector: Injector, Inject(ICommandService) private readonly _commandService: ICommandService ) { super(); } override onMounted(): void { const permissionService this._injector.get(IPermissionService); // 注册自定义权限检查器 permissionService.registerChecker(cell-editable, (ctx) { const { row, col, unitId, subUnitId } ctx; // 1. 检查静态规则 if (this._config.staticRules) { for (const rule of this._config.staticRules) { if (this._isInRange(rule.range, row, col)) { return rule.editable; } } } // 2. 检查动态规则 if (this._config.dynamicRules) { for (const rule of this._config.dynamicRules) { if (!rule({ row, col, value: null })) { return false; } } } // 默认锁定 return false; }); } // 辅助方法判断坐标是否在 range 内 private _isInRange(rangeStr: string, row: number, col: number): boolean { const [start, end] rangeStr.split(:).map(s s.trim()); const [startCol, startRow] this._parseCell(start); let endCol startCol, endRow startRow; if (end) { [endCol, endRow] this._parseCell(end); } return row startRow row endRow col startCol col endCol; } private _parseCell(cell: string): [number, number] { const match cell.match(/^([A-Z])(\d)$/); if (!match) return [0, 0]; const [, letters, num] match; let col 0; for (let i 0; i letters.length; i) { col col * 26 (letters.charCodeAt(i) - 64); } return [col - 1, parseInt(num) - 1]; // 转为 0-based } }步骤 2在主应用中使用// main.tsx import { CellPermissionPlugin } from ./plugins/CellPermissionPlugin; const univer new Univer({ // ...其他配置 }); univer.installPlugin(new SheetsPlugin()); univer.installPlugin(new SheetsFormulaPlugin()); univer.installPlugin(new SheetsUIPlugin()); // 安装权限插件传入规则 univer.installPlugin(new CellPermissionPlugin({ staticRules: [ { range: A1:C10, editable: true }, { range: E1:E100, editable: true }, ], dynamicRules: [ // 第1行标题栏永远不可编辑 (cell) cell.row ! 0, // F列审核意见仅当 E列值为 pending 时可编辑 (cell) { const worksheet univerInstance.getUnitWorkbook(unitId)?.getActiveSheet(); const eValue worksheet?.getCell(cell.row, 4)?.v; // E列是索引4 return eValue ! pending || cell.col ! 5; // F列是索引5 } ] })); univer.start();这个插件的优势在于规则可热更新调用plugin.updateConfig(newConfig)即可刷新权限与 Univer 命令系统深度集成所有编辑命令SetRangeValuesCommand,InsertRowCommand都会触发检查错误友好当权限拒绝时Univer 会自动播放一个轻微的震动反馈并在状态栏提示“该区域不可编辑”用户体验远超简单disabled。3.3 高级技巧让“可编辑区域”不只是“能输字”而是“智能表单”仅仅锁定单元格只是起点。真正的业务价值在于让可编辑区域承载业务逻辑。以下是三个我在项目中反复使用的实战技巧技巧 1输入即校验拒绝“提交后报错”传统表单习惯是用户填完所有字段点“提交”按钮然后后端返回一堆错误。Univer 可以做到实时前端校验且样式与 Excel 原生一致。// 在权限插件中监听单元格编辑事件 import { SetRangeValuesCommand } from univer/sheets; // 订阅命令执行 _commandService.onCommandExecuted((command) { if (command.id SetRangeValuesCommand.id) { const params command.params as { unitId: string; subUnitId: string; range: IRange; values: any[][] }; const worksheet univerInstance.getUnitWorkbook(params.unitId)?.getSheet(params.subUnitId); // 遍历修改的单元格 for (let r 0; r params.values.length; r) { for (let c 0; c params.values[r].length; c) { const row params.range.startRow r; const col params.range.startColumn c; const newValue params.values[r][c]; // 业务规则B列手机号必须是 11 位数字 if (col 1 typeof newValue string) { if (!/^1[3-9]\d{9}$/.test(newValue)) { // 设置错误样式红色边框 错误图标 worksheet?.setCellBorder(row, col, { top: { style: thin, color: #ff0000 }, bottom: { style: thin, color: #ff0000 }, left: { style: thin, color: #ff0000 }, right: { style: thin, color: #ff0000 }, }); // 显示浮动提示 worksheet?.setCellComment(row, col, 请输入正确的11位手机号); // 播放错误音效可选 // playErrorSound(); } else { // 清除错误样式 worksheet?.setCellBorder(row, col, null); worksheet?.setCellComment(row, col, ); } } } } } });效果是用户在 B 列输入138立刻看到红框和提示输入13812345678红框消失。整个过程无需离开当前单元格符合 Excel 用户心智模型。技巧 2联动计算让“填一个格子自动算一片”权限控制常与公式联动。比如销售表中“销售额”可编辑“提成比例”由职级决定“提成金额”销售额×比例且“提成金额”列锁定。// 初始化时设置公式 worksheet.setCellFormula(1, 2, B2*VLOOKUP(C2,Sheet2!A:B,2,FALSE)); // D2 B2 * 查找C2对应的提成比例 worksheet.setCellFormula(2, 2, B3*VLOOKUP(C3,Sheet2!A:B,2,FALSE)); // ... 以此类推 // 锁定 D 列提成金额 worksheet.setProtection(false, { ranges: [D:D] }); // 注意setProtection(false) 是解锁true 是锁定 // 但等等我们想锁定 D 列所以应该 worksheet.setProtection(true); // 先锁全表 worksheet.setProtection(false, { ranges: [A:A,B:B,C:C] }); // 只解锁 A/B/C 列Univer 的公式引擎会自动追踪依赖当 B2 或 C2 改变D2 实时重算。用户无法手动修改 D2但能看到计算结果。这是最自然的“只读计算列”实现。技巧 3提交即同步告别“复制粘贴式数据流转”最后一步也是最关键的一步如何把用户填写的数据安全、可靠地提交到你的后端Univer 提供了Workbook.exportToSnapshot()方法它返回一个 JSON 对象包含所有工作表的完整状态值、样式、公式、合并信息等。但直接提交这个快照往往过大且包含大量无关元数据。更优方案是只提取可编辑区域的变更// 监听保存事件 _commandService.onCommandExecuted((command) { if (command.id SaveCommand.id) { const workbook univerInstance.getUnitWorkbook(unitId); const worksheet workbook.getActiveSheet(); // 获取所有“可编辑区域”的当前值 const editableRanges [A1:C10, E1:E100]; const formData: Recordstring, any {}; editableRanges.forEach(range { const [start, end] range.split(:).map(s s.trim()); const [startCol, startRow] parseCell(start); const [endCol, endRow] end ? parseCell(end) : [startCol, startRow]; for (let r startRow; r endRow; r) { for (let c startCol; c endCol; c) { const cell worksheet.getCell(r, c); if (cell cell.v ! undefined) { const key ${String.fromCharCode(65 c)}${r 1}; // A1, B2... formData[key] cell.v; } } } }); // 提交到你的 API fetch(/api/form/submit, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ formId: daily-inspection, data: formData, timestamp: Date.now(), }) }); } });这个formData对象结构清晰后端可直接映射到数据库字段无需额外解析。我们线上系统用这套方案日均处理 20 万 表单提交零数据丢失。4. 实战问题排查与避坑指南那些文档里不会写的“血泪经验”4.1 常见问题速查表从环境搭建到生产发布问题现象可能原因解决方案实测耗时页面空白控制台无报错univer/sheets-ui未正确挂载到 DOM 节点检查iduniver-root的 div 是否存在且无display: none确认style.height有具体值不能是auto2 分钟公式显示#NAME?univer/sheets-formula插件未安装或安装顺序错误确保SheetsFormulaPlugin在SheetsUIPlugin之前安装检查pnpm list univer/sheets-formula是否为最新版5 分钟权限规则不生效setProtection调用时机错误在univer.start()前将所有setProtection逻辑移到univer.start()之后或监听onReady事件10 分钟动态规则中getCell()返回undefined单元格为空getCell()默认不创建 Cell 对象使用getCellRaw()获取原始数据或先调用setCell()初始化3 分钟移动端触摸体验差缩放卡顿、长按失灵Vite 默认未启用touch事件优化在vite.config.ts中添加build: { target: es2015 }并确保univer/sheets-ui版本 ≥ 1.4.015 分钟多人协作时编辑冲突未启用univer/collaboration插件安装univer/collaboration配置 WebSocket 地址注意免费版仅支持 3 人同时在线20 分钟导出 PDF 样式错乱字体缺失、表格变形未配置字体映射或 PDF 引擎未加载在Univer初始化时传入pdf: { fontMapping: { Microsoft YaHei: simhei } }8 分钟4.2 五个必知的“反直觉”细节新手最容易栽跟头的地方细节 1row和col是 0-based但 Excel 显示是 1-based这是最常引发 off-by-one 错误的地方。worksheet.getCell(0, 0)获取的是 A1 单元格不是 A0。当你写range: A1:C10时Univer 内部会自动转换为(0,0)到(9,2)。但如果你手动计算坐标务必记住第 1 行 →row 0第 1 列 →col 0Z列是第 26 列 →col 25AA列是第 27 列 →col 26我建议永远用parseCell()工具函数转换而不是手算。细节 2setProtection(true)不等于“锁定所有”而是“启用保护模式”很多开发者以为setProtection(true)就万事大吉结果发现所有单元格还是可
返回列表