[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:edit-node-text":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 固定布局图中的节点文本内联编辑\n\n## 这个示例构建了什么\n\n这个示例构建了一个固定位置的关系图，节点标签可以在原位直接编辑。界面上可以看到带点状背景的画布、蓝色直线连线和一个浮动辅助窗口，而节点本身则展示了不同的边框、颜色、尺寸、形状以及固定位置设置。\n\n主要的用户操作是维护标签。启用编辑后，双击节点会将普通标签切换为黄色的内联输入框，修改后的文本会直接写回图中，而无需重新加载整份数据集。\n\n关键设计选择在于通过自定义节点插槽替换节点主体。这样一来，示例就可以在组件层级自行控制焦点处理、键盘行为、提交与取消规则，以及全局编辑开关。\n\n## 数据如何组织\n\n图数据在 `initializeGraph()` 中以内联方式声明为一个 `RGJsonData` 对象，其中包含 `rootId`、扁平的 `nodes` 数组以及 `lines` 数组。每条节点记录都带有固定的 `x` 和 `y` 坐标，且部分节点还包含可视化覆盖项，例如 `borderWidth`、`borderColor`、`fontColor`、`color`、`width`、`height`、`fixed`、`className`，或节点级别的 `nodeShape`。\n\n在调用 `setJsonData()` 之前有两个很小的预处理步骤。代码会用 `line-${index}` 补齐缺失的连线 id，并将数值型的 `nodeShape` 转换为当前 API 所需的 `RGNodeShape` 枚举。完成这些规范化之后，整份数据只会加载一次，后续所有标签变更都通过定向的节点更新完成。\n\n在真实产品中，同样的数据结构可以表示流程节点、设备标签、组织单元、平面图标注，或需要快速维护文本而不必打开独立表单的知识图谱实体。\n\n## relation-graph 的使用方式\n\n入口组件用 `RGProvider` 包裹整个 demo，`MyGraph` 使用 `RGHooks.useGraphInstance()` 来初始化和变更图。主要选项设置了 `fixed` 布局、默认矩形节点、直线连线、边框连接点、蓝色连线颜色，以及线宽 `2`。由于布局是固定的，内联节点坐标决定了初始位置，而不是依赖运行时的树布局或力导布局。\n\n自定义渲染路径以 `RGSlotOnNode` 为中心。每个渲染出来的节点都会被替换为 `MyEditableNode`，它接收运行时的 `node`、全局 `isEditingEnabled` 标志，以及一个通过 `graphInstance.updateNode(...)` 持久化文本变更的回调。当前源码没有使用连线插槽或活动视图插槽；这里值得关注的扩展点是节点插槽。\n\n图实例 API 分两层使用。`MyGraph` 在挂载期间调用 `setJsonData()`、`moveToCenter()` 和 `zoomToFit()`，然后在确认标签编辑时调用 `updateNode()`。`DraggableWindow` 内部共享的 `CanvasSettingsPanel` 使用 `RGHooks.useGraphStore()` 读取当前滚轮和拖拽行为，使用 `setOptions()` 在运行时切换这些行为，并通过 `prepareForImageGeneration()`、`getOptions()` 和 `restoreAfterImageGeneration()` 执行导出流程。\n\n样式分布在图选项和 CSS 两部分。SCSS 文件通过画布偏移与缩放变量以及径向渐变的点状背景来自定义 `.relation-graph`，而可编辑节点组件则提供了黄色输入框，以及在可编辑时显示的 `cursor-text` 光标提示。\n\n## 关键交互\n\n双击是主要入口。只有当全局复选框启用了该模式时，节点才会进入内联编辑，并且处理函数会阻止事件冒泡，这样图就不会把同一次手势解释为普通的节点点击。\n\n开始编辑后，输入框会自动获得焦点并选中文本。按 Enter 或使输入框失焦会提交变更，按 Escape 则会恢复之前的节点文本并退出而不保存。\n\n编辑开关是全局的，而不是按节点单独控制。浮动窗口中的复选框会更新 `MyGraph` 中的 `isEditingEnabled`，这个布尔值随后会传递给每一个通过插槽渲染的节点。\n\n浮动窗口还增加了次级控制。它可以被拖动、最小化、展开为设置面板，可用来在滚动、缩放和无之间切换滚轮行为，可用来在选择、移动和无之间切换画布拖拽行为，也可以将当前图导出为图片。\n\n## 关键代码片段\n\n这段代码展示了固定布局图的配置，以及自定义节点渲染器所依赖的默认可视规则。\n\n```tsx\nconst graphOptions: RGOptions = {\n    defaultLineColor: '#43a2f1',\n    defaultLineWidth: 2,\n    layout: {\n        layoutName: 'fixed'\n    },\n    defaultNodeShape: RGNodeShape.rect,\n    defaultLineShape: RGLineShape.StandardStraight,\n    defaultJunctionPoint: RGJunctionPoint.border\n};\n```\n\n这段代码说明示例会在加载数据前先对输入数据做规范化处理，然后再将固定布局图居中显示。\n\n```tsx\nmyJsonData.lines.forEach((line, index) => {\n    if (!line.id) {\n        line.id = `line-${index}`;\n    }\n});\n\nmyJsonData.nodes.forEach(node => {\n    if (node.nodeShape === 0) node.nodeShape = RGNodeShape.circle;\n    if (node.nodeShape === 1) node.nodeShape = RGNodeShape.rect;\n});\n\nawait graphInstance.setJsonData(myJsonData);\ngraphInstance.moveToCenter();\ngraphInstance.zoomToFit();\n```\n\n这段代码证明，relation-graph 的节点插槽正是将默认节点主体替换为可编辑渲染器的机制。\n\n```tsx\n\u003CRelationGraph options={graphOptions}>\n    \u003CRGSlotOnNode>\n        {({ node, checked, dragging }: RGNodeSlotProps) => (\n            \u003CMyEditableNode\n                node={node}\n                enableEditingMode={isEditingEnabled}\n                onNodeTextChange={onNodeTextChange}\n            />\n        )}\n    \u003C/RGSlotOnNode>\n\u003C/RelationGraph>\n```\n\n这段代码展示了自定义节点如何进入编辑模式，并立即为文本替换准备输入框。\n\n```tsx\nuseEffect(() => {\n    if (isEditing && inputRef.current) {\n        inputRef.current.focus();\n        inputRef.current.select();\n    }\n}, [isEditing]);\n\nconst handleDoubleClick = (e: React.MouseEvent) => {\n    if (enableEditingMode) {\n        e.stopPropagation();\n        setIsEditing(true);\n    }\n};\n```\n\n这段代码展示了提交与取消流程，包括最终通过 `graphInstance.updateNode(...)` 持久化文本的回调。\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};\n\nconst finishEditing = () => {\n    if (editingText !== node.text) {\n        onNodeTextChange(node, editingText);\n    }\n    setIsEditing(false);\n};\n```\n\n这段代码展示了共享设置面板如何通过当前图实例实时修改图行为。\n\n```tsx\n\u003CSettingRow\n    label=\"Wheel Event:\"\n    options={[\n        { label: 'Scroll', value: 'scroll' },\n        { label: 'Zoom', value: 'zoom' },\n        { label: 'None', value: 'none' },\n    ]}\n    value={wheelMode}\n    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}\n/>\n```\n\n## 这个示例的独特之处\n\n根据已准备的文档上下文，这个示例的独特之处并不在于共享的浮动窗口或 provider 与 hook 的组合方式，因为附近的 `deep-each`、`investment` 和 `selections` 等示例也复用了这套脚手架。更少见的组合是自定义 `MyEditableNode` 渲染器，加上受模式控制的双击编辑、自动聚焦与文本全选、Enter 或失焦提交、Escape 取消，以及在固定布局图上通过 `updateNode()` 持久化修改。\n\n稀有度记录还表明，黄色内联编辑器状态、带点状背景的编辑画布，以及固定位置的样式示例节点，在这组示例中都较为少见。这使得该 demo 相比那些强调选择、遍历、尺寸调整或更广泛编辑器行为的示例，更适合作为节点标签维护的直接起点。\n\n它也比完整的图编辑器更聚焦。代码不会添加节点、重新连接连线，也不会管理多步骤编辑状态。它的价值在于隔离出一个具体能力：用内联编辑器替换默认节点内容，并让持久化路径保持简洁。\n\n## 这种模式还适用于哪里\n\n这种模式很适合用于结构已经明确，但标签需要在上下文中快速修正的图表。示例包括工作流命名工具、流程图、组织架构图、设备图、拓扑标签，以及知识图谱整理界面。\n\n生产版本可以保留相同的基于插槽的编辑策略，同时将内联示例数据替换为服务器数据，在调用 `updateNode()` 之前加入校验规则、保存审计历史，或为节点文本之外的字段打开更丰富的侧边面板。核心思想仍然不变：让图始终保持已加载状态，只编辑那个发生变化的标签。\n",false,500,1782615405363]