fix(timeline): prevent negative initial time

This commit is contained in:
2026-07-16 11:31:51 +08:00
parent f5e7312e3b
commit 041b4ef89d
4 changed files with 83 additions and 24 deletions
+25 -22
View File
@@ -30,6 +30,11 @@ import { useData } from "../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 "../MapComponent"; import { useMap } from "../MapComponent";
import {
formatTimelineTime,
getRoundedCurrentTimelineMinutes,
normalizeTimelineMinutes,
} from "./timelineTime";
interface TimelineProps { interface TimelineProps {
schemeDate?: Date; schemeDate?: Date;
@@ -99,6 +104,11 @@ const Timeline: React.FC<TimelineProps> = ({
const maxTime = timeRange const maxTime = timeRange
? timeRange.end.getHours() * 60 + timeRange.end.getMinutes() ? timeRange.end.getHours() * 60 + timeRange.end.getMinutes()
: 1440; : 1440;
const timelineCurrentTime = normalizeTimelineMinutes(
currentTime,
minTime,
maxTime,
);
useEffect(() => { useEffect(() => {
if (schemeDate) { if (schemeDate) {
setSelectedDate(schemeDate); setSelectedDate(schemeDate);
@@ -327,20 +337,17 @@ const Timeline: React.FC<TimelineProps> = ({
// 格式化时间显示 // 格式化时间显示
function formatTime(minutes: number): string { function formatTime(minutes: number): string {
const hours = Math.floor(minutes / 60); return formatTimelineTime(minutes, minTime, maxTime);
const mins = minutes % 60;
return `${hours.toString().padStart(2, "0")}:${mins
.toString()
.padStart(2, "0")}`;
} }
function currentTimeToDate(selectedDate: Date, minutes: number): Date { const currentTimeToDate = useCallback((selectedDate: Date, minutes: number): Date => {
const date = new Date(selectedDate); const date = new Date(selectedDate);
const hours = Math.floor(minutes / 60); const normalizedMinutes = normalizeTimelineMinutes(minutes, minTime, maxTime);
const mins = minutes % 60; const hours = Math.floor(normalizedMinutes / 60);
const mins = normalizedMinutes % 60;
date.setHours(hours, mins, 0, 0); date.setHours(hours, mins, 0, 0);
return date; return date;
} }, [maxTime, minTime]);
// 播放时间间隔选项 // 播放时间间隔选项
const intervalOptions = [ const intervalOptions = [
@@ -414,9 +421,7 @@ const Timeline: React.FC<TimelineProps> = ({
const handleStop = useCallback(() => { const handleStop = useCallback(() => {
setIsPlaying(false); setIsPlaying(false);
// 设置为当前时间 // 设置为当前时间
const currentTime = new Date(); setCurrentTime(getRoundedCurrentTimelineMinutes()); // 重置为当前时间
const minutes = currentTime.getHours() * 60 + currentTime.getMinutes();
setCurrentTime(minutes); // 组件卸载时重置时间
if (intervalRef.current) { if (intervalRef.current) {
clearInterval(intervalRef.current); clearInterval(intervalRef.current);
intervalRef.current = null; intervalRef.current = null;
@@ -514,7 +519,7 @@ const Timeline: React.FC<TimelineProps> = ({
// return; // return;
// } // }
fetchFrameData( fetchFrameData(
currentTimeToDate(selectedDate, currentTime), currentTimeToDate(selectedDate, timelineCurrentTime),
junctionText, junctionText,
pipeText, pipeText,
schemeName, schemeName,
@@ -526,6 +531,8 @@ const Timeline: React.FC<TimelineProps> = ({
junctionText, junctionText,
pipeText, pipeText,
currentTime, currentTime,
currentTimeToDate,
timelineCurrentTime,
selectedDate, selectedDate,
schemeName, schemeName,
schemeType, schemeType,
@@ -534,11 +541,7 @@ const Timeline: React.FC<TimelineProps> = ({
// 组件卸载时清理定时器和防抖 // 组件卸载时清理定时器和防抖
useEffect(() => { useEffect(() => {
// 设置为当前时间 // 设置为当前时间
const currentTime = new Date(); setCurrentTime(getRoundedCurrentTimelineMinutes()); // 初始化为当前时间
const minutes = currentTime.getHours() * 60 + currentTime.getMinutes();
// 找到最近的前15分钟刻度
const roundedMinutes = Math.floor(minutes / 15) * 15;
setCurrentTime(roundedMinutes); // 组件卸载时重置时间
return () => { return () => {
if (intervalRef.current) { if (intervalRef.current) {
@@ -603,7 +606,7 @@ const Timeline: React.FC<TimelineProps> = ({
clearCache(linkCacheRef); clearCache(linkCacheRef);
// 重新获取当前时刻的新数据 // 重新获取当前时刻的新数据
fetchFrameData( fetchFrameData(
currentTimeToDate(selectedDate, currentTime), currentTimeToDate(selectedDate, timelineCurrentTime),
junctionText, junctionText,
pipeText, pipeText,
schemeName, schemeName,
@@ -622,7 +625,7 @@ const Timeline: React.FC<TimelineProps> = ({
// 提前提取日期和时间值,避免异步操作期间被时间轴拖动改变 // 提前提取日期和时间值,避免异步操作期间被时间轴拖动改变
const calculationDate = selectedDate; const calculationDate = selectedDate;
const calculationTime = currentTime; const calculationTime = timelineCurrentTime;
const calculationDateTime = currentTimeToDate( const calculationDateTime = currentTimeToDate(
calculationDate, calculationDate,
calculationTime calculationTime
@@ -890,13 +893,13 @@ const Timeline: React.FC<TimelineProps> = ({
color: "primary.main", color: "primary.main",
}} }}
> >
{formatTime(currentTime)} {formatTime(timelineCurrentTime)}
</Typography> </Typography>
</Stack> </Stack>
<Box ref={timelineRef} sx={{ px: 2, position: "relative" }}> <Box ref={timelineRef} sx={{ px: 2, position: "relative" }}>
<Slider <Slider
value={currentTime} value={timelineCurrentTime}
min={0} min={0}
max={1440} // 24:00 = 1440分钟 max={1440} // 24:00 = 1440分钟
step={15} // 每15分钟一个步进 step={15} // 每15分钟一个步进
@@ -0,0 +1,21 @@
import {
formatTimelineTime,
getRoundedCurrentTimelineMinutes,
normalizeTimelineMinutes,
} from "./timelineTime";
describe("timelineTime", () => {
it("normalizes invalid minutes before formatting", () => {
expect(formatTimelineTime(-1)).toBe("00:00");
expect(formatTimelineTime(Number.NaN)).toBe("00:00");
});
it("clamps minutes to the configured range", () => {
expect(normalizeTimelineMinutes(-1, 60, 120)).toBe(60);
expect(normalizeTimelineMinutes(180, 60, 120)).toBe(120);
});
it("rounds the current time down to the timeline step", () => {
expect(getRoundedCurrentTimelineMinutes(new Date("2026-07-16T10:29:00"))).toBe(615);
});
});
@@ -0,0 +1,32 @@
export const TIMELINE_MINUTES_PER_DAY = 1440;
export const TIMELINE_STEP_MINUTES = 15;
export const normalizeTimelineMinutes = (
minutes: number | undefined,
minTime = 0,
maxTime = TIMELINE_MINUTES_PER_DAY,
) => {
if (typeof minutes !== "number" || !Number.isFinite(minutes)) {
return minTime;
}
return Math.min(Math.max(minutes, minTime), maxTime);
};
export const getRoundedCurrentTimelineMinutes = (date = new Date()) => {
const minutes = date.getHours() * 60 + date.getMinutes();
return Math.floor(minutes / TIMELINE_STEP_MINUTES) * TIMELINE_STEP_MINUTES;
};
export const formatTimelineTime = (
minutes: number | undefined,
minTime = 0,
maxTime = TIMELINE_MINUTES_PER_DAY,
) => {
const normalizedMinutes = normalizeTimelineMinutes(minutes, minTime, maxTime);
const hours = Math.floor(normalizedMinutes / 60);
const mins = normalizedMinutes % 60;
return `${hours.toString().padStart(2, "0")}:${mins
.toString()
.padStart(2, "0")}`;
};
+5 -2
View File
@@ -32,6 +32,7 @@ import {
markMapResourcePersistent, markMapResourcePersistent,
} from "./mapLifecycle"; } from "./mapLifecycle";
import { createOperationalMapResources } from "./operationalLayers"; import { createOperationalMapResources } from "./operationalLayers";
import { getRoundedCurrentTimelineMinutes } from "./Controls/timelineTime";
interface MapComponentProps { interface MapComponentProps {
children?: React.ReactNode; children?: React.ReactNode;
@@ -155,7 +156,9 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
const [compareMap, setCompareMap] = useState<OlMap>(); const [compareMap, setCompareMap] = useState<OlMap>();
const [compareDeckLayer, setCompareDeckLayer] = useState<DeckLayer>(); const [compareDeckLayer, setCompareDeckLayer] = useState<DeckLayer>();
// currentCalData 用于存储当前计算结果 // currentCalData 用于存储当前计算结果
const [currentTime, setCurrentTime] = useState<number>(-1); // 默认选择当前时间 const [currentTime, setCurrentTime] = useState<number>(
getRoundedCurrentTimelineMinutes,
); // 默认选择当前时间
// const [selectedDate, setSelectedDate] = useState<Date>(new Date("2025-9-17")); // const [selectedDate, setSelectedDate] = useState<Date>(new Date("2025-9-17"));
const [selectedDate, setSelectedDate] = useState<Date>(new Date()); // 默认今天 const [selectedDate, setSelectedDate] = useState<Date>(new Date()); // 默认今天
const [schemeName, setSchemeName] = useState<string>(""); // 当前方案名称 const [schemeName, setSchemeName] = useState<string>(""); // 当前方案名称
@@ -775,7 +778,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
}, [compareMap, isCompareMode, map]); }, [compareMap, isCompareMode, map]);
useEffect(() => { useEffect(() => {
setCurrentTime(-1); setCurrentTime(getRoundedCurrentTimelineMinutes());
setSelectedDate(new Date()); setSelectedDate(new Date());
setSchemeName(""); setSchemeName("");
setCurrentJunctionCalData([]); setCurrentJunctionCalData([]);