完成时间轴前后端数据连通
This commit is contained in:
@@ -7,7 +7,6 @@ import {
|
||||
Slider,
|
||||
Typography,
|
||||
Paper,
|
||||
TextField,
|
||||
MenuItem,
|
||||
Select,
|
||||
FormControl,
|
||||
@@ -22,26 +21,18 @@ import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { zhCN } from "date-fns/locale";
|
||||
import { PlayArrow, Pause, Stop, Refresh } from "@mui/icons-material";
|
||||
import { TbRewindBackward5, TbRewindForward5 } from "react-icons/tb";
|
||||
import { useData } from "../MapComponent";
|
||||
import { config } from "@/config/config";
|
||||
|
||||
interface TimelineProps {
|
||||
onTimeChange?: (time: string) => void;
|
||||
onDateChange?: (date: Date) => void;
|
||||
onPlay?: () => void;
|
||||
onPause?: () => void;
|
||||
onStop?: () => void;
|
||||
onRefresh?: () => void;
|
||||
onFetch?: () => void;
|
||||
}
|
||||
const backendUrl = config.backendUrl;
|
||||
const Timeline: React.FC = () => {
|
||||
const data = useData();
|
||||
if (!data) {
|
||||
return <div>Loading...</div>; // 或其他占位符
|
||||
}
|
||||
const { setJunctionDataState, setPipeDataState, junctionText, pipeText } =
|
||||
data;
|
||||
|
||||
const Timeline: React.FC<TimelineProps> = ({
|
||||
onTimeChange,
|
||||
onDateChange,
|
||||
onPlay,
|
||||
onPause,
|
||||
onStop,
|
||||
onRefresh,
|
||||
onFetch,
|
||||
}) => {
|
||||
const [currentTime, setCurrentTime] = useState<number>(0); // 分钟数 (0-1439)
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
||||
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
||||
@@ -51,6 +42,114 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const timelineRef = useRef<HTMLDivElement>(null);
|
||||
// 添加缓存引用
|
||||
const cacheRef = useRef<
|
||||
Map<string, { nodeRecords: any[]; linkRecords: any[] }>
|
||||
>(new Map());
|
||||
// 添加防抖引用
|
||||
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const fetchFrameData = async (queryTime: Date) => {
|
||||
const query_time = queryTime.toISOString();
|
||||
const cacheKey = query_time;
|
||||
|
||||
// 检查缓存
|
||||
if (cacheRef.current.has(cacheKey)) {
|
||||
const { nodeRecords, linkRecords } = cacheRef.current.get(cacheKey)!;
|
||||
// 使用缓存数据更新状态
|
||||
updateDataStates(nodeRecords, linkRecords);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 定义需要查询的属性
|
||||
const junctionProperties = junctionText;
|
||||
const pipeProperties = pipeText;
|
||||
if (
|
||||
!junctionProperties ||
|
||||
!pipeProperties ||
|
||||
junctionProperties === "" ||
|
||||
pipeProperties === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"Query Time:",
|
||||
queryTime.toLocaleDateString() + " " + queryTime.toLocaleTimeString()
|
||||
);
|
||||
// 同时查询节点和管道数据
|
||||
const [nodeResponse, linkResponse] = await Promise.all([
|
||||
fetch(
|
||||
`${backendUrl}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=node&property=${junctionProperties}`
|
||||
),
|
||||
fetch(
|
||||
`${backendUrl}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=link&property=${pipeProperties}`
|
||||
),
|
||||
]);
|
||||
|
||||
const nodeRecords = await nodeResponse.json();
|
||||
const linkRecords = await linkResponse.json();
|
||||
|
||||
// 缓存数据
|
||||
cacheRef.current.set(cacheKey, {
|
||||
nodeRecords: nodeRecords.results,
|
||||
linkRecords: linkRecords.results,
|
||||
});
|
||||
|
||||
// 更新状态
|
||||
updateDataStates(nodeRecords.results, linkRecords.results);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 提取更新状态的逻辑
|
||||
const updateDataStates = (nodeResults: any[], linkResults: any[]) => {
|
||||
const junctionProperties = junctionText;
|
||||
const pipeProperties = pipeText;
|
||||
|
||||
// 将 nodeRecords 转换为 Map 以提高查找效率
|
||||
const nodeMap: Map<string, any> = new Map(
|
||||
nodeResults.map((r: any) => [r.ID, r])
|
||||
);
|
||||
// 将 linkRecords 转换为 Map 以提高查找效率
|
||||
const linkMap: Map<string, any> = new Map(
|
||||
linkResults.map((r: any) => [r.ID, r])
|
||||
);
|
||||
|
||||
// 更新junctionData
|
||||
setJunctionDataState((prev: any[]) =>
|
||||
prev.map((j) => {
|
||||
const record = nodeMap.get(j.id);
|
||||
if (record) {
|
||||
return {
|
||||
...j,
|
||||
[junctionProperties]: record.value,
|
||||
};
|
||||
}
|
||||
return j;
|
||||
})
|
||||
);
|
||||
|
||||
// 更新pipeData
|
||||
setPipeDataState((prev: any[]) =>
|
||||
prev.map((p) => {
|
||||
const record = linkMap.get(p.id);
|
||||
if (record) {
|
||||
return {
|
||||
...p,
|
||||
flowFlag: pipeProperties === "flow" && record.value < 0 ? -1 : 1,
|
||||
path:
|
||||
pipeProperties === "flow" && record.value < 0 && p.flowFlag > 0
|
||||
? [...p.path].reverse()
|
||||
: p.path,
|
||||
[pipeProperties]: record.value,
|
||||
};
|
||||
}
|
||||
return p;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// 时间刻度数组 (每5分钟一个刻度)
|
||||
const timeMarks = Array.from({ length: 288 }, (_, i) => ({
|
||||
@@ -67,14 +166,22 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
.padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function currentTimeToDate(selectedDate: Date, minutes: number): Date {
|
||||
const date = new Date(selectedDate);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
date.setHours(hours, mins, 0, 0);
|
||||
return date;
|
||||
}
|
||||
|
||||
// 播放时间间隔选项
|
||||
const intervalOptions = [
|
||||
{ value: 1000, label: "1秒" },
|
||||
// { value: 1000, label: "1秒" },
|
||||
{ value: 2000, label: "2秒" },
|
||||
{ value: 5000, label: "5秒" },
|
||||
{ value: 10000, label: "10秒" },
|
||||
];
|
||||
// 播放时间间隔选项
|
||||
// 强制计算时间段选项
|
||||
const calculatedIntervalOptions = [
|
||||
{ value: 1440, label: "1 天" },
|
||||
{ value: 60, label: "1 小时" },
|
||||
@@ -88,79 +195,73 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
(event: Event, newValue: number | number[]) => {
|
||||
const value = Array.isArray(newValue) ? newValue[0] : newValue;
|
||||
setSliderValue(value);
|
||||
setCurrentTime(value);
|
||||
onTimeChange?.(formatTime(value));
|
||||
// 防抖设置currentTime,避免频繁触发数据获取
|
||||
if (debounceRef.current) {
|
||||
clearTimeout(debounceRef.current);
|
||||
}
|
||||
debounceRef.current = setTimeout(() => {
|
||||
setCurrentTime(value);
|
||||
}, 300); // 300ms 防抖延迟
|
||||
},
|
||||
[onTimeChange]
|
||||
[]
|
||||
);
|
||||
|
||||
// 播放控制
|
||||
const handlePlay = useCallback(() => {
|
||||
if (!isPlaying) {
|
||||
setIsPlaying(true);
|
||||
onPlay?.();
|
||||
|
||||
intervalRef.current = setInterval(() => {
|
||||
setCurrentTime((prev) => {
|
||||
const next = prev >= 1435 ? 0 : prev + 5; // 到达23:55后回到00:00
|
||||
setSliderValue(next);
|
||||
onTimeChange?.(formatTime(next));
|
||||
return next;
|
||||
});
|
||||
}, playInterval);
|
||||
}
|
||||
}, [isPlaying, playInterval, onPlay, onTimeChange]);
|
||||
}, [isPlaying, playInterval]);
|
||||
|
||||
const handlePause = useCallback(() => {
|
||||
setIsPlaying(false);
|
||||
onPause?.();
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}, [onPause]);
|
||||
}, []);
|
||||
|
||||
const handleStop = useCallback(() => {
|
||||
setIsPlaying(false);
|
||||
setCurrentTime(0);
|
||||
setSliderValue(0);
|
||||
onStop?.();
|
||||
onTimeChange?.(formatTime(0));
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}, [onStop, onTimeChange]);
|
||||
}, []);
|
||||
|
||||
// 步进控制
|
||||
const handleStepBackward = useCallback(() => {
|
||||
setCurrentTime((prev) => {
|
||||
const next = prev <= 0 ? 1435 : prev - 5;
|
||||
setSliderValue(next);
|
||||
onTimeChange?.(formatTime(next));
|
||||
return next;
|
||||
});
|
||||
}, [onTimeChange]);
|
||||
}, []);
|
||||
|
||||
const handleStepForward = useCallback(() => {
|
||||
setCurrentTime((prev) => {
|
||||
const next = prev >= 1435 ? 0 : prev + 5;
|
||||
setSliderValue(next);
|
||||
onTimeChange?.(formatTime(next));
|
||||
return next;
|
||||
});
|
||||
}, [onTimeChange]);
|
||||
}, []);
|
||||
|
||||
// 日期选择处理
|
||||
const handleDateChange = useCallback(
|
||||
(newDate: Date | null) => {
|
||||
if (newDate) {
|
||||
setSelectedDate(newDate);
|
||||
onDateChange?.(newDate);
|
||||
}
|
||||
},
|
||||
[onDateChange]
|
||||
);
|
||||
const handleDateChange = useCallback((newDate: Date | null) => {
|
||||
if (newDate) {
|
||||
setSelectedDate(newDate);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 播放间隔改变处理
|
||||
const handleIntervalChange = useCallback(
|
||||
@@ -175,25 +276,33 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
setCurrentTime((prev) => {
|
||||
const next = prev >= 1435 ? 0 : prev + 5;
|
||||
setSliderValue(next);
|
||||
onTimeChange?.(formatTime(next));
|
||||
return next;
|
||||
});
|
||||
}, newInterval);
|
||||
}
|
||||
},
|
||||
[isPlaying, onTimeChange]
|
||||
[isPlaying]
|
||||
);
|
||||
// 计算时间段改变处理
|
||||
const handleCalculatedIntervalChange = useCallback((event: any) => {
|
||||
const newInterval = event.target.value;
|
||||
setCalculatedInterval(newInterval);
|
||||
}, []);
|
||||
// 组件卸载时清理定时器
|
||||
|
||||
// 添加 useEffect 来监听 currentTime 和 selectedDate 的变化,并获取数据
|
||||
useEffect(() => {
|
||||
fetchFrameData(currentTimeToDate(selectedDate, currentTime));
|
||||
}, [currentTime, selectedDate]);
|
||||
|
||||
// 组件卸载时清理定时器和防抖
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
if (debounceRef.current) {
|
||||
clearTimeout(debounceRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -224,7 +333,13 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
<DatePicker
|
||||
label="模拟数据日期选择"
|
||||
value={selectedDate}
|
||||
onChange={(newValue) => handleDateChange(newValue)}
|
||||
onChange={(newValue) =>
|
||||
handleDateChange(
|
||||
newValue && "toDate" in newValue
|
||||
? newValue.toDate()
|
||||
: (newValue as Date | null)
|
||||
)
|
||||
}
|
||||
enableAccessibleFieldDOMStructure={false}
|
||||
format="yyyy-MM-dd"
|
||||
sx={{ width: 180, "& .MuiInputBase-root": { height: 40 } }}
|
||||
@@ -308,7 +423,7 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<Refresh />}
|
||||
onClick={onRefresh}
|
||||
// onClick={onRefresh}
|
||||
>
|
||||
强制计算
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user