[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:node-hover-tooltip":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 构建仅在悬停时显示的节点详情提示卡\n\n## 这个示例构建了什么\n\n这个示例构建了一个只读的 relation graph，当鼠标悬停在自定义节点上时，会在指针附近打开一个小型详情卡片。可见场景被刻意压缩得较为紧凑：绿色的圆形图标节点、渲染在节点下方的短标签，以及一个始终停留在图谱视图内部的白色浮动 tooltip。\n\n用户可以通过将指针移到节点上来查看节点详情，并在离开该节点时关闭卡片。节点点击和连线点击也都存在，但它们只会把选中的对象输出到控制台，因此这里的核心价值是被动查看，而不是编辑或执行命令。\n\n这个示例最有用的思路，是把职责拆分到 node slot 和 view slot：node slot 负责悬停触发，view slot 负责覆盖层 UI。\n\n## 数据是如何组织的\n\n图数据是在 `initializeGraph()` 内部以内联方式创建的，一个 `RGJsonData` 对象直接定义了这些数据。它包含 `rootId: '2'`、一个 `nodes` 数组，以及一个带有明确连线 id 和标签的 `lines` 数组。每个节点还携带 `data.myicon`，自定义节点渲染器会用它来选择一个 Lucide 图标。\n\n在调用 `setJsonData()` 之前没有任何预处理步骤。组件直接在代码中构建最终 payload，并原样加载，然后将视口居中并适配显示。在业务应用中，这种结构同样可以表示产品、服务、设备、部门或依赖项，而 `data.myicon` 也可以替换为类型、状态或分类元数据，用来选择对应的自定义视觉样式。\n\n## relation-graph 是如何使用的\n\n`index.tsx` 通过 `RGProvider` 包裹这个示例，而 `MyGraph.tsx` 使用 `RGHooks.useGraphInstance()` 获取实时图实例。一个 `useEffect()` hook 会在实例可用后执行 `initializeGraph()`，通过 `setJsonData()` 加载内联数据，然后调用 `moveToCenter()` 和 `zoomToFit()`。\n\n图配置非常少，而且主要聚焦视觉层面。`defaultNodeColor` 提供了绿色填充，自定义 slot 会通过 `node.color` 读取它；`defaultNodeShape` 被设置为 `RGNodeShape.circle`；`defaultJunctionPoint` 被设置为 `RGJunctionPoint.border`。从当前审阅的源码来看，并没有显式设置布局选项，因此这个数据集中的节点排列方式依赖 relation-graph 的默认行为。\n\n主要定制发生在两个 slot 上。`RGSlotOnNode` 用圆形图标渲染器替换了内置节点主体，并直接在节点 DOM 上绑定 `onMouseEnter` 和 `onMouseLeave`。`RGSlotOnView` 则在图场景内部以覆盖层 HTML 的方式渲染提示卡。提示卡的位置不是通过图事件回调计算的，而是根据相对于一个包装元素的指针坐标来计算。\n\n图级别的点击事件被有意弱化。`onNodeClick` 和 `onLineClick` 都绑定在 `RelationGraph` 上，但两个处理器都只是在控制台打印对象。这让示例保持在 viewer 模式下，也使悬停覆盖层成为唯一真正有意义的图交互。\n\n本地样式较轻，且目标明确。SCSS 文件通过 `.c-my-rg-node` 定义了圆形节点的尺寸和居中方式，而提示卡中的各行则通过 `.c-node-menu-item` 控制间距和排版。覆盖层本身还设置了 `pointerEvents: 'none'`，这样就不会把卡片变成交互式菜单，而是保留 slotted node 上的悬停流程。\n\n## 关键交互\n\n- 悬停节点时，会在当前指针位置附近打开一个详情提示卡。\n- 将指针移出节点后，提示卡会立即关闭。\n- 提示卡保持为非交互状态，因为它的覆盖层容器禁用了 pointer events。\n- 点击节点会输出节点对象，但不会改变图状态。\n- 点击连线会输出连线对象，但不会打开任何额外 UI。\n\n## 关键代码片段\n\n这个 options 配置块说明，这个示例主要把 relation-graph 当作 viewer 使用，只定制了基础的节点形状、颜色和连线连接点模式。\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这个内联 payload 说明，图标选择由每个节点自身的元数据驱动，而图内容则直接在组件代码中组装完成。\n\n```tsx\nconst myJsonData: RGJsonData = {\n    rootId: '2',\n    nodes: [\n        { id: '1', text: 'Node-1', data: { myicon: 'star' } },\n        { id: '2', text: 'Node-2', data: { myicon: 'settings' } },\n        // ... more icon-tagged nodes ...\n        { id: '5', text: 'Node-5', data: { myicon: 'gift' } }\n    ],\n    lines: [\n        { id: 'l1', from: '7', to: '71', text: 'Investment' },\n        // ... more labeled lines ...\n    ]\n};\n```\n\n这段初始化流程证明，图实例是通过标准的基于 hook 的启动流程加载的。\n\n```tsx\nawait graphInstance.setJsonData(myJsonData);\ngraphInstance.moveToCenter();\ngraphInstance.zoomToFit();\n```\n\n这个悬停处理器是核心实现细节，它通过相对于包装元素的指针坐标来定位覆盖层。\n\n```tsx\nconst showNodeTips = (nodeObject: RGNode, $event: React.MouseEvent) => {\n    setCurrentNode(nodeObject);\n    if (myPage.current) {\n        const _base_position = myPage.current.getBoundingClientRect();\n        setIsShowNodeTipsPanel(true);\n        setNodeMenuPanelPosition({\n            x: $event.clientX - _base_position.x + 10,\n            y: $event.clientY - _base_position.y + 10\n        });\n    }\n};\n```\n\n这个 node slot 片段表明，悬停触发器是挂在自定义节点 DOM 上的，而不是挂在图范围的鼠标移动监听器上。\n\n```tsx\n\u003CRGSlotOnNode>\n    {({ node }: RGNodeSlotProps) => (\n        \u003Cdiv\n            className=\"h-full\"\n            onMouseEnter={(e) => showNodeTips(node, e)}\n            onMouseLeave={() => hideNodeTips()}\n        >\n            \u003Cdiv className=\"c-my-rg-node\" style={{ backgroundColor: node.color }}>\n                \u003CGetLucideIcon name={node.data?.myicon} />\n            \u003C/div>\n        \u003C/div>\n    )}\n\u003C/RGSlotOnNode>\n```\n\n这个 view slot 片段证明，提示卡是在图场景内部渲染的，并且有意避免拦截指针输入。\n\n```tsx\n\u003CRGSlotOnView>\n    {isShowNodeTipsPanel && currentNode && (\n        \u003Cdiv\n            className=\"p-3 bg-white border border-gray-300 shadow-xl absolute rounded-lg\"\n            style={{\n                left: `${nodeMenuPanelPosition.x}px`,\n                top: `${nodeMenuPanelPosition.y}px`,\n                zIndex: 1000,\n                pointerEvents: 'none'\n            }}\n        >\n```\n\n## 这个示例的独特之处\n\n对比数据表明，这个示例与 `node-menu-2`、`node-menu` 和 `node-line-tips-contentmenu` 比较接近，但它把共享的 slot + overlay 构建方式用于更收敛的目标。与 `node-menu-2` 和 `node-menu` 相比，关键区别在于这个示例是被动的、由悬停驱动的：它不会打开可操作菜单，不会在节点、连线和画布目标之间切换，也不需要为交互面板实现点击外部关闭逻辑。\n\n与 `node-line-tips-contentmenu` 相比，这里是一个更小巧的、只针对节点的方案。它没有加入连线命中检测、右键菜单、辅助窗口或混合覆盖层类型。对比记录还强调了这里一个相对少见的组合：绿色圆形图标节点、节点下方的徽标式标签、相对于包装元素的指针定位计算、悬停进入与离开处理器，以及在一个只读 viewer 中带有 `pointerEvents: 'none'` 的白色浮动卡片。\n\n因此，当需求是在图场景内部进行轻量级节点查看，同时又不想引入菜单状态、编辑流程或图变更时，这个示例会是一个更合适的起点。它不应该被描述为唯一的覆盖层示例，但它确实是非交互式节点悬停详情模式中最清晰的参考之一。\n\n## 这种模式还适用于哪里\n\n这种模式很适合迁移到依赖关系图、服务拓扑查看器、产品关系浏览器、设备网络和组织浏览界面中。在这些场景里，用户需要快速查看只读详情，而不必离开图本身。提示卡内容可以替换为健康摘要、归属字段、状态元数据、风险标签或紧凑指标，同时继续沿用相同的悬停触发和 view overlay 结构。\n\n它同样是一个适合继续扩展的检查型 UI 起点。团队可以先从这个被动卡片开始，后续再加入视口边缘限制、悬停延迟、固定详情面板或适用于触摸设备的替代方案，同时仍然保留“slotted node 触发器 + 场景内覆盖层渲染”这个基础思路。\n",false,500,1782615422248]