fix(timeline): prevent negative initial time
This commit is contained in:
@@ -30,6 +30,11 @@ import { useData } from "../MapComponent";
|
||||
import { config, NETWORK_NAME } from "@/config/config";
|
||||
import { apiFetch } from "@/lib/apiFetch";
|
||||
import { useMap } from "../MapComponent";
|
||||
import {
|
||||
formatTimelineTime,
|
||||
getRoundedCurrentTimelineMinutes,
|
||||
normalizeTimelineMinutes,
|
||||
} from "./timelineTime";
|
||||
|
||||
interface TimelineProps {
|
||||
schemeDate?: Date;
|
||||
@@ -99,6 +104,11 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
const maxTime = timeRange
|
||||
? timeRange.end.getHours() * 60 + timeRange.end.getMinutes()
|
||||
: 1440;
|
||||
const timelineCurrentTime = normalizeTimelineMinutes(
|
||||
currentTime,
|
||||
minTime,
|
||||
maxTime,
|
||||
);
|
||||
useEffect(() => {
|
||||
if (schemeDate) {
|
||||
setSelectedDate(schemeDate);
|
||||
@@ -327,20 +337,17 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
|
||||
// 格式化时间显示
|
||||
function formatTime(minutes: number): string {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
return `${hours.toString().padStart(2, "0")}:${mins
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
return formatTimelineTime(minutes, minTime, maxTime);
|
||||
}
|
||||
|
||||
function currentTimeToDate(selectedDate: Date, minutes: number): Date {
|
||||
const currentTimeToDate = useCallback((selectedDate: Date, minutes: number): Date => {
|
||||
const date = new Date(selectedDate);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
const normalizedMinutes = normalizeTimelineMinutes(minutes, minTime, maxTime);
|
||||
const hours = Math.floor(normalizedMinutes / 60);
|
||||
const mins = normalizedMinutes % 60;
|
||||
date.setHours(hours, mins, 0, 0);
|
||||
return date;
|
||||
}
|
||||
}, [maxTime, minTime]);
|
||||
|
||||
// 播放时间间隔选项
|
||||
const intervalOptions = [
|
||||
@@ -414,9 +421,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
const handleStop = useCallback(() => {
|
||||
setIsPlaying(false);
|
||||
// 设置为当前时间
|
||||
const currentTime = new Date();
|
||||
const minutes = currentTime.getHours() * 60 + currentTime.getMinutes();
|
||||
setCurrentTime(minutes); // 组件卸载时重置时间
|
||||
setCurrentTime(getRoundedCurrentTimelineMinutes()); // 重置为当前时间
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
@@ -514,7 +519,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
// return;
|
||||
// }
|
||||
fetchFrameData(
|
||||
currentTimeToDate(selectedDate, currentTime),
|
||||
currentTimeToDate(selectedDate, timelineCurrentTime),
|
||||
junctionText,
|
||||
pipeText,
|
||||
schemeName,
|
||||
@@ -526,6 +531,8 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
junctionText,
|
||||
pipeText,
|
||||
currentTime,
|
||||
currentTimeToDate,
|
||||
timelineCurrentTime,
|
||||
selectedDate,
|
||||
schemeName,
|
||||
schemeType,
|
||||
@@ -534,11 +541,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
// 组件卸载时清理定时器和防抖
|
||||
useEffect(() => {
|
||||
// 设置为当前时间
|
||||
const currentTime = new Date();
|
||||
const minutes = currentTime.getHours() * 60 + currentTime.getMinutes();
|
||||
// 找到最近的前15分钟刻度
|
||||
const roundedMinutes = Math.floor(minutes / 15) * 15;
|
||||
setCurrentTime(roundedMinutes); // 组件卸载时重置时间
|
||||
setCurrentTime(getRoundedCurrentTimelineMinutes()); // 初始化为当前时间
|
||||
|
||||
return () => {
|
||||
if (intervalRef.current) {
|
||||
@@ -603,7 +606,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
clearCache(linkCacheRef);
|
||||
// 重新获取当前时刻的新数据
|
||||
fetchFrameData(
|
||||
currentTimeToDate(selectedDate, currentTime),
|
||||
currentTimeToDate(selectedDate, timelineCurrentTime),
|
||||
junctionText,
|
||||
pipeText,
|
||||
schemeName,
|
||||
@@ -622,7 +625,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
|
||||
// 提前提取日期和时间值,避免异步操作期间被时间轴拖动改变
|
||||
const calculationDate = selectedDate;
|
||||
const calculationTime = currentTime;
|
||||
const calculationTime = timelineCurrentTime;
|
||||
const calculationDateTime = currentTimeToDate(
|
||||
calculationDate,
|
||||
calculationTime
|
||||
@@ -890,13 +893,13 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
color: "primary.main",
|
||||
}}
|
||||
>
|
||||
{formatTime(currentTime)}
|
||||
{formatTime(timelineCurrentTime)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Box ref={timelineRef} sx={{ px: 2, position: "relative" }}>
|
||||
<Slider
|
||||
value={currentTime}
|
||||
value={timelineCurrentTime}
|
||||
min={0}
|
||||
max={1440} // 24:00 = 1440分钟
|
||||
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")}`;
|
||||
};
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
markMapResourcePersistent,
|
||||
} from "./mapLifecycle";
|
||||
import { createOperationalMapResources } from "./operationalLayers";
|
||||
import { getRoundedCurrentTimelineMinutes } from "./Controls/timelineTime";
|
||||
|
||||
interface MapComponentProps {
|
||||
children?: React.ReactNode;
|
||||
@@ -155,7 +156,9 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
const [compareMap, setCompareMap] = useState<OlMap>();
|
||||
const [compareDeckLayer, setCompareDeckLayer] = useState<DeckLayer>();
|
||||
// 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()); // 默认今天
|
||||
const [schemeName, setSchemeName] = useState<string>(""); // 当前方案名称
|
||||
@@ -775,7 +778,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
|
||||
}, [compareMap, isCompareMode, map]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentTime(-1);
|
||||
setCurrentTime(getRoundedCurrentTimelineMinutes());
|
||||
setSelectedDate(new Date());
|
||||
setSchemeName("");
|
||||
setCurrentJunctionCalData([]);
|
||||
|
||||
Reference in New Issue
Block a user