From dfaee645ffbe1c403bcd1165f0e43534ef27b1fc Mon Sep 17 00:00:00 2001 From: Huarch Date: Thu, 16 Jul 2026 14:16:07 +0800 Subject: [PATCH] fix(timeline): use backend timestep --- .../olmap/HealthRiskAnalysis/Timeline.tsx | 30 ++-- .../olmap/core/Controls/Timeline.tsx | 155 +++++++++++------- .../olmap/core/Controls/timelineTime.test.ts | 24 +++ .../olmap/core/Controls/timelineTime.ts | 61 ++++++- .../Controls/useTimelineTimeConfig.test.tsx | 69 ++++++++ .../core/Controls/useTimelineTimeConfig.ts | 105 ++++++++++++ src/components/olmap/core/MapComponent.tsx | 8 +- 7 files changed, 370 insertions(+), 82 deletions(-) create mode 100644 src/components/olmap/core/Controls/useTimelineTimeConfig.test.tsx create mode 100644 src/components/olmap/core/Controls/useTimelineTimeConfig.ts diff --git a/src/components/olmap/HealthRiskAnalysis/Timeline.tsx b/src/components/olmap/HealthRiskAnalysis/Timeline.tsx index 9f31964..c169883 100644 --- a/src/components/olmap/HealthRiskAnalysis/Timeline.tsx +++ b/src/components/olmap/HealthRiskAnalysis/Timeline.tsx @@ -30,6 +30,7 @@ import { FiSkipBack, FiSkipForward } from "react-icons/fi"; import { config, NETWORK_NAME } from "@/config/config"; import { apiFetch } from "@/lib/apiFetch"; import { useMap } from "@components/olmap/core/MapComponent"; +import { useTimelineTimeConfig } from "@components/olmap/core/Controls/useTimelineTimeConfig"; import { useHealthRisk } from "./HealthRiskContext"; import { PredictionResult, @@ -38,10 +39,11 @@ import { RISK_BREAKS, } from "./types"; -// 辅助函数:将日期向下取整到最近的15分钟 -const getRoundedDate = (date: Date): Date => { +// 辅助函数:将日期向下取整到配置的水力时间步长 +const getRoundedDate = (date: Date, stepMinutes: number): Date => { + const safeStep = stepMinutes > 0 ? stepMinutes : 15; const minutes = date.getHours() * 60 + date.getMinutes(); - const roundedMinutes = Math.floor(minutes / 15) * 15; + const roundedMinutes = Math.floor(minutes / safeStep) * safeStep; const roundedDate = new Date(date); roundedDate.setHours( Math.floor(roundedMinutes / 60), @@ -91,6 +93,7 @@ const Timeline: React.FC = ({ const [isPlaying, setIsPlaying] = useState(false); const [playInterval, setPlayInterval] = useState(5000); // 毫秒 const [isPredicting, setIsPredicting] = useState(false); + const { stepMinutes } = useTimelineTimeConfig(); // 使用 ref 存储当前的健康数据,供事件监听器读取,避免重复绑定 const healthDataRef = useRef>(new Map()); @@ -200,11 +203,14 @@ const Timeline: React.FC = ({ }, [minTime, maxTime, setCurrentYear]); // 日期时间选择处理 - const handleDateTimeChange = useCallback((newDate: Date | null) => { - if (newDate) { - setSelectedDateTime(getRoundedDate(newDate)); - } - }, []); + const handleDateTimeChange = useCallback( + (newDate: Date | null) => { + if (newDate) { + setSelectedDateTime(getRoundedDate(newDate, stepMinutes)); + } + }, + [stepMinutes], + ); // 播放间隔改变处理 const handleIntervalChange = useCallback( @@ -227,9 +233,9 @@ const Timeline: React.FC = ({ [isPlaying, maxTime, minTime, setCurrentYear], ); - // 组件加载时设置初始时间为当前时间的最近15分钟 + // 组件加载时设置初始时间为当前时间的配置时间步长 useEffect(() => { - setSelectedDateTime(getRoundedDate(new Date())); + setSelectedDateTime(getRoundedDate(new Date(), stepMinutes)); return () => { if (intervalRef.current) { @@ -239,7 +245,7 @@ const Timeline: React.FC = ({ clearTimeout(debounceRef.current); } }; - }, []); + }, [stepMinutes]); // 获取地图实例 const map = useMap(); @@ -513,7 +519,7 @@ const Timeline: React.FC = ({ } format="YYYY-MM-DD HH:mm" views={["year", "month", "day", "hours", "minutes"]} - minutesStep={15} + minutesStep={stepMinutes} sx={{ width: 200 }} slotProps={{ textField: { diff --git a/src/components/olmap/core/Controls/Timeline.tsx b/src/components/olmap/core/Controls/Timeline.tsx index 02d15fb..e7d5935 100644 --- a/src/components/olmap/core/Controls/Timeline.tsx +++ b/src/components/olmap/core/Controls/Timeline.tsx @@ -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 Draggable from "react-draggable"; @@ -36,6 +36,7 @@ import { getTimelineDisabledRangePercentages, normalizeTimelineMinutes, } from "./timelineTime"; +import { useTimelineTimeConfig } from "./useTimelineTimeConfig"; interface TimelineProps { schemeDate?: Date; @@ -64,6 +65,13 @@ const timelineIconButtonSx = { const NOOP_SET_CURRENT_TIME = (_: any) => undefined; const NOOP_SET_SELECTED_DATE = (_: any) => undefined; +const BASE_CALCULATED_INTERVAL_OPTIONS = [ + { value: 1440, label: "1 天" }, + { value: 60, label: "1 小时" }, + { value: 30, label: "30 分钟" }, + { value: 15, label: "15 分钟" }, + { value: 5, label: "5 分钟" }, +]; const Timeline: React.FC = ({ schemeDate, @@ -93,18 +101,28 @@ const Timeline: React.FC = ({ const pipeText = data?.pipeText ?? ""; const setForceStyleAutoApplyVersion = data?.setForceStyleAutoApplyVersion; const { open } = useNotification(); + const { durationMinutes, stepMinutes } = useTimelineTimeConfig(); const [isPlaying, setIsPlaying] = useState(false); const [playInterval, setPlayInterval] = useState(15000); // 毫秒 - const [calculatedInterval, setCalculatedInterval] = useState(15); // 分钟 + const [calculatedInterval, setCalculatedInterval] = + useState(stepMinutes); // 分钟 const [isCalculating, setIsCalculating] = useState(false); // 计算时间轴范围 const minTime = timeRange - ? timeRange.start.getHours() * 60 + timeRange.start.getMinutes() + ? normalizeTimelineMinutes( + timeRange.start.getHours() * 60 + timeRange.start.getMinutes(), + 0, + durationMinutes, + ) : 0; const maxTime = timeRange - ? timeRange.end.getHours() * 60 + timeRange.end.getMinutes() - : 1440; + ? normalizeTimelineMinutes( + timeRange.end.getHours() * 60 + timeRange.end.getMinutes(), + 0, + durationMinutes, + ) + : durationMinutes; const timelineCurrentTime = normalizeTimelineMinutes( currentTime, minTime, @@ -113,6 +131,7 @@ const Timeline: React.FC = ({ const disabledRangePercentages = getTimelineDisabledRangePercentages( minTime, maxTime, + durationMinutes, ); useEffect(() => { if (schemeDate) { @@ -334,16 +353,28 @@ const Timeline: React.FC = ({ [disableDateSelection, fetchDataBySource, isCompareMode] ); - // 时间刻度数组 (每5分钟一个刻度) - const timeMarks = Array.from({ length: 288 }, (_, i) => ({ - value: i * 5, - label: i % 24 === 0 ? formatTime(i * 5) : "", - })); - // 格式化时间显示 - function formatTime(minutes: number): string { + const formatTime = useCallback((minutes: number): string => { return formatTimelineTime(minutes, minTime, maxTime); - } + }, [maxTime, minTime]); + + const timeMarks = useMemo(() => { + const marks = []; + const markInterval = 120; + for (let value = 0; value <= durationMinutes; value += markInterval) { + marks.push({ + value, + label: formatTime(value), + }); + } + if (marks.at(-1)?.value !== durationMinutes) { + marks.push({ + value: durationMinutes, + label: formatTime(durationMinutes), + }); + } + return marks; + }, [durationMinutes, formatTime]); const currentTimeToDate = useCallback((selectedDate: Date, minutes: number): Date => { const date = new Date(selectedDate); @@ -362,13 +393,31 @@ const Timeline: React.FC = ({ { value: 20000, label: "20秒" }, ]; // 强制计算时间段选项 - const calculatedIntervalOptions = [ - { value: 1440, label: "1 天" }, - { value: 60, label: "1 小时" }, - { value: 30, label: "30 分钟" }, - { value: 15, label: "15 分钟" }, - { value: 5, label: "5 分钟" }, - ]; + const resolvedCalculatedIntervalOptions = useMemo(() => { + const options = BASE_CALCULATED_INTERVAL_OPTIONS.filter( + (option) => option.value >= stepMinutes, + ); + if (!options.some((option) => option.value === stepMinutes)) { + options.push({ value: stepMinutes, label: `${stepMinutes} 分钟` }); + } + return options.sort((a, b) => b.value - a.value); + }, [stepMinutes]); + + const advanceTimelineTime = useCallback( + (previousTime: number, direction: 1 | -1 = 1) => { + const baseTime = Number.isFinite(previousTime) ? previousTime : minTime; + let next = baseTime + stepMinutes * direction; + if (timeRange) { + if (next > maxTime) next = minTime; + if (next < minTime) next = maxTime; + } else { + if (next > durationMinutes) next = 0; + if (next < 0) next = durationMinutes; + } + return next; + }, + [durationMinutes, maxTime, minTime, stepMinutes, timeRange], + ); // 处理时间轴滑动 const handleSliderChange = useCallback( @@ -403,17 +452,11 @@ const Timeline: React.FC = ({ intervalRef.current = setInterval(() => { setCurrentTime((prev) => { - let next = prev + 15; - if (timeRange) { - if (next > maxTime) next = minTime; - } else { - if (next >= 1440) next = 0; - } - return next; + return advanceTimelineTime(prev); }); }, playInterval); } - }, [isPlaying, playInterval, timeRange, maxTime, minTime, setCurrentTime]); + }, [advanceTimelineTime, isPlaying, playInterval, setCurrentTime]); const handlePause = useCallback(() => { setIsPlaying(false); @@ -426,12 +469,14 @@ const Timeline: React.FC = ({ const handleStop = useCallback(() => { setIsPlaying(false); // 设置为当前时间 - setCurrentTime(getRoundedCurrentTimelineMinutes()); // 重置为当前时间 + setCurrentTime( + getRoundedCurrentTimelineMinutes(new Date(), stepMinutes, durationMinutes), + ); // 重置为当前时间 if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } - }, [setCurrentTime]); + }, [durationMinutes, setCurrentTime, stepMinutes]); // 步进控制 const handleDayStepBackward = useCallback(() => { @@ -450,27 +495,15 @@ const Timeline: React.FC = ({ }, [setSelectedDate]); const handleStepBackward = useCallback(() => { setCurrentTime((prev) => { - let next = prev - 15; - if (timeRange) { - if (next < minTime) next = maxTime; - } else { - if (next < 0) next += 1440; - } - return next; + return advanceTimelineTime(prev, -1); }); - }, [timeRange, minTime, maxTime, setCurrentTime]); + }, [advanceTimelineTime, setCurrentTime]); const handleStepForward = useCallback(() => { setCurrentTime((prev) => { - let next = prev + 15; - if (timeRange) { - if (next > maxTime) next = minTime; - } else { - if (next >= 1440) next = 0; - } - return next; + return advanceTimelineTime(prev); }); - }, [timeRange, minTime, maxTime, setCurrentTime]); + }, [advanceTimelineTime, setCurrentTime]); // 日期选择处理 const handleDateChange = useCallback((newDate: Date | null) => { @@ -490,18 +523,12 @@ const Timeline: React.FC = ({ clearInterval(intervalRef.current); intervalRef.current = setInterval(() => { setCurrentTime((prev) => { - let next = prev + 15; - if (timeRange) { - if (next > maxTime) next = minTime; - } else { - if (next >= 1440) next = 0; - } - return next; + return advanceTimelineTime(prev); }); }, newInterval); } }, - [isPlaying, timeRange, maxTime, minTime, setCurrentTime], + [advanceTimelineTime, isPlaying, setCurrentTime], ); // 计算时间段改变处理 const handleCalculatedIntervalChange = useCallback((event: any) => { @@ -509,6 +536,10 @@ const Timeline: React.FC = ({ setCalculatedInterval(newInterval); }, []); + useEffect(() => { + setCalculatedInterval(stepMinutes); + }, [stepMinutes]); + // 添加 useEffect 来监听 currentTime 和 selectedDate 的变化,并获取数据 useEffect(() => { // 首次加载时,如果 selectedDate 或 currentTime 未定义,则跳过执行,避免报错 @@ -546,7 +577,9 @@ const Timeline: React.FC = ({ // 组件卸载时清理定时器和防抖 useEffect(() => { // 设置为当前时间 - setCurrentTime(getRoundedCurrentTimelineMinutes()); // 初始化为当前时间 + setCurrentTime( + getRoundedCurrentTimelineMinutes(new Date(), stepMinutes, durationMinutes), + ); // 初始化为当前时间 return () => { if (intervalRef.current) { @@ -556,7 +589,7 @@ const Timeline: React.FC = ({ clearTimeout(debounceRef.current); } }; - }, [setCurrentTime]); + }, [durationMinutes, setCurrentTime, stepMinutes]); // 当 timeRange 改变时,设置 currentTime 到 minTime useEffect(() => { @@ -868,7 +901,7 @@ const Timeline: React.FC = ({ label="强制计算时间段" onChange={handleCalculatedIntervalChange} > - {calculatedIntervalOptions.map((option) => ( + {resolvedCalculatedIntervalOptions.map((option) => ( {option.label} @@ -906,9 +939,9 @@ const Timeline: React.FC = ({ index % 12 === 0)} // 每小时显示一个标记 + max={durationMinutes} + step={stepMinutes} + marks={timeMarks} onChange={handleSliderChange} valueLabelDisplay="auto" valueLabelFormat={formatTime} @@ -976,7 +1009,7 @@ const Timeline: React.FC = ({ /> )} {/* 右侧禁用区域 */} - {maxTime < 1440 && ( + {maxTime < durationMinutes && ( { @@ -20,6 +22,28 @@ describe("timelineTime", () => { expect(getRoundedCurrentTimelineMinutes(new Date("2026-07-16T10:29:00"))).toBe(615); }); + it("rounds the current time down to a custom timeline step", () => { + expect( + getRoundedCurrentTimelineMinutes( + new Date("2026-07-16T10:29:00"), + 60, + ), + ).toBe(600); + }); + + it("parses EPANET-style duration values as minutes", () => { + expect(parseTimelineDurationMinutes("0:05")).toBe(5); + expect(parseTimelineDurationMinutes("1:00")).toBe(60); + expect(parseTimelineDurationMinutes("24:00")).toBe(1440); + expect(parseTimelineDurationMinutes("0:05:00")).toBe(5); + }); + + it("falls back for invalid, missing, and zero duration values", () => { + expect(coerceTimelineDurationMinutes("invalid", 1440)).toBe(1440); + expect(coerceTimelineDurationMinutes(undefined, 1440)).toBe(1440); + expect(coerceTimelineDurationMinutes("0:00", 1440)).toBe(1440); + }); + it("calculates disabled range as full-day percentages", () => { expect(getTimelineDisabledRangePercentages(360, 1080)).toEqual({ leftWidth: 25, diff --git a/src/components/olmap/core/Controls/timelineTime.ts b/src/components/olmap/core/Controls/timelineTime.ts index 7c2438c..a997371 100644 --- a/src/components/olmap/core/Controls/timelineTime.ts +++ b/src/components/olmap/core/Controls/timelineTime.ts @@ -1,6 +1,41 @@ export const TIMELINE_MINUTES_PER_DAY = 1440; export const TIMELINE_STEP_MINUTES = 15; +export const parseTimelineDurationMinutes = ( + value: unknown, +): number | undefined => { + if (typeof value !== "string") { + return undefined; + } + + const parts = value + .trim() + .split(":") + .map((part) => Number(part)); + + if ( + (parts.length !== 2 && parts.length !== 3) || + parts.some((part) => !Number.isFinite(part) || part < 0) + ) { + return undefined; + } + + const [hours, minutes, seconds = 0] = parts; + if (minutes >= 60 || seconds >= 60) { + return undefined; + } + + return hours * 60 + minutes + Math.floor(seconds / 60); +}; + +export const coerceTimelineDurationMinutes = ( + value: unknown, + fallbackMinutes: number, +) => { + const minutes = parseTimelineDurationMinutes(value); + return minutes && minutes > 0 ? minutes : fallbackMinutes; +}; + export const normalizeTimelineMinutes = ( minutes: number | undefined, minTime = 0, @@ -13,9 +48,18 @@ export const normalizeTimelineMinutes = ( return Math.min(Math.max(minutes, minTime), maxTime); }; -export const getRoundedCurrentTimelineMinutes = (date = new Date()) => { +export const getRoundedCurrentTimelineMinutes = ( + date = new Date(), + stepMinutes = TIMELINE_STEP_MINUTES, + maxMinutes = TIMELINE_MINUTES_PER_DAY, +) => { + const safeStep = stepMinutes > 0 ? stepMinutes : TIMELINE_STEP_MINUTES; const minutes = date.getHours() * 60 + date.getMinutes(); - return Math.floor(minutes / TIMELINE_STEP_MINUTES) * TIMELINE_STEP_MINUTES; + return normalizeTimelineMinutes( + Math.floor(minutes / safeStep) * safeStep, + 0, + maxMinutes, + ); }; export const formatTimelineTime = ( @@ -34,14 +78,17 @@ export const formatTimelineTime = ( export const getTimelineDisabledRangePercentages = ( minTime: number, maxTime: number, + totalMinutes = TIMELINE_MINUTES_PER_DAY, ) => { - const rangeStart = normalizeTimelineMinutes(minTime); - const rangeEnd = normalizeTimelineMinutes(maxTime); + const safeTotalMinutes = + totalMinutes > 0 ? totalMinutes : TIMELINE_MINUTES_PER_DAY; + const rangeStart = normalizeTimelineMinutes(minTime, 0, safeTotalMinutes); + const rangeEnd = normalizeTimelineMinutes(maxTime, 0, safeTotalMinutes); return { - leftWidth: (rangeStart / TIMELINE_MINUTES_PER_DAY) * 100, - rightLeft: (rangeEnd / TIMELINE_MINUTES_PER_DAY) * 100, + leftWidth: (rangeStart / safeTotalMinutes) * 100, + rightLeft: (rangeEnd / safeTotalMinutes) * 100, rightWidth: - ((TIMELINE_MINUTES_PER_DAY - rangeEnd) / TIMELINE_MINUTES_PER_DAY) * 100, + ((safeTotalMinutes - rangeEnd) / safeTotalMinutes) * 100, }; }; diff --git a/src/components/olmap/core/Controls/useTimelineTimeConfig.test.tsx b/src/components/olmap/core/Controls/useTimelineTimeConfig.test.tsx new file mode 100644 index 0000000..c04fa12 --- /dev/null +++ b/src/components/olmap/core/Controls/useTimelineTimeConfig.test.tsx @@ -0,0 +1,69 @@ +import { renderHook, waitFor } from "@testing-library/react"; +import { useTimelineTimeConfig, toTimelineTimeConfig } from "./useTimelineTimeConfig"; + +const apiFetch = jest.fn(); + +jest.mock("@/lib/apiFetch", () => ({ + apiFetch: (...args: unknown[]) => apiFetch(...args), +})); + +jest.mock("@/contexts/ProjectContext", () => ({ + useProject: () => ({ networkName: "test-network" }), +})); + +describe("useTimelineTimeConfig", () => { + beforeEach(() => { + apiFetch.mockReset(); + }); + + it("derives duration and step from backend time properties", async () => { + apiFetch.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + DURATION: "24:00", + "HYDRAULIC TIMESTEP": "1:00", + }), + }); + + const { result } = renderHook(() => useTimelineTimeConfig()); + + await waitFor(() => { + expect(result.current.stepMinutes).toBe(60); + }); + expect(result.current.durationMinutes).toBe(1440); + expect(String(apiFetch.mock.calls[0][0])).toContain( + "/api/v1/gettimeproperties/?network=test-network", + ); + }); + + it("falls back when fetching time properties fails", async () => { + apiFetch.mockRejectedValueOnce(new Error("network failure")); + const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); + + const { result } = renderHook(() => useTimelineTimeConfig()); + + await waitFor(() => { + expect(apiFetch).toHaveBeenCalledTimes(1); + }); + expect(result.current).toMatchObject({ + durationMinutes: 1440, + stepMinutes: 15, + }); + + warnSpy.mockRestore(); + }); + + it("parses wrapped times payloads", () => { + expect( + toTimelineTimeConfig({ + times: { + DURATION: "24:00", + "HYDRAULIC TIMESTEP": "0:05", + }, + }), + ).toMatchObject({ + durationMinutes: 1440, + stepMinutes: 5, + }); + }); +}); diff --git a/src/components/olmap/core/Controls/useTimelineTimeConfig.ts b/src/components/olmap/core/Controls/useTimelineTimeConfig.ts new file mode 100644 index 0000000..3249e5d --- /dev/null +++ b/src/components/olmap/core/Controls/useTimelineTimeConfig.ts @@ -0,0 +1,105 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { config, NETWORK_NAME } from "@/config/config"; +import { useProject } from "@/contexts/ProjectContext"; +import { apiFetch } from "@/lib/apiFetch"; +import { + coerceTimelineDurationMinutes, + TIMELINE_MINUTES_PER_DAY, + TIMELINE_STEP_MINUTES, +} from "./timelineTime"; + +export interface TimelineTimeConfig { + durationMinutes: number; + stepMinutes: number; + properties: Record; +} + +const DEFAULT_TIMELINE_TIME_CONFIG: TimelineTimeConfig = { + durationMinutes: TIMELINE_MINUTES_PER_DAY, + stepMinutes: TIMELINE_STEP_MINUTES, + properties: {}, +}; + +const resolveTimeProperties = (data: unknown): Record => { + if (!data || typeof data !== "object") { + return {}; + } + + const record = data as Record; + if (record.times && typeof record.times === "object") { + return record.times as Record; + } + + return record; +}; + +export const toTimelineTimeConfig = (data: unknown): TimelineTimeConfig => { + const properties = resolveTimeProperties(data); + + return { + durationMinutes: coerceTimelineDurationMinutes( + properties.DURATION, + TIMELINE_MINUTES_PER_DAY, + ), + stepMinutes: coerceTimelineDurationMinutes( + properties["HYDRAULIC TIMESTEP"], + TIMELINE_STEP_MINUTES, + ), + properties, + }; +}; + +export const useTimelineTimeConfig = (): TimelineTimeConfig => { + const project = useProject(); + const networkName = project?.networkName || NETWORK_NAME; + const [timeConfig, setTimeConfig] = useState( + DEFAULT_TIMELINE_TIME_CONFIG, + ); + + useEffect(() => { + if (!networkName) { + setTimeConfig(DEFAULT_TIMELINE_TIME_CONFIG); + return; + } + + let isMounted = true; + const fetchTimeProperties = async () => { + try { + const response = await apiFetch( + `${config.BACKEND_URL}/api/v1/gettimeproperties/?network=${encodeURIComponent( + networkName, + )}`, + ); + if (!response.ok) { + throw new Error(`Failed to fetch time properties: ${response.status}`); + } + const data = await response.json(); + if (isMounted) { + setTimeConfig(toTimelineTimeConfig(data)); + } + } catch (error) { + console.warn("Failed to load timeline time properties:", error); + if (isMounted) { + setTimeConfig(DEFAULT_TIMELINE_TIME_CONFIG); + } + } + }; + + fetchTimeProperties(); + + return () => { + isMounted = false; + }; + }, [networkName]); + + return useMemo( + () => ({ + durationMinutes: timeConfig.durationMinutes, + stepMinutes: timeConfig.stepMinutes, + properties: timeConfig.properties, + }), + [timeConfig], + ); +}; diff --git a/src/components/olmap/core/MapComponent.tsx b/src/components/olmap/core/MapComponent.tsx index b757a37..6829155 100644 --- a/src/components/olmap/core/MapComponent.tsx +++ b/src/components/olmap/core/MapComponent.tsx @@ -33,6 +33,7 @@ import { } from "./mapLifecycle"; import { createOperationalMapResources } from "./operationalLayers"; import { getRoundedCurrentTimelineMinutes } from "./Controls/timelineTime"; +import { useTimelineTimeConfig } from "./Controls/useTimelineTimeConfig"; interface MapComponentProps { children?: React.ReactNode; @@ -140,6 +141,7 @@ const MapComponent: React.FC = ({ children }) => { ]; const MAP_URL = config.MAP_URL; const MAP_VIEW_STORAGE_KEY = `${MAP_WORKSPACE}_map_view`; // 持久化 key + const { durationMinutes, stepMinutes } = useTimelineTimeConfig(); const mapRef = useRef(null); const canvasRef = useRef(null); @@ -778,7 +780,9 @@ const MapComponent: React.FC = ({ children }) => { }, [compareMap, isCompareMode, map]); useEffect(() => { - setCurrentTime(getRoundedCurrentTimelineMinutes()); + setCurrentTime( + getRoundedCurrentTimelineMinutes(new Date(), stepMinutes, durationMinutes), + ); setSelectedDate(new Date()); setSchemeName(""); setCurrentJunctionCalData([]); @@ -803,7 +807,7 @@ const MapComponent: React.FC = ({ children }) => { deckLayerRef.current?.resetSessionLayers(); operationalResources.resetStyles(); }; - }, [pathname, map, operationalResources]); + }, [durationMinutes, pathname, map, operationalResources, stepMinutes]); // 当数据变化时,更新 deck.gl 图层 useEffect(() => {