From f4b91ac4cc6800df6feb5a6b9d0689fc7c4bee3c Mon Sep 17 00:00:00 2001 From: JIANG Date: Mon, 20 Oct 2025 16:51:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=99=9A=E4=B8=8A=E7=AE=A1=E7=BD=91=E5=88=86?= =?UTF-8?q?=E5=8C=BA=E4=BC=98=E5=8C=96=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../network-partition-optimization/page.tsx | 15 + src/components/olmap/ZonePropsPanel.tsx | 412 ++++++++++++++++++ 2 files changed, 427 insertions(+) create mode 100644 src/app/(main)/network-partition-optimization/page.tsx create mode 100644 src/components/olmap/ZonePropsPanel.tsx diff --git a/src/app/(main)/network-partition-optimization/page.tsx b/src/app/(main)/network-partition-optimization/page.tsx new file mode 100644 index 0000000..77da850 --- /dev/null +++ b/src/app/(main)/network-partition-optimization/page.tsx @@ -0,0 +1,15 @@ +"use client"; + +import MapComponent from "@app/OlMap/MapComponent"; +import MapToolbar from "@app/OlMap/Controls/Toolbar"; +import ZonePropsPanel from "@components/olmap/ZonePropsPanel"; +export default function Home() { + return ( +
+ + + + +
+ ); +} diff --git a/src/components/olmap/ZonePropsPanel.tsx b/src/components/olmap/ZonePropsPanel.tsx new file mode 100644 index 0000000..86e0c9e --- /dev/null +++ b/src/components/olmap/ZonePropsPanel.tsx @@ -0,0 +1,412 @@ +import React, { useEffect, useCallback, useState, useRef } from "react"; +import VectorLayer from "ol/layer/Vector"; +import VectorSource from "ol/source/Vector"; +import Style from "ol/style/Style"; +import Fill from "ol/style/Fill"; +import { Stroke } from "ol/style"; +import GeoJson from "ol/format/GeoJSON"; +import config from "@config/config"; +import { useMap } from "@app/OlMap/MapComponent"; + +interface PropertyItem { + key: string; + value: string | number | boolean; + label?: string; +} + +interface ZonePropsPanelProps { + title?: string; + isVisible?: boolean; + onClose?: () => void; +} + +const mapUrl = config.mapUrl; + +const ZonePropsPanel: React.FC = ({ + title = "分区属性信息", + isVisible = true, + onClose, +}) => { + const map = useMap(); + + const [props, setProps] = React.useState< + PropertyItem[] | Record + >({}); + const [highlightedFeature, setHighlightedFeature] = useState(null); + const highlightLayerRef = useRef | null>(null); + + const handleMapClickSelectFeatures = useCallback( + (pixel: number[]) => { + if (!map || !highlightLayerRef.current) return; + let clickedFeature: any = null; + map.forEachFeatureAtPixel(pixel, (feature) => { + if (!clickedFeature) { + clickedFeature = feature; + } + }); + if (clickedFeature) { + setHighlightedFeature(clickedFeature); + setProps(clickedFeature.getProperties()); + // 更新高亮图层 + const source = highlightLayerRef.current.getSource(); + source?.clear(); + source?.addFeature(clickedFeature); + } else { + setHighlightedFeature(null); + setProps({}); + // 清空高亮图层 + const source = highlightLayerRef.current.getSource(); + source?.clear(); + } + }, + [map] + ); + + // 将 properties 转换为统一格式 + const formatProperties = ( + props: PropertyItem[] | Record + ): PropertyItem[] => { + if (Array.isArray(props)) { + return props.filter((item) => !shouldHideProperty(item.key)); + } + + return Object.entries(props) + .filter(([key]) => !shouldHideProperty(key)) + .map(([key, value]) => ({ + key, + value, + label: getChineseLabel(key), + })); + }; + + // 判断是否应该隐藏某个属性 + const shouldHideProperty = (key: string): boolean => { + const hiddenKeys = [ + "id", + "geometry", + "Note1", + "Note3", + "Note4", + "Note5", + "Note6", + "Note7", + "Note8", + "Note9", + "Note10", + ]; + return hiddenKeys.includes(key); + }; + + useEffect(() => { + if (!map) { + return; + } + const networkZoneLayer = new VectorLayer({ + source: new VectorSource({ + url: `${mapUrl}/TJWater/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=TJWater:network_zone&outputFormat=application/json`, + format: new GeoJson(), + }), + style: new Style({ + fill: new Fill({ + color: "rgba(255, 255, 255, 0)", + }), + stroke: new Stroke({ + color: "#e01414ff", + width: 5, + }), + }), + properties: { + name: "管网分区", + value: "network_zone", + }, + }); + map.addLayer(networkZoneLayer); + + // 创建高亮图层 + const highlightLayer = new VectorLayer({ + source: new VectorSource(), + style: new Style({ + fill: new Fill({ + color: "rgba(255, 255, 0, 0.3)", + }), + stroke: new Stroke({ + color: "#ff0000", + width: 3, + }), + }), + properties: { + name: "高亮分区", + value: "highlight_zone", + }, + }); + map.addLayer(highlightLayer); + highlightLayerRef.current = highlightLayer; + + const clickListener = (evt: any) => { + handleMapClickSelectFeatures(evt.pixel); + }; + + map.on("click", clickListener); + + return () => { + map.removeLayer(networkZoneLayer); + map.removeLayer(highlightLayer); + map.un("click", clickListener); + }; + }, [map, handleMapClickSelectFeatures]); + // 获取中文标签 + const getChineseLabel = (key: string): string => { + const labelMap: Record = { + Id: "ID", + Area: "面积", + Complete: "完成度", + Consumptio: "消耗", + Descriptio: "描述", + FlowError: "流量误差", + Level: "级别", + ModelFlow: "模型流量", + NRW: "无收益水量", + NRWPercent: "无收益水量百分比", + Name: "名称", + Note1: "备注1", + Note2: "备注2", + Note3: "备注3", + Note4: "备注4", + Note5: "备注5", + Note6: "备注6", + Note7: "备注7", + Note8: "备注8", + Note9: "备注9", + Note10: "备注10", + ParentZone: "父区域", + PipeLength: "管道长度", + Population: "人口", + ScadaFlow: "SCADA流量", + Tag: "标签", + TotalFlowE: "总流量误差", + TotalModel: "总模型", + TotalScada: "总SCADA", + WaterConsu: "水消耗", + WaterSuppl: "水供应", + }; + return labelMap[key] || key; + }; + + // 优先使用从store中获取的props,如果没有则使用传入的properties + const dataToShow = props; + const formattedProperties = formatProperties(dataToShow); + + // 定义属性的显示顺序 + const propertyOrder = [ + "Id", + "Name", + "PipeLength", + "ModelFlow", + "Population", + "Level", + "Note2", + "Area", + "Descriptio", + "ParentZone", + "Tag", + "Complete", + "Consumptio", + "FlowError", + "NRW", + "NRWPercent", + "ScadaFlow", + "TotalFlowE", + "TotalModel", + "TotalScada", + "WaterConsu", + "WaterSuppl", + ]; + + // 根据自定义顺序对属性进行排序 + const sortedProperties = [...formattedProperties].sort((a, b) => { + const aIndex = propertyOrder.indexOf(a.key); + const bIndex = propertyOrder.indexOf(b.key); + + // 如果属性不在排序列表中,则将其放在末尾 + if (aIndex === -1) return 1; + if (bIndex === -1) return -1; + + return aIndex - bIndex; + }); + + // 格式化值显示 + const formatValue = (value: any, key: string): string => { + if (value === null || value === undefined) { + return "-"; + } + if (typeof value === "boolean") { + return value ? "是" : "否"; + } + if (typeof value === "string" && value.trim() === "") { + return "-"; + } + + // 对于特定的数值字段,添加单位 + if (typeof value === "number") { + switch (key) { + case "Area": + return `${value.toLocaleString()} m²`; + case "PipeLength": + return `${value.toLocaleString()} m`; + case "Population": + return `${value.toLocaleString()} 人`; + case "ModelFlow": + return `${value.toLocaleString()} L/天`; + case "ScadaFlow": + case "TotalModel": + case "TotalScada": + case "WaterConsu": + case "WaterSuppl": + return `${value.toLocaleString()} L/s`; + case "NRWPercent": + return value !== null ? `${value}%` : "-"; + default: + return value.toLocaleString(); + } + } + + return String(value); + }; + + if (!isVisible) { + return null; + } + + const isImportantKeys = ["Name", "Id", "Population", "Area", "PipeLength"]; + + return ( +
+ {/* 头部 */} +
+
+ + + +

{title}

+
+ {onClose && ( + + )} +
+ + {/* 内容区域 */} +
+ {sortedProperties.length === 0 ? ( +
+ + + +

暂无属性信息

+

点击地图分区查看详情

+
+ ) : ( +
+ {sortedProperties.map((item, index) => { + const isImportant = isImportantKeys.includes(item.key); + return ( +
+
+ + {item.label || item.key} + + + {formatValue(item.value, item.key)} + +
+
+ ); + })} +
+ )} +
+ + {/* 底部统计区域 */} +
+
+ + + + + 共 {sortedProperties.length} 个属性 + + {highlightedFeature && ( + + + 已选中 + + )} +
+
+
+ ); +}; + +export default ZonePropsPanel;