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 (
+
+ {/* 头部 */}
+
+
+ {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;