fix: 优化自适应界面
This commit is contained in:
@@ -6,6 +6,10 @@ import {
|
||||
Ship,
|
||||
Shield,
|
||||
Car,
|
||||
Scan,
|
||||
Rocket,
|
||||
Wind,
|
||||
Anchor,
|
||||
TrendingDown,
|
||||
UserCircle,
|
||||
Activity,
|
||||
@@ -31,10 +35,14 @@ export function CombatLossesPanel({ usLosses, iranLosses, conflictStats, civilia
|
||||
{ label: '战舰', icon: Ship, iconColor: 'text-blue-500', us: usLosses.warships, ir: iranLosses.warships },
|
||||
{ label: '装甲', icon: Shield, iconColor: 'text-emerald-500', us: usLosses.armor, ir: iranLosses.armor },
|
||||
{ label: '车辆', icon: Car, iconColor: 'text-slate-400', us: usLosses.vehicles, ir: iranLosses.vehicles },
|
||||
{ label: '无人机', icon: Scan, iconColor: 'text-violet-400', us: usLosses.drones ?? 0, ir: iranLosses.drones ?? 0 },
|
||||
{ label: '导弹', icon: Rocket, iconColor: 'text-orange-500', us: usLosses.missiles ?? 0, ir: iranLosses.missiles ?? 0 },
|
||||
{ label: '直升机', icon: Wind, iconColor: 'text-teal-400', us: usLosses.helicopters ?? 0, ir: iranLosses.helicopters ?? 0 },
|
||||
{ label: '潜艇', icon: Anchor, iconColor: 'text-indigo-400', us: usLosses.submarines ?? 0, ir: iranLosses.submarines ?? 0 },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={`flex min-h-[220px] max-h-[240px] min-w-0 flex-1 flex-col overflow-hidden rounded border border-military-border bg-military-panel/95 font-orbitron ${className}`}>
|
||||
<div className={`flex min-h-[200px] min-w-0 flex-1 flex-col overflow-auto rounded border border-military-border bg-military-panel/95 font-orbitron ${className}`}>
|
||||
<div className="mb-1.5 flex shrink-0 items-center justify-center gap-2 text-[10px] uppercase tracking-wider text-military-text-secondary">
|
||||
<TrendingDown className="h-2.5 w-2.5 shrink-0 text-amber-400" />
|
||||
战损
|
||||
@@ -46,7 +54,7 @@ export function CombatLossesPanel({ usLosses, iranLosses, conflictStats, civilia
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-2 overflow-hidden px-3 pb-2">
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-2 overflow-visible px-3 pb-2">
|
||||
{/* 人员伤亡 - 单独容器 */}
|
||||
<div className="flex shrink-0 flex-col justify-center overflow-hidden rounded-lg border border-red-900/50 bg-red-950/40 px-3 py-2">
|
||||
<div className="mb-1 flex shrink-0 items-center justify-center gap-3 text-[9px] text-military-text-secondary">
|
||||
@@ -57,14 +65,14 @@ export function CombatLossesPanel({ usLosses, iranLosses, conflictStats, civilia
|
||||
</div>
|
||||
<div className="grid min-w-0 grid-cols-2 gap-x-2 gap-y-1 overflow-hidden text-center tabular-nums sm:gap-x-4">
|
||||
<div className="min-w-0 truncate text-military-us" title={`美: ${formatMillions(usLosses.personnelCasualties.killed)} / ${formatMillions(usLosses.personnelCasualties.wounded)}`}>
|
||||
<span className="text-base font-bold text-red-500">{formatMillions(usLosses.personnelCasualties.killed)}</span>
|
||||
<span className="text-sm font-bold text-red-500 sm:text-base">{formatMillions(usLosses.personnelCasualties.killed)}</span>
|
||||
<span className="mx-0.5 text-military-text-secondary">/</span>
|
||||
<span className="text-base font-semibold text-amber-500">{formatMillions(usLosses.personnelCasualties.wounded)}</span>
|
||||
<span className="text-sm font-semibold text-amber-500 sm:text-base">{formatMillions(usLosses.personnelCasualties.wounded)}</span>
|
||||
</div>
|
||||
<div className="min-w-0 truncate text-military-iran" title={`伊: ${formatMillions(iranLosses.personnelCasualties.killed)} / ${formatMillions(iranLosses.personnelCasualties.wounded)}`}>
|
||||
<span className="text-base font-bold text-red-500">{formatMillions(iranLosses.personnelCasualties.killed)}</span>
|
||||
<span className="text-sm font-bold text-red-500 sm:text-base">{formatMillions(iranLosses.personnelCasualties.killed)}</span>
|
||||
<span className="mx-0.5 text-military-text-secondary">/</span>
|
||||
<span className="text-base font-semibold text-amber-500">{formatMillions(iranLosses.personnelCasualties.wounded)}</span>
|
||||
<span className="text-sm font-semibold text-amber-500 sm:text-base">{formatMillions(iranLosses.personnelCasualties.wounded)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,36 +83,36 @@ export function CombatLossesPanel({ usLosses, iranLosses, conflictStats, civilia
|
||||
<UserCircle className="h-3 w-3 text-amber-400" />
|
||||
平民伤亡(合计)
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-3 text-center tabular-nums">
|
||||
<div className="flex flex-wrap items-center justify-center gap-2 text-center tabular-nums sm:gap-3">
|
||||
<span className="flex items-center gap-0.5">
|
||||
<Skull className="h-3 w-3 text-red-500" />
|
||||
<span className="text-base font-bold text-red-500">{formatMillions(civ.killed)}</span>
|
||||
<span className="text-sm font-bold text-red-500 sm:text-base">{formatMillions(civ.killed)}</span>
|
||||
</span>
|
||||
<span className="text-military-text-secondary/60">/</span>
|
||||
<span className="flex items-center gap-0.5">
|
||||
<Bandage className="h-3 w-3 text-amber-500" />
|
||||
<span className="text-base font-semibold text-amber-500">{formatMillions(civ.wounded)}</span>
|
||||
<span className="text-sm font-semibold text-amber-500 sm:text-base">{formatMillions(civ.wounded)}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 其它 - 标签+图标+数字,单独容器 */}
|
||||
<div className="min-h-0 min-w-0 flex-1 overflow-hidden rounded border border-military-border/50 bg-military-dark/30 px-2 py-1.5">
|
||||
{/* 其它 - 标签+图标+数字,竖屏横屏均完整显示,自适应排版 */}
|
||||
<div className="min-w-0 shrink-0 rounded border border-military-border/50 bg-military-dark/30 px-2 py-1.5">
|
||||
<div className="mb-1 text-[8px] text-military-text-secondary">美:伊</div>
|
||||
<div className="grid grid-cols-2 gap-x-2 gap-y-0.5 overflow-hidden text-[11px] tabular-nums lg:grid-cols-3">
|
||||
<div className="grid min-w-0 grid-cols-2 gap-x-2 gap-y-1 tabular-nums sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
||||
{otherRows.map(({ label, icon: Icon, iconColor, ...rest }, i) => (
|
||||
<div key={i} className="flex min-w-0 items-center justify-between gap-1 overflow-hidden">
|
||||
<span className="flex shrink-0 items-center gap-0.5 text-military-text-primary">
|
||||
<Icon className={`h-3 w-3 ${iconColor}`} />
|
||||
<div key={i} className="flex min-w-0 items-center justify-between gap-1">
|
||||
<span className="flex shrink-0 items-center gap-0.5 text-military-text-primary text-[9px] sm:text-[10px]">
|
||||
<Icon className={`h-2.5 w-2.5 shrink-0 sm:h-3 sm:w-3 ${iconColor}`} />
|
||||
{label}
|
||||
</span>
|
||||
{'value' in rest ? (
|
||||
<span className="min-w-0 truncate text-right text-amber-400">{rest.value}</span>
|
||||
<span className="min-w-0 text-right text-[9px] text-amber-400 sm:text-[10px] tabular-nums">{String(rest.value)}</span>
|
||||
) : (
|
||||
<span className="min-w-0 truncate text-right">
|
||||
<span className="text-military-us">{rest.us}</span>
|
||||
<span className="min-w-0 shrink-0 text-right text-[9px] sm:text-[10px]">
|
||||
<span className="text-military-us tabular-nums">{rest.us}</span>
|
||||
<span className="text-military-text-secondary/60">:</span>
|
||||
<span className="text-military-iran">{rest.ir}</span>
|
||||
<span className="text-military-iran tabular-nums">{rest.ir}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -133,15 +133,15 @@ export function HeaderPanel() {
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="flex shrink-0 flex-wrap items-center justify-between gap-3 overflow-x-auto border-b border-military-border bg-military-panel/95 px-4 py-3 font-orbitron lg:flex-nowrap lg:px-6">
|
||||
<div className="flex flex-wrap items-center gap-3 lg:gap-6">
|
||||
<h1 className="text-base font-bold uppercase tracking-widest text-military-accent lg:text-2xl">
|
||||
<header className="flex shrink-0 flex-wrap items-center justify-between gap-2 overflow-hidden border-b border-military-border bg-military-panel/95 px-2 py-2 font-orbitron sm:gap-3 sm:px-4 sm:py-3 lg:flex-nowrap lg:gap-4 lg:px-6">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-2 sm:gap-3 lg:gap-6">
|
||||
<h1 className="truncate text-sm font-bold uppercase tracking-wider text-military-accent sm:text-base sm:tracking-widest lg:text-2xl">
|
||||
美伊军事态势显示
|
||||
</h1>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="flex items-center gap-2 text-sm text-military-text-secondary">
|
||||
<Clock className="h-4 w-4 shrink-0" />
|
||||
<span className="min-w-[11rem] tabular-nums">{formatDateTime(now)}</span>
|
||||
<div className="flex min-w-0 shrink-0 flex-col gap-0.5">
|
||||
<div className="flex items-center gap-1.5 text-xs text-military-text-secondary sm:gap-2 sm:text-sm">
|
||||
<Clock className="h-3.5 w-3.5 shrink-0 sm:h-4 sm:w-4" />
|
||||
<span className="tabular-nums sm:min-w-[10rem]">{formatDateTime(now)}</span>
|
||||
</div>
|
||||
{(isConnected || isReplayMode) && (
|
||||
<span className={`text-[10px] ${isReplayMode ? 'text-military-accent' : 'text-green-500/90'}`}>
|
||||
@@ -150,8 +150,8 @@ export function HeaderPanel() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-4">
|
||||
<div className="flex items-center gap-2 text-military-text-secondary">
|
||||
<div className="flex min-w-0 shrink flex-wrap items-center justify-end gap-2 sm:gap-3 lg:shrink-0 lg:gap-4">
|
||||
<div className="flex shrink-0 items-center gap-1.5 text-military-text-secondary sm:gap-2">
|
||||
<Eye className="h-3.5 w-3.5" />
|
||||
<span className="text-[10px]">在看 <b className="text-military-accent tabular-nums">{viewers}</b></span>
|
||||
<span className="text-[10px] opacity-70">|</span>
|
||||
@@ -160,48 +160,41 @@ export function HeaderPanel() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFeedbackOpen(true)}
|
||||
className="flex items-center gap-1 rounded border border-military-border px-2 py-1 text-[10px] text-military-text-secondary hover:bg-military-border/30 hover:text-cyan-400"
|
||||
className="flex shrink-0 items-center gap-1 rounded border border-military-border px-1.5 py-0.5 text-[9px] text-military-text-secondary hover:bg-military-border/30 hover:text-cyan-400 sm:px-2 sm:py-1 sm:text-[10px]"
|
||||
>
|
||||
<MessageSquare className="h-3 w-3" />
|
||||
<MessageSquare className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
|
||||
留言
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleShare}
|
||||
className="flex items-center gap-1 rounded border border-military-border px-2 py-1 text-[10px] text-military-text-secondary hover:bg-military-border/30 hover:text-cyan-400"
|
||||
className="flex shrink-0 items-center gap-1 rounded border border-military-border px-1.5 py-0.5 text-[9px] text-military-text-secondary hover:bg-military-border/30 hover:text-cyan-400 sm:px-2 sm:py-1 sm:text-[10px]"
|
||||
>
|
||||
<Share2 className="h-3 w-3" />
|
||||
<Share2 className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
|
||||
分享
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLike}
|
||||
className={`flex items-center gap-1 rounded border px-2 py-1 text-[10px] transition-colors ${
|
||||
className={`flex shrink-0 items-center gap-1 rounded border px-1.5 py-0.5 text-[9px] transition-colors sm:px-2 sm:py-1 sm:text-[10px] ${
|
||||
liked
|
||||
? 'border-red-500/50 bg-red-500/20 text-red-400'
|
||||
: 'border-military-border text-military-text-secondary hover:bg-military-border/30 hover:text-red-400'
|
||||
}`}
|
||||
>
|
||||
<Heart className={`h-3 w-3 ${liked ? 'fill-current' : ''}`} />
|
||||
<Heart className={`h-2.5 w-2.5 sm:h-3 sm:w-3 ${liked ? 'fill-current' : ''}`} />
|
||||
点赞 {likes > 0 && <span className="tabular-nums">{likes}</span>}
|
||||
</button>
|
||||
{isConnected ? (
|
||||
<>
|
||||
<Wifi className="h-3.5 w-3.5 text-green-500" />
|
||||
<span className="text-xs text-green-500">实时</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<WifiOff className="h-3.5 w-3.5 text-military-text-secondary" />
|
||||
<span className="text-xs text-military-text-secondary">已断开</span>
|
||||
</>
|
||||
)}
|
||||
<span className={`flex items-center gap-1 ${isConnected ? 'text-green-500' : 'text-military-text-secondary'}`}>
|
||||
{isConnected ? <Wifi className="h-3.5 w-3.5" /> : <WifiOff className="h-3.5 w-3.5" />}
|
||||
<span className="text-xs">{isConnected ? '实时' : '已断开'}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3 lg:gap-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[10px] uppercase tracking-wider text-military-text-secondary">国力对比</span>
|
||||
<div className="mt-0.5 flex h-2 w-28 overflow-hidden rounded-full bg-military-border">
|
||||
<div className="flex min-w-0 shrink flex-wrap items-center gap-2 sm:gap-3 lg:gap-4">
|
||||
<div className="flex shrink-0 flex-col">
|
||||
<span className="text-[9px] uppercase tracking-wider text-military-text-secondary sm:text-[10px]">国力对比</span>
|
||||
<div className="mt-0.5 flex h-1.5 w-20 overflow-hidden rounded-full bg-military-border sm:h-2 sm:w-28">
|
||||
<div
|
||||
className="bg-military-us transition-all"
|
||||
style={{ width: `${(usForces.powerIndex.overall / (usForces.powerIndex.overall + iranForces.powerIndex.overall)) * 100}%` }}
|
||||
@@ -211,29 +204,29 @@ export function HeaderPanel() {
|
||||
style={{ width: `${(iranForces.powerIndex.overall / (usForces.powerIndex.overall + iranForces.powerIndex.overall)) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-0.5 flex justify-between text-[9px] tabular-nums text-military-text-secondary">
|
||||
<div className="mt-0.5 flex justify-between text-[8px] tabular-nums text-military-text-secondary sm:text-[9px]">
|
||||
<span className="text-military-us">美 {usForces.powerIndex.overall}</span>
|
||||
<span className="text-military-iran">伊 {iranForces.powerIndex.overall}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-8 w-px shrink-0 bg-military-border" />
|
||||
<div className="hidden h-8 w-px shrink-0 bg-military-border sm:block" />
|
||||
<StatCard
|
||||
label="美国/盟国"
|
||||
value={usForces.powerIndex.overall}
|
||||
variant="us"
|
||||
className="border-military-us/50"
|
||||
className="shrink-0 border-military-us/50 px-1.5 py-1 sm:px-2 sm:py-1.5"
|
||||
/>
|
||||
<StatCard
|
||||
label="伊朗"
|
||||
value={iranForces.powerIndex.overall}
|
||||
variant="iran"
|
||||
className="border-military-iran/50"
|
||||
className="shrink-0 border-military-iran/50 px-1.5 py-1 sm:px-2 sm:py-1.5"
|
||||
/>
|
||||
<StatCard
|
||||
label="差距"
|
||||
value={`+${usForces.powerIndex.overall - iranForces.powerIndex.overall}`}
|
||||
variant="accent"
|
||||
className="border-military-accent/50"
|
||||
className="shrink-0 border-military-accent/50 px-1.5 py-1 sm:px-2 sm:py-1.5"
|
||||
/>
|
||||
</div>
|
||||
{/* 留言弹窗 */}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useEffect, useRef } from 'react'
|
||||
import { useMemo, useEffect, useRef, useCallback } from 'react'
|
||||
import Map, { Source, Layer } from 'react-map-gl'
|
||||
import type { MapRef } from 'react-map-gl'
|
||||
import type { Map as MapboxMap } from 'mapbox-gl'
|
||||
@@ -20,6 +20,11 @@ const MAPBOX_TOKEN = config.mapboxAccessToken || ''
|
||||
|
||||
// 相关区域 bbox:伊朗、以色列、胡塞区 (minLng, minLat, maxLng, maxLat),覆盖红蓝区域
|
||||
const THEATER_BBOX = [22, 11, 64, 41] as const
|
||||
/** 移动端/小屏时 fitBounds 使区域完整显示 */
|
||||
const THEATER_BOUNDS: [[number, number], [number, number]] = [
|
||||
[THEATER_BBOX[0], THEATER_BBOX[1]],
|
||||
[THEATER_BBOX[2], THEATER_BBOX[3]],
|
||||
]
|
||||
const THEATER_CENTER = {
|
||||
longitude: (THEATER_BBOX[0] + THEATER_BBOX[2]) / 2,
|
||||
latitude: (THEATER_BBOX[1] + THEATER_BBOX[3]) / 2,
|
||||
@@ -124,6 +129,7 @@ const FLIGHT_DURATION_MS = 2500 // 光点飞行单程时间
|
||||
|
||||
export function WarMap() {
|
||||
const mapRef = useRef<MapRef>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const animRef = useRef<number>(0)
|
||||
const startRef = useRef<number>(0)
|
||||
const attackPathsRef = useRef<[number, number][][]>([])
|
||||
@@ -433,6 +439,21 @@ export function WarMap() {
|
||||
return () => cancelAnimationFrame(animRef.current)
|
||||
}, [])
|
||||
|
||||
// 容器尺寸变化时 fitBounds,保证区域完整显示(移动端自适应)
|
||||
const fitToTheater = useCallback(() => {
|
||||
const map = mapRef.current?.getMap()
|
||||
if (!map) return
|
||||
map.fitBounds(THEATER_BOUNDS, { padding: 32, maxZoom: 6, duration: 0 })
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current
|
||||
if (!el) return
|
||||
const ro = new ResizeObserver(() => fitToTheater())
|
||||
ro.observe(el)
|
||||
return () => ro.disconnect()
|
||||
}, [fitToTheater])
|
||||
|
||||
if (!MAPBOX_TOKEN) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-military-dark">
|
||||
@@ -455,7 +476,7 @@ export function WarMap() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full">
|
||||
<div ref={containerRef} className="relative h-full w-full min-w-0">
|
||||
{/* 图例 - 随容器自适应,避免遮挡 */}
|
||||
<div className="absolute bottom-2 left-2 z-10 flex flex-wrap gap-x-3 gap-y-1 rounded bg-black/70 px-2 py-1.5 text-[9px] sm:text-[10px]">
|
||||
<span className="flex items-center gap-1">
|
||||
@@ -502,7 +523,7 @@ export function WarMap() {
|
||||
touchRotate={false}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
onLoad={(e) => {
|
||||
// 地图加载完成后启动动画;延迟确保 Source/Layer 已挂载
|
||||
fitToTheater()
|
||||
setTimeout(() => initAnimation.current(e.target), 150)
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -37,6 +37,11 @@ export interface CombatLosses {
|
||||
warships: number
|
||||
armor: number
|
||||
vehicles: number
|
||||
/** 其它 */
|
||||
drones?: number
|
||||
missiles?: number
|
||||
helicopters?: number
|
||||
submarines?: number
|
||||
}
|
||||
|
||||
export interface SituationUpdate {
|
||||
@@ -152,6 +157,10 @@ export const INITIAL_MOCK_DATA: MilitarySituation = {
|
||||
warships: 0,
|
||||
armor: 0,
|
||||
vehicles: 8,
|
||||
drones: 4,
|
||||
missiles: 12,
|
||||
helicopters: 1,
|
||||
submarines: 0,
|
||||
},
|
||||
wallStreetInvestmentTrend: [
|
||||
{ time: '2025-03-01T00:00:00', value: 82 },
|
||||
@@ -202,6 +211,10 @@ export const INITIAL_MOCK_DATA: MilitarySituation = {
|
||||
warships: 12,
|
||||
armor: 18,
|
||||
vehicles: 42,
|
||||
drones: 28,
|
||||
missiles: 156,
|
||||
helicopters: 8,
|
||||
submarines: 2,
|
||||
},
|
||||
retaliationSentiment: 78,
|
||||
retaliationSentimentHistory: [
|
||||
|
||||
@@ -76,6 +76,10 @@ export function useReplaySituation(): MilitarySituation {
|
||||
warships: lerp(0, usLoss.warships),
|
||||
armor: lerp(0, usLoss.armor),
|
||||
vehicles: lerp(0, usLoss.vehicles),
|
||||
drones: lerp(0, usLoss.drones ?? 0),
|
||||
missiles: lerp(0, usLoss.missiles ?? 0),
|
||||
helicopters: lerp(0, usLoss.helicopters ?? 0),
|
||||
submarines: lerp(0, usLoss.submarines ?? 0),
|
||||
}
|
||||
const irLossesAt = {
|
||||
bases: {
|
||||
@@ -91,6 +95,10 @@ export function useReplaySituation(): MilitarySituation {
|
||||
warships: lerp(0, irLoss.warships),
|
||||
armor: lerp(0, irLoss.armor),
|
||||
vehicles: lerp(0, irLoss.vehicles),
|
||||
drones: lerp(0, irLoss.drones ?? 0),
|
||||
missiles: lerp(0, irLoss.missiles ?? 0),
|
||||
helicopters: lerp(0, irLoss.helicopters ?? 0),
|
||||
submarines: lerp(0, irLoss.submarines ?? 0),
|
||||
}
|
||||
|
||||
// 被袭基地:按 damage_level 排序,高损毁先出现;根据 progress 决定显示哪些为 attacked
|
||||
|
||||
@@ -61,3 +61,5 @@ body,
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
/* 移动端横屏使用单列+滚动,不再做 zoom 缩放,保持比例正常 */
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export function Dashboard() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-full max-w-full min-h-0 flex-col overflow-hidden bg-military-dark font-orbitron">
|
||||
<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)
|
||||
@@ -34,20 +34,20 @@ export function Dashboard() {
|
||||
<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">
|
||||
{/* 竖屏/小屏:地图(100%宽) → 战损 → 左 → 右。横屏≥1280px:左|中|右 */}
|
||||
{/* 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-[220px] w-full shrink-0 xl:min-h-0 xl:flex-1">
|
||||
<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">
|
||||
<BaseStatusPanel keyLocations={situation.usForces.keyLocations} className="shrink-0 xl:min-w-[200px] xl:border-r xl:border-military-border xl:pr-4" />
|
||||
<CombatLossesPanel
|
||||
usLosses={situation.usForces.combatLosses}
|
||||
iranLosses={situation.iranForces.combatLosses}
|
||||
conflictStats={situation.conflictStats}
|
||||
civilianTotal={situation.civilianCasualtiesTotal}
|
||||
className="min-w-0 flex-1 py-1"
|
||||
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]"
|
||||
|
||||
Reference in New Issue
Block a user