修复 lint warnings

This commit is contained in:
JIANG
2026-03-10 18:15:11 +08:00
parent 73201ae44e
commit f0f9d3f4f9
19 changed files with 225 additions and 200 deletions
+8
View File
@@ -1,6 +1,14 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: "standalone", output: "standalone",
images: {
remotePatterns: [
{
protocol: "https",
hostname: "refine.ams3.cdn.digitaloceanspaces.com",
},
],
},
turbopack: { turbopack: {
rules: { rules: {
"*.svg": { "*.svg": {
+4 -1
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import Image from "next/image";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Container from "@mui/material/Container"; import Container from "@mui/material/Container";
@@ -38,10 +39,12 @@ export default function Login() {
</Button> </Button>
<Typography align="center" color={"text.secondary"} fontSize="12px"> <Typography align="center" color={"text.secondary"} fontSize="12px">
Powered by Powered by
<img <Image
style={{ padding: "0 5px" }} style={{ padding: "0 5px" }}
alt="Keycloak" alt="Keycloak"
src="https://refine.ams3.cdn.digitaloceanspaces.com/superplate-auth-icons%2Fkeycloak.svg" src="https://refine.ams3.cdn.digitaloceanspaces.com/superplate-auth-icons%2Fkeycloak.svg"
width={18}
height={18}
/> />
Keycloak Keycloak
</Typography> </Typography>
@@ -61,6 +61,39 @@ const AnalysisParameters: React.FC = () => {
duration > 0 && duration > 0 &&
schemeName.trim() !== ""; schemeName.trim() !== "";
// 地图点击选择要素事件处理函数
const handleMapClickSelectFeatures = useCallback(
async (event: { coordinate: number[] }) => {
if (!map) return;
const feature = await mapClickSelectFeatures(event, map);
const layer = feature?.getId()?.toString().split(".")[0];
if (!feature) return;
if (
feature.getGeometry()?.getType() === "Point" ||
(layer !== "geo_pipes_mat" && layer !== "geo_pipes")
) {
open?.({
type: "error",
message: "请选择线类型管道要素。",
});
return;
}
const featureId = feature.getProperties().id;
setHighlightFeatures((prev) => {
const existingIndex = prev.findIndex(
(f) => f.getProperties().id === featureId,
);
if (existingIndex !== -1) {
return prev.filter((_, i) => i !== existingIndex);
} else {
return [...prev, feature];
}
});
},
[map, open],
);
// 初始化管道图层和高亮图层 // 初始化管道图层和高亮图层
useEffect(() => { useEffect(() => {
if (!map) return; if (!map) return;
@@ -137,7 +170,7 @@ const AnalysisParameters: React.FC = () => {
map.removeLayer(highlightLayer); map.removeLayer(highlightLayer);
map.un("click", handleMapClickSelectFeatures); map.un("click", handleMapClickSelectFeatures);
}; };
}, [map]); }, [map, handleMapClickSelectFeatures]);
// 高亮要素的函数 // 高亮要素的函数
useEffect(() => { useEffect(() => {
if (!highlightLayer) { if (!highlightLayer) {
@@ -155,7 +188,7 @@ const AnalysisParameters: React.FC = () => {
source.addFeature(feature); source.addFeature(feature);
} }
}); });
}, [highlightFeatures]); }, [highlightFeatures, highlightLayer]);
// 同步高亮要素和爆管点信息 // 同步高亮要素和爆管点信息
useEffect(() => { useEffect(() => {
@@ -185,42 +218,6 @@ const AnalysisParameters: React.FC = () => {
}); });
}, [highlightFeatures]); }, [highlightFeatures]);
// 地图点击选择要素事件处理函数
const handleMapClickSelectFeatures = useCallback(
async (event: { coordinate: number[] }) => {
if (!map) return;
const feature = await mapClickSelectFeatures(event, map);
const layer = feature?.getId()?.toString().split(".")[0];
if (!feature) return;
if (
feature.getGeometry()?.getType() === "Point" ||
(layer !== "geo_pipes_mat" && layer !== "geo_pipes")
) {
// 点类型几何不处理
open?.({
type: "error",
message: "请选择线类型管道要素。",
});
return;
}
const featureId = feature.getProperties().id;
setHighlightFeatures((prev) => {
const existingIndex = prev.findIndex(
(f) => f.getProperties().id === featureId,
);
if (existingIndex !== -1) {
// 如果已存在,移除
return prev.filter((_, i) => i !== existingIndex);
} else {
// 如果不存在,添加
return [...prev, feature];
}
});
},
[map],
);
// 开始选择管道 // 开始选择管道
const handleStartSelection = () => { const handleStartSelection = () => {
if (!map) return; if (!map) return;
@@ -299,7 +299,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
source.addFeature(feature); source.addFeature(feature);
} }
}); });
}, [highlightFeatures]); }, [highlightFeatures, highlightLayer]);
return ( return (
<> <>
@@ -59,6 +59,32 @@ const AnalysisParameters: React.FC = () => {
); );
}, [network, startTime, sourceNode, concentration, duration, schemeName]); }, [network, startTime, sourceNode, concentration, duration, schemeName]);
const handleMapClickSelectFeatures = useCallback(
async (event: { coordinate: number[] }) => {
if (!map) return;
const feature = await mapClickSelectFeatures(event, map);
if (!feature) return;
const layerId = feature.getId()?.toString().split(".")[0] || "";
const isJunction = layerId.includes("junction");
if (!isJunction) {
open?.({
type: "error",
message: "请选择节点类型要素作为污染源。",
});
return;
}
const id = feature.getProperties().id;
if (!id) return;
setSourceNode(id);
setHighlightFeature(feature);
setIsSelecting(false);
map.un("click", handleMapClickSelectFeatures);
},
[map, open],
);
useEffect(() => { useEffect(() => {
if (!map) return; if (!map) return;
@@ -106,7 +132,7 @@ const AnalysisParameters: React.FC = () => {
map.removeLayer(layer); map.removeLayer(layer);
map.un("click", handleMapClickSelectFeatures); map.un("click", handleMapClickSelectFeatures);
}; };
}, [map]); }, [map, handleMapClickSelectFeatures]);
useEffect(() => { useEffect(() => {
if (!highlightLayer) return; if (!highlightLayer) return;
@@ -118,32 +144,6 @@ const AnalysisParameters: React.FC = () => {
} }
}, [highlightFeature, highlightLayer]); }, [highlightFeature, highlightLayer]);
const handleMapClickSelectFeatures = useCallback(
async (event: { coordinate: number[] }) => {
if (!map) return;
const feature = await mapClickSelectFeatures(event, map);
if (!feature) return;
const layerId = feature.getId()?.toString().split(".")[0] || "";
const isJunction = layerId.includes("junction");
if (!isJunction) {
open?.({
type: "error",
message: "请选择节点类型要素作为污染源。",
});
return;
}
const id = feature.getProperties().id;
if (!id) return;
setSourceNode(id);
setHighlightFeature(feature);
setIsSelecting(false);
map.un("click", handleMapClickSelectFeatures);
},
[map, open],
);
const handleStartSelection = () => { const handleStartSelection = () => {
if (!map) return; if (!map) return;
setIsSelecting(true); setIsSelecting(true);
@@ -55,7 +55,7 @@ const DMALeakDetectionPanel: React.FC = () => {
const drawerWidth = 450; const drawerWidth = 450;
const panelTitle = "DMA 漏损识别"; const panelTitle = "DMA 漏损识别";
const activeAreas = loadedResult?.areas ?? []; const activeAreas = useMemo(() => loadedResult?.areas ?? [], [loadedResult]);
const legendColors = useMemo( const legendColors = useMemo(
() => activeAreas.map((area) => getAreaColor(area.area_id)), () => activeAreas.map((area) => getAreaColor(area.area_id)),
[activeAreas], [activeAreas],
@@ -55,6 +55,54 @@ const AnalysisParameters: React.FC = () => {
const [highlightLayer, setHighlightLayer] = useState<VectorLayer<VectorSource> | null>(null); const [highlightLayer, setHighlightLayer] = useState<VectorLayer<VectorSource> | null>(null);
// Map click handler
const handleMapClickSelectFeatures = useCallback(
async (event: { coordinate: number[] }) => {
if (!map || selectionMode === 'none') return;
const feature = await mapClickSelectFeatures(event, map);
if (!feature) return;
const layer = feature.getId()?.toString().split(".")[0];
const featureId = feature.getProperties().id;
if (selectionMode === 'valve') {
if (layer !== 'geo_valves') {
open?.({
type: "error",
message: "请选择阀门要素",
});
return;
}
setValves((prev) => {
if (prev.some((v) => v.id === featureId)) {
open?.({
type: "error",
message: "该阀门已添加",
});
return prev;
}
return [...prev, { id: featureId, k: 1.0, feature }];
});
} else if (selectionMode === 'drainage') {
if (layer !== 'geo_junctions') {
open?.({
type: "error",
message: "请选择节点要素作为排水点",
});
return;
}
setDrainageNode(featureId);
setDrainageFeature(feature);
setSelectionMode('none');
map.un("click", handleMapClickSelectFeatures);
}
},
[map, selectionMode, open]
);
// Initialize highlight layer // Initialize highlight layer
useEffect(() => { useEffect(() => {
if (!map) return; if (!map) return;
@@ -103,7 +151,7 @@ const AnalysisParameters: React.FC = () => {
map.removeLayer(layer); map.removeLayer(layer);
map.un("click", handleMapClickSelectFeatures); map.un("click", handleMapClickSelectFeatures);
}; };
}, [map]); }, [map, handleMapClickSelectFeatures]);
// Update highlight layer features // Update highlight layer features
useEffect(() => { useEffect(() => {
@@ -134,54 +182,6 @@ const AnalysisParameters: React.FC = () => {
}, [highlightLayer, valves, drainageFeature]); }, [highlightLayer, valves, drainageFeature]);
// Map click handler
const handleMapClickSelectFeatures = useCallback(
async (event: { coordinate: number[] }) => {
if (!map || selectionMode === 'none') return;
const feature = await mapClickSelectFeatures(event, map);
if (!feature) return;
const layer = feature.getId()?.toString().split(".")[0];
const featureId = feature.getProperties().id;
if (selectionMode === 'valve') {
if (layer !== 'geo_valves') {
open?.({
type: "error",
message: "请选择阀门要素",
});
return;
}
setValves((prev) => {
if (prev.some((v) => v.id === featureId)) {
open?.({
type: "error",
message: "该阀门已添加",
});
return prev;
}
return [...prev, { id: featureId, k: 1.0, feature }]; // Default k=1.0? User can change.
});
} else if (selectionMode === 'drainage') {
if (layer !== 'geo_junctions') {
open?.({
type: "error",
message: "请选择节点要素作为排水点",
});
return;
}
setDrainageNode(featureId);
setDrainageFeature(feature);
setSelectionMode('none'); // Auto exit selection after picking one
map.un("click", handleMapClickSelectFeatures);
}
},
[map, selectionMode, open]
);
// Bind click event based on selection mode // Bind click event based on selection mode
useEffect(() => { useEffect(() => {
if (!map || selectionMode === "none") return; if (!map || selectionMode === "none") return;
@@ -117,7 +117,7 @@ const Timeline: React.FC<TimelineProps> = ({
setCurrentYear(value); setCurrentYear(value);
}, 500); // 500ms 防抖延迟 }, 500); // 500ms 防抖延迟
}, },
[minTime, maxTime], [minTime, maxTime, setCurrentYear],
); );
// 播放控制 // 播放控制
@@ -133,7 +133,7 @@ const Timeline: React.FC<TimelineProps> = ({
}); });
}, playInterval); }, playInterval);
} }
}, [isPlaying, playInterval]); }, [isPlaying, playInterval, maxTime, minTime, setCurrentYear]);
const handlePause = useCallback(() => { const handlePause = useCallback(() => {
setIsPlaying(false); setIsPlaying(false);
@@ -172,7 +172,7 @@ const Timeline: React.FC<TimelineProps> = ({
if (next < minTime) next += maxTime - minTime + 1; if (next < minTime) next += maxTime - minTime + 1;
return next; return next;
}); });
}, [minTime, maxTime]); }, [minTime, maxTime, setCurrentYear]);
const handleStepForward = useCallback(() => { const handleStepForward = useCallback(() => {
setCurrentYear((prev: number) => { setCurrentYear((prev: number) => {
@@ -180,7 +180,7 @@ const Timeline: React.FC<TimelineProps> = ({
if (next > maxTime) next = minTime; if (next > maxTime) next = minTime;
return next; return next;
}); });
}, [minTime, maxTime]); }, [minTime, maxTime, setCurrentYear]);
// 日期时间选择处理 // 日期时间选择处理
const handleDateTimeChange = useCallback((newDate: Date | null) => { const handleDateTimeChange = useCallback((newDate: Date | null) => {
@@ -207,7 +207,7 @@ const Timeline: React.FC<TimelineProps> = ({
}, newInterval); }, newInterval);
} }
}, },
[isPlaying], [isPlaying, maxTime, minTime, setCurrentYear],
); );
// 组件加载时设置初始时间为当前时间的最近15分钟 // 组件加载时设置初始时间为当前时间的最近15分钟
@@ -372,7 +372,7 @@ const Timeline: React.FC<TimelineProps> = ({
if (predictionResults.length > 0 && pipeLayer) { if (predictionResults.length > 0 && pipeLayer) {
applyPipeHealthStyle(); applyPipeHealthStyle();
} }
}, [applyPipeHealthStyle]); }, [applyPipeHealthStyle, pipeLayer, predictionResults.length]);
// 这里防止地图缩放时,瓦片重新加载引起的属性更新出错 // 这里防止地图缩放时,瓦片重新加载引起的属性更新出错
useEffect(() => { useEffect(() => {
@@ -140,7 +140,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
source.addFeature(feature); source.addFeature(feature);
} }
}); });
}, [highlightFeatures]); }, [highlightFeatures, highlightLayer]);
// 查询方案 // 查询方案
const handleQuery = async () => { const handleQuery = async () => {
@@ -416,6 +416,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
const hasDevices = deviceIds.length > 0; const hasDevices = deviceIds.length > 0;
const hasData = timeSeries.length > 0; const hasData = timeSeries.length > 0;
const deviceIdsKey = useMemo(() => deviceIds.join(","), [deviceIds]);
const dataset = useMemo( const dataset = useMemo(
() => buildDataset(timeSeries, deviceIds, fractionDigits, showCleaning), () => buildDataset(timeSeries, deviceIds, fractionDigits, showCleaning),
@@ -528,7 +529,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
} else { } else {
setTimeSeries([]); setTimeSeries([]);
} }
}, [deviceIds.join(",")]); }, [deviceIdsKey, handleFetch, hasDevices]);
// 当设备数量变化时,调整数据源选择 // 当设备数量变化时,调整数据源选择
useEffect(() => { useEffect(() => {
@@ -741,7 +741,7 @@ const SCADADeviceList: React.FC<SCADADeviceListProps> = ({
source.addFeature(feature); source.addFeature(feature);
} }
}); });
}, [selectedDeviceIds, highlightFeatures]); }, [selectedDeviceIds, highlightFeatures, highlightLayer]);
// 清理定时器 // 清理定时器
useEffect(() => { useEffect(() => {
return () => { return () => {
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import Image from "next/image";
import { useMap } from "../MapComponent"; import { useMap } from "../MapComponent";
import TileLayer from "ol/layer/Tile.js"; import TileLayer from "ol/layer/Tile.js";
import XYZ from "ol/source/XYZ.js"; import XYZ from "ol/source/XYZ.js";
@@ -136,7 +137,7 @@ const BaseLayers: React.FC = () => {
} }
layerInfo.layer.setVisible(layerInfo.id === activeId); layerInfo.layer.setVisible(layerInfo.id === activeId);
}); });
}, [map]); }, [map, activeId]);
const changeMapLayers = (id: string) => { const changeMapLayers = (id: string) => {
if (map) { if (map) {
@@ -187,7 +188,7 @@ const BaseLayers: React.FC = () => {
> >
<div className="w-20 h-20 p-1"> <div className="w-20 h-20 p-1">
<button onClick={() => handleQuickSwitch()}> <button onClick={() => handleQuickSwitch()}>
<img <Image
width={240} width={240}
height={100} height={100}
src={ src={
@@ -200,6 +201,7 @@ const BaseLayers: React.FC = () => {
? baseLayers[1].name ? baseLayers[1].name
: baseLayers[0].name : baseLayers[0].name
} }
sizes="72px"
className="object-cover object-left w-18 h-18 rounded-xl" className="object-cover object-left w-18 h-18 rounded-xl"
/> />
<div className=" absolute left-1 bottom-1 flex w-18 h-auto items-center justify-center rounded-b-xl text-xs text-white bg-black opacity-80"> <div className=" absolute left-1 bottom-1 flex w-18 h-auto items-center justify-center rounded-b-xl text-xs text-white bg-black opacity-80">
@@ -227,11 +229,12 @@ const BaseLayers: React.FC = () => {
className="flex flex-auto flex-col justify-center items-center text-gray-500 text-xs" className="flex flex-auto flex-col justify-center items-center text-gray-500 text-xs"
onClick={() => handleMapLayers(item.id)} onClick={() => handleMapLayers(item.id)}
> >
<img <Image
width={240} width={240}
height={100} height={100}
src={item.img} src={item.img}
alt={item.name} alt={item.name}
sizes="64px"
className={clsx( className={clsx(
"object-cover object-left w-16 h-16 rounded-md border-2 border-white hover:ring-2 ring-blue-300", "object-cover object-left w-16 h-16 rounded-md border-2 border-white hover:ring-2 ring-blue-300",
{ {
@@ -422,6 +422,10 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
const hasDevices = deviceIds.length > 0; const hasDevices = deviceIds.length > 0;
const hasData = timeSeries.length > 0; const hasData = timeSeries.length > 0;
const featureInfosKey = useMemo(
() => JSON.stringify(featureInfos),
[featureInfos]
);
const dataset = useMemo( const dataset = useMemo(
() => buildDataset(timeSeries, deviceIds, fractionDigits), () => buildDataset(timeSeries, deviceIds, fractionDigits),
@@ -468,7 +472,7 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
} else { } else {
setTimeSeries([]); setTimeSeries([]);
} }
}, [JSON.stringify(featureInfos)]); }, [featureInfosKey, handleFetch, hasDevices]);
// 当设备数量变化时,调整数据源选择 // 当设备数量变化时,调整数据源选择
useEffect(() => { useEffect(() => {
@@ -15,6 +15,18 @@ interface LayerItem {
layerRef: any; // OpenLayers Layer 实例或 deck.gl layer 对象 layerRef: any; // OpenLayers Layer 实例或 deck.gl layer 对象
} }
const LAYER_ORDER = [
"junctions",
"reservoirs",
"tanks",
"pipes",
"pumps",
"valves",
"scada",
"waterflowLayer",
"junctionContourLayer",
];
const LayerControl: React.FC = () => { const LayerControl: React.FC = () => {
const map = useMap(); const map = useMap();
const data = useData(); const data = useData();
@@ -25,19 +37,9 @@ const LayerControl: React.FC = () => {
const setShowWaterflowLayer = data?.setShowWaterflowLayer; const setShowWaterflowLayer = data?.setShowWaterflowLayer;
const setShowContourLayer = data?.setShowContourLayer; const setShowContourLayer = data?.setShowContourLayer;
const layerOrder = [
"junctions",
"reservoirs",
"tanks",
"pipes",
"pumps",
"valves",
"scada",
"waterflowLayer",
"junctionContourLayer",
];
const layerItems = useMemo(() => { const layerItems = useMemo(() => {
void refreshKey;
if (!map || !data) return []; if (!map || !data) return [];
const items: LayerItem[] = []; const items: LayerItem[] = [];
@@ -87,8 +89,8 @@ const LayerControl: React.FC = () => {
} }
return items return items
.filter((item) => layerOrder.includes(item.id)) .filter((item) => LAYER_ORDER.includes(item.id))
.sort((a, b) => layerOrder.indexOf(a.id) - layerOrder.indexOf(b.id)); .sort((a, b) => LAYER_ORDER.indexOf(a.id) - LAYER_ORDER.indexOf(b.id));
}, [ }, [
map, map,
data, data,
@@ -321,7 +321,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
} }
return colors; return colors;
}, },
[styleConfig.gradientPaletteIndex, parseColor] [styleConfig.gradientPaletteIndex]
); );
// 根据分段数生成彩虹色 // 根据分段数生成彩虹色
@@ -1065,6 +1065,8 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
if (!applyPipeStyle) { if (!applyPipeStyle) {
removeVectorTileSourceLoadedEvent("pipes"); removeVectorTileSourceLoadedEvent("pipes");
} }
// This effect is intentionally driven by explicit style triggers and data snapshots.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
styleUpdateTrigger, styleUpdateTrigger,
applyJunctionStyle, applyJunctionStyle,
+35 -31
View File
@@ -39,6 +39,9 @@ interface TimelineProps {
schemeType?: string; schemeType?: string;
} }
const NOOP_SET_CURRENT_TIME = (_: any) => undefined;
const NOOP_SET_SELECTED_DATE = (_: any) => undefined;
const Timeline: React.FC<TimelineProps> = ({ const Timeline: React.FC<TimelineProps> = ({
schemeDate, schemeDate,
timeRange, timeRange,
@@ -47,6 +50,7 @@ const Timeline: React.FC<TimelineProps> = ({
schemeType = "burst_Analysis", schemeType = "burst_Analysis",
}) => { }) => {
const data = useData(); const data = useData();
const fallbackSelectedDateRef = useRef(new Date());
const hasTimelineState = const hasTimelineState =
data && data &&
data.setCurrentTime !== undefined && data.setCurrentTime !== undefined &&
@@ -54,9 +58,9 @@ const Timeline: React.FC<TimelineProps> = ({
data.selectedDate !== undefined && data.selectedDate !== undefined &&
data.setSelectedDate !== undefined; data.setSelectedDate !== undefined;
const currentTime = data?.currentTime ?? -1; const currentTime = data?.currentTime ?? -1;
const setCurrentTime = data?.setCurrentTime ?? ((_: any) => undefined); const setCurrentTime = data?.setCurrentTime ?? NOOP_SET_CURRENT_TIME;
const selectedDate = data?.selectedDate ?? new Date(); const selectedDate = data?.selectedDate ?? fallbackSelectedDateRef.current;
const setSelectedDate = data?.setSelectedDate ?? ((_: any) => undefined); const setSelectedDate = data?.setSelectedDate ?? NOOP_SET_SELECTED_DATE;
const setCurrentJunctionCalData = data?.setCurrentJunctionCalData; const setCurrentJunctionCalData = data?.setCurrentJunctionCalData;
const setCurrentPipeCalData = data?.setCurrentPipeCalData; const setCurrentPipeCalData = data?.setCurrentPipeCalData;
const junctionText = data?.junctionText ?? ""; const junctionText = data?.junctionText ?? "";
@@ -78,7 +82,7 @@ const Timeline: React.FC<TimelineProps> = ({
if (schemeDate) { if (schemeDate) {
setSelectedDate(schemeDate); setSelectedDate(schemeDate);
} }
}, [schemeDate]); }, [schemeDate, setSelectedDate]);
// 新增:用于 Draggable 的 nodeRef // 新增:用于 Draggable 的 nodeRef
const draggableRef = useRef<HTMLDivElement>(null); const draggableRef = useRef<HTMLDivElement>(null);
@@ -90,7 +94,20 @@ const Timeline: React.FC<TimelineProps> = ({
// 添加防抖引用 // 添加防抖引用
const debounceRef = useRef<NodeJS.Timeout | null>(null); const debounceRef = useRef<NodeJS.Timeout | null>(null);
const fetchFrameData = async ( const updateDataStates = useCallback((nodeResults: any[], linkResults: any[]) => {
if (setCurrentJunctionCalData) {
setCurrentJunctionCalData(nodeResults);
} else {
console.log("setCurrentJunctionCalData is undefined");
}
if (setCurrentPipeCalData) {
setCurrentPipeCalData(linkResults);
} else {
console.log("setCurrentPipeCalData is undefined");
}
}, [setCurrentJunctionCalData, setCurrentPipeCalData]);
const fetchFrameData = useCallback(async (
queryTime: Date, queryTime: Date,
junctionProperties: string, junctionProperties: string,
pipeProperties: string, pipeProperties: string,
@@ -170,21 +187,7 @@ const Timeline: React.FC<TimelineProps> = ({
} }
// 更新状态 // 更新状态
updateDataStates(nodeRecords.results || [], linkRecords.results || []); updateDataStates(nodeRecords.results || [], linkRecords.results || []);
}; }, [disableDateSelection, updateDataStates]);
// 提取更新状态的逻辑
const updateDataStates = (nodeResults: any[], linkResults: any[]) => {
if (setCurrentJunctionCalData) {
setCurrentJunctionCalData(nodeResults);
} else {
console.log("setCurrentJunctionCalData is undefined");
}
if (setCurrentPipeCalData) {
setCurrentPipeCalData(linkResults);
} else {
console.log("setCurrentPipeCalData is undefined");
}
};
// 时间刻度数组 (每5分钟一个刻度) // 时间刻度数组 (每5分钟一个刻度)
const timeMarks = Array.from({ length: 288 }, (_, i) => ({ const timeMarks = Array.from({ length: 288 }, (_, i) => ({
@@ -241,7 +244,7 @@ const Timeline: React.FC<TimelineProps> = ({
setCurrentTime(value); setCurrentTime(value);
}, 500); // 500ms 防抖延迟 }, 500); // 500ms 防抖延迟
}, },
[timeRange, minTime, maxTime], [timeRange, minTime, maxTime, setCurrentTime],
); );
// 播放控制 // 播放控制
@@ -268,7 +271,7 @@ const Timeline: React.FC<TimelineProps> = ({
}); });
}, playInterval); }, playInterval);
} }
}, [isPlaying, playInterval]); }, [isPlaying, playInterval, timeRange, maxTime, minTime, setCurrentTime]);
const handlePause = useCallback(() => { const handlePause = useCallback(() => {
setIsPlaying(false); setIsPlaying(false);
@@ -288,7 +291,7 @@ const Timeline: React.FC<TimelineProps> = ({
clearInterval(intervalRef.current); clearInterval(intervalRef.current);
intervalRef.current = null; intervalRef.current = null;
} }
}, []); }, [setCurrentTime]);
// 步进控制 // 步进控制
const handleDayStepBackward = useCallback(() => { const handleDayStepBackward = useCallback(() => {
@@ -297,14 +300,14 @@ const Timeline: React.FC<TimelineProps> = ({
newDate.setDate(newDate.getDate() - 1); newDate.setDate(newDate.getDate() - 1);
return newDate; return newDate;
}); });
}, []); }, [setSelectedDate]);
const handleDayStepForward = useCallback(() => { const handleDayStepForward = useCallback(() => {
setSelectedDate((prev) => { setSelectedDate((prev) => {
const newDate = new Date(prev); const newDate = new Date(prev);
newDate.setDate(newDate.getDate() + 1); newDate.setDate(newDate.getDate() + 1);
return newDate; return newDate;
}); });
}, []); }, [setSelectedDate]);
const handleStepBackward = useCallback(() => { const handleStepBackward = useCallback(() => {
setCurrentTime((prev) => { setCurrentTime((prev) => {
let next = prev - 15; let next = prev - 15;
@@ -315,7 +318,7 @@ const Timeline: React.FC<TimelineProps> = ({
} }
return next; return next;
}); });
}, [timeRange, minTime, maxTime]); }, [timeRange, minTime, maxTime, setCurrentTime]);
const handleStepForward = useCallback(() => { const handleStepForward = useCallback(() => {
setCurrentTime((prev) => { setCurrentTime((prev) => {
@@ -327,14 +330,14 @@ const Timeline: React.FC<TimelineProps> = ({
} }
return next; return next;
}); });
}, [timeRange, minTime, maxTime]); }, [timeRange, minTime, maxTime, setCurrentTime]);
// 日期选择处理 // 日期选择处理
const handleDateChange = useCallback((newDate: Date | null) => { const handleDateChange = useCallback((newDate: Date | null) => {
if (newDate) { if (newDate) {
setSelectedDate(newDate); setSelectedDate(newDate);
} }
}, []); }, [setSelectedDate]);
// 播放间隔改变处理 // 播放间隔改变处理
const handleIntervalChange = useCallback( const handleIntervalChange = useCallback(
@@ -358,7 +361,7 @@ const Timeline: React.FC<TimelineProps> = ({
}, newInterval); }, newInterval);
} }
}, },
[isPlaying], [isPlaying, timeRange, maxTime, minTime, setCurrentTime],
); );
// 计算时间段改变处理 // 计算时间段改变处理
const handleCalculatedIntervalChange = useCallback((event: any) => { const handleCalculatedIntervalChange = useCallback((event: any) => {
@@ -389,6 +392,7 @@ const Timeline: React.FC<TimelineProps> = ({
); );
} }
}, [ }, [
fetchFrameData,
junctionText, junctionText,
pipeText, pipeText,
currentTime, currentTime,
@@ -414,14 +418,14 @@ const Timeline: React.FC<TimelineProps> = ({
clearTimeout(debounceRef.current); clearTimeout(debounceRef.current);
} }
}; };
}, []); }, [setCurrentTime]);
// 当 timeRange 改变时,设置 currentTime 到 minTime // 当 timeRange 改变时,设置 currentTime 到 minTime
useEffect(() => { useEffect(() => {
if (timeRange) { if (timeRange) {
setCurrentTime(minTime); setCurrentTime(minTime);
} }
}, [timeRange, minTime]); }, [timeRange, minTime, setCurrentTime]);
// 获取地图实例 // 获取地图实例
const map = useMap(); const map = useMap();
// 这里防止地图缩放时,瓦片重新加载引起的属性更新出错 // 这里防止地图缩放时,瓦片重新加载引起的属性更新出错
@@ -409,8 +409,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
setComputedProperties({}); setComputedProperties({});
} else { } else {
setComputedProperties(data.result[0] || {}); setComputedProperties(data.result[0] || {});
console.log("查询到的计算属性:", data.result[0]); // console.log("查询到的计算属性:", data.result[0]);
console.log(computedProperties);
} }
} catch (error) { } catch (error) {
console.error("Error querying computed properties:", error); console.error("Error querying computed properties:", error);
@@ -419,7 +418,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
}; };
// 仅当 currentTime 有效时查询 // 仅当 currentTime 有效时查询
if (currentTime !== -1 && queryType) queryComputedProperties(); if (currentTime !== -1 && queryType) queryComputedProperties();
}, [highlightFeatures, currentTime, selectedDate, queryType, schemeName, schemeType]); }, [highlightFeatures, currentTime, selectedDate, queryType, schemeName, schemeType, showPropertyPanel]);
// 从要素属性中提取属性面板需要的数据 // 从要素属性中提取属性面板需要的数据
const getFeatureProperties = useCallback(() => { const getFeatureProperties = useCallback(() => {
@@ -515,6 +515,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
}, },
}); });
// The map and layer instances are intentionally rebuilt only when workspace or extent changes.
useEffect(() => { useEffect(() => {
if (!mapRef.current) return; if (!mapRef.current) return;
// 缓存 junction、pipe 数据,提供给 deck.gl 提供坐标供标签显示 // 缓存 junction、pipe 数据,提供给 deck.gl 提供坐标供标签显示
@@ -807,6 +808,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
map.dispose(); map.dispose();
deck.finalize(); deck.finalize();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [MAP_WORKSPACE, MAP_EXTENT]); }, [MAP_WORKSPACE, MAP_EXTENT]);
// 当数据变化时,更新 deck.gl 图层 // 当数据变化时,更新 deck.gl 图层
+21 -21
View File
@@ -1,5 +1,5 @@
"use client"; "use client";
import React, { createContext, useContext, useEffect, useState } from "react"; import React, { createContext, useCallback, useContext, useEffect, useState } from "react";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { config, NETWORK_NAME, setMapWorkspace, setNetworkName, setMapExtent } from "@/config/config"; import { config, NETWORK_NAME, setMapWorkspace, setNetworkName, setMapExtent } from "@/config/config";
import { ProjectSelector } from "@/components/project/ProjectSelector"; import { ProjectSelector } from "@/components/project/ProjectSelector";
@@ -28,25 +28,7 @@ export const ProjectProvider: React.FC<{ children: React.ReactNode }> = ({
extent: config.MAP_EXTENT, extent: config.MAP_EXTENT,
}); });
useEffect(() => { const applyConfig = useCallback(async (
// Check localStorage
const savedWorkspace = localStorage.getItem("NEXT_PUBLIC_MAP_WORKSPACE");
const savedNetwork = localStorage.getItem("NEXT_PUBLIC_NETWORK_NAME");
const savedExtent = localStorage.getItem("NEXT_PUBLIC_MAP_EXTENT");
const savedProjectId = localStorage.getItem("active_project");
// If we have saved config, use it.
if (savedWorkspace && savedNetwork) {
applyConfig(
savedProjectId || savedNetwork || savedWorkspace,
savedWorkspace,
savedNetwork,
savedExtent ? savedExtent.split(",").map(Number) : config.MAP_EXTENT,
);
}
}, []);
const applyConfig = async (
projectId: string, projectId: string,
ws: string, ws: string,
net: string, net: string,
@@ -91,7 +73,25 @@ export const ProjectProvider: React.FC<{ children: React.ReactNode }> = ({
} catch (error) { } catch (error) {
console.error("Failed to open project:", error); console.error("Failed to open project:", error);
} }
}; }, [setCurrentProjectId]);
useEffect(() => {
// Check localStorage
const savedWorkspace = localStorage.getItem("NEXT_PUBLIC_MAP_WORKSPACE");
const savedNetwork = localStorage.getItem("NEXT_PUBLIC_NETWORK_NAME");
const savedExtent = localStorage.getItem("NEXT_PUBLIC_MAP_EXTENT");
const savedProjectId = localStorage.getItem("active_project");
// If we have saved config, use it.
if (savedWorkspace && savedNetwork) {
applyConfig(
savedProjectId || savedNetwork || savedWorkspace,
savedWorkspace,
savedNetwork,
savedExtent ? savedExtent.split(",").map(Number) : config.MAP_EXTENT,
);
}
}, [applyConfig]);
// Only show selector if authenticated and not configured // Only show selector if authenticated and not configured
if (status === "authenticated" && !isConfigured) { if (status === "authenticated" && !isConfigured) {