import { useEffect, useRef, useState } from 'react' import { Play, Pause, SkipBack, SkipForward, History } from 'lucide-react' import { usePlaybackStore, getTicks, REPLAY_START, REPLAY_END, type ReplayScale } from '@/store/playbackStore' import { useSituationStore } from '@/store/situationStore' import { NewsTicker } from './NewsTicker' import { config } from '@/config' /** 冲突开始时间:2月28日凌晨 03:00(本地时间) */ const CONFLICT_START = new Date(2026, 1, 28, 3, 0, 0, 0) function formatTick(iso: string): string { const d = new Date(iso) return d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false, }) } function getConflictDuration(toTime: Date): { days: number; hours: number } { const diffMs = toTime.getTime() - CONFLICT_START.getTime() if (diffMs <= 0) return { days: 0, hours: 0 } const days = Math.floor(diffMs / (24 * 60 * 60 * 1000)) const hours = Math.floor((diffMs % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000)) return { days, hours } } export function TimelinePanel() { const situation = useSituationStore((s) => s.situation) const [now, setNow] = useState(() => new Date()) const { isReplayMode, playbackTime, replayScale, isPlaying, speedSecPerTick, setReplayMode, setPlaybackTime, setReplayScale, setIsPlaying, stepForward, stepBack, setSpeed, } = usePlaybackStore() const replayTicks = getTicks(replayScale) const timerRef = useRef | null>(null) useEffect(() => { const t = setInterval(() => setNow(new Date()), 1000) return () => clearInterval(t) }, []) const toTime = isReplayMode ? new Date(playbackTime) : now const conflictDuration = getConflictDuration(toTime) useEffect(() => { if (!isPlaying || !isReplayMode) { if (timerRef.current) { clearInterval(timerRef.current) timerRef.current = null } return } timerRef.current = setInterval(() => { const { playbackTime: current, replayScale: scale } = usePlaybackStore.getState() const ticks = getTicks(scale) const i = ticks.indexOf(current) if (i >= ticks.length - 1) { setIsPlaying(false) return } setPlaybackTime(ticks[i + 1]) }, speedSecPerTick * 1000) return () => { if (timerRef.current) clearInterval(timerRef.current) } }, [isPlaying, isReplayMode, speedSecPerTick, setPlaybackTime, setIsPlaying]) const index = replayTicks.indexOf(playbackTime) const value = index >= 0 ? index : replayTicks.length - 1 const handleSliderChange = (e: React.ChangeEvent) => { const i = parseInt(e.target.value, 10) setPlaybackTime(replayTicks[i]) } const scaleLabels: Record = { '30m': '30分钟', '1h': '1小时', '1d': '1天' } return (
{!isReplayMode && (
冲突已持续 {conflictDuration.days} 天 {conflictDuration.hours} 小时
)}
{!isReplayMode && config.showNewsTicker && (
)} {isReplayMode && ( <>
时间刻度
{/* 按刻度划分的时间轴:均匀取约 5~6 个刻度标签 */}
{replayTicks.length <= 1 ? ( {formatTick(replayTicks[0] ?? REPLAY_START)} ) : ( (() => { const n = replayTicks.length - 1 const maxLabels = 6 const step = Math.max(1, Math.floor(n / (maxLabels - 1))) const indices = [0, ...Array.from({ length: maxLabels - 2 }, (_, j) => Math.min((j + 1) * step, n)), n] return [...new Set(indices)].map((i) => ( {formatTick(replayTicks[i])} )) })() )}
)} 数据来源于互联网资讯
) }