"use client"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { Box, Typography, Chip, IconButton, Tooltip, Table, TableBody, TableCell, TableHead, TableRow, Button, } from "@mui/material"; import { FormatListBulleted, LocationOn as LocationOnIcon, Map as MapIcon, } from "@mui/icons-material"; import dayjs from "dayjs"; import { useMap } from "@components/olmap/core/MapComponent"; import { queryFeaturesByIds } from "@/utils/mapQueryService"; import { GeoJSON } from "ol/format"; import Feature from "ol/Feature"; import VectorLayer from "ol/layer/Vector"; import VectorSource from "ol/source/Vector"; import { Stroke, Style, Circle, Fill } from "ol/style"; import { bbox, featureCollection } from "@turf/turf"; import { BurstCandidate, BurstLocationResult } from "./types"; import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units"; interface Props { result: BurstLocationResult | null; } interface MetricCardProps { label: string; value: string; hint?: string; tone: "blue" | "orange" | "purple" | "green"; } const toneStyles: Record< MetricCardProps["tone"], { bg: string; border: string; text: string; darkText: string } > = { blue: { bg: "from-blue-50 to-blue-100", border: "border-blue-200", text: "text-blue-700", darkText: "text-blue-900", }, orange: { bg: "from-orange-50 to-orange-100", border: "border-orange-200", text: "text-orange-700", darkText: "text-orange-900", }, purple: { bg: "from-purple-50 to-purple-100", border: "border-purple-200", text: "text-purple-700", darkText: "text-purple-900", }, green: { bg: "from-green-50 to-green-100", border: "border-green-200", text: "text-green-700", darkText: "text-green-900", }, }; const formatDateTime = (value?: string) => value ? dayjs(value).format("MM-DD HH:mm") : "-"; const MetricCard = ({ label, value, hint, tone }: MetricCardProps) => { const style = toneStyles[tone]; return ( {label} {value} {hint ? ( {hint} ) : null} ); }; const EmptyState = () => ( 等待定位结果 请先提交爆管定位分析,结果面板将展示定位摘要、时间窗、采样情况和候选管段。 ); const LocationResults: React.FC = ({ result }) => { const map = useMap(); const highlightLayerRef = useRef | null>(null); const [highlightFeatures, setHighlightFeatures] = useState([]); const candidatePipes = useMemo(() => { if (!result) return []; const base = result.top_candidates ?? []; const hasLocated = base.some((item) => item.pipe_id === result.located_pipe); if (result.located_pipe && !hasLocated) { return [{ pipe_id: result.located_pipe, similarity: 1 }, ...base]; } return base; }, [result]); const allCandidatePipeIds = (() => { const ids = candidatePipes.map((item) => item.pipe_id); if (result?.located_pipe) { ids.unshift(result.located_pipe); } return Array.from(new Set(ids.filter(Boolean))); })(); useEffect(() => { if (!map) return; const layer = new VectorLayer({ source: new VectorSource(), style: new Style({ stroke: new Stroke({ color: "#ef4444", width: 6, }), image: new Circle({ radius: 8, fill: new Fill({ color: "#ef4444" }), stroke: new Stroke({ color: "#fff", width: 2 }), }), zIndex: 999, }), properties: { name: "爆管定位高亮", value: "burst_location_highlight", }, }); map.addLayer(layer); highlightLayerRef.current = layer; return () => { highlightLayerRef.current = null; map.removeLayer(layer); }; }, [map]); useEffect(() => { const source = highlightLayerRef.current?.getSource(); if (!source) return; source.clear(); highlightFeatures.forEach((feature) => source.addFeature(feature)); }, [highlightFeatures]); const locatePipes = async (pipeIds: string[]) => { if (!pipeIds.length || !map) return; try { let features = await queryFeaturesByIds(pipeIds, "geo_pipes_mat"); if (features.length === 0) { features = await queryFeaturesByIds(pipeIds, "geo_pipes"); } if (features.length === 0) return; setHighlightFeatures(features); const geojsonFormat = new GeoJSON(); const geojsonFeatures = features.map((feature) => geojsonFormat.writeFeatureObject(feature)); // @ts-ignore turf typing with ol geojson objects const extent = bbox(featureCollection(geojsonFeatures)); map.getView().fit(extent, { maxZoom: 19, duration: 1000, padding: [100, 100, 100, 100], }); } catch (error) { console.error("Locate failed", error); } }; if (!result) { return ; } const burstSamples = result.pressure_samples?.burst ?? 0; const normalSamples = result.pressure_samples?.normal ?? 0; const elapsedText = result.elapsed_seconds && result.elapsed_seconds > 0 ? `${result.elapsed_seconds.toFixed(1)} s` : "-"; const bestSimilarity = candidatePipes[0]?.similarity ?? 0; const burstTime = result.scada_window?.burst_start ? formatDateTime(result.scada_window.burst_start) : "-"; return ( {/* Header & Metrics */} {result.scheme_name || "爆管定位结果"} {result.username ? ( ) : null} {/* Candidate List */} 候选管段列表 locatePipes(allCandidatePipeIds)} disabled={allCandidatePipeIds.length === 0} className="text-blue-600 hover:bg-blue-50 disabled:text-gray-300" > 排名 管段 ID 相似度 操作 {candidatePipes.map((candidate, index) => { const similarityPercent = candidate.similarity * 100; const isTop = index === 0; return ( {index + 1} {candidate.pipe_id} {similarityPercent.toFixed(2)}% locatePipes([candidate.pipe_id])} className="text-blue-600 hover:bg-blue-50" title="定位" > ); })}
); }; export default LocationResults;