"use client"; import { useEffect, useMemo, useState } from "react"; import { AlertTriangle, ChevronDown, ChevronRight, Columns3, UnfoldVertical } from "lucide-react"; import type { InspectorModule, InspectorWorkbenchProjection, RetrievalActivity, SourceBinding } from "@/lib/types"; import { ComparisonRowCells } from "@/components/inspector/ComparisonRowCells"; interface Props { data?: InspectorWorkbenchProjection; loading?: boolean; error?: string; onNavigate?: (activity: RetrievalActivity) => void; onActiveBindingChange?: (bindingId?: string) => void; clearActiveSignal?: number; } export function InspectorComparison({ data, loading, error, onActiveBindingChange, clearActiveSignal }: Props) { const [openModules, setOpenModules] = useState>(new Set()); const [activeBindingId, setActiveBindingId] = useState(); const markers = useMemo(() => bindingMarkers(data), [data]); const moduleSignature = (data?.modules || []).map((module) => `${module.id}:${module.defaultOpen}`).join("|"); useEffect(() => { setOpenModules(new Set((data?.modules || []).filter((module) => module.defaultOpen).map((module) => module.id))); setActiveBindingId(undefined); onActiveBindingChange?.(undefined); }, [data?.detailRef, moduleSignature, onActiveBindingChange]); useEffect(() => { if (clearActiveSignal === undefined) return; setActiveBindingId(undefined); }, [clearActiveSignal]); if (loading) return ; if (error) return ; if (!data) return ; const allOpen = data.modules.length > 0 && data.modules.every((module) => openModules.has(module.id)); const activate = (bindingId: string, toggle = true) => { const next = toggle && activeBindingId === bindingId ? undefined : bindingId; setActiveBindingId(next); onActiveBindingChange?.(next); if (!next) return; requestAnimationFrame(() => document.querySelector(`.inspectorComparison [data-binding-id="${cssEscape(next)}"]`)?.scrollIntoView({ block: "nearest", behavior: "smooth" })); }; const clearActive = () => { setActiveBindingId(undefined); onActiveBindingChange?.(undefined); }; return
{ if (event.key === "Escape" && activeBindingId) { event.preventDefault(); event.stopPropagation(); clearActive(); } }}>
{activeBindingId ? : null}
{data.notices.length ?
{data.notices.map((notice, index) =>

)}
: null}
业务详情中的内容这一项的数据依据对应的原始字段或原文
{data.modules.map((module, index) => { const open = openModules.has(module.id); return
{open ?
{(module.rows || []).map((row) => )}
: null}
; })}
; } export function allBindings(module: InspectorModule): SourceBinding[] { const seen = new Set(); return (module.bindings || []).filter((binding) => { if (seen.has(binding.id)) return false; seen.add(binding.id); return true; }); } function bindingMarkers(data?: InspectorWorkbenchProjection) { const result = new Map(); let index = 1; for (const module of data?.modules || []) for (const binding of allBindings(module)) if (!result.has(binding.id)) result.set(binding.id, `S${index++}`); return result; } function completenessLabel(value: InspectorWorkbenchProjection["completeness"]) { return value === "complete" ? "来源完整" : value === "partial" ? "部分来源缺失" : "来源记录缺失"; } function cssEscape(value: string) { return typeof CSS !== "undefined" && CSS.escape ? CSS.escape(value) : value.replace(/[^a-zA-Z0-9_-]/g, "\\$&"); } function ComparisonState({ title, message }: { title: string; message: string }) { return

{title}

{message}

; } function ComparisonSkeleton() { return
; }