[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:node-context-menu-via-slots":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 使用 relation-graph Slots 实现节点上下文菜单\n\n## 这个示例构建了什么\n这个示例构建了一个只读 relation graph，其节点不是渲染为默认的节点主体，而是渲染为绿色的圆形图标徽标。在图场景内部点击或右键单击节点时，会在指针位置打开一个浮动操作菜单；选择其中一个固定操作后，会先显示成功消息，然后关闭菜单。\n\n这个示例的核心要点是两个 slot 层之间的职责拆分：`RGSlotOnNode` 提供可交互的触发表面，`RGSlotOnView` 负责在图视口内渲染覆盖菜单。\n\n## 数据如何组织\n图数据在 `initializeGraph` 内以内联方式声明为一个 `RGJsonData` 对象。它使用 `rootId: '2'`，包含一个带有 `id`、`text` 和 `data.myicon` 的 `nodes` 数组，以及一个每条记录都显式包含 `id`、`from`、`to` 和 `text` 字段的 `lines` 数组。\n\n在调用 `setJsonData` 之前，这里没有任何预处理步骤，只有在代码中组装这个对象。在生产环境图谱中，`data.myicon` 可以替换为角色、状态、类型或分类元数据，同时驱动节点外观和可用的节点操作。\n\n## relation-graph 是如何使用的\n`RGProvider` 包裹整个 demo，以便 `RGHooks.useGraphInstance()` 能读取当前激活的图实例。`useEffect` 会等待该实例可用，然后调用 `setJsonData()`、`moveToCenter()` 和 `zoomToFit()` 来加载示例网络并让其适配视图。\n\n本地的 `graphOptions` 对象只调整默认展示效果：节点颜色为绿色，节点形状为圆形，连接点在边界上连接。审阅到的文件没有设置显式布局选项，因此最终排布依赖于此示例之外的 relation-graph 默认行为。\n\n`RGSlotOnNode` 用自定义 DOM 替换默认节点主体。该 slot 会读取 `node.data.myicon`，渲染一个 Lucide 图标，在节点下方添加一个标题块，并将 `onClick` 和 `onContextMenu` 都绑定到同一个打开菜单的处理函数上。\n\n`RGSlotOnView` 在图视图层内部渲染菜单面板。React state 会跟踪当前激活的节点、面板是否可见，以及相对于包裹元素的菜单坐标。这里注册的 `onNodeClick` 和 `onLineClick` 处理器只是次要部分；在审阅到的源码中，它们只会向控制台输出日志。该示例没有实现图编辑或数据变更。\n\n样式分散在 SCSS 和内联样式之间。SCSS 定义圆形节点外壳和白色浮动菜单卡片，内联样式则负责把标签块放在每个节点下方。\n\n## 关键交互\n左键单击自定义节点，会打开与右键单击相同的菜单。\n\n右键单击不会显示浏览器原生上下文菜单，因为节点处理函数调用了 `preventDefault()`。\n\n菜单位置基于相对于包裹元素的指针坐标计算，因此它会在交互发生的位置出现，并且不会离开图场景。\n\n点击面板外部会关闭它，而点击面板内部不会关闭，因为菜单阻止了事件冒泡，并由包裹元素处理点击空白处关闭的逻辑。\n\n选择某个操作不会改变图数据。它会通过 `SimpleGlobalMessage` 发送一条成功提示，然后隐藏菜单。\n\n## 关键代码片段\n这个 options 对象表明，该示例改变了节点外观和连接点行为，但没有引入自定义布局配置。\n\n```tsx\nconst graphOptions: RGOptions = {\n  defaultNodeColor: 'rgba(66,187,66,1)',\n  defaultNodeShape: RGNodeShape.circle,\n  defaultJunctionPoint: RGJunctionPoint.border\n};\n```\n\n这个处理函数证明，菜单位置是根据相对于包裹元素的指针位置计算的，而不是根据图模型坐标计算的。\n\n```tsx\nconst showNodeMenus = (node: RGNode, $event: React.MouseEvent\u003CHTMLDivElement>) => {\n  setCurrentNode(node);\n  if (myPage.current) {\n    const _base_position = myPage.current.getBoundingClientRect();\n    setIsShowNodeMenuPanel(true);\n    setNodeMenuPanelPosition({\n      x: $event.clientX - _base_position.x,\n      y: $event.clientY - _base_position.y\n    });\n  }\n  $event.stopPropagation();\n  $event.preventDefault();\n};\n```\n\n这个节点 slot 才是真正的触发表面：点击和上下文菜单事件都绑定在自定义节点 DOM 上，而不是依赖图级别的上下文菜单钩子。\n\n```tsx\n\u003CRGSlotOnNode>\n  {({ node }: RGNodeSlotProps) => (\n    \u003Cdiv\n      className=\"c-my-rg-node\"\n      onClick={(event) => showNodeMenus(node, event)}\n      onContextMenu={(event) => showNodeMenus(node, event)}\n    >\n      \u003CNodeIcon name={node.data?.myicon} />\n      \u003Cdiv>\n        {node.data?.myicon}\n      \u003C/div>\n    \u003C/div>\n  )}\n\u003C/RGSlotOnNode>\n```\n\n这个视图 slot 表明，菜单是在图层内部渲染的，并且只会在本地可见性状态为 true 时存在。\n\n```tsx\n\u003CRGSlotOnView>\n  {isShowNodeMenuPanel && (\n    \u003Cdiv\n      className=\"pointer-events-auto context-menu-panel\"\n      style={{ left: nodeMenuPanelPosition.x, top: nodeMenuPanelPosition.y }}\n      onClick={(e) => e.stopPropagation()}\n    >\n      \u003Cdiv className=\"py-1 px-2 text-gray-400 text-xs border-b\">\n        Node Actions:\n      \u003C/div>\n      {/* menu items */}\n    \u003C/div>\n  )}\n\u003C/RGSlotOnView>\n```\n\n这个操作处理函数确认，菜单选择只会发出共享的成功反馈，然后关闭面板。\n\n```tsx\nconst doAction = (actionName: string) => {\n  SimpleGlobalMessage.showMessage({\n    message: `Performed action ${actionName} on node: ${currentNode?.text}`,\n    type: 'success'\n  });\n  setIsShowNodeMenuPanel(false);\n};\n```\n\n## 这个示例的独特之处\n对比数据将这个示例归在 `node-menu`、`node-tips`、`simple`、`node-content-lines` 和 `node` 附近，但它的关注点更窄。它最突出的组合是 `RGSlotOnNode` 加 `RGSlotOnView`、基于指针位置的菜单状态、对浏览器上下文菜单的抑制、点击外部关闭，以及在一个原本只读的查看器中通过 toast 确认操作。\n\n与 `node-menu` 相比，这不是一个面向节点、连线和画布的全局上下文菜单系统。只有自定义节点 DOM 会打开菜单，并且同一个触发表面同时支持左键和右键。与 `node-tips` 相比，这个浮动面板是可操作的而不是信息性的，因为用户可以点击操作并收到反馈，而不是只看到悬浮信息。与 `simple` 和 `node` 相比，这里的视图 slot 用于临时性的上下文 UI，而不是持久性的导航或工具组件。\n\n因此，当需求是在一个基本固定的图上为每个节点提供操作，而不是构建更宽泛的编辑器或仪表盘时，这个示例会是更强的起点。\n\n## 这种模式还适用于哪里\n这种模式可以迁移到组织架构图中，在那里一个节点需要打开诸如查看资料、分配负责人或跳转到详情页之类的操作。\n\n它同样适用于服务地图、资产拓扑查看器和知识图谱浏览器，这些场景中的图保持只读，但每个节点都需要轻量级的操作命令。\n\n同样的结构还可以扩展到工作流监控、案件管理或依赖分析工具中，只需把硬编码的操作列表替换为基于权限的命令，并把 `data.myicon` 替换为领域元数据。\n",false,500,1782615423593]