修复lint errors
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -115,7 +115,7 @@ const EmptyState = () => (
|
|||||||
|
|
||||||
const LocationResults: React.FC<Props> = ({ result }) => {
|
const LocationResults: React.FC<Props> = ({ result }) => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const [highlightLayer, setHighlightLayer] = useState<VectorLayer<VectorSource> | null>(null);
|
const highlightLayerRef = useRef<VectorLayer<VectorSource> | null>(null);
|
||||||
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
||||||
|
|
||||||
const candidatePipes = useMemo<BurstCandidate[]>(() => {
|
const candidatePipes = useMemo<BurstCandidate[]>(() => {
|
||||||
@@ -128,13 +128,13 @@ const LocationResults: React.FC<Props> = ({ result }) => {
|
|||||||
return base;
|
return base;
|
||||||
}, [result]);
|
}, [result]);
|
||||||
|
|
||||||
const allCandidatePipeIds = useMemo<string[]>(() => {
|
const allCandidatePipeIds = (() => {
|
||||||
const ids = candidatePipes.map((item) => item.pipe_id);
|
const ids = candidatePipes.map((item) => item.pipe_id);
|
||||||
if (result?.located_pipe) {
|
if (result?.located_pipe) {
|
||||||
ids.unshift(result.located_pipe);
|
ids.unshift(result.located_pipe);
|
||||||
}
|
}
|
||||||
return Array.from(new Set(ids.filter(Boolean)));
|
return Array.from(new Set(ids.filter(Boolean)));
|
||||||
}, [candidatePipes, result?.located_pipe]);
|
})();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!map) return;
|
if (!map) return;
|
||||||
@@ -159,19 +159,20 @@ const LocationResults: React.FC<Props> = ({ result }) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
map.addLayer(layer);
|
map.addLayer(layer);
|
||||||
setHighlightLayer(layer);
|
highlightLayerRef.current = layer;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
highlightLayerRef.current = null;
|
||||||
map.removeLayer(layer);
|
map.removeLayer(layer);
|
||||||
};
|
};
|
||||||
}, [map]);
|
}, [map]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const source = highlightLayer?.getSource();
|
const source = highlightLayerRef.current?.getSource();
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
source.clear();
|
source.clear();
|
||||||
highlightFeatures.forEach((feature) => source.addFeature(feature));
|
highlightFeatures.forEach((feature) => source.addFeature(feature));
|
||||||
}, [highlightFeatures, highlightLayer]);
|
}, [highlightFeatures]);
|
||||||
|
|
||||||
const locatePipes = async (pipeIds: string[]) => {
|
const locatePipes = async (pipeIds: string[]) => {
|
||||||
if (!pipeIds.length || !map) return;
|
if (!pipeIds.length || !map) return;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -41,8 +41,7 @@ interface LocationResultsProps {
|
|||||||
const LocationResults: React.FC<LocationResultsProps> = ({
|
const LocationResults: React.FC<LocationResultsProps> = ({
|
||||||
results = [],
|
results = [],
|
||||||
}) => {
|
}) => {
|
||||||
const [highlightLayer, setHighlightLayer] =
|
const highlightLayerRef = useRef<VectorLayer<VectorSource> | null>(null);
|
||||||
useState<VectorLayer<VectorSource> | null>(null);
|
|
||||||
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
|
|
||||||
@@ -145,19 +144,17 @@ const LocationResults: React.FC<LocationResultsProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
map.addLayer(highlightLayer);
|
map.addLayer(highlightLayer);
|
||||||
setHighlightLayer(highlightLayer);
|
highlightLayerRef.current = highlightLayer;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
highlightLayerRef.current = null;
|
||||||
map.removeLayer(highlightLayer);
|
map.removeLayer(highlightLayer);
|
||||||
};
|
};
|
||||||
}, [map]);
|
}, [map]);
|
||||||
|
|
||||||
// 高亮要素的函数
|
// 高亮要素的函数
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!highlightLayer) {
|
const source = highlightLayerRef.current?.getSource();
|
||||||
return;
|
|
||||||
}
|
|
||||||
const source = highlightLayer.getSource();
|
|
||||||
if (!source) {
|
if (!source) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -169,7 +166,7 @@ const LocationResults: React.FC<LocationResultsProps> = ({
|
|||||||
source.addFeature(feature);
|
source.addFeature(feature);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [highlightFeatures, highlightLayer]);
|
}, [highlightFeatures]);
|
||||||
|
|
||||||
// 取第一条记录或空对象
|
// 取第一条记录或空对象
|
||||||
const result = results.length > 0 ? results[0] : null;
|
const result = results.length > 0 ? results[0] : null;
|
||||||
|
|||||||
@@ -1090,7 +1090,7 @@ const ValveIsolation: React.FC<ValveIsolationProps> = ({
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Alert severity="info" variant="outlined">
|
<Alert severity="info" variant="outlined">
|
||||||
请先在流程2中选择不可用阀门,然后点击"扩大搜索"按钮
|
请先在流程2中选择不可用阀门,然后点击“扩大搜索”按钮
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
import React, { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
||||||
import { useNotification } from "@refinedev/core";
|
import { useNotification } from "@refinedev/core";
|
||||||
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
|
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
|
||||||
|
|
||||||
@@ -27,7 +27,6 @@ import dayjs from "dayjs";
|
|||||||
import { PlayArrow, Pause, Stop, Refresh } from "@mui/icons-material";
|
import { PlayArrow, Pause, Stop, Refresh } from "@mui/icons-material";
|
||||||
import { TbArrowBackUp, TbArrowForwardUp } from "react-icons/tb";
|
import { TbArrowBackUp, TbArrowForwardUp } from "react-icons/tb";
|
||||||
import { FiSkipBack, FiSkipForward } from "react-icons/fi";
|
import { FiSkipBack, FiSkipForward } from "react-icons/fi";
|
||||||
import { useData } from "@components/olmap/core/MapComponent";
|
|
||||||
import { config, NETWORK_NAME } from "@/config/config";
|
import { config, NETWORK_NAME } from "@/config/config";
|
||||||
import { apiFetch } from "@/lib/apiFetch";
|
import { apiFetch } from "@/lib/apiFetch";
|
||||||
import { useMap } from "@components/olmap/core/MapComponent";
|
import { useMap } from "@components/olmap/core/MapComponent";
|
||||||
@@ -63,10 +62,6 @@ interface TimelineProps {
|
|||||||
const Timeline: React.FC<TimelineProps> = ({
|
const Timeline: React.FC<TimelineProps> = ({
|
||||||
disableDateSelection = false,
|
disableDateSelection = false,
|
||||||
}) => {
|
}) => {
|
||||||
const data = useData();
|
|
||||||
if (!data) {
|
|
||||||
return <div>Loading...</div>; // 或其他占位符
|
|
||||||
}
|
|
||||||
const { open } = useNotification();
|
const { open } = useNotification();
|
||||||
const {
|
const {
|
||||||
predictionResults,
|
predictionResults,
|
||||||
@@ -79,7 +74,6 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
||||||
const [playInterval, setPlayInterval] = useState<number>(5000); // 毫秒
|
const [playInterval, setPlayInterval] = useState<number>(5000); // 毫秒
|
||||||
const [isPredicting, setIsPredicting] = useState<boolean>(false);
|
const [isPredicting, setIsPredicting] = useState<boolean>(false);
|
||||||
const [pipeLayer, setPipeLayer] = useState<WebGLVectorTileLayer | null>(null);
|
|
||||||
|
|
||||||
// 使用 ref 存储当前的健康数据,供事件监听器读取,避免重复绑定
|
// 使用 ref 存储当前的健康数据,供事件监听器读取,避免重复绑定
|
||||||
const healthDataRef = useRef<Map<string, number>>(new Map());
|
const healthDataRef = useRef<Map<string, number>>(new Map());
|
||||||
@@ -228,10 +222,21 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
clearTimeout(debounceRef.current);
|
clearTimeout(debounceRef.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [pipeLayer]);
|
}, []);
|
||||||
|
|
||||||
// 获取地图实例
|
// 获取地图实例
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
|
const pipeLayer = useMemo(() => {
|
||||||
|
if (!map) return null;
|
||||||
|
|
||||||
|
const layers = map.getLayers().getArray();
|
||||||
|
return (
|
||||||
|
layers.find(
|
||||||
|
(layer) =>
|
||||||
|
layer instanceof WebGLVectorTileLayer && layer.get("value") === "pipes",
|
||||||
|
) as WebGLVectorTileLayer | undefined
|
||||||
|
) ?? null;
|
||||||
|
}, [map]);
|
||||||
|
|
||||||
// 根据索引从 survival_function 中获取生存概率
|
// 根据索引从 survival_function 中获取生存概率
|
||||||
const getSurvivalProbabilityAtYear = useCallback(
|
const getSurvivalProbabilityAtYear = useCallback(
|
||||||
@@ -362,21 +367,6 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
updatePipeHealthData,
|
updatePipeHealthData,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 初始化管道图层
|
|
||||||
useEffect(() => {
|
|
||||||
if (!map) return;
|
|
||||||
|
|
||||||
const layers = map.getLayers().getArray();
|
|
||||||
const pipesLayer = layers.find(
|
|
||||||
(layer) =>
|
|
||||||
layer instanceof WebGLVectorTileLayer && layer.get("value") === "pipes",
|
|
||||||
) as WebGLVectorTileLayer | undefined;
|
|
||||||
|
|
||||||
if (pipesLayer) {
|
|
||||||
setPipeLayer(pipesLayer);
|
|
||||||
}
|
|
||||||
}, [map]);
|
|
||||||
|
|
||||||
// 监听依赖变化,更新样式
|
// 监听依赖变化,更新样式
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (predictionResults.length > 0 && pipeLayer) {
|
if (predictionResults.length > 0 && pipeLayer) {
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ import { useMap } from "../MapComponent";
|
|||||||
const DrawPanel: React.FC = () => {
|
const DrawPanel: React.FC = () => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const [activeTool, setActiveTool] = useState<string>("pan");
|
const [activeTool, setActiveTool] = useState<string>("pan");
|
||||||
const [drawLayer, setDrawLayer] = useState<VectorLayer<VectorSource> | null>(
|
const drawLayerRef = useRef<VectorLayer<VectorSource> | null>(null);
|
||||||
null
|
|
||||||
);
|
|
||||||
const [drawnFeatures, setDrawnFeatures] = useState<Feature<Geometry>[]>([]);
|
const [drawnFeatures, setDrawnFeatures] = useState<Feature<Geometry>[]>([]);
|
||||||
const [history, setHistory] = useState<{
|
const [history, setHistory] = useState<{
|
||||||
stack: Feature<Geometry>[][];
|
stack: Feature<Geometry>[][];
|
||||||
@@ -79,13 +77,14 @@ const DrawPanel: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
map.addLayer(drawVectorLayer);
|
map.addLayer(drawVectorLayer);
|
||||||
setDrawLayer(drawVectorLayer);
|
drawLayerRef.current = drawVectorLayer;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (drawInteractionRef.current && map) {
|
if (drawInteractionRef.current && map) {
|
||||||
map.removeInteraction(drawInteractionRef.current);
|
map.removeInteraction(drawInteractionRef.current);
|
||||||
drawInteractionRef.current = null;
|
drawInteractionRef.current = null;
|
||||||
}
|
}
|
||||||
|
drawLayerRef.current = null;
|
||||||
map.removeLayer(drawVectorLayer);
|
map.removeLayer(drawVectorLayer);
|
||||||
};
|
};
|
||||||
}, [map, drawInteractionRef]);
|
}, [map, drawInteractionRef]);
|
||||||
@@ -110,6 +109,7 @@ const DrawPanel: React.FC = () => {
|
|||||||
type: GeometryType,
|
type: GeometryType,
|
||||||
geometryFunction?: GeometryFunction
|
geometryFunction?: GeometryFunction
|
||||||
) => {
|
) => {
|
||||||
|
const drawLayer = drawLayerRef.current;
|
||||||
if (!drawLayer) return;
|
if (!drawLayer) return;
|
||||||
if (!map) return;
|
if (!map) return;
|
||||||
|
|
||||||
@@ -285,6 +285,7 @@ const DrawPanel: React.FC = () => {
|
|||||||
|
|
||||||
// 删除所有绘制的要素
|
// 删除所有绘制的要素
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
|
const drawLayer = drawLayerRef.current;
|
||||||
if (!drawLayer) return;
|
if (!drawLayer) return;
|
||||||
|
|
||||||
const source = drawLayer.getSource();
|
const source = drawLayer.getSource();
|
||||||
@@ -301,6 +302,7 @@ const DrawPanel: React.FC = () => {
|
|||||||
|
|
||||||
// 更新绘图图层
|
// 更新绘图图层
|
||||||
const updateDrawLayer = (features: Feature<Geometry>[]) => {
|
const updateDrawLayer = (features: Feature<Geometry>[]) => {
|
||||||
|
const drawLayer = drawLayerRef.current;
|
||||||
if (!drawLayer) return;
|
if (!drawLayer) return;
|
||||||
|
|
||||||
const source = drawLayer.getSource();
|
const source = drawLayer.getSource();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useMemo } from "react";
|
||||||
import { useData, useMap } from "../MapComponent";
|
import { useData, useMap } from "../MapComponent";
|
||||||
import { Checkbox, FormControlLabel } from "@mui/material";
|
import { Checkbox, FormControlLabel } from "@mui/material";
|
||||||
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
|
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
|
||||||
@@ -18,15 +18,12 @@ interface LayerItem {
|
|||||||
const LayerControl: React.FC = () => {
|
const LayerControl: React.FC = () => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const data = useData();
|
const data = useData();
|
||||||
if (!data) return;
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
const {
|
const deckLayer = data?.deckLayer;
|
||||||
deckLayer,
|
const isContourLayerAvailable = data?.isContourLayerAvailable;
|
||||||
isContourLayerAvailable,
|
const isWaterflowLayerAvailable = data?.isWaterflowLayerAvailable;
|
||||||
isWaterflowLayerAvailable,
|
const setShowWaterflowLayer = data?.setShowWaterflowLayer;
|
||||||
setShowWaterflowLayer,
|
const setShowContourLayer = data?.setShowContourLayer;
|
||||||
setShowContourLayer,
|
|
||||||
} = data;
|
|
||||||
const [layerItems, setLayerItems] = useState<LayerItem[]>([]);
|
|
||||||
|
|
||||||
const layerOrder = [
|
const layerOrder = [
|
||||||
"junctions",
|
"junctions",
|
||||||
@@ -40,16 +37,12 @@ const LayerControl: React.FC = () => {
|
|||||||
"junctionContourLayer",
|
"junctionContourLayer",
|
||||||
];
|
];
|
||||||
|
|
||||||
// 更新图层列表
|
const layerItems = useMemo(() => {
|
||||||
const updateLayers = useCallback(() => {
|
if (!map || !data) return [];
|
||||||
if (!map || !data) return;
|
|
||||||
|
|
||||||
const items: LayerItem[] = [];
|
const items: LayerItem[] = [];
|
||||||
|
|
||||||
// 1. 获取 OpenLayers 图层
|
map.getLayers().getArray().forEach((layer) => {
|
||||||
const mapLayers = map.getLayers().getArray();
|
|
||||||
mapLayers.forEach((layer) => {
|
|
||||||
// 筛选特定类型的 OpenLayers 图层
|
|
||||||
if (
|
if (
|
||||||
layer instanceof WebGLVectorTileLayer ||
|
layer instanceof WebGLVectorTileLayer ||
|
||||||
layer instanceof VectorTileLayer ||
|
layer instanceof VectorTileLayer ||
|
||||||
@@ -57,7 +50,6 @@ const LayerControl: React.FC = () => {
|
|||||||
) {
|
) {
|
||||||
const value = layer.get("value");
|
const value = layer.get("value");
|
||||||
const name = layer.get("name");
|
const name = layer.get("name");
|
||||||
// 只有设置了 value (作为 ID) 的图层才会被纳入控制
|
|
||||||
if (value) {
|
if (value) {
|
||||||
items.push({
|
items.push({
|
||||||
id: value,
|
id: value,
|
||||||
@@ -70,66 +62,56 @@ const LayerControl: React.FC = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. 获取 DeckLayer 中的子图层
|
|
||||||
if (deckLayer && deckLayer instanceof DeckLayer) {
|
if (deckLayer && deckLayer instanceof DeckLayer) {
|
||||||
const deckLayers = deckLayer.getDeckLayers();
|
deckLayer.getDeckLayers().forEach((layer: any) => {
|
||||||
deckLayers.forEach((layer: any) => {
|
if (!layer?.id) return;
|
||||||
if (layer && layer.id) {
|
if (layer.id !== "junctionContourLayer" && layer.id !== "waterflowLayer") {
|
||||||
// 仅处理 junctionContourLayer 和 waterflowLayer
|
return;
|
||||||
if (
|
|
||||||
layer.id !== "junctionContourLayer" &&
|
|
||||||
layer.id !== "waterflowLayer"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 检查可用性
|
|
||||||
if (
|
|
||||||
(layer.id === "junctionContourLayer" && !isContourLayerAvailable) ||
|
|
||||||
(layer.id === "waterflowLayer" && !isWaterflowLayerAvailable)
|
|
||||||
) {
|
|
||||||
return; // 跳过不可用图层
|
|
||||||
}
|
|
||||||
const visible =
|
|
||||||
deckLayer.getDeckLayerVisible(layer.id) ??
|
|
||||||
layer.props?.visible ??
|
|
||||||
true;
|
|
||||||
items.push({
|
|
||||||
id: layer.props.id,
|
|
||||||
name: layer.props.name, // 使用 name 属性作为显示名称
|
|
||||||
visible: visible,
|
|
||||||
type: "deck",
|
|
||||||
layerRef: layer,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
(layer.id === "junctionContourLayer" && !isContourLayerAvailable) ||
|
||||||
|
(layer.id === "waterflowLayer" && !isWaterflowLayerAvailable)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
id: layer.props.id,
|
||||||
|
name: layer.props.name,
|
||||||
|
visible:
|
||||||
|
deckLayer.getDeckLayerVisible(layer.id) ?? layer.props?.visible ?? true,
|
||||||
|
type: "deck",
|
||||||
|
layerRef: layer,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤并排序
|
return items
|
||||||
const sortedItems = items
|
|
||||||
.filter((item) => layerOrder.includes(item.id))
|
.filter((item) => layerOrder.includes(item.id))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => layerOrder.indexOf(a.id) - layerOrder.indexOf(b.id));
|
||||||
const indexA = layerOrder.indexOf(a.id);
|
}, [
|
||||||
const indexB = layerOrder.indexOf(b.id);
|
map,
|
||||||
return indexA - indexB;
|
data,
|
||||||
});
|
deckLayer,
|
||||||
|
isContourLayerAvailable,
|
||||||
setLayerItems(sortedItems);
|
isWaterflowLayerAvailable,
|
||||||
}, [map, deckLayer, isWaterflowLayerAvailable, isContourLayerAvailable]);
|
refreshKey,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateLayers();
|
if (!map) return;
|
||||||
|
|
||||||
if (map) {
|
const layerCollection = map.getLayers();
|
||||||
const layerCollection = map.getLayers();
|
const handleLayerChange = () => {
|
||||||
layerCollection.on("change:length", updateLayers);
|
setRefreshKey((prev) => prev + 1);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
layerCollection.on("change:length", handleLayerChange);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (map) {
|
map.getLayers().un("change:length", handleLayerChange);
|
||||||
map.getLayers().un("change:length", updateLayers);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [map, updateLayers]);
|
}, [map]);
|
||||||
|
|
||||||
const handleVisibilityChange = (item: LayerItem, checked: boolean) => {
|
const handleVisibilityChange = (item: LayerItem, checked: boolean) => {
|
||||||
if (item.type === "ol") {
|
if (item.type === "ol") {
|
||||||
@@ -142,10 +124,7 @@ const LayerControl: React.FC = () => {
|
|||||||
setShowWaterflowLayer && setShowWaterflowLayer(checked);
|
setShowWaterflowLayer && setShowWaterflowLayer(checked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setRefreshKey((prev) => prev + 1);
|
||||||
setLayerItems((prev) =>
|
|
||||||
prev.map((i) => (i.id === item.id ? { ...i, visible: checked } : i)),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useCallback, useRef } from "react";
|
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
||||||
|
|
||||||
// 导入Material-UI图标和组件
|
// 导入Material-UI图标和组件
|
||||||
import ColorLensIcon from "@mui/icons-material/ColorLens";
|
import ColorLensIcon from "@mui/icons-material/ColorLens";
|
||||||
@@ -180,26 +180,21 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const data = useData();
|
const data = useData();
|
||||||
if (!data) {
|
const currentJunctionCalData = data?.currentJunctionCalData;
|
||||||
return <div>Loading...</div>; // 或其他占位符
|
const currentPipeCalData = data?.currentPipeCalData;
|
||||||
}
|
const junctionText = data?.junctionText ?? "";
|
||||||
const {
|
const pipeText = data?.pipeText ?? "";
|
||||||
currentJunctionCalData,
|
const setShowJunctionTextLayer = data?.setShowJunctionTextLayer;
|
||||||
currentPipeCalData,
|
const setShowPipeTextLayer = data?.setShowPipeTextLayer;
|
||||||
junctionText,
|
const setShowJunctionId = data?.setShowJunctionId;
|
||||||
pipeText,
|
const setShowPipeId = data?.setShowPipeId;
|
||||||
setShowJunctionTextLayer,
|
const setContourLayerAvailable = data?.setContourLayerAvailable;
|
||||||
setShowPipeTextLayer,
|
const setWaterflowLayerAvailable = data?.setWaterflowLayerAvailable;
|
||||||
setShowJunctionId,
|
const setJunctionText = data?.setJunctionText;
|
||||||
setShowPipeId,
|
const setPipeText = data?.setPipeText;
|
||||||
setContourLayerAvailable,
|
const setContours = data?.setContours;
|
||||||
setWaterflowLayerAvailable,
|
const diameterRange = data?.diameterRange;
|
||||||
setJunctionText,
|
const elevationRange = data?.elevationRange;
|
||||||
setPipeText,
|
|
||||||
setContours,
|
|
||||||
diameterRange,
|
|
||||||
elevationRange,
|
|
||||||
} = data;
|
|
||||||
|
|
||||||
const unitHeadlossRange = [0, 5];
|
const unitHeadlossRange = [0, 5];
|
||||||
|
|
||||||
@@ -213,9 +208,6 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
const [renderLayers, setRenderLayers] = useState<WebGLVectorTileLayer[]>([]);
|
const [renderLayers, setRenderLayers] = useState<WebGLVectorTileLayer[]>([]);
|
||||||
const [selectedRenderLayer, setSelectedRenderLayer] =
|
const [selectedRenderLayer, setSelectedRenderLayer] =
|
||||||
useState<WebGLVectorTileLayer>();
|
useState<WebGLVectorTileLayer>();
|
||||||
const [availableProperties, setAvailableProperties] = useState<
|
|
||||||
{ name: string; value: string }[]
|
|
||||||
>([]);
|
|
||||||
const [styleConfig, setStyleConfig] = useState<StyleConfig>({
|
const [styleConfig, setStyleConfig] = useState<StyleConfig>({
|
||||||
property: "",
|
property: "",
|
||||||
classificationMethod: "pretty_breaks",
|
classificationMethod: "pretty_breaks",
|
||||||
@@ -237,6 +229,74 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
customColors: [],
|
customColors: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getDefaultCustomColors = (
|
||||||
|
segments: number,
|
||||||
|
existingColors: string[] = []
|
||||||
|
) => {
|
||||||
|
const nextColors = [...existingColors];
|
||||||
|
const baseColors = RAINBOW_PALETTES[0].colors;
|
||||||
|
|
||||||
|
while (nextColors.length < segments) {
|
||||||
|
nextColors.push(baseColors[nextColors.length % baseColors.length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextColors.slice(0, segments);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDefaultCustomBreaks = (
|
||||||
|
segments: number,
|
||||||
|
property: string,
|
||||||
|
layer: WebGLVectorTileLayer | undefined = selectedRenderLayer
|
||||||
|
) => {
|
||||||
|
if (!layer || !property) {
|
||||||
|
return Array.from({ length: segments }, () => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedLayerId = layer.get("value");
|
||||||
|
let dataArr: number[] = [];
|
||||||
|
|
||||||
|
const isElevation =
|
||||||
|
selectedLayerId === "junctions" && property === "elevation";
|
||||||
|
const isDiameter = selectedLayerId === "pipes" && property === "diameter";
|
||||||
|
|
||||||
|
if (isElevation && elevationRange) {
|
||||||
|
dataArr = [elevationRange[0], elevationRange[1]];
|
||||||
|
} else if (isDiameter && diameterRange) {
|
||||||
|
dataArr = [diameterRange[0], diameterRange[1]];
|
||||||
|
} else if (selectedLayerId === "junctions" && currentJunctionCalData) {
|
||||||
|
dataArr = currentJunctionCalData.map((d: any) => d.value);
|
||||||
|
} else if (selectedLayerId === "pipes" && currentPipeCalData) {
|
||||||
|
dataArr = currentPipeCalData.map((d: any) => d.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataArr.length === 0) {
|
||||||
|
return Array.from({ length: segments }, () => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultBreaks = calculateClassification(
|
||||||
|
dataArr,
|
||||||
|
segments,
|
||||||
|
"pretty_breaks"
|
||||||
|
).slice(0, segments);
|
||||||
|
|
||||||
|
while (defaultBreaks.length < segments) {
|
||||||
|
defaultBreaks.push(defaultBreaks[defaultBreaks.length - 1] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultBreaks;
|
||||||
|
};
|
||||||
|
|
||||||
|
const availableProperties = useMemo<{ name: string; value: string }[]>(() => {
|
||||||
|
if (!selectedRenderLayer) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (selectedRenderLayer.get("properties") || []) as {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
}, [selectedRenderLayer]);
|
||||||
|
|
||||||
// 根据分段数生成相应数量的渐进颜色
|
// 根据分段数生成相应数量的渐进颜色
|
||||||
const generateGradientColors = useCallback(
|
const generateGradientColors = useCallback(
|
||||||
(segments: number): string[] => {
|
(segments: number): string[] => {
|
||||||
@@ -278,63 +338,56 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
[styleConfig.rainbowPaletteIndex]
|
[styleConfig.rainbowPaletteIndex]
|
||||||
);
|
);
|
||||||
// 保存当前图层的样式状态
|
// 保存当前图层的样式状态
|
||||||
const saveLayerStyle = useCallback(
|
const saveLayerStyle = (
|
||||||
(
|
layerId?: string,
|
||||||
layerId?: string,
|
newLegendConfig?: LegendStyleConfig,
|
||||||
newLegendConfig?: LegendStyleConfig,
|
overrideStyleConfig?: StyleConfig
|
||||||
overrideStyleConfig?: StyleConfig
|
) => {
|
||||||
) => {
|
const currentStyleConfig = overrideStyleConfig || styleConfig;
|
||||||
const currentStyleConfig = overrideStyleConfig || styleConfig;
|
|
||||||
|
|
||||||
if (!currentStyleConfig.property) {
|
if (!currentStyleConfig.property) {
|
||||||
console.warn("无法保存样式:缺少必要的图层或样式配置");
|
console.warn("无法保存样式:缺少必要的图层或样式配置");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
if (!layerId) return;
|
||||||
|
|
||||||
|
const layerName =
|
||||||
|
newLegendConfig?.layerName ||
|
||||||
|
selectedRenderLayer?.get("name") ||
|
||||||
|
`图层${layerId}`;
|
||||||
|
const property = availableProperties.find(
|
||||||
|
(p) => p.value === currentStyleConfig.property
|
||||||
|
);
|
||||||
|
const legendConfig: LegendStyleConfig = newLegendConfig || {
|
||||||
|
layerId,
|
||||||
|
layerName,
|
||||||
|
property: property?.name || currentStyleConfig.property,
|
||||||
|
colors: [],
|
||||||
|
type: selectedRenderLayer?.get("type") || "point",
|
||||||
|
dimensions: [],
|
||||||
|
breaks: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStyleState: LayerStyleState = {
|
||||||
|
layerId,
|
||||||
|
layerName,
|
||||||
|
styleConfig: { ...currentStyleConfig },
|
||||||
|
legendConfig: { ...legendConfig },
|
||||||
|
isActive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
setLayerStyleStates((prev) => {
|
||||||
|
const existingIndex = prev.findIndex((state) => state.layerId === layerId);
|
||||||
|
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
const updated = [...prev];
|
||||||
|
updated[existingIndex] = newStyleState;
|
||||||
|
return updated;
|
||||||
}
|
}
|
||||||
if (!layerId) return; // 如果没有传入 layerId,则不保存
|
|
||||||
// 如果没有传入图例配置,则创建一个默认的空配置
|
|
||||||
const layerName =
|
|
||||||
newLegendConfig?.layerName ||
|
|
||||||
selectedRenderLayer?.get("name") ||
|
|
||||||
`图层${layerId}`;
|
|
||||||
const property = availableProperties.find(
|
|
||||||
(p) => p.value === currentStyleConfig.property
|
|
||||||
);
|
|
||||||
let legendConfig: LegendStyleConfig = newLegendConfig || {
|
|
||||||
layerId,
|
|
||||||
layerName,
|
|
||||||
property: property?.name || currentStyleConfig.property,
|
|
||||||
colors: [],
|
|
||||||
type: selectedRenderLayer?.get("type") || "point",
|
|
||||||
dimensions: [],
|
|
||||||
breaks: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const newStyleState: LayerStyleState = {
|
return [...prev, newStyleState];
|
||||||
layerId,
|
});
|
||||||
layerName,
|
};
|
||||||
styleConfig: { ...currentStyleConfig },
|
|
||||||
legendConfig: { ...legendConfig },
|
|
||||||
isActive: true,
|
|
||||||
};
|
|
||||||
setLayerStyleStates((prev) => {
|
|
||||||
// 检查是否已存在该图层的样式状态
|
|
||||||
const existingIndex = prev.findIndex(
|
|
||||||
(state) => state.layerId === layerId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingIndex !== -1) {
|
|
||||||
// 更新已存在的状态
|
|
||||||
const updated = [...prev];
|
|
||||||
updated[existingIndex] = newStyleState;
|
|
||||||
return updated;
|
|
||||||
} else {
|
|
||||||
// 添加新的状态
|
|
||||||
return [...prev, newStyleState];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[selectedRenderLayer, styleConfig, availableProperties]
|
|
||||||
);
|
|
||||||
// 设置分类样式参数,触发样式应用
|
// 设置分类样式参数,触发样式应用
|
||||||
const setStyleState = () => {
|
const setStyleState = () => {
|
||||||
if (!selectedRenderLayer) return;
|
if (!selectedRenderLayer) return;
|
||||||
@@ -787,7 +840,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 重置样式
|
// 重置样式
|
||||||
const resetStyle = useCallback(() => {
|
const resetStyle = () => {
|
||||||
if (!selectedRenderLayer) return;
|
if (!selectedRenderLayer) return;
|
||||||
// 重置 WebGL 图层样式
|
// 重置 WebGL 图层样式
|
||||||
const defaultFlatStyle: FlatStyleLike = config.MAP_DEFAULT_STYLE;
|
const defaultFlatStyle: FlatStyleLike = config.MAP_DEFAULT_STYLE;
|
||||||
@@ -815,7 +868,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
setWaterflowLayerAvailable && setWaterflowLayerAvailable(false);
|
setWaterflowLayerAvailable && setWaterflowLayerAvailable(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selectedRenderLayer]);
|
};
|
||||||
// 更新当前 VectorTileSource 中的所有缓冲要素属性
|
// 更新当前 VectorTileSource 中的所有缓冲要素属性
|
||||||
const updateVectorTileSource = (property: string, data: any[]) => {
|
const updateVectorTileSource = (property: string, data: any[]) => {
|
||||||
if (!map) return;
|
if (!map) return;
|
||||||
@@ -857,7 +910,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 新增事件,监听 VectorTileSource 的 tileloadend 事件,为新增瓦片数据动态更新要素属性
|
// 新增事件,监听 VectorTileSource 的 tileloadend 事件,为新增瓦片数据动态更新要素属性
|
||||||
const [tileLoadListeners, setTileLoadListeners] = useState<
|
const tileLoadListenersRef = useRef<
|
||||||
Map<VectorTileSource, (event: any) => void>
|
Map<VectorTileSource, (event: any) => void>
|
||||||
>(new Map());
|
>(new Map());
|
||||||
|
|
||||||
@@ -879,8 +932,6 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
dataMap.set(d.ID, d.value || 0);
|
dataMap.set(d.ID, d.value || 0);
|
||||||
});
|
});
|
||||||
// 新增监听器并保存
|
// 新增监听器并保存
|
||||||
const newListeners = new Map<VectorTileSource, (event: any) => void>();
|
|
||||||
|
|
||||||
const listener = (event: any) => {
|
const listener = (event: any) => {
|
||||||
try {
|
try {
|
||||||
if (event.tile instanceof VectorTile) {
|
if (event.tile instanceof VectorTile) {
|
||||||
@@ -906,8 +957,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
vectorTileSource.on("tileloadend", listener);
|
vectorTileSource.on("tileloadend", listener);
|
||||||
newListeners.set(vectorTileSource, listener);
|
tileLoadListenersRef.current.set(vectorTileSource, listener);
|
||||||
setTileLoadListeners(newListeners);
|
|
||||||
};
|
};
|
||||||
// 新增函数:取消对应 layerId 已添加的 on 事件
|
// 新增函数:取消对应 layerId 已添加的 on 事件
|
||||||
const removeVectorTileSourceLoadedEvent = (layerId: string) => {
|
const removeVectorTileSourceLoadedEvent = (layerId: string) => {
|
||||||
@@ -918,14 +968,10 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
.map((layer) => layer.getSource() as VectorTileSource)
|
.map((layer) => layer.getSource() as VectorTileSource)
|
||||||
.filter((source) => source)[0];
|
.filter((source) => source)[0];
|
||||||
if (!vectorTileSource) return;
|
if (!vectorTileSource) return;
|
||||||
const listener = tileLoadListeners.get(vectorTileSource);
|
const listener = tileLoadListenersRef.current.get(vectorTileSource);
|
||||||
if (listener) {
|
if (listener) {
|
||||||
vectorTileSource.un("tileloadend", listener);
|
vectorTileSource.un("tileloadend", listener);
|
||||||
setTileLoadListeners((prev) => {
|
tileLoadListenersRef.current.delete(vectorTileSource);
|
||||||
const newMap = new Map(prev);
|
|
||||||
newMap.delete(vectorTileSource);
|
|
||||||
return newMap;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1044,117 +1090,9 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
|
|
||||||
updateVisibleLayers();
|
updateVisibleLayers();
|
||||||
}, [map]);
|
}, [map]);
|
||||||
// 获取选中图层的属性,并检查是否有已缓存的样式状态
|
if (!data) {
|
||||||
useEffect(() => {
|
return <div>Loading...</div>;
|
||||||
// 如果没有矢量图层或没有选中图层,清空属性列表
|
}
|
||||||
if (!renderLayers || renderLayers.length === 0) {
|
|
||||||
setAvailableProperties([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 如果没有选中图层,清空属性列表
|
|
||||||
if (!selectedRenderLayer) {
|
|
||||||
setAvailableProperties([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取第一个要素的数值型属性
|
|
||||||
const properties = selectedRenderLayer.get("properties") || {};
|
|
||||||
setAvailableProperties(properties);
|
|
||||||
|
|
||||||
// 设置选中的渲染图层
|
|
||||||
const renderLayer = renderLayers.filter((layer) => {
|
|
||||||
return layer.get("value") === selectedRenderLayer?.get("value");
|
|
||||||
})[0];
|
|
||||||
setSelectedRenderLayer(renderLayer);
|
|
||||||
|
|
||||||
// 检查是否有已缓存的样式状态,如果有则自动恢复
|
|
||||||
const layerId = selectedRenderLayer.get("value");
|
|
||||||
const cachedStyleState = layerStyleStates.find(
|
|
||||||
(state) => state.layerId === layerId
|
|
||||||
);
|
|
||||||
if (cachedStyleState) {
|
|
||||||
setStyleConfig(cachedStyleState.styleConfig);
|
|
||||||
}
|
|
||||||
}, [renderLayers, selectedRenderLayer, map, renderLayers, layerStyleStates]);
|
|
||||||
|
|
||||||
// 监听颜色类型变化,当切换到单一色时自动勾选宽度调整选项
|
|
||||||
useEffect(() => {
|
|
||||||
if (styleConfig.colorType === "single") {
|
|
||||||
setStyleConfig((prev) => ({
|
|
||||||
...prev,
|
|
||||||
adjustWidthByProperty: true,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}, [styleConfig.colorType]);
|
|
||||||
|
|
||||||
// 初始化或调整自定义断点数组长度,默认使用 pretty_breaks 生成若存在数据
|
|
||||||
useEffect(() => {
|
|
||||||
if (styleConfig.classificationMethod !== "custom_breaks") return;
|
|
||||||
|
|
||||||
const numBreaks = styleConfig.segments;
|
|
||||||
setStyleConfig((prev) => {
|
|
||||||
const prevBreaks = prev.customBreaks || [];
|
|
||||||
if (prevBreaks.length === numBreaks) return prev;
|
|
||||||
|
|
||||||
const selectedLayerId = selectedRenderLayer?.get("value");
|
|
||||||
let dataArr: number[] = [];
|
|
||||||
|
|
||||||
const isElevation =
|
|
||||||
selectedLayerId === "junctions" && styleConfig.property === "elevation";
|
|
||||||
const isDiameter =
|
|
||||||
selectedLayerId === "pipes" && styleConfig.property === "diameter";
|
|
||||||
|
|
||||||
if (isElevation && elevationRange) {
|
|
||||||
dataArr = [elevationRange[0], elevationRange[1]];
|
|
||||||
} else if (isDiameter && diameterRange) {
|
|
||||||
dataArr = [diameterRange[0], diameterRange[1]];
|
|
||||||
} else if (selectedLayerId === "junctions" && currentJunctionCalData) {
|
|
||||||
dataArr = currentJunctionCalData.map((d: any) => d.value);
|
|
||||||
} else if (selectedLayerId === "pipes" && currentPipeCalData) {
|
|
||||||
dataArr = currentPipeCalData.map((d: any) => d.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
let defaultBreaks: number[] = Array.from({ length: numBreaks }, () => 0);
|
|
||||||
if (dataArr && dataArr.length > 0) {
|
|
||||||
defaultBreaks = calculateClassification(
|
|
||||||
dataArr,
|
|
||||||
styleConfig.segments,
|
|
||||||
"pretty_breaks"
|
|
||||||
);
|
|
||||||
defaultBreaks = defaultBreaks.slice(0, numBreaks);
|
|
||||||
if (defaultBreaks.length < numBreaks)
|
|
||||||
while (defaultBreaks.length < numBreaks)
|
|
||||||
defaultBreaks.push(defaultBreaks[defaultBreaks.length - 1] ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...prev, customBreaks: defaultBreaks };
|
|
||||||
});
|
|
||||||
}, [
|
|
||||||
styleConfig.classificationMethod,
|
|
||||||
styleConfig.segments,
|
|
||||||
styleConfig.property,
|
|
||||||
selectedRenderLayer,
|
|
||||||
currentJunctionCalData,
|
|
||||||
currentPipeCalData,
|
|
||||||
elevationRange,
|
|
||||||
diameterRange,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 初始化或调整自定义颜色数组长度
|
|
||||||
useEffect(() => {
|
|
||||||
const numColors = styleConfig.segments;
|
|
||||||
setStyleConfig((prev) => {
|
|
||||||
const prevColors = prev.customColors || [];
|
|
||||||
if (prevColors.length === numColors) return prev;
|
|
||||||
|
|
||||||
const newColors = [...prevColors];
|
|
||||||
const baseColors = RAINBOW_PALETTES[0].colors;
|
|
||||||
while (newColors.length < numColors) {
|
|
||||||
newColors.push(baseColors[newColors.length % baseColors.length]);
|
|
||||||
}
|
|
||||||
return { ...prev, customColors: newColors.slice(0, numColors) };
|
|
||||||
});
|
|
||||||
}, [styleConfig.segments]);
|
|
||||||
|
|
||||||
const getColorSetting = () => {
|
const getColorSetting = () => {
|
||||||
if (styleConfig.colorType === "single") {
|
if (styleConfig.colorType === "single") {
|
||||||
@@ -1624,9 +1562,21 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
const cachedStyleState = layerStyleStates.find(
|
const cachedStyleState = layerStyleStates.find(
|
||||||
(state) => state.layerId === layerId
|
(state) => state.layerId === layerId
|
||||||
);
|
);
|
||||||
// 只有在没有缓存时才清空属性
|
if (cachedStyleState) {
|
||||||
if (!cachedStyleState) {
|
setStyleConfig(cachedStyleState.styleConfig);
|
||||||
setStyleConfig((prev) => ({ ...prev, property: "" }));
|
} else {
|
||||||
|
setStyleConfig((prev) => ({
|
||||||
|
...prev,
|
||||||
|
property: "",
|
||||||
|
customBreaks:
|
||||||
|
prev.classificationMethod === "custom_breaks"
|
||||||
|
? getDefaultCustomBreaks(prev.segments, "", newLayer)
|
||||||
|
: prev.customBreaks,
|
||||||
|
customColors: getDefaultCustomColors(
|
||||||
|
prev.segments,
|
||||||
|
prev.customColors
|
||||||
|
),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -1647,7 +1597,15 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
<Select
|
<Select
|
||||||
value={styleConfig.property}
|
value={styleConfig.property}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setStyleConfig((prev) => ({ ...prev, property: e.target.value }));
|
const nextProperty = e.target.value;
|
||||||
|
setStyleConfig((prev) => ({
|
||||||
|
...prev,
|
||||||
|
property: nextProperty,
|
||||||
|
customBreaks:
|
||||||
|
prev.classificationMethod === "custom_breaks"
|
||||||
|
? getDefaultCustomBreaks(prev.segments, nextProperty)
|
||||||
|
: prev.customBreaks,
|
||||||
|
}));
|
||||||
}}
|
}}
|
||||||
disabled={!selectedRenderLayer}
|
disabled={!selectedRenderLayer}
|
||||||
>
|
>
|
||||||
@@ -1664,9 +1622,14 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
<Select
|
<Select
|
||||||
value={styleConfig.classificationMethod}
|
value={styleConfig.classificationMethod}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
const nextMethod = e.target.value;
|
||||||
setStyleConfig((prev) => ({
|
setStyleConfig((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
classificationMethod: e.target.value,
|
classificationMethod: nextMethod,
|
||||||
|
customBreaks:
|
||||||
|
nextMethod === "custom_breaks"
|
||||||
|
? getDefaultCustomBreaks(prev.segments, prev.property)
|
||||||
|
: prev.customBreaks,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -1695,7 +1658,14 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
segments: newSegments,
|
segments: newSegments,
|
||||||
customColors: newCustomColors,
|
customBreaks:
|
||||||
|
prev.classificationMethod === "custom_breaks"
|
||||||
|
? getDefaultCustomBreaks(newSegments, prev.property)
|
||||||
|
: prev.customBreaks,
|
||||||
|
customColors: getDefaultCustomColors(
|
||||||
|
newSegments,
|
||||||
|
newCustomColors
|
||||||
|
),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1782,6 +1752,10 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
|||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
colorType: newColorType,
|
colorType: newColorType,
|
||||||
|
adjustWidthByProperty:
|
||||||
|
newColorType === "single"
|
||||||
|
? true
|
||||||
|
: prev.adjustWidthByProperty,
|
||||||
customColors: newCustomColors,
|
customColors: newCustomColors,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,29 +47,21 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
schemeType = "burst_Analysis",
|
schemeType = "burst_Analysis",
|
||||||
}) => {
|
}) => {
|
||||||
const data = useData();
|
const data = useData();
|
||||||
if (!data) {
|
const hasTimelineState =
|
||||||
return <div>Loading...</div>; // 或其他占位符
|
data &&
|
||||||
}
|
data.setCurrentTime !== undefined &&
|
||||||
const {
|
data.currentTime !== undefined &&
|
||||||
currentTime,
|
data.selectedDate !== undefined &&
|
||||||
setCurrentTime,
|
data.setSelectedDate !== undefined;
|
||||||
selectedDate,
|
const currentTime = data?.currentTime ?? -1;
|
||||||
setSelectedDate,
|
const setCurrentTime = data?.setCurrentTime ?? ((_: any) => undefined);
|
||||||
setCurrentJunctionCalData,
|
const selectedDate = data?.selectedDate ?? new Date();
|
||||||
setCurrentPipeCalData,
|
const setSelectedDate = data?.setSelectedDate ?? ((_: any) => undefined);
|
||||||
junctionText,
|
const setCurrentJunctionCalData = data?.setCurrentJunctionCalData;
|
||||||
pipeText,
|
const setCurrentPipeCalData = data?.setCurrentPipeCalData;
|
||||||
} = data;
|
const junctionText = data?.junctionText ?? "";
|
||||||
if (
|
const pipeText = data?.pipeText ?? "";
|
||||||
setCurrentTime === undefined ||
|
|
||||||
currentTime === undefined ||
|
|
||||||
selectedDate === undefined ||
|
|
||||||
setSelectedDate === undefined
|
|
||||||
) {
|
|
||||||
return <div>Loading...</div>; // 或其他占位符
|
|
||||||
}
|
|
||||||
const { open } = useNotification();
|
const { open } = useNotification();
|
||||||
|
|
||||||
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
||||||
const [playInterval, setPlayInterval] = useState<number>(15000); // 毫秒
|
const [playInterval, setPlayInterval] = useState<number>(15000); // 毫秒
|
||||||
const [calculatedInterval, setCalculatedInterval] = useState<number>(15); // 分钟
|
const [calculatedInterval, setCalculatedInterval] = useState<number>(15); // 分钟
|
||||||
@@ -549,6 +541,10 @@ const Timeline: React.FC<TimelineProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!hasTimelineState) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable nodeRef={draggableRef} handle=".drag-handle">
|
<Draggable nodeRef={draggableRef} handle=".drag-handle">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
const map = useMap();
|
const map = useMap();
|
||||||
const data = useData();
|
const data = useData();
|
||||||
const { open } = useNotification();
|
const { open } = useNotification();
|
||||||
if (!data) return null;
|
|
||||||
const { currentTime, selectedDate, schemeName } = data;
|
|
||||||
const [activeTools, setActiveTools] = useState<string[]>([]);
|
const [activeTools, setActiveTools] = useState<string[]>([]);
|
||||||
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
||||||
const [showPropertyPanel, setShowPropertyPanel] = useState<boolean>(false);
|
const [showPropertyPanel, setShowPropertyPanel] = useState<boolean>(false);
|
||||||
@@ -49,6 +47,9 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
const [showHistoryPanel, setShowHistoryPanel] = useState<boolean>(false);
|
const [showHistoryPanel, setShowHistoryPanel] = useState<boolean>(false);
|
||||||
const [highlightLayer, setHighlightLayer] =
|
const [highlightLayer, setHighlightLayer] =
|
||||||
useState<VectorLayer<VectorSource> | null>(null);
|
useState<VectorLayer<VectorSource> | null>(null);
|
||||||
|
const currentTime = data?.currentTime;
|
||||||
|
const selectedDate = data?.selectedDate;
|
||||||
|
const schemeName = data?.schemeName;
|
||||||
|
|
||||||
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
|
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
|
||||||
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>([
|
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>([
|
||||||
@@ -721,6 +722,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
return {};
|
return {};
|
||||||
}, [highlightFeatures, computedProperties]);
|
}, [highlightFeatures, computedProperties]);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="absolute top-4 left-4 bg-white p-1 rounded-xl shadow-lg flex opacity-85 hover:opacity-100 transition-opacity">
|
<div className="absolute top-4 left-4 bg-white p-1 rounded-xl shadow-lg flex opacity-85 hover:opacity-100 transition-opacity">
|
||||||
|
|||||||
@@ -78,15 +78,33 @@ const MapContext = createContext<OlMap | undefined>(undefined);
|
|||||||
const DataContext = createContext<DataContextType | undefined>(undefined);
|
const DataContext = createContext<DataContextType | undefined>(undefined);
|
||||||
|
|
||||||
// 添加防抖函数
|
// 添加防抖函数
|
||||||
function debounce<F extends (...args: any[]) => any>(func: F, waitFor: number) {
|
type DebouncedFunction<F extends (...args: any[]) => any> = ((
|
||||||
|
...args: Parameters<F>
|
||||||
|
) => void) & {
|
||||||
|
cancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function debounce<F extends (...args: any[]) => any>(
|
||||||
|
func: F,
|
||||||
|
waitFor: number
|
||||||
|
): DebouncedFunction<F> {
|
||||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
return (...args: Parameters<F>): void => {
|
const debounced = (...args: Parameters<F>): void => {
|
||||||
if (timeout !== null) {
|
if (timeout !== null) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
timeout = setTimeout(() => func(...args), waitFor);
|
timeout = setTimeout(() => func(...args), waitFor);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
debounced.cancel = () => {
|
||||||
|
if (timeout !== null) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return debounced;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMap = () => {
|
export const useMap = () => {
|
||||||
@@ -187,20 +205,6 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
[number, number] | undefined
|
[number, number] | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
// 防抖更新函数
|
|
||||||
const debouncedUpdateData = useRef(
|
|
||||||
debounce(() => {
|
|
||||||
if (tileJunctionDataBuffer.current.length > 0) {
|
|
||||||
setJunctionData(tileJunctionDataBuffer.current);
|
|
||||||
tileJunctionDataBuffer.current = [];
|
|
||||||
}
|
|
||||||
if (tilePipeDataBuffer.current.length > 0) {
|
|
||||||
setPipeData(tilePipeDataBuffer.current);
|
|
||||||
tilePipeDataBuffer.current = [];
|
|
||||||
}
|
|
||||||
}, 100),
|
|
||||||
);
|
|
||||||
|
|
||||||
const setJunctionData = (newData: any[]) => {
|
const setJunctionData = (newData: any[]) => {
|
||||||
const uniqueNewData = newData.filter((item) => {
|
const uniqueNewData = newData.filter((item) => {
|
||||||
if (!item || !item.id) return false;
|
if (!item || !item.id) return false;
|
||||||
@@ -232,6 +236,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPipeData = (newData: any[]) => {
|
const setPipeData = (newData: any[]) => {
|
||||||
const uniqueNewData = newData.filter((item) => {
|
const uniqueNewData = newData.filter((item) => {
|
||||||
if (!item || !item.id) return false;
|
if (!item || !item.id) return false;
|
||||||
@@ -263,6 +268,28 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const debouncedUpdateDataRef = useRef<DebouncedFunction<() => void> | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
debouncedUpdateDataRef.current = debounce(() => {
|
||||||
|
if (tileJunctionDataBuffer.current.length > 0) {
|
||||||
|
setJunctionData(tileJunctionDataBuffer.current);
|
||||||
|
tileJunctionDataBuffer.current = [];
|
||||||
|
}
|
||||||
|
if (tilePipeDataBuffer.current.length > 0) {
|
||||||
|
setPipeData(tilePipeDataBuffer.current);
|
||||||
|
tilePipeDataBuffer.current = [];
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
debouncedUpdateDataRef.current?.cancel();
|
||||||
|
debouncedUpdateDataRef.current = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
// 配置地图数据源、图层和样式
|
// 配置地图数据源、图层和样式
|
||||||
const defaultFlatStyle: FlatStyleLike = config.MAP_DEFAULT_STYLE;
|
const defaultFlatStyle: FlatStyleLike = config.MAP_DEFAULT_STYLE;
|
||||||
// 定义 SCADA 图层的样式函数,根据 type 字段选择不同图标
|
// 定义 SCADA 图层的样式函数,根据 type 字段选择不同图标
|
||||||
@@ -520,7 +547,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
uniqueData.forEach((item) =>
|
uniqueData.forEach((item) =>
|
||||||
tileJunctionDataBuffer.current.push(item),
|
tileJunctionDataBuffer.current.push(item),
|
||||||
);
|
);
|
||||||
debouncedUpdateData.current();
|
debouncedUpdateDataRef.current?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -600,7 +627,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
|||||||
const uniqueData = Array.from(data.values());
|
const uniqueData = Array.from(data.values());
|
||||||
if (uniqueData.length > 0) {
|
if (uniqueData.length > 0) {
|
||||||
uniqueData.forEach((item) => tilePipeDataBuffer.current.push(item));
|
uniqueData.forEach((item) => tilePipeDataBuffer.current.push(item));
|
||||||
debouncedUpdateData.current();
|
debouncedUpdateDataRef.current?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -29,26 +29,20 @@ type ColorModeContextProviderProps = {
|
|||||||
export const ColorModeContextProvider: React.FC<
|
export const ColorModeContextProvider: React.FC<
|
||||||
PropsWithChildren<ColorModeContextProviderProps>
|
PropsWithChildren<ColorModeContextProviderProps>
|
||||||
> = ({ children, defaultMode }) => {
|
> = ({ children, defaultMode }) => {
|
||||||
const [isMounted, setIsMounted] = useState(false);
|
|
||||||
const [mode, setMode] = useState(defaultMode || "light");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const systemTheme = useMediaQuery(`(prefers-color-scheme: dark)`);
|
const systemTheme = useMediaQuery(`(prefers-color-scheme: dark)`);
|
||||||
|
const [storedMode, setStoredMode] = useState<string | null>(() => {
|
||||||
useEffect(() => {
|
if (typeof window === "undefined") {
|
||||||
if (isMounted) {
|
return defaultMode ?? null;
|
||||||
const theme = Cookies.get("theme") || (systemTheme ? "dark" : "light");
|
|
||||||
setMode(theme);
|
|
||||||
}
|
}
|
||||||
}, [isMounted, systemTheme]);
|
|
||||||
|
return Cookies.get("theme") || defaultMode || null;
|
||||||
|
});
|
||||||
|
const mode = storedMode || (systemTheme ? "dark" : "light");
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
const nextTheme = mode === "light" ? "dark" : "light";
|
const nextTheme = mode === "light" ? "dark" : "light";
|
||||||
|
|
||||||
setMode(nextTheme);
|
setStoredMode(nextTheme);
|
||||||
Cookies.set("theme", nextTheme);
|
Cookies.set("theme", nextTheme);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user