"use client"; import React, { useState, useEffect, useCallback, useRef } from "react"; import { Box, Typography, Chip, CircularProgress, IconButton, Tooltip, } from "@mui/material"; import { LocationOn as LocationIcon } from "@mui/icons-material"; import axios from "axios"; import { config, NETWORK_NAME } from "@config/config"; import { ValveIsolationResult } from "./types"; import { useNotification } from "@refinedev/core"; import { queryFeaturesByIds } from "@/utils/mapQueryService"; import { useMap } from "@app/OlMap/MapComponent"; import { GeoJSON } from "ol/format"; import VectorLayer from "ol/layer/Vector"; import VectorSource from "ol/source/Vector"; import { Circle as CircleStyle, Fill, Stroke, Style, Icon } from "ol/style"; import Feature, { FeatureLike } from "ol/Feature"; import { bbox, featureCollection, along, lineString, length, toMercator, } from "@turf/turf"; import { Point } from "ol/geom"; import { toLonLat } from "ol/proj"; interface ValveIsolationProps { initialPipeIds?: string[]; shouldFetch?: boolean; onFetchComplete?: () => void; loading?: boolean; result?: ValveIsolationResult | null; onLoadingChange?: (loading: boolean) => void; onResultChange?: (result: ValveIsolationResult | null) => void; } const ValveIsolation: React.FC = ({ initialPipeIds, shouldFetch = false, onFetchComplete, loading: externalLoading, result: externalResult, onLoadingChange, onResultChange, }) => { const [internalLoading, setInternalLoading] = useState(false); const [internalResult, setInternalResult] = useState(null); // 使用外部状态或内部状态 const loading = externalLoading !== undefined ? externalLoading : internalLoading; const result = externalResult !== undefined ? externalResult : internalResult; const setLoading = onLoadingChange || setInternalLoading; const setResult = onResultChange || setInternalResult; const [highlightLayer, setHighlightLayer] = useState | null>(null); const [highlightFeatures, setHighlightFeatures] = useState([]); const [highlightType, setHighlightType] = useState< "must_close" | "optional" | "affected_node" | "pipe" >("affected_node"); const { open } = useNotification(); const lastPipeIdsRef = useRef(""); const map = useMap(); const handleLocatePipes = (pipeIds: string[]) => { if (pipeIds.length > 0) { queryFeaturesByIds(pipeIds, "geo_pipes_mat").then((features) => { if (features.length > 0) { // 设置高亮类型为管段 setHighlightType("pipe"); // 设置高亮要素 setHighlightFeatures(features); // 将 OpenLayers Feature 转换为 GeoJSON Feature const geojsonFormat = new GeoJSON(); const geojsonFeatures = features.map((feature) => geojsonFormat.writeFeatureObject(feature), ); const extent = bbox(featureCollection(geojsonFeatures as any)); if (extent) { map?.getView().fit(extent, { maxZoom: 18, duration: 1000 }); } } }); } }; const handleLocateNodes = (nodeIds: string[]) => { if (nodeIds.length > 0) { queryFeaturesByIds(nodeIds, "geo_junctions").then((features) => { if (features.length > 0) { // 设置高亮类型为受影响节点 setHighlightType("affected_node"); // 设置高亮要素 setHighlightFeatures(features); // 将 OpenLayers Feature 转换为 GeoJSON Feature const geojsonFormat = new GeoJSON(); const geojsonFeatures = features.map((feature) => geojsonFormat.writeFeatureObject(feature), ); const extent = bbox(featureCollection(geojsonFeatures as any)); if (extent) { map?.getView().fit(extent, { maxZoom: 18, duration: 1000 }); } } }); } }; const handleLocateMustCloseValves = (valveIds: string[]) => { if (valveIds.length > 0) { queryFeaturesByIds(valveIds, "geo_valves").then((features) => { if (features.length > 0) { // 设置高亮类型为必关阀门 setHighlightType("must_close"); // 设置高亮要素 setHighlightFeatures(features); // 将 OpenLayers Feature 转换为 GeoJSON Feature const geojsonFormat = new GeoJSON(); const geojsonFeatures = features.map((feature) => geojsonFormat.writeFeatureObject(feature), ); const extent = bbox(featureCollection(geojsonFeatures as any)); if (extent) { map?.getView().fit(extent, { maxZoom: 18, duration: 1000 }); } } }); } }; const handleLocateOptionalValves = (valveIds: string[]) => { if (valveIds.length > 0) { queryFeaturesByIds(valveIds, "geo_valves").then((features) => { if (features.length > 0) { // 设置高亮类型为可选阀门 setHighlightType("optional"); // 设置高亮要素 setHighlightFeatures(features); // 将 OpenLayers Feature 转换为 GeoJSON Feature const geojsonFormat = new GeoJSON(); const geojsonFeatures = features.map((feature) => geojsonFormat.writeFeatureObject(feature), ); const extent = bbox(featureCollection(geojsonFeatures as any)); if (extent) { map?.getView().fit(extent, { maxZoom: 18, duration: 1000 }); } } }); } }; const fetchAnalysis = useCallback( async (ids: string[]) => { if (!ids || ids.length === 0) { open?.({ type: "error", message: "请提供管段ID" }); return; } setLoading(true); setResult(null); try { const response = await axios.get( `${config.BACKEND_URL}/api/v1/valve_isolation_analysis/`, { params: { network: NETWORK_NAME, accident_element: ids, }, paramsSerializer: { indexes: null, // 生成格式: accident_element=P1&accident_element=P2 }, }, ); setResult(response.data); open?.({ type: "success", message: "分析成功" }); } catch (error) { console.error(error); open?.({ type: "error", message: "分析失败", description: "无法获取关阀分析结果", }); } finally { setLoading(false); onFetchComplete?.(); } }, [open, onFetchComplete], ); useEffect(() => { // 只有在明确要求获取数据时才调用 API if (shouldFetch && initialPipeIds && initialPipeIds.length > 0) { // 使用排序后的字符串作为唯一标识,避免数组引用变化导致重复调用 const pipeIdsKey = [...initialPipeIds].sort().join(","); // 只有当 pipeIds 真正改变时才调用 API if (pipeIdsKey !== lastPipeIdsRef.current) { lastPipeIdsRef.current = pipeIdsKey; fetchAnalysis(initialPipeIds); } else { // 如果 pipeIds 相同,直接调用完成回调 onFetchComplete?.(); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [shouldFetch, initialPipeIds]); // 初始化高亮图层 useEffect(() => { if (!map) return; // 动态样式函数,根据 highlightType 返回不同的样式 const getHighlightStyle = (feature: FeatureLike) => { if (highlightType === "pipe") { // 管段 - 多层红色线条样式 + 中点图标 const styles = []; // 线条样式(底层发光,主线条,内层高亮线) styles.push( new Style({ stroke: new Stroke({ color: "rgba(255, 0, 0, 0.3)", width: 12, }), }), new Style({ stroke: new Stroke({ color: "rgba(255, 0, 0, 1)", width: 6, lineDash: [15, 10], }), }), new Style({ stroke: new Stroke({ color: "rgba(255, 102, 102, 1)", width: 3, lineDash: [15, 10], }), }), ); const geometry = feature.getGeometry(); const lineCoords = geometry?.getType() === "LineString" ? (geometry as any).getCoordinates() : null; if (geometry && lineCoords) { const lineCoordsWGS84 = lineCoords.map((coord: []) => { const [lon, lat] = toLonLat(coord); return [lon, lat]; }); // 计算中点 const lineStringFeature = lineString(lineCoordsWGS84); const lineLength = length(lineStringFeature); const midPoint = along(lineStringFeature, lineLength / 2).geometry .coordinates; // 在中点添加 icon 样式 const midPointMercator = toMercator(midPoint); styles.push( new Style({ geometry: new Point(midPointMercator), image: new Icon({ src: "/icons/burst_pipe.svg", scale: 0.2, anchor: [0.5, 1], }), }), ); } return styles; } // 阀门和节点的样式 let color: string; let strokeColor: string; let radius: number; switch (highlightType) { case "must_close": // 必关阀门 - 深红色 color = "rgba(211, 47, 47, 0.6)"; strokeColor = "rgba(211, 47, 47, 1)"; radius = 10; break; case "optional": // 可选阀门 - 橙色 color = "rgba(237, 108, 2, 0.6)"; strokeColor = "rgba(237, 108, 2, 1)"; radius = 10; break; case "affected_node": default: // 受影响节点 - 蓝色 color = "rgba(25, 118, 210, 0.6)"; strokeColor = "rgba(25, 118, 210, 1)"; radius = 8; break; } return new Style({ image: new CircleStyle({ radius: radius, fill: new Fill({ color: color, }), stroke: new Stroke({ color: strokeColor, width: 3, }), }), }); }; // 创建高亮图层 const highlightLayer = new VectorLayer({ source: new VectorSource(), style: getHighlightStyle, maxZoom: 24, minZoom: 12, properties: { name: "阀门节点高亮", value: "valve_node_highlight", }, }); map.addLayer(highlightLayer); setHighlightLayer(highlightLayer); return () => { map.removeLayer(highlightLayer); }; }, [map, highlightType]); // 高亮要素的函数 useEffect(() => { if (!highlightLayer) { return; } const source = highlightLayer.getSource(); if (!source) { return; } // 清除之前的高亮 source.clear(); // 添加新的高亮要素 highlightFeatures.forEach((feature) => { if (feature instanceof Feature) { source.addFeature(feature); } }); }, [highlightFeatures, highlightLayer]); return ( {/* Results Section */} {loading ? ( 正在分析... ) : result ? ( {/* 头部:状态信息 */} 关阀分析结果 爆管管段 {result.accident_elements && result.accident_elements.length > 0 && ( handleLocatePipes(result.accident_elements!) } sx={{ backgroundColor: "rgba(255, 0, 0, 0.1)", "&:hover": { backgroundColor: "rgba(255, 0, 0, 0.2)", }, }} > )} {result.accident_elements?.map( (pipeId: string, idx: number) => ( handleLocatePipes([pipeId])} sx={{ backgroundColor: "rgba(255, 255, 255, 0.9)", border: "1.5px solid rgb(248, 113, 113)", color: "rgb(185, 28, 28)", fontWeight: 600, fontSize: "0.8rem", cursor: "pointer", transition: "all 0.2s", "&:hover": { backgroundColor: "rgb(254, 226, 226)", borderColor: "rgb(220, 38, 38)", transform: "translateY(-1px)", boxShadow: "0 2px 4px rgba(220, 38, 38, 0.2)", }, }} /> ), )} {/* 主要信息:三栏卡片布局 */} {/* 必关阀门卡片 */} 必关阀门 {result.must_close_valves?.length || 0} 个 {/* 可选阀门卡片 */} 可选阀门 {result.optional_valves?.length || 0} 个 {/* 受影响节点卡片 */} 受影响节点 {result.affected_nodes?.length || 0} 个 {/* 必须关闭阀门详细列表 */} {result.must_close_valves && result.must_close_valves.length > 0 && ( 必须关闭阀门 handleLocateMustCloseValves(result.must_close_valves!) } color="error" sx={{ backgroundColor: "rgba(211, 47, 47, 0.1)", "&:hover": { backgroundColor: "rgba(211, 47, 47, 0.2)", }, }} > {result.must_close_valves.map((valveId, idx) => ( handleLocateMustCloseValves([valveId])} sx={{ "&:active": { transform: "scale(0.98)", boxShadow: "0 1px 2px rgba(211, 47, 47, 0.2)", }, }} > {valveId} ))} )} {/* 可选关闭阀门详细列表 */} {result.optional_valves && result.optional_valves.length > 0 && ( 可选关闭阀门 handleLocateOptionalValves(result.optional_valves!) } color="warning" sx={{ backgroundColor: "rgba(237, 108, 2, 0.1)", "&:hover": { backgroundColor: "rgba(237, 108, 2, 0.2)", }, }} > {result.optional_valves.map((valveId, idx) => ( handleLocateOptionalValves([valveId])} sx={{ "&:active": { transform: "scale(0.98)", boxShadow: "0 1px 2px rgba(237, 108, 2, 0.2)", }, }} > {valveId} ))} )} {/* 受影响节点详细列表 */} {result.affected_nodes && result.affected_nodes.length > 0 && ( 受影响节点 handleLocateNodes(result.affected_nodes!)} color="primary" sx={{ backgroundColor: "rgba(37, 125, 212, 0.1)", "&:hover": { backgroundColor: "rgba(37, 125, 212, 0.2)", }, }} > {result.affected_nodes.map((nodeId, idx) => ( handleLocateNodes([nodeId])} sx={{ "&:active": { transform: "scale(0.98)", boxShadow: "0 1px 2px rgba(25, 118, 210, 0.2)", }, }} > {nodeId} ))} )} ) : ( 暂无关阀分析结果 请先查看定位结果 )} ); }; export default ValveIsolation;