"use client"; import React, { useEffect, useState } from "react"; import ReactDOM from "react-dom"; import { Box, Button, Typography, Checkbox, FormControlLabel, IconButton, Card, CardContent, Chip, Tooltip, Collapse, Link, } from "@mui/material"; import { Info as InfoIcon, LocationOn as LocationIcon, } from "@mui/icons-material"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import "dayjs/locale/zh-cn"; import dayjs, { Dayjs } from "dayjs"; import { api } from "@/lib/api"; import moment from "moment"; import { config, NETWORK_NAME } from "@config/config"; import { useNotification } from "@refinedev/core"; import { useData, useMap } from "@components/olmap/core/MapComponent"; import { queryFeaturesByIds } from "@/utils/mapQueryService"; import { GeoJSON } from "ol/format"; import VectorLayer from "ol/layer/Vector"; import VectorSource from "ol/source/Vector"; import { Style, Icon, Circle, Fill, Stroke } from "ol/style"; import Feature, { FeatureLike } from "ol/Feature"; import { bbox, featureCollection } from "@turf/turf"; import Timeline from "@components/olmap/core/Controls/Timeline"; import { SchemeRecord, SchemaItem } from "./types"; import { FLOW_DISPLAY_UNIT } from "@utils/units"; interface SchemeQueryProps { schemes?: SchemeRecord[]; onSchemesChange?: (schemes: SchemeRecord[]) => void; network?: string; } const SCHEME_TYPE = "flushing_analysis"; const SchemeQuery: React.FC = ({ schemes: externalSchemes, onSchemesChange, network = NETWORK_NAME, }) => { const [queryAll, setQueryAll] = useState(true); const [queryDate, setQueryDate] = useState(dayjs(new Date())); const [internalSchemes, setInternalSchemes] = useState([]); const [loading, setLoading] = useState(false); const [expandedId, setExpandedId] = useState(null); const [highlightLayer, setHighlightLayer] = useState | null>(null); const [highlightFeatures, setHighlightFeatures] = useState([]); // Timeline related state const [showTimeline, setShowTimeline] = useState(false); const [selectedDate, setSelectedDate] = useState(undefined); const [timeRange, setTimeRange] = useState<{ start: Date; end: Date } | undefined>(); const [mapContainer, setMapContainer] = useState(null); const { open } = useNotification(); const map = useMap(); const data = useData(); const { schemeName, setSchemeName } = data || {}; const schemes = externalSchemes !== undefined ? externalSchemes : internalSchemes; const setSchemes = onSchemesChange || setInternalSchemes; useEffect(() => { if (!map) return; const target = map.getTargetElement(); if (target) { setMapContainer(target); } }, [map]); // Initialize highlight layer useEffect(() => { if (!map) return; const themeColor = "rgba(0, 0, 255"; // Blue for drainage const valveColor = "rgba(255, 165, 0"; // Orange for valves const sourceStyle = function (feature: FeatureLike) { const type = (feature as any).get("type"); if (type === "valve") { return [ new Style({ image: new Circle({ radius: 8, fill: new Fill({ color: `${valveColor}, 0.8)` }), stroke: new Stroke({ color: "white", width: 2 }), }), }) ]; } else { // Default drainage return [ new Style({ image: new Circle({ radius: 12, fill: new Fill({ color: `${themeColor}, 0.2)` }), }), }), new Style({ image: new Circle({ radius: 8, stroke: new Stroke({ color: `${themeColor}, 0.5)`, width: 2 }), fill: new Fill({ color: `${themeColor}, 0.3)` }), }), }), new Style({ image: new Circle({ radius: 4, fill: new Fill({ color: `${themeColor}, 1)` }), stroke: new Stroke({ color: "white", width: 1 }), }) }), ]; } }; const layer = new VectorLayer({ source: new VectorSource(), style: sourceStyle, zIndex: 1000, properties: { name: "FlushingQueryResultHighlight", }, }); map.addLayer(layer); setHighlightLayer(layer); return () => { map.removeLayer(layer); }; }, [map]); // Update highlight features 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]); const handleLocateDrainageNode = (nodeId: string) => { if (!nodeId) return; queryFeaturesByIds([nodeId], "geo_junctions_mat").then((features) => { if (features.length > 0) { // Add type property to distinguish styling features.forEach(f => f.set("type", "drainage")); setHighlightFeatures(features); zoomToFeatures(features); } else { open?.({ type: "error", message: "未找到该节点要素", }); } }); }; const handleLocateValves = (valveIds: string[]) => { if (!valveIds || valveIds.length === 0) return; queryFeaturesByIds(valveIds, "geo_valves").then((features) => { if (features.length > 0) { features.forEach(f => f.set("type", "valve")); setHighlightFeatures(features); zoomToFeatures(features); } else { open?.({ type: "error", message: "未找到阀门要素", }); } }); }; const zoomToFeatures = (features: 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, padding: [50, 50, 50, 50], }); } }; const formatTime = (timeStr: string) => { return moment(timeStr).format("MM-DD HH:mm"); }; const handleQuery = async () => { if (!queryAll && !queryDate) return; setLoading(true); try { const response = await api.get( `${config.BACKEND_URL}/api/v1/getallschemes/?network=${network}`, ); let filteredResults = response.data; // Filter by type filteredResults = filteredResults.filter((item: SchemaItem) => item.scheme_type === SCHEME_TYPE); if (!queryAll && queryDate) { const formattedDate = queryDate.format("YYYY-MM-DD"); filteredResults = filteredResults.filter((item: SchemaItem) => { const itemDate = moment(item.create_time).format("YYYY-MM-DD"); return itemDate === formattedDate; }); } const nextSchemes = filteredResults.map((item: SchemaItem) => ({ id: item.scheme_id, schemeName: item.scheme_name, type: item.scheme_type, user: item.username, create_time: item.create_time, startTime: item.scheme_start_time, schemeDetail: item.scheme_detail, })); setSchemes(nextSchemes); if (filteredResults.length === 0) { open?.({ type: "error", message: "未找到相关方案", description: "请尝试更改查询条件", }); } } catch (error) { console.error("查询请求失败:", error); open?.({ type: "error", message: "查询失败", description: "获取方案列表失败,请稍后重试", }); } finally { setLoading(false); } }; const handleViewResults = (scheme: SchemeRecord) => { setShowTimeline(true); const schemeDate = scheme.startTime ? new Date(scheme.startTime) : undefined; if (scheme.startTime && scheme.schemeDetail?.duration) { const start = new Date(scheme.startTime); const end = new Date(start.getTime() + scheme.schemeDetail.duration * 1000); setSelectedDate(schemeDate); setTimeRange({ start, end }); } setSchemeName?.(scheme.schemeName); // Locate drainage node by default if available if (scheme.schemeDetail?.drainage_node_ID) { handleLocateDrainageNode(scheme.schemeDetail.drainage_node_ID); } }; return ( <> {showTimeline && mapContainer && ReactDOM.createPortal( , mapContainer, )} {/* Query Controls */} setQueryAll(e.target.checked)} size="small" /> } label={查询全部} className="m-0" /> value && dayjs.isDayjs(value) && setQueryDate(value) } format="YYYY-MM-DD" disabled={queryAll} slotProps={{ textField: { size: "small", sx: { width: 200 }, }, }} /> {/* Results List */} {schemes.length === 0 ? ( 总共 0 条 No data ) : ( 共 {schemes.length} 条记录 {schemes.map((scheme) => ( {scheme.schemeName} 用户: {scheme.user} · 时间: {formatTime(scheme.create_time)} scheme.schemeDetail?.drainage_node_ID && handleLocateDrainageNode(scheme.schemeDetail.drainage_node_ID) } color="primary" className="p-1" > setExpandedId( expandedId === scheme.id ? null : scheme.id, ) } color="primary" className="p-1" > {/* 排水节点 */} 排水节点: {scheme.schemeDetail?.drainage_node_ID ? ( { e.preventDefault(); handleLocateDrainageNode(scheme.schemeDetail!.drainage_node_ID); }} > {scheme.schemeDetail.drainage_node_ID} ) : ( N/A )} {/* 冲洗流量 */} 冲洗流量: {scheme.schemeDetail?.flushing_flow ?? "-"} {FLOW_DISPLAY_UNIT} {/* 持续时长 */} 持续时长: {scheme.schemeDetail?.duration ?? "-"} 秒 {/* 用户 */} 用户: {scheme.user} {/* 创建时间 */} 创建时间: {formatTime(scheme.create_time)} {/* 开始时间 */} 模拟开始: {formatTime(scheme.startTime)} {/* 阀门列表 */} 参与阀门及开度: {scheme.schemeDetail?.valve_opening && Object.entries(scheme.schemeDetail.valve_opening).length > 0 ? ( Object.entries(scheme.schemeDetail.valve_opening).map(([id, k]) => ( handleLocateValves([id])} className="text-xs h-6 bg-gray-50 cursor-pointer hover:bg-orange-50 hover:border-orange-200" /> )) ) : ( )} ))} )} ); }; export default SchemeQuery;