import React, { useState, useEffect, useCallback } from "react"; 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"; import PaletteOutlinedIcon from "@mui/icons-material/PaletteOutlined"; import QueryStatsOutlinedIcon from "@mui/icons-material/QueryStatsOutlined"; import PropertyPanel from "./PropertyPanel"; // 引入属性面板组件 import DrawPanel from "./DrawPanel"; // 引入绘图面板组件 import VectorSource from "ol/source/Vector"; import VectorLayer from "ol/layer/Vector"; import { Style, Stroke, Fill, Circle } from "ol/style"; import { FeatureLike } from "ol/Feature"; import Feature from "ol/Feature"; import StyleEditorPanel from "./StyleEditorPanel"; import StyleLegend from "./StyleLegend"; // 引入图例组件 import { handleMapClickSelectFeatures as mapClickSelectFeatures } from "@/utils/mapQueryService"; import { config } from "@/config/config"; const backendUrl = config.BACKEND_URL; import { LayerStyleState } from "./StyleEditorPanel"; // 添加接口定义隐藏按钮的props interface ToolbarProps { hiddenButtons?: string[]; // 可选的隐藏按钮列表,例如 ['info', 'draw', 'style'] queryType?: string; // 可选的查询类型参数 } const Toolbar: React.FC = ({ hiddenButtons, queryType }) => { const map = useMap(); const data = useData(); if (!data) return null; const { currentTime, selectedDate, schemeName } = data; const [activeTools, setActiveTools] = useState([]); const [highlightFeature, setHighlightFeature] = useState( null ); const [showPropertyPanel, setShowPropertyPanel] = useState(false); const [showDrawPanel, setShowDrawPanel] = useState(false); const [showStyleEditor, setShowStyleEditor] = useState(false); const [highlightLayer, setHighlightLayer] = useState | null>(null); // 样式状态管理 - 在 Toolbar 中管理,带有默认样式 const [layerStyleStates, setLayerStyleStates] = useState([ { isActive: false, // 默认不激活,不显示图例 layerId: "junctions", layerName: "节点图层", styleConfig: { property: "pressure", classificationMethod: "pretty_breaks", segments: 6, minSize: 4, maxSize: 12, minStrokeWidth: 2, maxStrokeWidth: 8, fixedStrokeWidth: 3, colorType: "gradient", singlePaletteIndex: 0, gradientPaletteIndex: 0, rainbowPaletteIndex: 0, showLabels: false, opacity: 0.9, adjustWidthByProperty: true, }, legendConfig: { layerId: "junctions", layerName: "节点图层", property: "压力", // 暂时为空,等计算后更新 colors: [], type: "point", dimensions: [], breaks: [], }, }, { isActive: false, // 默认不激活,不显示图例 layerId: "pipes", layerName: "管道图层", styleConfig: { property: "flow", classificationMethod: "pretty_breaks", segments: 6, minSize: 4, maxSize: 12, minStrokeWidth: 2, maxStrokeWidth: 8, fixedStrokeWidth: 3, colorType: "gradient", singlePaletteIndex: 0, gradientPaletteIndex: 0, rainbowPaletteIndex: 0, showLabels: false, opacity: 0.9, adjustWidthByProperty: true, }, legendConfig: { layerId: "pipes", layerName: "管道图层", property: "流量", // 暂时为空,等计算后更新 colors: [], type: "linestring", dimensions: [], breaks: [], }, }, ]); // 计算激活的图例配置 const activeLegendConfigs = layerStyleStates .filter((state) => state.isActive && state.legendConfig.property) .map((state) => ({ ...state.legendConfig, layerName: state.layerName, layerId: state.layerId, })); // 创建高亮图层 useEffect(() => { if (!map) return; const highLightSource = new VectorSource(); const highLightLayer = new VectorLayer({ source: highLightSource, style: new Style({ stroke: new Stroke({ color: `rgba(255, 0, 0, 1)`, width: 5, }), fill: new Fill({ color: `rgba(255, 0, 0, 0.2)`, }), image: new Circle({ radius: 7, stroke: new Stroke({ color: `rgba(255, 0, 0, 1)`, width: 3, }), fill: new Fill({ color: `rgba(255, 0, 0, 0.2)`, }), }), }), properties: { name: "属性查询高亮图层", // 设置图层名称 value: "info_highlight_layer", type: "multigeometry", properties: [], }, }); map.addLayer(highLightLayer); setHighlightLayer(highLightLayer); return () => { map.removeLayer(highLightLayer); }; }, [map]); // 高亮要素的函数 useEffect(() => { if (!highlightLayer) { return; } const source = highlightLayer.getSource(); if (!source) { return; } // 清除之前的高亮 source.clear(); // 添加新的高亮要素 if (highlightFeature instanceof Feature) { source.addFeature(highlightFeature); } }, [highlightFeature]); // 地图点击选择要素事件处理函数 const handleMapClickSelectFeatures = useCallback( async (event: { coordinate: number[] }) => { if (!map) return; const feature = await mapClickSelectFeatures(event, map); // 调用导入的函数 setHighlightFeature(feature); }, [map, setHighlightFeature] ); // 添加矢量属性查询事件监听器 useEffect(() => { if (!activeTools.includes("info") || !map) return; map.on("click", handleMapClickSelectFeatures); return () => { map.un("click", handleMapClickSelectFeatures); }; }, [activeTools, map, handleMapClickSelectFeatures]); // 处理工具栏按钮点击事件 const handleToolClick = (tool: string) => { // 样式工具的特殊处理 - 只有再次点击时才会取消激活和关闭 if (tool === "style") { if (activeTools.includes("style")) { // 如果样式工具已激活,点击时关闭 setShowStyleEditor(false); setActiveTools((prev) => prev.filter((t) => t !== "style")); } else { // 激活样式工具,打开样式面板 setActiveTools((prev) => [...prev, "style"]); setShowStyleEditor(true); } return; } // 其他工具的处理逻辑 if (activeTools.includes(tool)) { // 如果当前工具已激活,再次点击时取消激活并关闭面板 deactivateTool(tool); setActiveTools((prev) => prev.filter((t) => t !== tool)); } else { // 如果当前工具未激活,先关闭所有其他工具,然后激活当前工具 // 关闭所有面板(但保持样式编辑器状态) closeAllPanelsExceptStyle(); // 取消激活所有非样式工具 setActiveTools((prev) => { const styleActive = prev.includes("style"); return styleActive ? ["style", tool] : [tool]; }); // 激活当前工具并打开对应面板 activateTool(tool); } }; // 取消激活指定工具并关闭对应面板 const deactivateTool = (tool: string) => { switch (tool) { case "info": setShowPropertyPanel(false); setHighlightFeature(null); break; case "draw": setShowDrawPanel(false); break; case "history": // 取消历史查询激活时的清理(目前不保留额外状态) break; } }; // 激活指定工具并打开对应面板 const activateTool = (tool: string) => { switch (tool) { case "info": setShowPropertyPanel(true); break; case "draw": setShowDrawPanel(true); break; case "history": // 激活历史查询后立即触发一次网络历史数据查询(结果暂时打印到控制台) queryNetworkHistory(); break; } }; // 查询管网历史数据的函数(激活时调用) const queryNetworkHistory = async () => { try { // 由当前选中日期和 currentTime 构造查询时间(UTC ISO) let dateObj: Date; if (selectedDate instanceof Date) { dateObj = new Date(selectedDate); } else { dateObj = new Date(selectedDate as any); } const minutes = Number(currentTime) || 0; dateObj.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0); const querytime = dateObj.toISOString(); let url: string; if (queryType === "scheme") { url = `${backendUrl}/queryschemesimulationrecordsbytime/?scheme_name=${schemeName}&querytime=${querytime}`; } else { url = `${backendUrl}/querysimulationrecordsbytime/?querytime=${querytime}`; } const response = await fetch(url); if (!response.ok) { console.error("查询管网历史数据失败:", response.statusText); return; } const result = await response.json(); // TODO: 根据需要把结果展示到面板或状态中,目前先打印 console.log("管网历史数据:", result); // 简单提示用户已查询(可改为更友好的 UI) // eslint-disable-next-line no-alert alert("已查询管网历史数据(请查看控制台或后续面板展示)。"); } catch (error) { console.error("查询管网历史数据出错:", error); } }; // 关闭所有面板(除了样式编辑器) const closeAllPanelsExceptStyle = () => { setShowPropertyPanel(false); setHighlightFeature(null); 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" let response; if (queryType === "scheme") { response = await fetch( `${backendUrl}/queryschemesimulationrecordsbyidtime/?scheme_name=${schemeName}&id=${id}&querytime=${querytime}&type=${type}` ); } else { response = await fetch( `${backendUrl}/querysimulationrecordsbyidtime/?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({}); } }; // 仅当 currentTime 有效时查询 if (currentTime !== -1 && queryType) queryComputedProperties(); }, [highlightFeature, currentTime, selectedDate]); // 从要素属性中提取属性面板需要的数据 const getFeatureProperties = useCallback(() => { if (!highlightFeature) return {}; const layer = highlightFeature?.getId()?.toString().split(".")[0]; const properties = highlightFeature.getProperties(); // 计算属性字段,增加 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: "m" }, { key: "quality", label: "水质", unit: "mg/L" }, ]; if (layer === "geo_pipes_mat" || layer === "geo_pipes") { 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.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?.(3) || computedProperties[key], unit, }); } }); } return result; } if (layer === "geo_junctions_mat" || layer === "geo_junctions") { let result = { id: properties.id, type: "节点", properties: [ { label: "海拔", value: properties.elevation?.toFixed?.(1), unit: "m", }, // 将 demand1~demand5 与 pattern1~pattern5 作为二级表格展示 { type: "table", label: "基本需水量", columns: ["demand", "pattern"], rows: Array.from({ length: 5 }, (_, i) => i + 1) .map((idx) => { const d = properties?.[`demand${idx}`]?.toFixed?.(3); const p = properties?.[`pattern${idx}`]; // 仅当 demand 有效时展示该行 if (d !== undefined && d !== null && d !== "") { return [typeof d === "number" ? d.toFixed(3) : d, p ?? "-"]; } }) .filter(Boolean) as (string | number)[][], } as any, ], }; // 追加计算属性 if (computedProperties) { nodeComputedFields.forEach(({ key, label, unit }) => { if (computedProperties[key] !== undefined) { result.properties.push({ label, value: computedProperties[key].toFixed?.(3) || computedProperties[key], unit, }); } }); } return result; } if (layer === "geo_tanks_mat" || layer === "geo_tanks") { return { id: properties.id, type: "水池", properties: [ { label: "海拔", value: properties.elevation?.toFixed?.(1), unit: "m", }, { label: "初始水位", value: properties.init_level?.toFixed?.(1), unit: "m", }, { label: "最低水位", value: properties.min_level?.toFixed?.(1), unit: "m", }, { label: "最高水位", value: properties.max_level?.toFixed?.(1), unit: "m", }, { label: "直径", value: properties.diameter?.toFixed?.(1), unit: "m", }, { label: "最小容积", value: properties.min_vol?.toFixed?.(1), unit: "m³", }, // { // label: "容积曲线", // value: properties.vol_curve, // }, { label: "溢出", value: properties.overflow ? "是" : "否", }, ], }; } if (layer === "geo_reservoirs_mat" || layer === "geo_reservoirs") { return { id: properties.id, type: "水库", properties: [ { label: "水头", value: properties.head?.toFixed?.(1), unit: "m", }, // { // label: "模式", // value: properties.pattern, // }, ], }; } if (layer === "geo_pumps_mat" || layer === "geo_pumps") { return { id: properties.id, type: "水泵", properties: [ { label: "起始节点 ID", value: properties.node1 }, { label: "终点节点 ID", value: properties.node2 }, { label: "功率", value: properties.power?.toFixed?.(1), unit: "kW", }, { label: "扬程", value: properties.head?.toFixed?.(1), unit: "m", }, { label: "转速", value: properties.speed?.toFixed?.(1), unit: "rpm", }, { label: "模式", value: properties.pattern, }, ], }; } if (layer === "geo_valves_mat" || layer === "geo_valves") { return { id: properties.id, type: "阀门", properties: [ { label: "起始节点 ID", value: properties.node1 }, { label: "终点节点 ID", value: properties.node2 }, { label: "直径", value: properties.diameter?.toFixed?.(1), unit: "mm", }, { label: "阀门类型", value: properties.v_type, }, // { // label: "设置", // value: properties.setting?.toFixed?.(2), // }, { label: "局部损失", value: properties.minor_loss?.toFixed?.(2), }, ], }; } // 传输频率文字对应 const getTransmissionFrequency = (transmission_frequency: string) => { // 传输频率文本:00:01:00,00:05:00,00:10:00,00:30:00,01:00:00,转换为分钟数 const parts = transmission_frequency.split(":"); if (parts.length !== 3) return transmission_frequency; const hours = parseInt(parts[0], 10); const minutes = parseInt(parts[1], 10); const seconds = parseInt(parts[2], 10); const totalMinutes = hours * 60 + minutes + (seconds >= 30 ? 1 : 0); return totalMinutes; }; // 可靠度文字映射 const getReliability = (reliability: number) => { switch (reliability) { case 1: return "高"; case 2: return "中"; case 3: return "低"; default: return "未知"; } }; if (layer === "geo_scada_mat" || layer === "geo_scada") { let result = { id: properties.id, type: "SCADA设备", properties: [ { label: "类型", value: properties.type === "pipe_flow" ? "流量传感器" : "压力传感器", }, { label: "关联节点 ID", value: properties.associated_element_id, }, { label: "传输模式", value: properties.transmission_mode === "non_realtime" ? "定时传输" : "实时传输", }, { label: "传输频率", value: getTransmissionFrequency(properties.transmission_frequency), unit: "分钟", }, { label: "可靠性", value: getReliability(properties.reliability), }, ], }; return result; } return {}; }, [highlightFeature, computedProperties]); return ( <>
{!hiddenButtons?.includes("info") && ( } name="查看属性" isActive={activeTools.includes("info")} onClick={() => handleToolClick("info")} /> )} {!hiddenButtons?.includes("history") && ( } name="查询历史数据" isActive={activeTools.includes("history")} onClick={() => handleToolClick("history")} /> )} {!hiddenButtons?.includes("draw") && ( } name="标记绘制" isActive={activeTools.includes("draw")} onClick={() => handleToolClick("draw")} /> )} {!hiddenButtons?.includes("style") && ( } name="图层样式" isActive={activeTools.includes("style")} onClick={() => handleToolClick("style")} /> )}
{showPropertyPanel && } {showDrawPanel && map && } {showStyleEditor && ( )} {/* 图例显示 */} {activeLegendConfigs.length > 0 && (
{activeLegendConfigs.map((config, index) => ( ))}
)} ); }; export default Toolbar;