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 { 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")}`;
|
||||||
|
};
|
||||||
@@ -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([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user