[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:system-architecture-diagram-editor":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 支持分组重排的系统架构图编辑器\n\n## 这个示例构建了什么\n\n这个示例构建了一个分层的系统架构画板，用户可以直接在画布上进行调整。可见效果是一组层层嵌套的分区容器，带有左侧标题栏、基于层级深度的背景颜色，以及白色叶子卡片，而结构性的父子连线保持隐藏。\n\n用户可以切换可见深度预设、开关 minimap、显示或隐藏展开按钮、修改层级配色方案、打开共享画布设置面板、导出图片、选择单个节点、选择整个子树、内联重命名节点、调整所选分组大小、重新贴合分组边框、重新布局子项，以及追加新的子项。\n\n这个实现的主要亮点在于，它并不只依赖默认的自动布局。它会把固定位置的层级数据加载到 relation-graph 中，等待 `node.lot.level` 和 `node.lot.childs` 这类层级元数据生成完成，然后执行自定义的打包与重新贴合流程，从而让图看起来更像架构工作台，而不是普通的节点连线图。\n\n## 数据是如何组织的\n\n内置的数据源是在 `ai-json-data.ts` 中硬编码的嵌套树，名称为 `aiOutputJson`。每一项都包含 `text`、`x`、`y` 和可选的 `children`，因此源数据本身已经是层级结构，也已经包含了大致的位置提示。\n\n`parseJsonDataByAI()` 会递归地把这棵树转换为 `RGJsonData`，将每一项扁平化为一个节点，生成 id，保留原始坐标，在 `node.data` 中存储 `hasChildren` 和 `deep`，并为每个非根节点关系创建一条父子连线。图依然会加载这些连线，因为层级逻辑依赖它们，但该示例会在加载前后将它们隐藏，以保持展示效果以盒状分组为主。\n\n在 `setJsonData()` 之后，还有两个重要的预处理结果。第一，relation-graph 会生成后续逻辑所依赖的 `node.lot` 层级元数据。第二，自定义 actions 层可以把图重新打开到指定深度，重新打包每个分组的子节点，并根据后代边界重新调整祖先容器的尺寸。\n\n在真实产品中，这种数据结构同样可以表示平台架构、能力地图、分层产品模块、企业系统版图、服务分类体系，或任何其他已经预先整理好的层级结构，在这些场景中，分组区块比可见连接线更重要。\n\n## relation-graph 是如何使用的\n\n入口组件使用 `RGProvider` 包裹整个示例，`MyGraph` 则通过 `RGHooks.useGraphInstance()` 作为主要的命令式控制入口。初始 `RGOptions` 配置了 `layout.layoutName = 'fixed'`、矩形节点、默认节点零边框、曲线连线、边框连接点、左侧展开控制、展开或收起时重新布局、滚轮滚动、画布拖拽，以及调试模式。\n\n初始化时，relation-graph 被当作一个分阶段加载器来使用。示例会调用 `loading()`、`setJsonData()`、`moveToCenter()`、`zoomToFit()` 和 `clearLoading()`，同时 `MyGraphActions` 会调用 `doLayout()`、`updateNodesVisibleProperty()`、`getNodesRectBox()`、`updateNodeData()`、`updateNode()`、`addNodes()`、`addLines()`、`generateNewNodeId()`、`getDescendantNodes()` 和 `updateEditingControllerView()`，以保持这个可编辑画板的一致性。\n\n插槽承担了大部分视觉适配工作。`RGSlotOnNode` 会用叶子卡片或分组容器替换默认图节点，这两种形式都通过 `MyEditableNode` 渲染。`RGSlotOnView` 则挂载 `RGMiniView`、`RGEditingReferenceLine`、`RGEditingNodeController`、`RGEditingResize` 和自定义的 `MyNodeToolbar`，因此编辑交互能力位于图的覆盖层中，而不是侧边表单中。\n\n共享的 `DraggableWindow` 组件则在图周围提供展示控制。它通过 `setOptions(...)` 提供深度预设、minimap 与展开按钮开关、颜色方案切换、滚轮模式与拖拽模式切换，并通过 `prepareForImageGeneration()` 和 `restoreAfterImageGeneration()` 支持图片导出。\n\n样式也是 relation-graph 使用方式的一部分。SCSS 用 `var(--rg-node-color)` 重绘展开按钮，为叶子节点赋予带边框的卡片外观，让分组节点看起来像带内边距的容器，并把顶层和一级标签移成左侧标题栏样式，其中包括一个较大的纵向根标签。\n\n## 关键交互\n\n- 单击节点会通过 `setEditingNodes([node])` 将其设为当前编辑选中项。\n- 双击叶子包装器或分组包装器，会通过 `getDescendantNodes(...)` 选择该节点及其所有后代。\n- 双击 `MyEditableNode` 中的文本会进入内联编辑；失焦或按 Enter 会通过 `updateNode(...)` 保存，按 Escape 会取消。\n- 点击画布会同时清除勾选状态和当前编辑选中状态。\n- 深度预设选择器会重新执行 `openByLevel(...)`、自定义布局流程和 `zoomToFit()`，从而可以在不同层级深度下查看同一张画板。\n- 浮动控件可以显示或隐藏 `RGMiniView`，切换展开控制的可见性，并通过修改层级配色来改变画板外观。\n- 共享设置面板可以切换滚轮模式、切换画布拖拽模式，并将当前图下载为图片。\n- 当某个已选节点完成尺寸调整时，`onResizeEnd` 会触发该节点的子项重新布局。\n- 当某个已选节点拥有子节点时，覆盖层工具栏会直接提供三个操作：重新布局子项、让容器贴合后代内容，以及追加新的子项。\n\n## 关键代码片段\n\n这个递归辅助函数说明，内置源数据起点是一棵嵌套树，在加载 relation-graph 之前，它会先被转换为扁平的图节点和连线。\n\n```ts\nconst nodeJson = {\n    id: 'n-' + nodes_collect.length,\n    text: item.text,\n    x: item.x,\n    y: item.y,\n    data: { hasChildren, deep }\n};\nnodes_collect.push(nodeJson);\nif (parentNode) {\n    links_collect.push({ id: `${parentNode.id}-to-${nodeJson.id}`, from: parentNode.id, to: nodeJson.id });\n}\n```\n\n这个初始化流程会隐藏结构连接线、加载准备好的图，然后应用深度展开、自定义布局和视口自适应。\n\n```ts\nconst initializeGraph = async () => {\n    const myJsonData: RGJsonData = await parseJsonDataByAI();\n    myJsonData.lines.forEach(line => {\n        line.hidden = true;\n    });\n    graphInstance.loading();\n    await graphInstance.setJsonData(myJsonData);\n    updateGraphStyles();\n    await myGraphActions.current.openByLevel(8);\n    await myGraphActions.current.doMyLayout();\n    graphInstance.moveToCenter();\n    graphInstance.zoomToFit();\n};\n```\n\n这个自定义布局流程会重新打包每个非叶子分组，然后统一一级分区的宽度。\n\n```ts\nfor (const node of allNodes) {\n    if (node.rgChildrenSize > 0) {\n        const childrenNodes = node.lot.childs;\n        const nodesWithNewXy = adjustNodeLayout({\n            nodes: childrenNodes,\n            width: node.lot.level \u003C 2 ? 1600 : 200,\n            gap: 20\n        });\n        nodesWithNewXy.forEach(newXy => {\n            const cnode = childrenNodes.find(n => n.id === newXy.nodeId);\n            if (cnode) this.moveNodeTo(cnode, newXy.newX, newXy.newY);\n        });\n        this.updateNodeSizeByChildrenSize(node);\n    }\n}\n```\n\n这个重新贴合步骤展示了如何根据后代边界重新计算分组的包围框，并将结果向上传递到祖先容器。\n\n```ts\nconst childrenNodesSize = graphInstance.getNodesRectBox(childrenNodes);\nconst padding = 10;\nconst leftTitleWidth = node.lot.level \u003C= 1 ? 250 : 0;\nconst titleHeight = leftTitleWidth > 0 ? 0 : 30;\ngraphInstance.updateNode(node, {\n    x: childrenNodesSize.minX - padding - leftTitleWidth,\n    y: childrenNodesSize.minY - padding - titleHeight,\n    width: Math.max(groupMinWidth, childrenNodesSize.width + padding * 2) + leftTitleWidth,\n    height: childrenNodesSize.height + padding * 2 + titleHeight\n});\n```\n\n这个编辑处理器说明，内联重命名是实际实现的行为，而不只是视觉上的输入框演示。\n\n```tsx\nconst handleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === 'Enter') {\n        finishEditing();\n    } else if (e.key === 'Escape') {\n        setEditingText(node.text || '');\n        setIsEditing(false);\n    }\n};\nconst finishEditing = () => {\n    if (editingText !== node.text) {\n        onNodeTextChange(node, editingText);\n    }\n    setIsEditing(false);\n};\n```\n\n这个视图插槽就是示例挂载 minimap、吸附参考线、尺寸调整手柄和分组操作工具栏的位置。\n\n```tsx\n\u003CRGSlotOnView>\n    {showMiniView && \u003CRGMiniView />}\n    \u003CRGEditingReferenceLine adsorption={true} />\n    \u003CRGEditingNodeController>\n        \u003CMyNodeToolbar\n            onLayoutItemsButtonClick={onLayoutItemsButtonClick}\n            onFitContentButtonClick={onFitContentButtonClick}\n            onAddChildrenButtonClick={onAddChildrenButtonClick}\n        />\n        \u003CRGEditingResize />\n    \u003C/RGEditingNodeController>\n\u003C/RGSlotOnView>\n```\n\n## 这个示例的独特之处\n\n根据对比数据，最接近的偏查看器风格示例是 `system-architecture-diagram`。这两个示例都共享同样的隐藏连线架构画板基础、固定布局层级加载、基于深度的配色、左侧标题栏，以及布局后容器打包。不同之处在于，这个示例加入了编辑覆盖层：在同样的分组视觉语言之上，增加了内联重命名、尺寸调整手柄、吸附参考线、子树选择、贴合内容重算，以及新增子项操作。\n\n与 `undo-redo-example` 和 `freely-draw-lines-on-canvas` 这类偏编辑器的相邻示例相比，这个示例更窄、更聚焦具体场景。它不会扩展到历史管理、通用连线绘制或自由创建，而是保持一套预先准备好的分层结构，并聚焦于架构感知的修改动作，例如重新打包后代节点、重新贴合祖先容器边框，以及给选中分区新增一个子项。\n\n与 `industry-chain` 相比，可复用的经验也不同。两个示例都支持基于深度的查看方式和浮动工作台外壳，但 `industry-chain` 主要是一个只读层级查看器。这个示例则更进一步，把深度预设与分组容器上的直接操作结合起来，同时仍通过隐藏连接线来保持画面的整洁有序。\n\n因此，真正有辨识度的并不是某一个孤立功能，而是这一整组能力的组合：固定位置层级扁平化、隐藏连线的分组呈现、自定义布局后重排、递归分组重新贴合、内联文本编辑、子树多选、尺寸驱动的重新布局，以及同一画布上的轻量运行时控制。\n\n## 这种模式还适用于哪里\n\n这种模式很适合用于半可编辑的能力地图、平台架构画板、分层产品清单、企业应用版图，以及运营模型图这类场景。在这些场景中，层级结构通常预先已知，但团队仍需要有限的画布内维护能力。\n\n它也适用于 AI 流水线、服务域、模块归属图、合规控制栈，以及技术工作台界面的内部规划工具。在这些场景中，分组区块需要保持可读，而不适合始终显示大量连接线。最关键的可复用思路是，逻辑和编辑层面保留真实的图结构数据，而展示层则把结果呈现为嵌套的架构面板，而不是传统的重边连线图。\n",false,500,1782616038046]