Univer Sheets 绘图集成指南:深入解析 @univerjs/sheets-drawing 的安装、锚点体系与 Facade API
【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer
本篇技术指南以@univerjs/sheets-drawing为核心,讲解如何在 Univer 的电子表格(Sheets)中承载图片、形状与浮动 DOM 等绘图(Drawing)对象。你将掌握该包的安装方式、插件注册流程、三种单元格锚点(OneCell / TwoCell / Absolute)的底层语义,以及通过 Facade API 以命令链完成插入、查询、更新、分组绘图的完整实战方法。
包概览:sheets-drawing 在 Univer 分层架构中的位置
@univerjs/sheets-drawing是 Univer 生态中"绘图模型层"与"表格业务层"之间的桥梁包。正如其 README 所述,它把共享的绘图模型(drawing model)连接到 Univer Sheets,让工作表可以承载绘图对象。绘图对象在这里是一个广义概念,包括:
- 图片(
ISheetImage):最常见的用例,例如把 Logo、图表截图插入到单元格区域上方; - 形状(
ISheetShape):源码注释中标记为测试类型,基于IDrawingParam扩展; - 浮动 DOM(
ISheetFloatDom):携带componentKey与可选data,可在表格上叠加自定义组件。
这三种类型统一收敛为联合类型ISheetDrawing,定义见 sheet-drawing.service.ts。
该包在 npm 上的发布信息与其源码目录结构一一对应:
| 包 | UMD 全局名 | CSS | Locales | Facade 入口 |
|---|---|---|---|---|
@univerjs/sheets-drawing | UniverSheetsDrawing | 无 | 无 | 有 |
从 package.json 可以确认:包名@univerjs/sheets-drawing、版本1.0.0-beta.2、许可证 Apache-2.0,核心依赖为@univerjs/core、@univerjs/drawing、@univerjs/engine-render与@univerjs/sheets。其中exports暴露了三条入口:
.(主入口):插件、服务、命令与类型;./*:按子路径引用的源码模块;./facade:面向用户的 Facade API(FWorksheetDrawingMixin、FOverGridImage、FOverGridImageBuilder等)。
需要特别说明的是:该包不包含任何 CSS 与 Locales 资源(表格中标注为 No),因此绘图对象的样式渲染完全依赖共享渲染引擎与 UI 层,这正是下一步需要搭配@univerjs/sheets-drawing-ui的原因。
安装:版本一致性是硬性要求
README 给出了两种主流包管理器安装方式:
pnpm add @univerjs/sheets-drawing # or npm install @univerjs/sheets-drawing官方明确要求保持所有@univerjs/*包版本一致(Keep all@univerjs/*packages on the same version)。这是 Univer 插件生态的通用约束:因为各包之间通过类型定义与内部接口相互依赖,版本错位极易导致 DI 标识(Identifier)或类型不匹配的运行时错误。由于该包是纯逻辑层(无样式),在 monorepo 工作区中它只声明了运行依赖与测试依赖(vitest),没有 postcss/tailwind 相关配置,这与 UI 类插件(如sheets-drawing-ui)的 package.json 结构有明显区别。
快速上手:三步注册绘图能力
最小可用示例非常简洁,README 中给出的核心代码如下:
import { UniverSheetsDrawingPlugin } from '@univerjs/sheets-drawing'; univer.registerPlugin(UniverSheetsDrawingPlugin);在 plugin.ts 中可以看到这个插件类的完整定义:
- 插件标识:
SHEET_DRAWING_PLUGIN(常量值为'SHEET_DRAWING_PLUGIN'),type为UniverInstanceType.UNIVER_SHEET,即它只服务于电子表格单元; - 声明式依赖:通过
@DependentOn(UniverDrawingPlugin, UniverSheetsPlugin)注解强制依赖共享绘图插件与表格核心插件,注册顺序由框架自动保证; - 配置管理:构造函数将用户传入的
Partial<IUniverSheetsDrawingConfig>与defaultPluginConfig合并后写入IConfigService。当前版本 config.ts 中的IUniverSheetsDrawingConfig为空接口,defaultPluginConfig为空对象,为后续扩展预留了位置; - 服务注册:
onStarting()阶段向注入器注册了四个依赖:SheetsDrawingLoadController:负责命令注册、资源快照与工作表变更联动;SheetDrawingTransformPlanService:变换计划服务,协调行/列变更对绘图的影响;SheetDrawingTransformAffectedController:变换受影响控制器;ISheetDrawingService(实现为SheetDrawingService):绘图数据服务。
插件运行时:命令、快照与工作表联动
SheetsDrawingLoadController(见 sheet-drawing.controller.ts)在初始化时做了三件事,理解它们有助于把握整个包的边界:
1. 注册 5 条命令 + 1 条 Mutation + 1 条 Operation
SetSheetDrawingCommand // 更新既有绘图 InsertSheetDrawingCommand // 插入新绘图 RemoveSheetDrawingCommand // 删除绘图 SetDrawingArrangeCommand // 调整层级(置于顶层/底层等) SetSheetDrawingPlacementCommand // 设置锚点位置 SetDrawingApplyMutation // 应用绘图数据变更(undo/redo 复用) ClearSheetDrawingTransformerOperation // 清除变换器缓存以 insert-sheet-drawing.command.ts 为例,插入流程是标准的"命令 → Mutation → UndoRedo"三件套:先通过sheetDrawingService.getBatchAddOp(drawings)生成IDrawingJsonUndo1(含 redo/undo 两套操作),再经SheetInterceptorService拦截扩展,最终把SetDrawingApplyMutation(类型DrawingApplyType.INSERT)与ClearSheetDrawingTransformerOperation组装成 redo/undo 序列并推入IUndoRedoService。这意味着所有绘图操作天然支持撤销与重做。
2. 注册插件资源快照(Snapshot)
_initSnapshot()通过IResourceManagerService.registerPluginResource将绘图数据接入 Univer 的资源持久化管线:
toJson:按单元(unitId)序列化IDrawingSubunitMap<ISheetDrawing>;parseJson:反序列化并容错(解析失败返回空对象);onLoad:把快照同时写入ISheetDrawingService与共享的IDrawingManagerService;onUnLoad:反向清理两个服务的缓存。
这保证了绘图数据可以随工作簿一起保存、加载与协同。
3. 拦截工作表变更(删除 / 复制)
_initSheetChange()拦截两条表格命令并自动生成对应的绘图变更:
RemoveSheetCommand:删除工作表时,批量移除该表上的全部绘图,并把删除操作注册为可撤销的 Mutation(DrawingApplyType.REMOVE/ 反向INSERT);CopySheetCommand:复制工作表时,依据getDrawingsInOrder保证绘图层级顺序(先按drawingOrder排列、再补齐未排序项),通过共享包中的getOrCreateDrawingCopyPlan生成复制计划后批量插入到目标表。
由此可见,sheets-drawing并不是简单的数据容器,它深度参与了表格的增删改与快照体系,确保绘图对象与工作表生命周期严格同步。
锚点体系:Position / Both / None 与 OOXML 的对应关系
绘图对象区别于普通单元格内容的核心特征是"悬浮在网格之上",因此必须回答一个问题:当行/列发生增删或尺寸变化时,绘图应当如何跟随?
SheetDrawingAnchorType枚举(sheet-drawing.service.ts)给出了三种语义:
| 枚举值 | 字符串值 | 行为 | OOXML 对应 |
|---|---|---|---|
Position | '0' | 仅位置跟随单元格变化;插入/删除行列时绘图位置改变,但尺寸不变 | OneCell |
Both | '1' | 尺寸与位置都跟随单元格变化,行列变化时二者同步调整 | TwoCell |
None | '2' | 位置与尺寸均不跟随单元格,行列变化后保持原样 | Absolute |
配合 sheet-drawing-placement.ts 中的 Placement 模型,三种锚点被表达为三种可序列化的结构:
- OneCell(
ISheetDrawingOneCellPlacement):kind: Position+ 起始单元格from: ICellOverGridPosition+ 固定像素宽高; - TwoCell(
ISheetDrawingTwoCellPlacement):kind: Both+ 起始from+ 结束to两个单元格标记,宽高由两标记之差推导; - Absolute(
ISheetDrawingAbsolutePlacement):kind: None+ 画布坐标系下的left/top/width/height。
围绕这套模型,包内提供了三个核心工具函数(均已在 index.ts 导出):
getSheetDrawingPlacement(drawing):从绘图对象反解其当前 Placement;normalizeSheetDrawingPlacement(input, skeleton?):把"精确标记"或"模型空间 bounds"统一归一化为权威 Placement,并对越界(宽高非正、坐标为负等)抛出带错误码的异常(如SHEET_DRAWING_PLACEMENT_EXTENT_INVALID、SHEET_DRAWING_PLACEMENT_SKELETON_REQUIRED);applySheetDrawingPlacement(drawing, input, skeleton?):把归一化后的 Placement 写回绘图的sheetTransform、transform与axisAlignSheetTransform。
坐标换算层在 basics/transform-position.ts:
drawingPositionToTransform:把基于单元格的定位(from/to)转换为画布绝对像素变换,并自动将超出表格右下边界的绘图收回到边界内;transformToDrawingPosition:反向换算,利用 skeleton 的getCellIndexAndOffsetByPosition从像素坐标反推单元格标记;transformToAxisAlignPosition:Excel 兼容的轴对齐旋转规则——为模拟 Excel 中"旋转后的基本绘图使用主轴切换、轴对齐包围盒"的行为,当旋转角规范化到[-45°, 45°)与[135°, 225°)区间时使用原始包围盒,落在[45°, 135°)与[225°, 315°)区间时则交换宽高后再换算。
此外 common/rotate-enabled.ts 提供了resolveSheetDrawingRotateEnabled与isKnownSheetNonRotatableDrawingType,用于判定哪些绘图类型允许旋转,并配套了对应的单测(见rotate-enabled.spec.ts)。
Facade API 实战:以命令链操作表格图片
该包是"Facade 入口 = Yes"的插件,通过FWorksheet.extend(FWorksheetDrawingMixin)把绘图能力直接挂到FWorksheet上(f-worksheet.ts)。以下示例全部取自该文件的官方 JSDoc,可直接在univerAPI环境中运行。
插入图片(一行搞定)
const fWorksheet = univerAPI.getActiveWorkbook().getSheetByName('Sheet1'); if (!fWorksheet) return; const result = await fWorksheet.insertImage('https://example.com/logo.png'); console.log(result); // trueinsertImage是重载方法,支持(url)、(url, column, row)、(url, column, row, offsetX, offsetY)以及IFBlobSource(本地文件转 Base64)等多种形态。未指定位置时默认插入到 A1 单元格,行列索引从 0 开始,偏移量单位为像素。
使用构建器精确控制位置与尺寸
当需要精细控制时,推荐newOverGridImage()构建器链式调用:
const fWorkbook = univerAPI.getActiveWorkbook(); const fWorksheet = fWorkbook.getSheetByName('Sheet1'); if (!fWorksheet) return; const image = await fWorksheet.newOverGridImage() .setSource('https://example.com/logo.png', univerAPI.Enum.ImageSourceType.URL) .setColumn(5) .setRow(5) .setWidth(500) .setHeight(300) .buildAsync(); fWorksheet.insertImages([image]);FOverGridImageBuilder(f-over-grid-image.ts)提供的链式方法还包括:
setColumnOffset/setRowOffset:像素级偏移;setAnchorType:直接指定SheetDrawingAnchorType.Position / Both / None;setPlacement:以显式 Placement(OneCell/TwoCell/Absolute 或 bounds 推断)覆盖逐字段设置,优先级高于独立的行列宽高字段;setRotate:旋转角度(如 90、180、270);setCropTop/Left/Bottom/Right:按像素裁剪图片显示区域;buildAsync:若未设置宽高,会调用getImageSize读取源图实际尺寸自动填充。
查询、更新与删除
// 获取当前工作表全部图片 const images = fWorksheet.getImages(); images.forEach((image) => console.log(image.getId())); // 按 id 查询 const image = fWorksheet.getImageById('xxxx'); if (image) { // 修改宽高后更新 const builder = image.toBuilder(); const newImage = await builder.setWidth(100).setHeight(50).buildAsync(); fWorksheet.updateImages([newImage]); } // 删除 fWorksheet.deleteImages([image]);getActiveImages()则返回当前处于选中(focus)状态的图片。所有写操作最终都会落到InsertSheetDrawingCommand/RemoveSheetDrawingCommand/SetSheetDrawingCommand,因此同样具备撤销能力。
锚点位置读写:getDrawingPlacement / setDrawingPlacement
const sheet = univerAPI.getActiveWorkbook().getActiveSheet(); // 读取任意绘图的锚点 const placement = sheet.getDrawingPlacement('drawing-id'); if (placement?.kind === univerAPI.Enum.SheetDrawingAnchorType.Both) { console.log(placement.from, placement.to); } // 设置为一格锚定(OneCell):位置随单元格移动,尺寸固定 sheet.setDrawingPlacement('drawing-id', { kind: univerAPI.Enum.SheetDrawingAnchorType.Position, from: { row: 2, column: 2, rowOffset: 8, columnOffset: 8 }, width: 240, height: 120, }); // 两格锚定(TwoCell):位置与尺寸都随单元格变化 sheet.setDrawingPlacement('drawing-id', { kind: univerAPI.Enum.SheetDrawingAnchorType.Both, from: { row: 2, column: 2, rowOffset: 8, columnOffset: 8 }, to: { row: 8, column: 6, rowOffset: 0, columnOffset: 0 }, }); // 绝对定位(Absolute):行列变化不影响绘图 sheet.setDrawingPlacement('drawing-id', { kind: univerAPI.Enum.SheetDrawingAnchorType.None, left: 640, top: 96, width: 240, height: 120, });此外resolveDrawingPlacement可在 Node/无头环境中把模型空间 bounds 归一化为锚点标记(Position/Both需要 skeleton,None不需要),getDrawingLayout则返回整个表格的模型坐标布局(gridBounds、dataBounds与按层级排序的绘图列表),方便服务端排版或导出场景。
组合与分组
Facade 还暴露了绘图分组能力:groupDrawings(drawingIds, groupId?)将至少两个绘图组合并返回组 id,ungroupDrawings(groupIds)解组并还原子绘图变换,getDrawingGroupChildren/getDrawingParentGroup/isDrawingGrouped用于遍历组关系。分组操作同样基于SetDrawingApplyMutation(DrawingApplyType.GROUP / UNGROUP)并注册 undo/redo。
与 sheets-drawing-ui 的集成方式
README 的 Integration Notes 明确给出了分层建议:当用户需要在表格 UI 中进行绘图交互(拖拽、缩放、旋转、选择等)时,必须搭配@univerjs/sheets-drawing-ui一起使用。
pnpm add @univerjs/sheets-drawing @univerjs/sheets-drawing-uiimport { UniverSheetsDrawingPlugin } from '@univerjs/sheets-drawing'; import { UniverSheetsDrawingUIPlugin } from '@univerjs/sheets-drawing-ui'; univer.registerPlugin(UniverSheetsDrawingPlugin); univer.registerPlugin(UniverSheetsDrawingUIPlugin);从架构职责看,sheets-drawing只负责数据模型、命令、快照与坐标换算(无 CSS、无交互、可无头运行),而 UI 插件负责渲染与交互。这种"逻辑与表现分离"的设计让同一套绘图数据既能运行在浏览器,也能在 Node/服务端做布局计算(getDrawingLayout、resolveDrawingPlacement即为此设计)。
测试与验证:如何确认集成正确
该包为每一条核心链路都配套了测试,是验证理解的最佳参照:
- 集成测试:commands/commands/tests/sheet-drawing.integration.spec.ts 覆盖插入/删除/更新命令与撤销重做的完整链路;
- 服务测试:
services/__tests__/sheet-drawing.service.spec.ts与sheet-drawing-transform-plan.service.spec.ts验证数据服务与行/列变换计划; - 控制器测试:
controllers/__tests__/下验证删除/复制工作表时绘图数据的联动; - Facade 测试:
facade/__tests__/f-over-grid-image.spec.ts、f-worksheet.spec.ts、f-univer.spec.ts验证面向用户的 API 行为; - 工具测试:
common/__tests__/rotate-enabled.spec.ts覆盖旋转开关判定。
本地运行测试的方式与包内脚本一致:
pnpm --filter @univerjs/sheets-drawing test小结
@univerjs/sheets-drawing是 Univer Sheets 绘图能力的"数据中枢":它定义了ISheetDrawing数据模型与三种锚点语义(Position/Both/None,对应 OOXML 的 OneCell/TwoCell/Absolute),通过命令链把绘图操作纳入撤销重做体系,并借助资源快照与工作表拦截器让绘图数据与工作簿生命周期深度绑定。对于开发者而言,纯数据场景可只注册本插件,配合 Facade API 的insertImage、newOverGridImage、setDrawingPlacement等方法即可完成图片的增删改查;需要交互能力时再叠加@univerjs/sheets-drawing-ui,二者互补构成完整方案。
【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考