101 lines
4.9 KiB
TypeScript
101 lines
4.9 KiB
TypeScript
import { useEffect } from 'react'
|
||
import { HeaderPanel } from '@/components/HeaderPanel'
|
||
import { TimelinePanel } from '@/components/TimelinePanel'
|
||
import { ForcePanel } from '@/components/ForcePanel'
|
||
import { WarMap } from '@/components/WarMap'
|
||
import { CombatLossesPanel } from '@/components/CombatLossesPanel'
|
||
import { IranBaseStatusPanel } from '@/components/IranBaseStatusPanel'
|
||
import { BaseStatusPanel } from '@/components/BaseStatusPanel'
|
||
import { PowerChart } from '@/components/PowerChart'
|
||
import { InvestmentTrendChart } from '@/components/InvestmentTrendChart'
|
||
import { RetaliationGauge } from '@/components/RetaliationGauge'
|
||
import { useSituationStore } from '@/store/situationStore'
|
||
import { useReplaySituation } from '@/hooks/useReplaySituation'
|
||
import { fetchAndSetSituation, startSituationWebSocket, stopSituationWebSocket } from '@/store/situationStore'
|
||
|
||
export function Dashboard() {
|
||
const situation = useReplaySituation()
|
||
const isLoading = useSituationStore((s) => s.isLoading)
|
||
const lastError = useSituationStore((s) => s.lastError)
|
||
|
||
useEffect(() => {
|
||
fetchAndSetSituation().finally(() => startSituationWebSocket())
|
||
return () => stopSituationWebSocket()
|
||
}, [])
|
||
|
||
return (
|
||
<div className="landscape-scaler flex h-screen w-full max-w-full min-h-0 flex-col overflow-hidden bg-military-dark font-orbitron">
|
||
{lastError && (
|
||
<div className="shrink-0 bg-amber-500/20 px-4 py-2 text-center text-sm text-amber-400">
|
||
{lastError}(使用本地缓存,请确保 API + WebSocket 已启动:npm run api)
|
||
</div>
|
||
)}
|
||
<HeaderPanel />
|
||
<TimelinePanel />
|
||
|
||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-auto overflow-x-hidden xl:flex-row xl:overflow-hidden">
|
||
{/* xl:左|中|右。竖屏:地图→战损→美国基地→伊朗基地→左→右 */}
|
||
<main className="flex w-full min-h-[280px] shrink-0 flex-col overflow-hidden xl:order-2 xl:min-h-0 xl:min-w-0 xl:flex-1 xl:shrink">
|
||
<div className="h-[45vmin] min-h-[180px] w-full shrink-0 xl:min-h-0 xl:flex-1">
|
||
<WarMap />
|
||
</div>
|
||
<div className="flex shrink-0 flex-col gap-2 overflow-x-auto border-t border-military-border bg-military-panel/95 px-3 py-2 xl:flex-row xl:items-stretch xl:overflow-visible xl:px-4">
|
||
<CombatLossesPanel
|
||
usLosses={situation.usForces.combatLosses}
|
||
iranLosses={situation.iranForces.combatLosses}
|
||
conflictStats={situation.conflictStats}
|
||
civilianTotal={situation.civilianCasualtiesTotal}
|
||
className="min-w-0 flex-1 shrink-0 py-1"
|
||
/>
|
||
<BaseStatusPanel keyLocations={situation.usForces.keyLocations} className="shrink-0 xl:min-w-[200px] xl:border-r xl:border-military-border xl:pr-4" />
|
||
<IranBaseStatusPanel
|
||
keyLocations={situation.iranForces.keyLocations}
|
||
className="min-w-0 shrink-0 xl:min-w-[200px]"
|
||
/>
|
||
</div>
|
||
</main>
|
||
|
||
<aside className="flex min-h-0 min-w-0 shrink-0 flex-col gap-2 overflow-y-auto overflow-x-visible border-b border-military-border p-3 xl:order-1 xl:min-w-[320px] xl:max-w-[340px] xl:border-b-0 xl:border-r xl:p-4">
|
||
<div className="flex h-40 shrink-0 flex-col gap-0 rounded-lg border border-military-us/30 bg-military-panel/80 p-1 xl:h-48">
|
||
<div className="h-[55%] min-h-0 shrink-0">
|
||
<PowerChart
|
||
usPower={situation.usForces.powerIndex}
|
||
iranPower={situation.iranForces.powerIndex}
|
||
className="h-full w-full"
|
||
/>
|
||
</div>
|
||
<div className="h-[45%] min-h-0 shrink-0">
|
||
<InvestmentTrendChart
|
||
history={situation.usForces.wallStreetInvestmentTrend}
|
||
className="h-full"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<ForcePanel
|
||
side="us"
|
||
summary={situation.usForces.summary}
|
||
powerIndex={situation.usForces.powerIndex}
|
||
assets={situation.usForces.assets}
|
||
/>
|
||
</aside>
|
||
|
||
<aside className="flex min-h-0 min-w-0 shrink-0 flex-col gap-2 overflow-y-auto overflow-x-visible border-t border-military-border p-3 xl:order-3 xl:min-w-[320px] xl:max-w-[340px] xl:border-t-0 xl:border-l xl:p-4">
|
||
<div className="h-40 shrink-0 rounded-lg border border-military-iran/30 bg-military-panel/80 p-1 xl:h-48">
|
||
<RetaliationGauge
|
||
value={situation.iranForces.retaliationSentiment}
|
||
history={situation.iranForces.retaliationSentimentHistory}
|
||
className="h-full"
|
||
/>
|
||
</div>
|
||
<ForcePanel
|
||
side="iran"
|
||
summary={situation.iranForces.summary}
|
||
powerIndex={situation.iranForces.powerIndex}
|
||
assets={situation.iranForces.assets}
|
||
/>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|