修复lint errors

This commit is contained in:
JIANG
2026-03-10 17:52:00 +08:00
parent 62914f80c3
commit 73201ae44e
11 changed files with 343 additions and 378 deletions
@@ -1,6 +1,6 @@
"use client";
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import {
Box,
Typography,
@@ -115,7 +115,7 @@ const EmptyState = () => (
const LocationResults: React.FC<Props> = ({ result }) => {
const map = useMap();
const [highlightLayer, setHighlightLayer] = useState<VectorLayer<VectorSource> | null>(null);
const highlightLayerRef = useRef<VectorLayer<VectorSource> | null>(null);
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
const candidatePipes = useMemo<BurstCandidate[]>(() => {
@@ -128,13 +128,13 @@ const LocationResults: React.FC<Props> = ({ result }) => {
return base;
}, [result]);
const allCandidatePipeIds = useMemo<string[]>(() => {
const allCandidatePipeIds = (() => {
const ids = candidatePipes.map((item) => item.pipe_id);
if (result?.located_pipe) {
ids.unshift(result.located_pipe);
}
return Array.from(new Set(ids.filter(Boolean)));
}, [candidatePipes, result?.located_pipe]);
})();
useEffect(() => {
if (!map) return;
@@ -159,19 +159,20 @@ const LocationResults: React.FC<Props> = ({ result }) => {
},
});
map.addLayer(layer);
setHighlightLayer(layer);
highlightLayerRef.current = layer;
return () => {
highlightLayerRef.current = null;
map.removeLayer(layer);
};
}, [map]);
useEffect(() => {
const source = highlightLayer?.getSource();
const source = highlightLayerRef.current?.getSource();
if (!source) return;
source.clear();
highlightFeatures.forEach((feature) => source.addFeature(feature));
}, [highlightFeatures, highlightLayer]);
}, [highlightFeatures]);
const locatePipes = async (pipeIds: string[]) => {
if (!pipeIds.length || !map) return;
@@ -1,6 +1,6 @@
"use client";
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import {
Box,
Typography,
@@ -41,8 +41,7 @@ interface LocationResultsProps {
const LocationResults: React.FC<LocationResultsProps> = ({
results = [],
}) => {
const [highlightLayer, setHighlightLayer] =
useState<VectorLayer<VectorSource> | null>(null);
const highlightLayerRef = useRef<VectorLayer<VectorSource> | null>(null);
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
const map = useMap();
@@ -145,19 +144,17 @@ const LocationResults: React.FC<LocationResultsProps> = ({
});
map.addLayer(highlightLayer);
setHighlightLayer(highlightLayer);
highlightLayerRef.current = highlightLayer;
return () => {
highlightLayerRef.current = null;
map.removeLayer(highlightLayer);
};
}, [map]);
// 高亮要素的函数
useEffect(() => {
if (!highlightLayer) {
return;
}
const source = highlightLayer.getSource();
const source = highlightLayerRef.current?.getSource();
if (!source) {
return;
}
@@ -169,7 +166,7 @@ const LocationResults: React.FC<LocationResultsProps> = ({
source.addFeature(feature);
}
});
}, [highlightFeatures, highlightLayer]);
}, [highlightFeatures]);
// 取第一条记录或空对象
const result = results.length > 0 ? results[0] : null;
@@ -1090,7 +1090,7 @@ const ValveIsolation: React.FC<ValveIsolationProps> = ({
</>
) : (
<Alert severity="info" variant="outlined">
2"扩大搜索"
2
</Alert>
)}
</Box>
@@ -1,6 +1,6 @@
"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 WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
@@ -27,7 +27,6 @@ import dayjs from "dayjs";
import { PlayArrow, Pause, Stop, Refresh } from "@mui/icons-material";
import { TbArrowBackUp, TbArrowForwardUp } from "react-icons/tb";
import { FiSkipBack, FiSkipForward } from "react-icons/fi";
import { useData } from "@components/olmap/core/MapComponent";
import { config, NETWORK_NAME } from "@/config/config";
import { apiFetch } from "@/lib/apiFetch";
import { useMap } from "@components/olmap/core/MapComponent";
@@ -63,10 +62,6 @@ interface TimelineProps {
const Timeline: React.FC<TimelineProps> = ({
disableDateSelection = false,
}) => {
const data = useData();
if (!data) {
return <div>Loading...</div>; // 或其他占位符
}
const { open } = useNotification();
const {
predictionResults,
@@ -79,7 +74,6 @@ const Timeline: React.FC<TimelineProps> = ({
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [playInterval, setPlayInterval] = useState<number>(5000); // 毫秒
const [isPredicting, setIsPredicting] = useState<boolean>(false);
const [pipeLayer, setPipeLayer] = useState<WebGLVectorTileLayer | null>(null);
// 使用 ref 存储当前的健康数据,供事件监听器读取,避免重复绑定
const healthDataRef = useRef<Map<string, number>>(new Map());
@@ -228,10 +222,21 @@ const Timeline: React.FC<TimelineProps> = ({
clearTimeout(debounceRef.current);
}
};
}, [pipeLayer]);
}, []);
// 获取地图实例
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 中获取生存概率
const getSurvivalProbabilityAtYear = useCallback(
@@ -362,21 +367,6 @@ const Timeline: React.FC<TimelineProps> = ({
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(() => {
if (predictionResults.length > 0 && pipeLayer) {
@@ -31,9 +31,7 @@ import { useMap } from "../MapComponent";
const DrawPanel: React.FC = () => {
const map = useMap();
const [activeTool, setActiveTool] = useState<string>("pan");
const [drawLayer, setDrawLayer] = useState<VectorLayer<VectorSource> | null>(
null
);
const drawLayerRef = useRef<VectorLayer<VectorSource> | null>(null);
const [drawnFeatures, setDrawnFeatures] = useState<Feature<Geometry>[]>([]);
const [history, setHistory] = useState<{
stack: Feature<Geometry>[][];
@@ -79,13 +77,14 @@ const DrawPanel: React.FC = () => {
});
map.addLayer(drawVectorLayer);
setDrawLayer(drawVectorLayer);
drawLayerRef.current = drawVectorLayer;
return () => {
if (drawInteractionRef.current && map) {
map.removeInteraction(drawInteractionRef.current);
drawInteractionRef.current = null;
}
drawLayerRef.current = null;
map.removeLayer(drawVectorLayer);
};
}, [map, drawInteractionRef]);
@@ -110,6 +109,7 @@ const DrawPanel: React.FC = () => {
type: GeometryType,
geometryFunction?: GeometryFunction
) => {
const drawLayer = drawLayerRef.current;
if (!drawLayer) return;
if (!map) return;
@@ -285,6 +285,7 @@ const DrawPanel: React.FC = () => {
// 删除所有绘制的要素
const handleDelete = () => {
const drawLayer = drawLayerRef.current;
if (!drawLayer) return;
const source = drawLayer.getSource();
@@ -301,6 +302,7 @@ const DrawPanel: React.FC = () => {
// 更新绘图图层
const updateDrawLayer = (features: Feature<Geometry>[]) => {
const drawLayer = drawLayerRef.current;
if (!drawLayer) return;
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 { Checkbox, FormControlLabel } from "@mui/material";
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
@@ -18,15 +18,12 @@ interface LayerItem {
const LayerControl: React.FC = () => {
const map = useMap();
const data = useData();
if (!data) return;
const {
deckLayer,
isContourLayerAvailable,
isWaterflowLayerAvailable,
setShowWaterflowLayer,
setShowContourLayer,
} = data;
const [layerItems, setLayerItems] = useState<LayerItem[]>([]);
const [refreshKey, setRefreshKey] = useState(0);
const deckLayer = data?.deckLayer;
const isContourLayerAvailable = data?.isContourLayerAvailable;
const isWaterflowLayerAvailable = data?.isWaterflowLayerAvailable;
const setShowWaterflowLayer = data?.setShowWaterflowLayer;
const setShowContourLayer = data?.setShowContourLayer;
const layerOrder = [
"junctions",
@@ -40,16 +37,12 @@ const LayerControl: React.FC = () => {
"junctionContourLayer",
];
// 更新图层列表
const updateLayers = useCallback(() => {
if (!map || !data) return;
const layerItems = useMemo(() => {
if (!map || !data) return [];
const items: LayerItem[] = [];
// 1. 获取 OpenLayers 图层
const mapLayers = map.getLayers().getArray();
mapLayers.forEach((layer) => {
// 筛选特定类型的 OpenLayers 图层
map.getLayers().getArray().forEach((layer) => {
if (
layer instanceof WebGLVectorTileLayer ||
layer instanceof VectorTileLayer ||
@@ -57,7 +50,6 @@ const LayerControl: React.FC = () => {
) {
const value = layer.get("value");
const name = layer.get("name");
// 只有设置了 value (作为 ID) 的图层才会被纳入控制
if (value) {
items.push({
id: value,
@@ -70,66 +62,56 @@ const LayerControl: React.FC = () => {
}
});
// 2. 获取 DeckLayer 中的子图层
if (deckLayer && deckLayer instanceof DeckLayer) {
const deckLayers = deckLayer.getDeckLayers();
deckLayers.forEach((layer: any) => {
if (layer && layer.id) {
// 仅处理 junctionContourLayer 和 waterflowLayer
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,
});
deckLayer.getDeckLayers().forEach((layer: any) => {
if (!layer?.id) return;
if (layer.id !== "junctionContourLayer" && layer.id !== "waterflowLayer") {
return;
}
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,
});
});
}
// 过滤并排序
const sortedItems = items
return items
.filter((item) => layerOrder.includes(item.id))
.sort((a, b) => {
const indexA = layerOrder.indexOf(a.id);
const indexB = layerOrder.indexOf(b.id);
return indexA - indexB;
});
setLayerItems(sortedItems);
}, [map, deckLayer, isWaterflowLayerAvailable, isContourLayerAvailable]);
.sort((a, b) => layerOrder.indexOf(a.id) - layerOrder.indexOf(b.id));
}, [
map,
data,
deckLayer,
isContourLayerAvailable,
isWaterflowLayerAvailable,
refreshKey,
]);
useEffect(() => {
updateLayers();
if (!map) return;
if (map) {
const layerCollection = map.getLayers();
layerCollection.on("change:length", updateLayers);
}
const layerCollection = map.getLayers();
const handleLayerChange = () => {
setRefreshKey((prev) => prev + 1);
};
layerCollection.on("change:length", handleLayerChange);
return () => {
if (map) {
map.getLayers().un("change:length", updateLayers);
}
map.getLayers().un("change:length", handleLayerChange);
};
}, [map, updateLayers]);
}, [map]);
const handleVisibilityChange = (item: LayerItem, checked: boolean) => {
if (item.type === "ol") {
@@ -142,10 +124,7 @@ const LayerControl: React.FC = () => {
setShowWaterflowLayer && setShowWaterflowLayer(checked);
}
}
setLayerItems((prev) =>
prev.map((i) => (i.id === item.id ? { ...i, visible: checked } : i)),
);
setRefreshKey((prev) => prev + 1);
};
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图标和组件
import ColorLensIcon from "@mui/icons-material/ColorLens";
@@ -180,26 +180,21 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
}) => {
const map = useMap();
const data = useData();
if (!data) {
return <div>Loading...</div>; // 或其他占位符
}
const {
currentJunctionCalData,
currentPipeCalData,
junctionText,
pipeText,
setShowJunctionTextLayer,
setShowPipeTextLayer,
setShowJunctionId,
setShowPipeId,
setContourLayerAvailable,
setWaterflowLayerAvailable,
setJunctionText,
setPipeText,
setContours,
diameterRange,
elevationRange,
} = data;
const currentJunctionCalData = data?.currentJunctionCalData;
const currentPipeCalData = data?.currentPipeCalData;
const junctionText = data?.junctionText ?? "";
const pipeText = data?.pipeText ?? "";
const setShowJunctionTextLayer = data?.setShowJunctionTextLayer;
const setShowPipeTextLayer = data?.setShowPipeTextLayer;
const setShowJunctionId = data?.setShowJunctionId;
const setShowPipeId = data?.setShowPipeId;
const setContourLayerAvailable = data?.setContourLayerAvailable;
const setWaterflowLayerAvailable = data?.setWaterflowLayerAvailable;
const setJunctionText = data?.setJunctionText;
const setPipeText = data?.setPipeText;
const setContours = data?.setContours;
const diameterRange = data?.diameterRange;
const elevationRange = data?.elevationRange;
const unitHeadlossRange = [0, 5];
@@ -213,9 +208,6 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
const [renderLayers, setRenderLayers] = useState<WebGLVectorTileLayer[]>([]);
const [selectedRenderLayer, setSelectedRenderLayer] =
useState<WebGLVectorTileLayer>();
const [availableProperties, setAvailableProperties] = useState<
{ name: string; value: string }[]
>([]);
const [styleConfig, setStyleConfig] = useState<StyleConfig>({
property: "",
classificationMethod: "pretty_breaks",
@@ -237,6 +229,74 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
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(
(segments: number): string[] => {
@@ -278,63 +338,56 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
[styleConfig.rainbowPaletteIndex]
);
// 保存当前图层的样式状态
const saveLayerStyle = useCallback(
(
layerId?: string,
newLegendConfig?: LegendStyleConfig,
overrideStyleConfig?: StyleConfig
) => {
const currentStyleConfig = overrideStyleConfig || styleConfig;
const saveLayerStyle = (
layerId?: string,
newLegendConfig?: LegendStyleConfig,
overrideStyleConfig?: StyleConfig
) => {
const currentStyleConfig = overrideStyleConfig || styleConfig;
if (!currentStyleConfig.property) {
console.warn("无法保存样式:缺少必要的图层或样式配置");
return;
if (!currentStyleConfig.property) {
console.warn("无法保存样式:缺少必要的图层或样式配置");
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 = {
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]
);
return [...prev, newStyleState];
});
};
// 设置分类样式参数,触发样式应用
const setStyleState = () => {
if (!selectedRenderLayer) return;
@@ -787,7 +840,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
};
// 重置样式
const resetStyle = useCallback(() => {
const resetStyle = () => {
if (!selectedRenderLayer) return;
// 重置 WebGL 图层样式
const defaultFlatStyle: FlatStyleLike = config.MAP_DEFAULT_STYLE;
@@ -815,7 +868,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
setWaterflowLayerAvailable && setWaterflowLayerAvailable(false);
}
}
}, [selectedRenderLayer]);
};
// 更新当前 VectorTileSource 中的所有缓冲要素属性
const updateVectorTileSource = (property: string, data: any[]) => {
if (!map) return;
@@ -857,7 +910,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
});
};
// 新增事件,监听 VectorTileSource 的 tileloadend 事件,为新增瓦片数据动态更新要素属性
const [tileLoadListeners, setTileLoadListeners] = useState<
const tileLoadListenersRef = useRef<
Map<VectorTileSource, (event: any) => void>
>(new Map());
@@ -879,8 +932,6 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
dataMap.set(d.ID, d.value || 0);
});
// 新增监听器并保存
const newListeners = new Map<VectorTileSource, (event: any) => void>();
const listener = (event: any) => {
try {
if (event.tile instanceof VectorTile) {
@@ -906,8 +957,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
};
vectorTileSource.on("tileloadend", listener);
newListeners.set(vectorTileSource, listener);
setTileLoadListeners(newListeners);
tileLoadListenersRef.current.set(vectorTileSource, listener);
};
// 新增函数:取消对应 layerId 已添加的 on 事件
const removeVectorTileSourceLoadedEvent = (layerId: string) => {
@@ -918,14 +968,10 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
.map((layer) => layer.getSource() as VectorTileSource)
.filter((source) => source)[0];
if (!vectorTileSource) return;
const listener = tileLoadListeners.get(vectorTileSource);
const listener = tileLoadListenersRef.current.get(vectorTileSource);
if (listener) {
vectorTileSource.un("tileloadend", listener);
setTileLoadListeners((prev) => {
const newMap = new Map(prev);
newMap.delete(vectorTileSource);
return newMap;
});
tileLoadListenersRef.current.delete(vectorTileSource);
}
};
@@ -1044,117 +1090,9 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
updateVisibleLayers();
}, [map]);
// 获取选中图层的属性,并检查是否有已缓存的样式状态
useEffect(() => {
// 如果没有矢量图层或没有选中图层,清空属性列表
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]);
if (!data) {
return <div>Loading...</div>;
}
const getColorSetting = () => {
if (styleConfig.colorType === "single") {
@@ -1624,9 +1562,21 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
const cachedStyleState = layerStyleStates.find(
(state) => state.layerId === layerId
);
// 只有在没有缓存时才清空属性
if (!cachedStyleState) {
setStyleConfig((prev) => ({ ...prev, property: "" }));
if (cachedStyleState) {
setStyleConfig(cachedStyleState.styleConfig);
} 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
value={styleConfig.property}
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}
>
@@ -1664,9 +1622,14 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
<Select
value={styleConfig.classificationMethod}
onChange={(e) => {
const nextMethod = e.target.value;
setStyleConfig((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 {
...prev,
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 {
...prev,
colorType: newColorType,
adjustWidthByProperty:
newColorType === "single"
? true
: prev.adjustWidthByProperty,
customColors: newCustomColors,
};
});
+18 -22
View File
@@ -47,29 +47,21 @@ const Timeline: React.FC<TimelineProps> = ({
schemeType = "burst_Analysis",
}) => {
const data = useData();
if (!data) {
return <div>Loading...</div>; // 或其他占位符
}
const {
currentTime,
setCurrentTime,
selectedDate,
setSelectedDate,
setCurrentJunctionCalData,
setCurrentPipeCalData,
junctionText,
pipeText,
} = data;
if (
setCurrentTime === undefined ||
currentTime === undefined ||
selectedDate === undefined ||
setSelectedDate === undefined
) {
return <div>Loading...</div>; // 或其他占位符
}
const hasTimelineState =
data &&
data.setCurrentTime !== undefined &&
data.currentTime !== undefined &&
data.selectedDate !== undefined &&
data.setSelectedDate !== undefined;
const currentTime = data?.currentTime ?? -1;
const setCurrentTime = data?.setCurrentTime ?? ((_: any) => undefined);
const selectedDate = data?.selectedDate ?? new Date();
const setSelectedDate = data?.setSelectedDate ?? ((_: any) => undefined);
const setCurrentJunctionCalData = data?.setCurrentJunctionCalData;
const setCurrentPipeCalData = data?.setCurrentPipeCalData;
const junctionText = data?.junctionText ?? "";
const pipeText = data?.pipeText ?? "";
const { open } = useNotification();
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [playInterval, setPlayInterval] = useState<number>(15000); // 毫秒
const [calculatedInterval, setCalculatedInterval] = useState<number>(15); // 分钟
@@ -549,6 +541,10 @@ const Timeline: React.FC<TimelineProps> = ({
}
};
if (!hasTimelineState) {
return <div>Loading...</div>;
}
return (
<Draggable nodeRef={draggableRef} handle=".drag-handle">
<div
@@ -39,8 +39,6 @@ const Toolbar: React.FC<ToolbarProps> = ({
const map = useMap();
const data = useData();
const { open } = useNotification();
if (!data) return null;
const { currentTime, selectedDate, schemeName } = data;
const [activeTools, setActiveTools] = useState<string[]>([]);
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
const [showPropertyPanel, setShowPropertyPanel] = useState<boolean>(false);
@@ -49,6 +47,9 @@ const Toolbar: React.FC<ToolbarProps> = ({
const [showHistoryPanel, setShowHistoryPanel] = useState<boolean>(false);
const [highlightLayer, setHighlightLayer] =
useState<VectorLayer<VectorSource> | null>(null);
const currentTime = data?.currentTime;
const selectedDate = data?.selectedDate;
const schemeName = data?.schemeName;
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>([
@@ -721,6 +722,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
return {};
}, [highlightFeatures, computedProperties]);
if (!data) {
return null;
}
return (
<>
<div className="absolute top-4 left-4 bg-white p-1 rounded-xl shadow-lg flex opacity-85 hover:opacity-100 transition-opacity">
+45 -18
View File
@@ -78,15 +78,33 @@ const MapContext = createContext<OlMap | 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;
return (...args: Parameters<F>): void => {
const debounced = (...args: Parameters<F>): void => {
if (timeout !== null) {
clearTimeout(timeout);
}
timeout = setTimeout(() => func(...args), waitFor);
};
debounced.cancel = () => {
if (timeout !== null) {
clearTimeout(timeout);
timeout = null;
}
};
return debounced;
}
export const useMap = () => {
@@ -187,20 +205,6 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
[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 uniqueNewData = newData.filter((item) => {
if (!item || !item.id) return false;
@@ -232,6 +236,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
});
}
};
const setPipeData = (newData: any[]) => {
const uniqueNewData = newData.filter((item) => {
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;
// 定义 SCADA 图层的样式函数,根据 type 字段选择不同图标
@@ -520,7 +547,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
uniqueData.forEach((item) =>
tileJunctionDataBuffer.current.push(item),
);
debouncedUpdateData.current();
debouncedUpdateDataRef.current?.();
}
}
} catch (error) {
@@ -600,7 +627,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
const uniqueData = Array.from(data.values());
if (uniqueData.length > 0) {
uniqueData.forEach((item) => tilePipeDataBuffer.current.push(item));
debouncedUpdateData.current();
debouncedUpdateDataRef.current?.();
}
}
} catch (error) {
+8 -14
View File
@@ -29,26 +29,20 @@ type ColorModeContextProviderProps = {
export const ColorModeContextProvider: React.FC<
PropsWithChildren<ColorModeContextProviderProps>
> = ({ children, defaultMode }) => {
const [isMounted, setIsMounted] = useState(false);
const [mode, setMode] = useState(defaultMode || "light");
useEffect(() => {
setIsMounted(true);
}, []);
const systemTheme = useMediaQuery(`(prefers-color-scheme: dark)`);
useEffect(() => {
if (isMounted) {
const theme = Cookies.get("theme") || (systemTheme ? "dark" : "light");
setMode(theme);
const [storedMode, setStoredMode] = useState<string | null>(() => {
if (typeof window === "undefined") {
return defaultMode ?? null;
}
}, [isMounted, systemTheme]);
return Cookies.get("theme") || defaultMode || null;
});
const mode = storedMode || (systemTheme ? "dark" : "light");
const toggleTheme = () => {
const nextTheme = mode === "light" ? "dark" : "light";
setMode(nextTheme);
setStoredMode(nextTheme);
Cookies.set("theme", nextTheme);
};