diff --git a/src/app/OlMap/Controls/StyleEditorPanel.tsx b/src/app/OlMap/Controls/StyleEditorPanel.tsx index 11a2fcd..80d4044 100644 --- a/src/app/OlMap/Controls/StyleEditorPanel.tsx +++ b/src/app/OlMap/Controls/StyleEditorPanel.tsx @@ -28,6 +28,7 @@ import { FlatStyleLike } from "ol/style/flat"; import { calculateClassification } from "@utils/breaks_classification"; import { parseColor } from "@utils/parseColor"; import { VectorTile } from "ol"; +import { useNotification } from "@refinedev/core"; interface StyleConfig { property: string; @@ -117,6 +118,8 @@ const StyleEditorPanel: React.FC = () => { setPipeText, } = data; + const { open, close } = useNotification(); + const [applyJunctionStyle, setApplyJunctionStyle] = useState(false); const [applyPipeStyle, setApplyPipeStyle] = useState(false); const [styleUpdateTrigger, setStyleUpdateTrigger] = useState(0); // 用于触发样式更新的状态 @@ -194,7 +197,7 @@ const StyleEditorPanel: React.FC = () => { const finalLegendConfig: LegendStyleConfig = legendConfig || { layerId, layerName, - property: styleConfig.property, + property: selectedProperty.name, colors: [], type: "point", dimensions: [], @@ -240,15 +243,22 @@ const StyleEditorPanel: React.FC = () => { setShowJunctionText(styleConfig.showLabels); setApplyJunctionStyle(true); saveLayerStyle(layerId); + open?.({ + type: "success", + message: "节点图层样式设置成功,等待数据更新。", + }); } } if (layerId === "pipes") { - console.log(styleConfig); if (setPipeText && setShowPipeText) { setPipeText(property); setShowPipeText(styleConfig.showLabels); setApplyPipeStyle(true); saveLayerStyle(layerId); + open?.({ + type: "success", + message: "管道图层样式设置成功,等待数据更新。", + }); } } // 触发样式更新 diff --git a/src/app/OlMap/Controls/Timeline.tsx b/src/app/OlMap/Controls/Timeline.tsx index 3dca1a6..ac08144 100644 --- a/src/app/OlMap/Controls/Timeline.tsx +++ b/src/app/OlMap/Controls/Timeline.tsx @@ -33,16 +33,25 @@ const Timeline: React.FC = () => { return
Loading...
; // 或其他占位符 } const { + currentTime, + setCurrentTime, + selectedDate, + setSelectedDate, setCurrentJunctionCalData, setCurrentPipeCalData, junctionText, pipeText, } = data; + if ( + setCurrentTime === undefined || + currentTime === undefined || + selectedDate === undefined || + setSelectedDate === undefined + ) { + return
Loading...
; // 或其他占位符 + } const { open, close } = useNotification(); - const [currentTime, setCurrentTime] = useState(0); // 分钟数 (0-1439) - const [selectedDate, setSelectedDate] = useState(new Date("2025-9-17")); - // const [selectedDate, setSelectedDate] = useState(new Date()); // 默认今天 const [isPlaying, setIsPlaying] = useState(false); const [playInterval, setPlayInterval] = useState(5000); // 毫秒 const [calculatedInterval, setCalculatedInterval] = useState(1440); // 分钟 @@ -51,62 +60,79 @@ const Timeline: React.FC = () => { const intervalRef = useRef(null); const timelineRef = useRef(null); // 添加缓存引用 - const cacheRef = useRef< - Map - >(new Map()); + const nodeCacheRef = useRef>(new Map()); + const linkCacheRef = useRef>(new Map()); // 添加防抖引用 const debounceRef = useRef(null); - const fetchFrameData = async (queryTime: Date) => { + const fetchFrameData = async ( + queryTime: Date, + junctionProperties: string, + pipeProperties: string + ) => { const query_time = queryTime.toISOString(); - const cacheKey = query_time; - // console.log("Fetching data for time:", query_time); - // console.log("Junction Property:", junctionText); - // console.log("Pipe Property:", pipeText); - // 检查缓存 - if (cacheRef.current.has(cacheKey)) { - const { nodeRecords, linkRecords } = cacheRef.current.get(cacheKey)!; - // 使用缓存数据更新状态 - updateDataStates(nodeRecords, linkRecords); - return; - } - - try { - // 定义需要查询的属性 - const junctionProperties = junctionText; - const pipeProperties = pipeText; - // 如果属性未定义或为空,直接返回 - if (junctionProperties === "" || pipeProperties === "") { - return; - } - console.log( - "Query Time:", - queryTime.toLocaleDateString() + " " + queryTime.toLocaleTimeString() - ); - // 同时查询节点和管道数据 - const [nodeResponse, linkResponse] = await Promise.all([ - fetch( + let nodeRecords: any = { results: [] }; + let linkRecords: any = { results: [] }; + const requests: Promise[] = []; + let nodePromise: Promise | null = null; + let linkPromise: Promise | null = null; + // 检查node缓存 + if (junctionProperties !== "") { + const nodeCacheKey = `${query_time}_${junctionProperties}`; + if (nodeCacheRef.current.has(nodeCacheKey)) { + nodeRecords = nodeCacheRef.current.get(nodeCacheKey)!; + } else { + nodePromise = fetch( `${backendUrl}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=node&property=${junctionProperties}` - ), - fetch( - `${backendUrl}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=link&property=${pipeProperties}` - ), - ]); - - const nodeRecords = await nodeResponse.json(); - const linkRecords = await linkResponse.json(); - - // 缓存数据 - cacheRef.current.set(cacheKey, { - nodeRecords: nodeRecords.results, - linkRecords: linkRecords.results, - }); - - // 更新状态 - updateDataStates(nodeRecords.results, linkRecords.results); - } catch (error) { - console.error("Error fetching data:", error); + ); + requests.push(nodePromise); + } } + + // 检查link缓存 + if (pipeProperties !== "") { + const linkCacheKey = `${query_time}_${pipeProperties}`; + if (linkCacheRef.current.has(linkCacheKey)) { + linkRecords = linkCacheRef.current.get(linkCacheKey)!; + } else { + linkPromise = fetch( + `${backendUrl}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=link&property=${pipeProperties}` + ); + requests.push(linkPromise); + } + } + + console.log( + "Query Time:", + queryTime.toLocaleDateString() + " " + queryTime.toLocaleTimeString() + ); + // 等待所有有效请求 + const responses = await Promise.all(requests); + + if (nodePromise) { + const nodeResponse = responses.shift()!; + if (!nodeResponse.ok) + throw new Error(`Node fetch failed: ${nodeResponse.status}`); + nodeRecords = await nodeResponse.json(); + // 缓存数据 + nodeCacheRef.current.set( + `${query_time}_${junctionProperties}`, + nodeRecords || [] + ); + } + if (linkPromise) { + const linkResponse = responses.shift()!; + if (!linkResponse.ok) + throw new Error(`Link fetch failed: ${linkResponse.status}`); + linkRecords = await linkResponse.json(); + // 缓存数据 + linkCacheRef.current.set( + `${query_time}_${pipeProperties}`, + linkRecords || [] + ); + } + // 更新状态 + updateDataStates(nodeRecords.results || [], linkRecords.results || []); }; // 提取更新状态的逻辑 @@ -181,10 +207,10 @@ const Timeline: React.FC = () => { // 播放控制 const handlePlay = useCallback(() => { if (!isPlaying) { - if (junctionText === "" || pipeText === "") { + if (junctionText === "" && pipeText === "") { open?.({ type: "error", - message: "请先设置节点和管道的属性。", + message: "请至少设定并应用一个图层的样式。", }); return; } @@ -270,8 +296,25 @@ const Timeline: React.FC = () => { // 添加 useEffect 来监听 currentTime 和 selectedDate 的变化,并获取数据 useEffect(() => { - fetchFrameData(currentTimeToDate(selectedDate, currentTime)); - }, [currentTime, selectedDate]); + // 首次加载时,如果 selectedDate 或 currentTime 未定义,则跳过执行,避免报错 + if (selectedDate && currentTime !== undefined) { + // 检查至少一个属性有值 + const junctionProperties = junctionText; + const pipeProperties = pipeText; + if (junctionProperties === "" && pipeProperties === "") { + open?.({ + type: "error", + message: "请至少设定并应用一个图层的样式。", + }); + return; + } + fetchFrameData( + currentTimeToDate(selectedDate, currentTime), + junctionText, + pipeText + ); + } + }, [junctionText, pipeText, currentTime, selectedDate]); // 组件卸载时清理定时器和防抖 useEffect(() => { diff --git a/src/app/OlMap/Controls/Toolbar.tsx b/src/app/OlMap/Controls/Toolbar.tsx index 72fee94..835d07d 100644 --- a/src/app/OlMap/Controls/Toolbar.tsx +++ b/src/app/OlMap/Controls/Toolbar.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useCallback } from "react"; -import { useMap } from "../MapComponent"; +import { useData, useMap } from "../MapComponent"; import ToolbarButton from "@/components/olmap/common/ToolbarButton"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; @@ -23,8 +23,14 @@ import { toLonLat } from "ol/proj"; import { booleanIntersects, buffer, point, toWgs84 } from "@turf/turf"; import RenderFeature from "ol/render/Feature"; +import { config } from "@/config/config"; +const backendUrl = config.backendUrl; + const Toolbar: React.FC = () => { const map = useMap(); + const data = useData(); + if (!data) return null; + const { currentTime, selectedDate } = data; const [activeTools, setActiveTools] = useState([]); const [highlightFeature, setHighlightFeature] = useState( null @@ -186,7 +192,7 @@ const Toolbar: React.FC = () => { const features = results .filter((json) => json !== null) // 过滤掉失败的请求 .flatMap((json) => new GeoJSON().readFeatures(json)); - console.log("查询到的要素:", features); + // console.log("查询到的要素:", features); return features; } else { // 查询指定图层 @@ -201,7 +207,7 @@ const Toolbar: React.FC = () => { } const json = await response.json(); const features = new GeoJSON().readFeatures(json); - console.log("查询到的要素:", features); + // console.log("查询到的要素:", features); return features; } } catch (error) { @@ -399,45 +405,145 @@ const Toolbar: React.FC = () => { setShowDrawPanel(false); // 样式编辑器保持其当前状态,不自动关闭 }; + const [computedProperties, setComputedProperties] = useState< + Record + >({}); + // 添加 useEffect 来查询计算属性 + useEffect(() => { + if (!highlightFeature || !selectedDate) { + setComputedProperties({}); + return; + } + + const id = highlightFeature.getProperties().id; + if (!id) { + setComputedProperties({}); + return; + } + + const queryComputedProperties = async () => { + try { + const properties = highlightFeature?.getProperties?.() || {}; + const type = + properties.geometry?.getType?.() === "LineString" ? "link" : "node"; + // selectedDate 格式化为 YYYY-MM-DD + let dateObj: Date; + if (selectedDate instanceof Date) { + dateObj = new Date(selectedDate); + } else { + dateObj = new Date(selectedDate); + } + const minutes = Number(currentTime) || 0; + dateObj.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0); + // 转为 UTC ISO 字符串 + const querytime = dateObj.toISOString(); // 例如 "2025-09-16T16:30:00.000Z" + const response = await fetch( + `${backendUrl}/queryrecordsbyidtime/?id=${id}&querytime=${querytime}&type=${type}` + ); + if (!response.ok) { + throw new Error("API request failed"); + } + const data = await response.json(); + setComputedProperties(data.results[0] || {}); + } catch (error) { + console.error("Error querying computed properties:", error); + setComputedProperties({}); + } + }; + + queryComputedProperties(); + }, [highlightFeature, currentTime, selectedDate]); // 从要素属性中提取属性面板需要的数据 const getFeatureProperties = useCallback(() => { if (!highlightFeature) return {}; const properties = highlightFeature.getProperties(); - console.log(properties, properties.geometry.type, "properties"); + // 计算属性字段,增加 key 字段 + const pipeComputedFields = [ + { key: "flow", label: "流量", unit: "m³/s" }, + { key: "friction", label: "摩阻", unit: "" }, + { key: "headloss", label: "水头损失", unit: "m" }, + { key: "quality", label: "水质", unit: "mg/L" }, + { key: "reaction", label: "反应", unit: "1/s" }, + { key: "setting", label: "设置", unit: "" }, + { key: "status", label: "状态", unit: "" }, + { key: "velocity", label: "流速", unit: "m/s" }, + ]; + const nodeComputedFields = [ + { key: "actualdemand", label: "实际需水量", unit: "m³/s" }, + { key: "head", label: "水头", unit: "m" }, + { key: "pressure", label: "压力", unit: "kPa" }, + { key: "quality", label: "水质", unit: "mg/L" }, + ]; + if (properties.geometry.getType() === "LineString") { - console.log(properties, "properties"); - return { + let result = { id: properties.id, type: "管道", properties: [ { label: "起始节点ID", value: properties.node1 }, { label: "终点节点ID", value: properties.node2 }, - { label: "长度", value: properties.length.toFixed(1), unit: "m" }, - { label: "管径", value: properties.diameter.toFixed(1), unit: "mm" }, + { label: "长度", value: properties.length?.toFixed?.(1), unit: "m" }, + { + label: "管径", + value: properties.diameter?.toFixed?.(1), + unit: "mm", + }, { label: "粗糙度", value: properties.roughness }, { label: "局部损失", value: properties.minor_loss }, { label: "初始状态", value: "开" }, ], }; + // 追加计算属性 + if (computedProperties) { + pipeComputedFields.forEach(({ key, label, unit }) => { + if (computedProperties[key] !== undefined) { + result.properties.push({ + label, + value: + computedProperties[key].toFixed?.(2) || computedProperties[key], + unit, + }); + } + }); + } + return result; } if (properties.geometry.getType() === "Point") { - return { + let result = { id: properties.id, type: "节点", properties: [ - { label: "海拔", value: properties.elevation.toFixed(1), unit: "m" }, + { + label: "海拔", + value: properties.elevation?.toFixed?.(1), + unit: "m", + }, { label: "需求量", - value: properties.demand.toFixed(1), + value: properties.demand?.toFixed?.(1), unit: "m³/s", }, ], }; + // 追加计算属性 + if (computedProperties) { + nodeComputedFields.forEach(({ key, label, unit }) => { + if (computedProperties[key] !== undefined) { + result.properties.push({ + label, + value: + computedProperties[key].toFixed?.(2) || computedProperties[key], + unit, + }); + } + }); + } + return result; } return {}; - }, [highlightFeature]); + }, [highlightFeature, computedProperties]); return ( <> diff --git a/src/app/OlMap/MapComponent.tsx b/src/app/OlMap/MapComponent.tsx index 8a20693..982b4a6 100644 --- a/src/app/OlMap/MapComponent.tsx +++ b/src/app/OlMap/MapComponent.tsx @@ -29,6 +29,10 @@ interface MapComponentProps { children?: React.ReactNode; } interface DataContextType { + currentTime?: number; // 当前时间 + setCurrentTime?: React.Dispatch>; + selectedDate?: Date; // 选择的日期 + setSelectedDate?: React.Dispatch>; currentJunctionCalData?: any[]; // 当前计算结果 setCurrentJunctionCalData?: React.Dispatch>; currentPipeCalData?: any[]; // 当前计算结果 @@ -97,6 +101,10 @@ const MapComponent: React.FC = ({ children }) => { const [map, setMap] = useState(); // currentCalData 用于存储当前计算结果 + const [currentTime, setCurrentTime] = useState(0); + const [selectedDate, setSelectedDate] = useState(new Date("2025-9-17")); + // const [selectedDate, setSelectedDate] = useState(new Date()); // 默认今天 + const [currentJunctionCalData, setCurrentJunctionCalData] = useState( [] ); @@ -113,8 +121,8 @@ const MapComponent: React.FC = ({ children }) => { const [showPipeText, setShowPipeText] = useState(false); // 控制管道文本显示 const [showJunctionTextLayer, setShowJunctionTextLayer] = useState(true); // 控制节点文本图层显示 const [showPipeTextLayer, setShowPipeTextLayer] = useState(true); // 控制管道文本图层显示 - const [junctionText, setJunctionText] = useState("pressure"); - const [pipeText, setPipeText] = useState("flow"); + const [junctionText, setJunctionText] = useState(""); + const [pipeText, setPipeText] = useState(""); const flowAnimation = useRef(false); // 添加动画控制标志 const [currentZoom, setCurrentZoom] = useState(12); // 当前缩放级别 // 防抖更新函数 @@ -418,7 +426,8 @@ const MapComponent: React.FC = ({ children }) => { fontFamily: "Monaco, monospace", getText: (d: any) => d[junctionText] ? (d[junctionText] as number).toFixed(3) : "", - getSize: 12, + getSize: 14, + fontWeight: "bold", getColor: [150, 150, 255], getAngle: 0, getTextAnchor: "middle", @@ -448,7 +457,8 @@ const MapComponent: React.FC = ({ children }) => { fontFamily: "Monaco, monospace", getText: (d: any) => d[pipeText] ? (d[pipeText] as number).toFixed(3) : "", - getSize: 14, + getSize: 12, + fontWeight: "bold", getColor: [120, 128, 181], getAngle: (d: any) => d.angle || 0, getPixelOffset: [0, -8], @@ -491,13 +501,14 @@ const MapComponent: React.FC = ({ children }) => { const waterflowLayer = new TripsLayer({ id: "waterflowLayer", data: pipeData, - getPath: (d) => (flowAnimation.current ? d.path : []), + getPath: (d) => d.path, getTimestamps: (d) => { return d.timestamps; // 这些应该是与 currentTime 匹配的数值 }, getColor: [0, 220, 255], opacity: 0.8, - visible: currentZoom >= 12 && currentZoom <= 24, + visible: + flowAnimation.current && currentZoom >= 12 && currentZoom <= 24, widthMinPixels: 5, jointRounded: true, // 拐角变圆 // capRounded: true, // 端点变圆 @@ -589,6 +600,10 @@ const MapComponent: React.FC = ({ children }) => { <>