fix: bug
This commit is contained in:
@@ -4,17 +4,8 @@ import type { MapRef } from 'react-map-gl'
|
||||
import type { Map as MapboxMap } from 'mapbox-gl'
|
||||
import 'mapbox-gl/dist/mapbox-gl.css'
|
||||
import { useReplaySituation } from '@/hooks/useReplaySituation'
|
||||
import { usePlaybackStore } from '@/store/playbackStore'
|
||||
import { config } from '@/config'
|
||||
import {
|
||||
ATTACKED_TARGETS,
|
||||
ALLIED_STRIKE_LOCATIONS,
|
||||
LINCOLN_COORDS,
|
||||
LINCOLN_STRIKE_TARGETS,
|
||||
FORD_COORDS,
|
||||
FORD_STRIKE_TARGETS,
|
||||
ISRAEL_STRIKE_SOURCE,
|
||||
ISRAEL_STRIKE_TARGETS,
|
||||
} from '@/data/mapLocations'
|
||||
import { EXTENDED_WAR_ZONES } from '@/data/extendedWarData'
|
||||
|
||||
const MAPBOX_TOKEN = config.mapboxAccessToken || ''
|
||||
@@ -65,8 +56,45 @@ const ALLIES_ADMIN = [
|
||||
// 伊朗攻击源 德黑兰 [lng, lat]
|
||||
const TEHRAN_SOURCE: [number, number] = [51.389, 35.6892]
|
||||
|
||||
// 真主党打击源(黎巴嫩南部大致位置),用于绘制向以色列北部的攻击矢量
|
||||
const HEZBOLLAH_SOURCE: [number, number] = [35.3, 33.2]
|
||||
// API 未返回 mapData 时的静态 fallback,保证美/以打击线与动画不消失(与 server/seed.js 一致)
|
||||
const FALLBACK_STRIKE_SOURCES: { id: string; name: string; lng: number; lat: number }[] = [
|
||||
{ id: 'israel', name: '以色列', lng: 34.78, lat: 32.08 },
|
||||
{ id: 'lincoln', name: '林肯号航母', lng: 58.4215, lat: 24.1568 },
|
||||
{ id: 'ford', name: '福特号航母', lng: 24.1002, lat: 35.7397 },
|
||||
]
|
||||
const FALLBACK_STRIKE_LINES: { sourceId: string; targets: { lng: number; lat: number; name?: string }[] }[] = [
|
||||
{
|
||||
sourceId: 'israel',
|
||||
targets: [
|
||||
{ lng: 50.88, lat: 34.64, name: '库姆' },
|
||||
{ lng: 50.876409, lat: 34.625448, name: '伊朗专家会议秘书处' },
|
||||
{ lng: 51.916, lat: 33.666, name: '纳坦兹' },
|
||||
{ lng: 51.002, lat: 35.808, name: '卡拉季无人机厂' },
|
||||
],
|
||||
},
|
||||
{
|
||||
sourceId: 'lincoln',
|
||||
targets: [
|
||||
{ lng: 56.27, lat: 27.18, name: '阿巴斯港海军司令部' },
|
||||
{ lng: 57.08, lat: 27.13, name: '米纳布' },
|
||||
{ lng: 56.5, lat: 27.0, name: '霍尔木兹岸防阵地' },
|
||||
{ lng: 50.838, lat: 28.968, name: '布什尔雷达站' },
|
||||
{ lng: 51.667, lat: 32.654, name: '伊斯法罕核设施' },
|
||||
],
|
||||
},
|
||||
{
|
||||
sourceId: 'ford',
|
||||
targets: [
|
||||
{ lng: 51.42, lat: 35.69, name: '哈梅内伊官邸' },
|
||||
{ lng: 51.41, lat: 35.72, name: '总统府/情报部' },
|
||||
{ lng: 51.15, lat: 35.69, name: '梅赫拉巴德机场' },
|
||||
{ lng: 46.29, lat: 38.08, name: '大不里士空军基地' },
|
||||
{ lng: 47.076, lat: 34.314, name: '克尔曼沙赫导弹掩体' },
|
||||
{ lng: 46.42, lat: 33.64, name: '伊拉姆导弹阵地' },
|
||||
{ lng: 48.35, lat: 33.48, name: '霍拉马巴德储备库' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
/** 二次贝塞尔曲线路径,更平滑的弧线 height 控制弧高 */
|
||||
function parabolaPath(
|
||||
@@ -157,6 +185,7 @@ export function WarMap() {
|
||||
const hezbollahPathsRef = useRef<[number, number][][]>([])
|
||||
const hormuzPathsRef = useRef<[number, number][][]>([])
|
||||
const situation = useReplaySituation()
|
||||
const { isReplayMode } = usePlaybackStore()
|
||||
const { usForces, iranForces, conflictEvents = [] } = situation
|
||||
|
||||
const usLocs = (usForces.keyLocations || []) as KeyLoc[]
|
||||
@@ -196,30 +225,52 @@ export function WarMap() {
|
||||
}
|
||||
}, [usForces.keyLocations, iranForces.keyLocations])
|
||||
|
||||
// 德黑兰到 27 个被袭目标的攻击路径(静态线条)
|
||||
const mapData = situation.mapData
|
||||
const attackedTargets = mapData?.attackedTargets ?? []
|
||||
const strikeSources =
|
||||
mapData?.strikeSources?.length > 0 ? mapData.strikeSources : FALLBACK_STRIKE_SOURCES
|
||||
const strikeLines =
|
||||
mapData?.strikeLines?.length > 0 ? mapData.strikeLines : FALLBACK_STRIKE_LINES
|
||||
|
||||
const attackPaths = useMemo(
|
||||
() => ATTACKED_TARGETS.map((target) => parabolaPath(TEHRAN_SOURCE, target as [number, number])),
|
||||
[]
|
||||
() => attackedTargets.map((target) => parabolaPath(TEHRAN_SOURCE, target as [number, number])),
|
||||
[attackedTargets]
|
||||
)
|
||||
|
||||
attackPathsRef.current = attackPaths
|
||||
|
||||
const lincolnPaths = useMemo(
|
||||
() => LINCOLN_STRIKE_TARGETS.map((t) => parabolaPath(LINCOLN_COORDS, t)),
|
||||
[]
|
||||
)
|
||||
const fordPaths = useMemo(
|
||||
() => FORD_STRIKE_TARGETS.map((t) => parabolaPath(FORD_COORDS, t)),
|
||||
[]
|
||||
)
|
||||
const israelPaths = useMemo(
|
||||
() => ISRAEL_STRIKE_TARGETS.map((t) => parabolaPath(ISRAEL_STRIKE_SOURCE, t)),
|
||||
[]
|
||||
)
|
||||
// 真主党 → 以色列北部三处目标(低平弧线)
|
||||
const sourceCoords = useMemo(() => {
|
||||
const m: Record<string, [number, number]> = {}
|
||||
strikeSources.forEach((s) => { m[s.id] = [s.lng, s.lat] })
|
||||
return m
|
||||
}, [strikeSources])
|
||||
|
||||
const lincolnPaths = useMemo(() => {
|
||||
const line = strikeLines.find((l) => l.sourceId === 'lincoln')
|
||||
const coords = sourceCoords.lincoln
|
||||
if (!coords || !line) return []
|
||||
return line.targets.map((t) => parabolaPath(coords, [t.lng, t.lat]))
|
||||
}, [strikeLines, sourceCoords])
|
||||
const fordPaths = useMemo(() => {
|
||||
const line = strikeLines.find((l) => l.sourceId === 'ford')
|
||||
const coords = sourceCoords.ford
|
||||
if (!coords || !line) return []
|
||||
return line.targets.map((t) => parabolaPath(coords, [t.lng, t.lat]))
|
||||
}, [strikeLines, sourceCoords])
|
||||
const israelPaths = useMemo(() => {
|
||||
const line = strikeLines.find((l) => l.sourceId === 'israel')
|
||||
const coords = sourceCoords.israel
|
||||
if (!coords || !line) return []
|
||||
return line.targets.map((t) => parabolaPath(coords, [t.lng, t.lat]))
|
||||
}, [strikeLines, sourceCoords])
|
||||
// 真主党 → 以色列北部三处目标(与美/以打击弧线一致:同一 parabola 高度与动画方式)
|
||||
const hezbollahSource = EXTENDED_WAR_ZONES.hezbollahStrikeSource
|
||||
const hezbollahPaths = useMemo(
|
||||
() => EXTENDED_WAR_ZONES.activeAttacks.map((t) => parabolaPath(HEZBOLLAH_SOURCE, t.coords, 1.5)),
|
||||
[]
|
||||
() =>
|
||||
isReplayMode
|
||||
? []
|
||||
: EXTENDED_WAR_ZONES.activeAttacks.map((t) => parabolaPath(hezbollahSource, t.coords, 3)),
|
||||
[hezbollahSource, isReplayMode]
|
||||
)
|
||||
// 伊朗不同地点 → 霍尔木兹海峡多点攻击(黄色轨迹)
|
||||
const hormuzTargetPoints = useMemo(
|
||||
@@ -232,6 +283,7 @@ export function WarMap() {
|
||||
[]
|
||||
)
|
||||
const hormuzPaths = useMemo(() => {
|
||||
if (isReplayMode) return []
|
||||
// 使用更远的伊朗腹地/纵深位置,弧线更明显
|
||||
const sources: [number, number][] = [
|
||||
TEHRAN_SOURCE, // 德黑兰
|
||||
@@ -241,7 +293,7 @@ export function WarMap() {
|
||||
return hormuzTargetPoints.map((target, idx) =>
|
||||
parabolaPath(sources[idx % sources.length], target, 3)
|
||||
)
|
||||
}, [hormuzTargetPoints])
|
||||
}, [hormuzTargetPoints, isReplayMode])
|
||||
lincolnPathsRef.current = lincolnPaths
|
||||
fordPathsRef.current = fordPaths
|
||||
israelPathsRef.current = israelPaths
|
||||
@@ -319,28 +371,34 @@ export function WarMap() {
|
||||
|
||||
// 真主党当前攻击目标点
|
||||
const hezbollahTargetsGeoJson = useMemo(
|
||||
() => ({
|
||||
type: 'FeatureCollection' as const,
|
||||
features: EXTENDED_WAR_ZONES.activeAttacks.map((t) => ({
|
||||
type: 'Feature' as const,
|
||||
properties: { name: t.name, type: t.type, damage: t.damage },
|
||||
geometry: { type: 'Point' as const, coordinates: t.coords },
|
||||
})),
|
||||
}),
|
||||
[]
|
||||
() =>
|
||||
isReplayMode
|
||||
? { type: 'FeatureCollection' as const, features: [] }
|
||||
: {
|
||||
type: 'FeatureCollection' as const,
|
||||
features: EXTENDED_WAR_ZONES.activeAttacks.map((t) => ({
|
||||
type: 'Feature' as const,
|
||||
properties: { name: t.name, type: t.type, damage: t.damage },
|
||||
geometry: { type: 'Point' as const, coordinates: t.coords },
|
||||
})),
|
||||
},
|
||||
[isReplayMode]
|
||||
)
|
||||
|
||||
// 霍尔木兹海峡被持续打击的海面目标(用于脉冲与标记)
|
||||
const hormuzTargetsGeoJson = useMemo(
|
||||
() => ({
|
||||
type: 'FeatureCollection' as const,
|
||||
features: hormuzTargetPoints.map((coords, idx) => ({
|
||||
type: 'Feature' as const,
|
||||
properties: { id: `H${idx + 1}` },
|
||||
geometry: { type: 'Point' as const, coordinates: coords },
|
||||
})),
|
||||
}),
|
||||
[hormuzTargetPoints]
|
||||
() =>
|
||||
isReplayMode
|
||||
? { type: 'FeatureCollection' as const, features: [] }
|
||||
: {
|
||||
type: 'FeatureCollection' as const,
|
||||
features: hormuzTargetPoints.map((coords, idx) => ({
|
||||
type: 'Feature' as const,
|
||||
properties: { id: `H${idx + 1}` },
|
||||
geometry: { type: 'Point' as const, coordinates: coords },
|
||||
})),
|
||||
},
|
||||
[hormuzTargetPoints, isReplayMode]
|
||||
)
|
||||
|
||||
// 霍尔木兹海峡交战区 & 真主党势力范围(静态面)
|
||||
@@ -491,7 +549,7 @@ export function WarMap() {
|
||||
)
|
||||
israelSrc.setData({ type: 'FeatureCollection', features })
|
||||
}
|
||||
// 真主党打击以色列北部:橙红色光点,低平飞行
|
||||
// 真主党打击以色列北部:橙红光点,与林肯/福特/以色列同一动画方式
|
||||
const hezSrc = map.getSource('hezbollah-strike-dots') as
|
||||
| { setData: (d: GeoJSON.FeatureCollection) => void }
|
||||
| undefined
|
||||
@@ -550,12 +608,12 @@ export function WarMap() {
|
||||
map.setPaintProperty('gdelt-events-red-pulse', 'circle-radius', r)
|
||||
map.setPaintProperty('gdelt-events-red-pulse', 'circle-opacity', opacity)
|
||||
}
|
||||
// 真主党攻击目标:橙红脉冲,效果与 allied-strike-targets 保持一致
|
||||
// 真主党攻击目标:橙红脉冲,与 allied-strike-targets 同一周期与半径
|
||||
if (map.getLayer('hezbollah-attack-targets-pulse')) {
|
||||
const cycle = 2000
|
||||
const phase = Math.max(0, Math.min(1, (elapsed % cycle) / cycle))
|
||||
const r = Math.max(0, 30 * phase * zoomScale)
|
||||
const opacity = Math.min(1, Math.max(0, 1 - phase * 1.15))
|
||||
const r = Math.max(0, 35 * phase * zoomScale)
|
||||
const opacity = Math.min(1, Math.max(0, 1 - phase * 1.2))
|
||||
map.setPaintProperty('hezbollah-attack-targets-pulse', 'circle-radius', r)
|
||||
map.setPaintProperty('hezbollah-attack-targets-pulse', 'circle-opacity', opacity)
|
||||
}
|
||||
@@ -891,18 +949,18 @@ export function WarMap() {
|
||||
/>
|
||||
</Source>
|
||||
|
||||
{/* 真主党对以色列北部的攻击矢量线(低平红线) */}
|
||||
{/* 真主党对以色列北部的攻击矢量线(与林肯/福特/以色列线宽一致) */}
|
||||
<Source id="hezbollah-attack-lines" type="geojson" data={hezbollahLinesGeoJson}>
|
||||
<Layer
|
||||
id="hezbollah-attack-lines"
|
||||
type="line"
|
||||
paint={{
|
||||
'line-color': 'rgba(248, 113, 113, 0.7)',
|
||||
'line-width': ['interpolate', ['linear'], ['zoom'], 4, 0.6, 8, 1.2, 12, 2],
|
||||
'line-color': 'rgba(248, 113, 113, 0.45)',
|
||||
'line-width': ['interpolate', ['linear'], ['zoom'], 4, 0.5, 8, 1, 12, 2],
|
||||
}}
|
||||
/>
|
||||
</Source>
|
||||
{/* 真主党打击光点(沿矢量路径移动) */}
|
||||
{/* 真主党打击光点(与林肯/福特/以色列光点半径与动画一致) */}
|
||||
<Source
|
||||
id="hezbollah-strike-dots"
|
||||
type="geojson"
|
||||
@@ -919,17 +977,17 @@ export function WarMap() {
|
||||
id="hezbollah-strike-dots-glow"
|
||||
type="circle"
|
||||
paint={{
|
||||
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 2.5, 8, 4.5, 12, 7],
|
||||
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 3, 8, 6, 12, 10],
|
||||
'circle-color': 'rgba(248, 113, 113, 0.6)',
|
||||
'circle-blur': 0.25,
|
||||
'circle-blur': 0.3,
|
||||
}}
|
||||
/>
|
||||
<Layer
|
||||
id="hezbollah-strike-dots-core"
|
||||
type="circle"
|
||||
paint={{
|
||||
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 1, 8, 2, 12, 3.5],
|
||||
'circle-color': '#fb923c',
|
||||
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 1, 8, 2, 12, 4],
|
||||
'circle-color': '#F97316',
|
||||
'circle-stroke-width': 0.5,
|
||||
'circle-stroke-color': '#fff',
|
||||
}}
|
||||
@@ -1093,10 +1151,10 @@ export function WarMap() {
|
||||
type="geojson"
|
||||
data={{
|
||||
type: 'FeatureCollection',
|
||||
features: ALLIED_STRIKE_LOCATIONS.map((s) => ({
|
||||
features: (situation.iranForces?.keyLocations ?? []).map((s) => ({
|
||||
type: 'Feature' as const,
|
||||
properties: { name: s.name },
|
||||
geometry: { type: 'Point' as const, coordinates: s.coords },
|
||||
geometry: { type: 'Point' as const, coordinates: [s.lng, s.lat] },
|
||||
})),
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user