fix:优化数据来源

This commit is contained in:
Daniel
2026-03-02 01:00:04 +08:00
parent 91d9e48e1e
commit 4a8fff5a00
26 changed files with 1361 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
import { useMemo } from 'react'
import type { MilitarySituation } from '@/data/mockData'
import { useSituationStore } from '@/store/situationStore'
import { usePlaybackStore } from '@/store/playbackStore'
/** 将系列时间映射到回放日 (2026-03-01) 以便按当天时刻插值 */
function toReplayDay(iso: string, baseDay: string): string {
const d = new Date(iso)
const [y, m, day] = baseDay.slice(0, 10).split('-').map(Number)
return new Date(y, (m || 1) - 1, day || 1, d.getUTCHours(), d.getUTCMinutes(), 0, 0).toISOString()
}
function interpolateAt(
series: { time: string; value: number }[],
at: string,
baseDay = '2026-03-01'
): number {
if (series.length === 0) return 0
const t = new Date(at).getTime()
const mapped = series.map((p) => ({
time: toReplayDay(p.time, baseDay),
value: p.value,
}))
const sorted = [...mapped].sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime())
const before = sorted.filter((p) => new Date(p.time).getTime() <= t)
const after = sorted.filter((p) => new Date(p.time).getTime() > t)
if (before.length === 0) return sorted[0].value
if (after.length === 0) return sorted[sorted.length - 1].value
const a = before[before.length - 1]
const b = after[0]
const ta = new Date(a.time).getTime()
const tb = new Date(b.time).getTime()
const f = tb === ta ? 1 : (t - ta) / (tb - ta)
return a.value + f * (b.value - a.value)
}
function linearProgress(start: string, end: string, at: string): number {
const ts = new Date(start).getTime()
const te = new Date(end).getTime()
const ta = new Date(at).getTime()
if (ta <= ts) return 0
if (ta >= te) return 1
return (ta - ts) / (te - ts)
}
/** 根据回放时刻派生态势数据 */
export function useReplaySituation(): MilitarySituation {
const situation = useSituationStore((s) => s.situation)
const { isReplayMode, playbackTime } = usePlaybackStore()
return useMemo(() => {
if (!isReplayMode) return situation
const progress = linearProgress('2026-03-01T02:00:00.000Z', '2026-03-01T11:45:00.000Z', playbackTime)
// 华尔街趋势、反击情绪:按时间插值
const wsValue = interpolateAt(situation.usForces.wallStreetInvestmentTrend, playbackTime)
const retValue = interpolateAt(situation.iranForces.retaliationSentimentHistory, playbackTime)
// 战斗损失:从 0 线性增长到当前值
const lerp = (a: number, b: number) => Math.round(a + progress * (b - a))
const usLoss = situation.usForces.combatLosses
const irLoss = situation.iranForces.combatLosses
const civUs = usLoss.civilianCasualties ?? { killed: 0, wounded: 0 }
const civIr = irLoss.civilianCasualties ?? { killed: 0, wounded: 0 }
const usLossesAt = {
bases: {
destroyed: lerp(0, usLoss.bases.destroyed),
damaged: lerp(0, usLoss.bases.damaged),
},
personnelCasualties: {
killed: lerp(0, usLoss.personnelCasualties.killed),
wounded: lerp(0, usLoss.personnelCasualties.wounded),
},
civilianCasualties: { killed: lerp(0, civUs.killed), wounded: lerp(0, civUs.wounded) },
aircraft: lerp(0, usLoss.aircraft),
warships: lerp(0, usLoss.warships),
armor: lerp(0, usLoss.armor),
vehicles: lerp(0, usLoss.vehicles),
}
const irLossesAt = {
bases: {
destroyed: lerp(0, irLoss.bases.destroyed),
damaged: lerp(0, irLoss.bases.damaged),
},
personnelCasualties: {
killed: lerp(0, irLoss.personnelCasualties.killed),
wounded: lerp(0, irLoss.personnelCasualties.wounded),
},
civilianCasualties: { killed: lerp(0, civIr.killed), wounded: lerp(0, civIr.wounded) },
aircraft: lerp(0, irLoss.aircraft),
warships: lerp(0, irLoss.warships),
armor: lerp(0, irLoss.armor),
vehicles: lerp(0, irLoss.vehicles),
}
// 被袭基地:按 damage_level 排序,高损毁先出现;根据 progress 决定显示哪些为 attacked
const usLocs = situation.usForces.keyLocations || []
const attackedBases = usLocs
.filter((loc) => loc.status === 'attacked')
.sort((a, b) => (b.damage_level ?? 0) - (a.damage_level ?? 0))
const totalAttacked = attackedBases.length
const shownAttackedCount = Math.round(progress * totalAttacked)
const attackedNames = new Set(
attackedBases.slice(0, shownAttackedCount).map((l) => l.name)
)
const usLocsAt = usLocs.map((loc) => {
if (loc.status === 'attacked' && !attackedNames.has(loc.name)) {
return { ...loc, status: 'operational' as const }
}
return { ...loc }
})
return {
...situation,
lastUpdated: playbackTime,
usForces: {
...situation.usForces,
keyLocations: usLocsAt,
combatLosses: usLossesAt,
wallStreetInvestmentTrend: [
...situation.usForces.wallStreetInvestmentTrend.filter((p) => new Date(p.time).getTime() <= new Date(playbackTime).getTime()),
{ time: playbackTime, value: wsValue },
].slice(-20),
},
iranForces: {
...situation.iranForces,
combatLosses: irLossesAt,
retaliationSentiment: retValue,
retaliationSentimentHistory: [
...situation.iranForces.retaliationSentimentHistory.filter((p) => new Date(p.time).getTime() <= new Date(playbackTime).getTime()),
{ time: playbackTime, value: retValue },
].slice(-20),
},
recentUpdates: (situation.recentUpdates || []).filter(
(u) => new Date(u.timestamp).getTime() <= new Date(playbackTime).getTime()
),
conflictEvents: situation.conflictEvents || [],
conflictStats: situation.conflictStats || { total_events: 0, high_impact_events: 0, estimated_casualties: 0, estimated_strike_count: 0 },
}
}, [situation, isReplayMode, playbackTime])
}