移除优化分区页面
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
import { MapSkeleton } from "@components/loading/MapSkeleton";
|
|
||||||
|
|
||||||
export default function Loading() {
|
|
||||||
return <MapSkeleton />;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import MapComponent from "@components/olmap/core/MapComponent";
|
|
||||||
import MapToolbar from "@components/olmap/core/Controls/Toolbar";
|
|
||||||
import ZonePropsPanel from "@components/olmap/NetworkPartitionOptimization/ZonePropsPanel";
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<div className="relative w-full h-full overflow-hidden">
|
|
||||||
<MapComponent>
|
|
||||||
<ZonePropsPanel />
|
|
||||||
</MapComponent>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,6 @@ import { LiaNetworkWiredSolid } from "react-icons/lia";
|
|||||||
import { TbDatabaseEdit, TbLocationPin, TbActivity } from "react-icons/tb";
|
import { TbDatabaseEdit, TbLocationPin, TbActivity } from "react-icons/tb";
|
||||||
import { LuReplace } from "react-icons/lu";
|
import { LuReplace } from "react-icons/lu";
|
||||||
import { AiOutlineSecurityScan } from "react-icons/ai";
|
import { AiOutlineSecurityScan } from "react-icons/ai";
|
||||||
import { AiOutlinePartition } from "react-icons/ai";
|
|
||||||
import { MdWater, MdOutlineWaterDrop, MdCleaningServices } from "react-icons/md";
|
import { MdWater, MdOutlineWaterDrop, MdCleaningServices } from "react-icons/md";
|
||||||
import {
|
import {
|
||||||
MyLocation as MyLocationIcon,
|
MyLocation as MyLocationIcon,
|
||||||
@@ -228,14 +227,6 @@ const App = (props: React.PropsWithChildren<AppProps>) => {
|
|||||||
label: "管道冲洗",
|
label: "管道冲洗",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "管网优化分区",
|
|
||||||
list: "/network-partition-optimization",
|
|
||||||
meta: {
|
|
||||||
icon: <AiOutlinePartition className="w-6 h-6" />,
|
|
||||||
label: "管网优化分区",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
options={{
|
options={{
|
||||||
syncWithLocation: true,
|
syncWithLocation: true,
|
||||||
|
|||||||
@@ -1,418 +0,0 @@
|
|||||||
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 "@components/olmap/core/MapComponent";
|
|
||||||
import { useProject } from "@/contexts/ProjectContext";
|
|
||||||
|
|
||||||
interface PropertyItem {
|
|
||||||
key: string;
|
|
||||||
value: string | number | boolean;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ZonePropsPanelProps {
|
|
||||||
title?: string;
|
|
||||||
isVisible?: boolean;
|
|
||||||
onClose?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ZonePropsPanel: React.FC<ZonePropsPanelProps> = ({
|
|
||||||
title = "分区属性信息",
|
|
||||||
isVisible = true,
|
|
||||||
onClose,
|
|
||||||
}) => {
|
|
||||||
const map = useMap();
|
|
||||||
const project = useProject();
|
|
||||||
const workspace = project?.workspace;
|
|
||||||
|
|
||||||
const [props, setProps] = React.useState<
|
|
||||||
PropertyItem[] | Record<string, any>
|
|
||||||
>({});
|
|
||||||
const [highlightedFeature, setHighlightedFeature] = useState<any>(null);
|
|
||||||
const highlightLayerRef = useRef<VectorLayer<VectorSource> | 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) {
|
|
||||||
const layer = clickedFeature?.getId()?.toString().split(".")[0];
|
|
||||||
if (layer !== "network_zone") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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<string, any>
|
|
||||||
): 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 workspaceValue = workspace || config.MAP_WORKSPACE;
|
|
||||||
const networkZoneLayer = new VectorLayer({
|
|
||||||
source: new VectorSource({
|
|
||||||
url: `${config.MAP_URL}/${workspaceValue}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${workspaceValue}: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, workspace]);
|
|
||||||
// 获取中文标签
|
|
||||||
const getChineseLabel = (key: string): string => {
|
|
||||||
const labelMap: Record<string, string> = {
|
|
||||||
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", "ModelFlow", "Area", "PipeLength"];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="absolute top-4 right-4 bg-white shadow-2xl rounded-xl overflow-hidden w-96 max-h-[850px] flex flex-col backdrop-blur-sm opacity-95 hover:opacity-100 transition-all duration-300">
|
|
||||||
{/* 头部 */}
|
|
||||||
<div className="flex justify-between items-center px-5 py-4 bg-[#257DD4] text-white">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<h3 className="text-lg font-semibold">{title}</h3>
|
|
||||||
</div>
|
|
||||||
{onClose && (
|
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
className="text-white hover:bg-white hover:bg-opacity-20 rounded-full p-1 transition-all duration-200"
|
|
||||||
aria-label="关闭"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M6 18L18 6M6 6l12 12"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 内容区域 */}
|
|
||||||
<div className="flex-1 overflow-y-auto px-4 py-3">
|
|
||||||
{sortedProperties.length === 0 ? (
|
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-gray-400">
|
|
||||||
<svg
|
|
||||||
className="w-16 h-16 mb-3"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={1.5}
|
|
||||||
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<p className="text-sm">暂无属性信息</p>
|
|
||||||
<p className="text-xs mt-1">点击地图分区查看详情</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{sortedProperties.map((item, index) => {
|
|
||||||
const isImportant = isImportantKeys.includes(item.key);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={item.key || index}
|
|
||||||
className={`group rounded-lg p-3 transition-all duration-200 ${
|
|
||||||
isImportant
|
|
||||||
? "bg-blue-50 hover:bg-blue-100 border-l-4 border-blue-500"
|
|
||||||
: "bg-gray-50 hover:bg-gray-100"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex justify-between items-start gap-3">
|
|
||||||
<span
|
|
||||||
className={`font-medium text-xs uppercase tracking-wide ${
|
|
||||||
isImportant ? "text-blue-700" : "text-gray-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{item.label || item.key}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`text-sm font-semibold text-right flex-1 ${
|
|
||||||
isImportant ? "text-blue-900" : "text-gray-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{formatValue(item.value, item.key)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 底部统计区域 */}
|
|
||||||
<div className="px-5 py-3 bg-gray-50 border-t border-gray-200">
|
|
||||||
<div className="flex items-center justify-between text-xs">
|
|
||||||
<span className="text-gray-600 flex items-center gap-1">
|
|
||||||
<svg
|
|
||||||
className="w-4 h-4"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
共 {sortedProperties.length} 个属性
|
|
||||||
</span>
|
|
||||||
{highlightedFeature && (
|
|
||||||
<span className="text-green-600 flex items-center gap-1 font-medium">
|
|
||||||
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
|
|
||||||
已选中
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ZonePropsPanel;
|
|
||||||
Reference in New Issue
Block a user