diff --git a/web/src/app/(main)/function/tasks/detail/_tabs/graph-tab.tsx b/web/src/app/(main)/function/tasks/detail/_tabs/graph-tab.tsx index 6a483d2..6240bc9 100644 --- a/web/src/app/(main)/function/tasks/detail/_tabs/graph-tab.tsx +++ b/web/src/app/(main)/function/tasks/detail/_tabs/graph-tab.tsx @@ -10,16 +10,28 @@ import type { Edge, TaskNode } from "@/lib/types"; export function GraphTab({ taskId }: { taskId: string }) { const [nodes, setNodes] = React.useState([]); const [edges, setEdges] = React.useState([]); + // 上一次图数据的签名:轮询拿到相同数据时跳过 setState,避免整图无谓重建(拖动时 + // 才不会被 20s 轮询打断而顿挫)。只取影响渲染的字段。 + const sigRef = React.useRef(""); React.useEffect(() => { let cancelled = false; + sigRef.current = ""; // 换任务:强制下一次刷新 const load = () => { api .explorationGraph(taskId) .then((g) => { if (cancelled) return; - setNodes(g.nodes ?? []); - setEdges(g.edges ?? []); + const ns = g.nodes ?? []; + const es = g.edges ?? []; + const sig = JSON.stringify([ + ns.map((n) => [n.id, n.type, n.state, n.priority, n.payload]), + es.map((e) => [e.src, e.dst, e.rel]), + ]); + if (sig === sigRef.current) return; // 无变化 → 不重建 + sigRef.current = sig; + setNodes(ns); + setEdges(es); }) .catch(() => { /* keep last good data */ diff --git a/web/src/components/exploration-graph.tsx b/web/src/components/exploration-graph.tsx index 49672fa..1fccfb5 100644 --- a/web/src/components/exploration-graph.tsx +++ b/web/src/components/exploration-graph.tsx @@ -444,6 +444,7 @@ function ExplorationGraphInner({ fitView fitViewOptions={{ padding: 0.2 }} proOptions={{ hideAttribution: true }} + onlyRenderVisibleElements minZoom={0.1} className="!bg-[#f0f2f7] dark:!bg-neutral-950" >