☰
Open Pencil 的 LayerTreeRoot:构建无样式可定制图层树面板的 SDK 组件指南
2026/9/27 9:39:28 网站建设 项目流程
  • 前端
  • 桌面应用
  • AI 应用
  • MCP 服务

【免费下载链接】open-pencil

AI-native design editor. Open-source Figma alternative.

项目地址:https://gitcode.com/gh_mirrors/op/open-pencil
点击查看免费下载

LayerTreeRoot是 Open Pencil(开源的 AI 原生设计编辑器)Vue SDK(@open-pencil/vue)中面向图层树(layer tree)的无样式(headless)结构原语。它把图层树的树形结构、选择状态、展开/折叠分支、可见性/锁定切换、重命名与拖拽排序等交互逻辑全部封装在内,而把 DOM 标记(markup)与样式(styling)完全交给应用自身。本文将以 LayerTreeRoot 官方文档 为主线,结合 源码实现 与配套 API,讲解它的职责边界、上下文模型、生命周期同步机制,并给出可直接复制的实战用法。

1. 组件定位:结构由 SDK 管,外观由应用管

LayerTreeRoot的设计目标可以概括为一句话:提供可复用的树结构与交互接线,而不内置任何表现层(without built-in presentation)。它不像传统组件库那样输出一套固定的带样式 UI,而是通过作用域插槽(scoped slot)暴露数据与动作,由你在插槽内自由组装行内容、缩进样式、选中高亮与拖拽指示器。

这意味着在实际使用中你需要:

  • 用LayerTreeRoot包住整个面板区域,让 SDK 负责从编辑器场景图(scene graph)构建树模型、维护展开状态、计算可见行、处理选择与拖拽;
  • 在插槽内用自有组件(例如你自己的TreeView)渲染items,并通过插槽暴露的select、toggleExpand等动作把点击事件接回 SDK;
  • 每一行既可以使用配套的LayerTreeItem(它提供选择、展开、可见性、锁定、直接重命名等行级状态与动作),也可以完全自写行组件,仅从useLayerTree()取所需上下文。

2. 核心概念:树模型、可见行与选择状态

要正确使用LayerTreeRoot,先理解它向下游暴露的三个核心概念。它们都定义在 context.ts 中:

概念类型含义
itemsRef<LayerNode[]>当前页面下的图层树根级节点数组,LayerNode含id、name、type、layoutMode、visible、locked与嵌套的children
visibleRowsComputedRef<LayerRow[]>按当前展开状态拍平后的可见行列表,每行含node、level(层级,从 1 起)与hasChildren
selectedIdsComputedRef<Set<string>>当前编辑器中选中的节点 id 集合,与画布选区保持同步

树模型的构建:buildLayerTreeModel

树模型由 model.ts 中的buildLayerTreeModel(graph, parentId)从场景图构建:它从当前页开始,深度优先遍历parent.childIds,跳过internalOnly的内部节点,把每个SceneNode映射为轻量LayerNode,并同时返回byId索引用于后续的补丁式更新(patch)。

visibleLayerRows(items, expandedIds)则把树按展开集合拍平成带level的行序列:某行有子节点且其 id 在展开集合中,才会继续展开下一层。也就是说,展开状态完全由expanded数组驱动,LayerTreeRoot会替你维护这个数组,并保证删除节点后残留的展开 id 会被自动过滤(见下文的 rebuild 逻辑)。

选择模式:layerSelectionForTarget

选择逻辑集中在layerSelectionForTarget:根据目标 id、锚点 id(selectionAnchorId)与LayerSelectionMode(additive、range两个布尔位)计算新选区:

  • 非 range:单击(additive=false)直接替换选区;additive 则在当前选区上增/删目标;
  • range:结合锚点与目标在可见行中的索引,取二者之间的连续区间;配合 additive 可把区间并入现有选区。

这正是实现按住 Ctrl/Shift 多选、框选连选等交互的底层依据,Open Pencil 编辑器的选择行为与画布是一致的。

3. 插槽协议:组件暴露的数据与动作

LayerTreeRoot的模板基于 reka-ui 的TreeRoot构建,并通过单个默认插槽把全部能力下放(见 LayerTreeRoot.vue)。插槽参数完整列表如下:

插槽参数说明
items树根级LayerNode[]
flattenItemsreka-ui 计算出的拍平节点列表
visibleRows按展开状态拍平的LayerRow[](含 level/hasChildren)
expanded当前展开的 id 数组
treeVersion树版本号,每次重建模型 +1,可用来强制行组件刷新
selectedIdsSet<string>当前选区
focused面板是否获得焦点(用于键盘导航与视觉聚焦态)
draggingId正在拖拽的行 id(无拖拽时为 null)
instruction拖拽放置指令:reorder-above/reorder-below/make-child
instructionTargetId拖拽指令对应的目标行 id
actions动作集合:select(id, mode)、toggleExpand(id)、setFocused(bool)、setVirtualizer(v)

下面是根据 导航面板指南 给出的最小可运行示例——SDK 管理结构,应用管理标记与样式:

<LayerTreeRoot v-slot="{ items, selectedIds, select, toggleExpand, getKey, getChildren }"> <TreeView :items="items" :selected-ids="selectedIds" :get-key="getKey" :get-children="getChildren" @select="select" @toggle-expand="toggleExpand" /> </LayerTreeRoot>

注:示例中的getKey/getChildren由内部TreeRoot提供,分别返回节点 id 与子节点数组;如果你完全自写行渲染,可改用visibleRows+ 插槽内的actions.select/actions.toggleExpand。

4. 行级渲染:LayerTreeItem 与 useLayerTree

LayerTreeRoot官方文档特别指出:可以搭配LayerTreeItem使用,也可以用自己的插槽渲染行。LayerTreeItem是默认的行组件,其默认插槽会收到以下行级状态与动作(见 LayerTreeItem.vue):

  • 状态:node、level、hasChildren、isSelected、isDragging、instruction、instructionTargetId、focused、padLeft(按(level-1) * indentPerLevel计算的左缩进值);
  • 动作:actions.select(additive)、actions.toggleExpand()、actions.toggleVisibility()、actions.toggleLock()、actions.rename(name)。

行级动作会先emit到LayerTreeRoot(供外部监听),再落到编辑器:toggleVisibility调用editor.toggleNodeVisibility(id)、toggleLock调用editor.toggleNodeLock(id)、rename调用editor.renameNode(id, name),因此这些操作默认就是带撤销(undo)语义的编辑器动作。

如果你在自写行组件中只想读取上下文而无需外层插槽,可以使用配套的组合式函数:

import { useLayerTree } from '@open-pencil/vue' // 在 LayerTreeRoot 的任何后代组件中 const ctx = useLayerTree() console.log(ctx.visibleRows.value) // 当前可见行 ctx.select(nodeId, { additive: true, range: false }) ctx.toggleExpand(nodeId)

useLayerTree()通过注入键LAYER_TREE_KEY(Symbol('layer-tree'))返回最近的LayerTreeRoot上下文;如果在LayerTreeRoot之外调用,会直接抛出错误提示,这能帮助你在开发期尽早发现结构错误(见 context.ts)。

上下文完整字段

LayerTreeContext除了上述状态外,还包含供内部协作的成员:editor(当前编辑器实例)、treeVersion、setupDrag、setVirtualizer、setRowRef等。多数情况下你只需关注插槽参数与select/toggleExpand/toggleVisibility/toggleLock/rename这套动作。所有公开类型(LayerNode、LayerRow、LayerSelectionMode、LayerDragInstruction、LayerTreeContext、LayerTreeVirtualizer)均从 index.ts 导出。

5. 与编辑器生命周期的同步机制

LayerTreeRoot不是一次性渲染的静态树,而是与编辑器场景图保持实时同步的“活”结构。从 LayerTreeRoot.vue 可以看到它订阅了一系列编辑器事件:

事件处理策略
graph:replaced/page:changed立即重建整棵树(rebuildTree)
node:created/node:deleted/node:reparented/node:reordered合并到微任务队列中调度一次重建(scheduleTreeRebuild),避免高频操作反复重建
node:updated只做补丁更新(patchTreeNode):仅当改动涉及name、type、layoutMode、visible、locked时才就地同步对应行,否则忽略
selection:changed自动展开选中节点的祖先分支(expandSelectionAncestors),必要时滚动到选中行

两个细节值得注意:

  1. 增量更新而非全量重建:普通属性变化走patchLayerNode原地更新LayerNode,只有结构变化(增删/换父/重排)才触发整树重建,这是它在大文档中保持流畅的关键设计;
  2. 选区联动:当选区变化时,组件会自动把选中节点的祖先 id 加入expanded,确保“选中即展开可见”,这与主流设计工具的行为一致。组件在onScopeDispose中统一退订所有事件,避免内存泄漏。

6. 拖拽重排与换容器:useLayerDrag

LayerTreeRoot内部内置了基于@atlaskit/pragmatic-drag-and-drop的拖拽能力(useLayerDrag,见 useLayerDrag.ts),支持三种放置指令:

  • reorder-above:把源行插入目标行之前;
  • reorder-below:把源行插入目标行之后;
  • make-child:把源行放入目标容器末尾(成为子节点)。

实现要点:

  • 每行通过setupItem注册为可拖拽源(draggable)与放置目标(dropTargetForElements),并结合命中框计算(attachInstruction)与缩进量indentPerLevel推导指令;
  • 容器行(editor.graph.isContainer)允许被放入子节点(block 列表不含make-child),普通叶子节点则禁止;
  • 放置校验包含source.id !== data.id(不能放到自己身上)与isDescendant(targetId, sourceId)(不能放进自己的后代里);
  • 放置成功后调用editor.reorderChildWithUndo(sourceId, parentId, index)执行带撤销的重排,make-child场景还会把目标容器自动展开,保证移动后节点立即可见。

拖拽过程中的draggingId、instruction、instructionTargetId会随插槽参数实时下发,因此你可以据此绘制“插入线/放入高亮”等视觉反馈——这也是为什么组件本身不内置样式、但交互体验却能完全自定义的原因。

7. 在真实面板中的位置与配套 API

在 Open Pencil 中,图层树是左侧导航面板的核心区域。官方 导航面板指南 推荐的布局是:上方为页面列表(PageListRoot),下方为图层树(LayerTreeRoot),行组件内部承载属性详情与直接重命名。与之配套的 API 还包括:

  • LayerTreeItem:单行组件,提供选择、展开、可见性、锁定与重命名;
  • useLayerTree:在任意后代组件中获取最近LayerTreeRoot的上下文;
  • useLayerDrag:拖拽重排/换容器状态与动作的组合式函数;
  • useSelectionState:与选择状态相关的组合式 API;
  • PageListRoot:页面列表根组件。

这些组件同属@open-pencil/vue的无样式组件体系,均可在 组件索引 中查阅。

8. 完整实战:组装一个自定义图层树面板

综合以上内容,一个典型的“自带标记与样式”的面板可以这样组织:

<script setup lang="ts"> import { LayerTreeRoot } from '@open-pencil/vue' </script> <template> <LayerTreeRoot :indent-per-level="20" v-slot="{ items, visibleRows, expanded, selectedIds, toggleExpand, actions }" > <!-- 行渲染完全自定义,样式由应用决定 --> <ul class="layer-panel"> <template v-for="row in visibleRows" :key="row.node.id"> <li :class="{ selected: selectedIds.has(row.node.id), dragging: row.node.id === $attrs.draggingId }" :style="{ paddingLeft: (row.level - 1) * 20 + 'px' }" @click="actions.select(row.node.id, false)" @dblclick="row.hasChildren && actions.toggleExpand(row.node.id)" > {{ row.node.name }} </li> </template> </ul> </LayerTreeRoot> </template>

要点回顾:

  • visibleRows已经替你算好每个节点的level与hasChildren,无需自己递归;
  • 缩进可以用indentPerLevel属性(默认 16)统一控制,也可以在行内自行计算;
  • actions中的动作直接作用于编辑器,操作自动进入撤销栈;
  • 如需更细粒度的行级能力(可见性/锁定/重命名/拖拽),改用LayerTreeItem或直接调用useLayerTree()。

9. 总结

LayerTreeRoot是 Open Pencil Vue SDK 中“结构即服务、样式归应用”理念的典型代表:它把图层树的模型构建、展开状态、可见行计算、选区同步、事件订阅与拖拽重排全部封装,并通过插槽与useLayerTree()把状态和动作完整暴露给应用层。掌握它之后,你可以在不触碰渲染细节的前提下,快速搭建出与 Open Pencil 编辑器深度联动、交互行为一致的自定义图层树面板。进一步可阅读 LayerTreeItem、useLayerTree 与 useLayerDrag 继续深入。

  • 前端
  • 桌面应用
  • AI 应用
  • MCP 服务

【免费下载链接】open-pencil

AI-native design editor. Open-source Figma alternative.

项目地址:https://gitcode.com/gh_mirrors/op/open-pencil
点击查看免费下载
上一篇:重构AI编程范式:Cline如何颠覆IDE开发体验
下一篇:Apache Hamilton UI完全指南:可视化、监控和调试数据流水线的终极工具

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询