fix: 优化虫 机制,新增伊朗支援

This commit is contained in:
Daniel
2026-03-06 10:34:52 +08:00
parent 89145a6743
commit 9f2442f2e3
20 changed files with 411 additions and 62 deletions

View File

@@ -219,9 +219,35 @@ export function WarMap() {
const situation = useReplaySituation()
const { isReplayMode, playbackTime } = usePlaybackStore()
const { usForces, iranForces, conflictEvents = [] } = situation
/** 时间衰减基准:回放模式用回放时刻,否则用数据更新时间或当前时间 */
const referenceTime =
isReplayMode ? playbackTime : situation.lastUpdated || new Date().toISOString()
/** 时间衰减基准:回放模式用回放时刻;实时模式用「当前数据中最新 struck_at/attacked_at」无事件时再用 lastUpdated/now。
* 这样爬虫推送后 lastUpdated 跳到「当前」时,窗口仍以数据中最新事件为右端,不会前移导致已有攻击路线被排除(动画消失)。 */
const referenceTime = useMemo(() => {
if (isReplayMode) return playbackTime
let maxTs = 0
for (const line of situation.mapData?.strikeLines ?? []) {
for (const t of line.targets ?? []) {
if (t.struck_at) {
const ts = new Date(t.struck_at).getTime()
if (ts > maxTs) maxTs = ts
}
}
}
for (const loc of [...(usForces.keyLocations ?? []), ...(iranForces.keyLocations ?? [])]) {
if (loc.attacked_at) {
const ts = new Date(loc.attacked_at).getTime()
if (ts > maxTs) maxTs = ts
}
}
if (maxTs > 0) return new Date(maxTs).toISOString()
return situation.lastUpdated || new Date().toISOString()
}, [
isReplayMode,
playbackTime,
situation.lastUpdated,
situation.mapData?.strikeLines,
usForces.keyLocations,
iranForces.keyLocations,
])
const usLocs = (usForces.keyLocations || []) as KeyLoc[]
const irLocs = (iranForces.keyLocations || []) as KeyLoc[]
@@ -500,9 +526,33 @@ export function WarMap() {
[hormuzTargetPoints, isReplayMode]
)
// 霍尔木兹海峡交战区 & 真主党势力范围(静态面)
const hormuzZone = EXTENDED_WAR_ZONES.hormuzCombatZone
const hezbollahZone = EXTENDED_WAR_ZONES.hezbollahZone
// 霍尔木兹海峡交战区 & 真主党势力范围(静态面);保持引用稳定,避免广播更新时触发 setData 导致轮廓线丢失
const hormuzZone = useMemo(() => EXTENDED_WAR_ZONES.hormuzCombatZone, [])
const hezbollahZone = useMemo(() => EXTENDED_WAR_ZONES.hezbollahZone, [])
const hormuzLabelData = useMemo(
() => ({
type: 'Feature' as const,
properties: { name: (EXTENDED_WAR_ZONES.hormuzCombatZone.properties as { name?: string }).name ?? '霍尔木兹海峡交战区' },
geometry: { type: 'Point' as const, coordinates: EXTENDED_WAR_ZONES.hormuzLabelCenter },
}),
[]
)
const hezbollahLabelData = useMemo(
() => ({
type: 'Feature' as const,
properties: { name: (EXTENDED_WAR_ZONES.hezbollahZone.properties as { name?: string }).name ?? '真主党势力范围' },
geometry: { type: 'Point' as const, coordinates: EXTENDED_WAR_ZONES.hezbollahLabelCenter },
}),
[]
)
const kurdishLabelData = useMemo(
() => ({
type: 'Feature' as const,
properties: { name: '库尔德武装' },
geometry: { type: 'Point' as const, coordinates: EXTENDED_WAR_ZONES.kurdishLabelCenter },
}),
[]
)
// GDELT 冲突事件13 绿, 46 橙闪, 710 红脉
const { conflictEventsGreen, conflictEventsOrange, conflictEventsRed } = useMemo(() => {
@@ -546,6 +596,8 @@ export function WarMap() {
}
const initAnimation = useRef<(map: MapboxMap) => void>(null!)
const [zoneSourceKey, setZoneSourceKey] = useState(0)
const lastCheckedZoneRef = useRef<string>('')
initAnimation.current = (map: MapboxMap) => {
startRef.current = performance.now()
@@ -837,6 +889,26 @@ export function WarMap() {
return () => ro.disconnect()
}, [fitToTheater])
// 广播更新后检查轮廓层是否仍在;若被误删则递进 zoneSourceKey 强制轮廓 Source 重新挂载
useEffect(() => {
const lastUpdated = situation.lastUpdated ?? ''
if (lastUpdated === lastCheckedZoneRef.current) return
lastCheckedZoneRef.current = lastUpdated
const map = mapRef.current?.getMap()
if (!map?.isStyleLoaded()) return
const t = setTimeout(() => {
const map2 = mapRef.current?.getMap()
if (!map2) return
const hasKurdish = !!map2.getLayer('kurdish-zones')
const hasHormuz = !!map2.getLayer('hormuz-combat-fill')
const hasHezbollah = !!map2.getLayer('hezbollah-fill')
if (!hasKurdish || !hasHormuz || !hasHezbollah) {
setZoneSourceKey((k) => k + 1)
}
}, 150)
return () => clearTimeout(t)
}, [situation.lastUpdated])
if (!MAPBOX_TOKEN) {
return (
<div className="flex h-full w-full items-center justify-center bg-military-dark">
@@ -877,6 +949,9 @@ export function WarMap() {
<span className="flex items-center gap-1">
<span className="h-1.5 w-1.5 rounded-sm bg-red-500/40" />
</span>
<span className="flex items-center gap-1">
<span className="h-1.5 w-1.5 rounded-sm bg-red-500/40" />
</span>
<span className="flex items-center gap-1">
<span className="h-1.5 w-1.5 rounded-full bg-[#3B82F6]" />
</span>
@@ -1241,7 +1316,7 @@ export function WarMap() {
</Source>
{/* 跨国库尔德势力:土(Bakur)/叙(Rojava)/伊(Bashur) 三区 MultiPolygon + 北/南钳形箭头 */}
<Source id="kurdish-front-source" type="geojson" data={KURDISH_FRONT_GEOJSON}>
<Source key={`zone-kurdish-${zoneSourceKey}`} id="kurdish-front-source" type="geojson" data={KURDISH_FRONT_GEOJSON}>
{/* 势力范围:紫色半透明,远景更显、近景更透(描边单独 line 层,参考真主党) */}
<Layer
id="kurdish-zones"
@@ -1686,6 +1761,62 @@ export function WarMap() {
/>
</Source>
{/* 苏丹武装势力范围(支持伊朗,伊朗色标轮廓) */}
<Source
id="sudan-area"
type="geojson"
data={EXTENDED_WAR_ZONES.sudanZone}
>
<Layer
id="sudan-fill"
type="fill"
paint={{
'fill-color': 'rgba(239, 68, 68, 0.2)',
'fill-outline-color': 'transparent',
'fill-opacity': 0.4,
}}
/>
</Source>
{/* 苏丹国境线(独立线图层,伊朗红) */}
<Source
id="sudan-border"
type="geojson"
data={EXTENDED_WAR_ZONES.sudanBorderLine}
>
<Layer
id="sudan-border-line"
type="line"
paint={{
'line-color': '#EF4444',
'line-width': 2,
'line-opacity': 1,
}}
/>
</Source>
<Source
id="sudan-label"
type="geojson"
data={{
type: 'Feature',
properties: { name: '苏丹武装' },
geometry: { type: 'Point', coordinates: EXTENDED_WAR_ZONES.sudanLabelCenter },
}}
>
<Layer
id="sudan-label-text"
type="symbol"
layout={{
'text-field': '苏丹武装',
'text-size': ['interpolate', ['linear'], ['zoom'], 4, 9, 8, 12],
'text-anchor': 'center',
}}
paint={{
'text-color': '#EF4444',
'text-halo-width': 0,
}}
/>
</Source>
{/* 伊朗标注 */}
<Source
id="iran-label"
@@ -1805,7 +1936,7 @@ export function WarMap() {
</Source>
{/* 霍尔木兹海峡交战区 - 金黄色 mesh 区域 */}
<Source id="hormuz-combat-zone" type="geojson" data={hormuzZone}>
<Source key={`zone-hormuz-${zoneSourceKey}`} id="hormuz-combat-zone" type="geojson" data={hormuzZone}>
<Layer
id="hormuz-combat-fill"
type="fill"
@@ -1826,7 +1957,7 @@ export function WarMap() {
</Source>
{/* 真主党势力范围 - 绿色半透明区域 */}
<Source id="hezbollah-zone" type="geojson" data={hezbollahZone}>
<Source key={`zone-hezbollah-${zoneSourceKey}`} id="hezbollah-zone" type="geojson" data={hezbollahZone}>
<Layer
id="hezbollah-fill"
type="fill"
@@ -1846,18 +1977,7 @@ export function WarMap() {
</Source>
{/* 霍尔木兹海峡区域标注 */}
<Source
id="hormuz-label"
type="geojson"
data={{
type: 'Feature',
properties: { name: (hormuzZone.properties as any).name },
geometry: {
type: 'Point',
coordinates: EXTENDED_WAR_ZONES.hormuzLabelCenter,
},
}}
>
<Source id="hormuz-label" type="geojson" data={hormuzLabelData}>
<Layer
id="hormuz-label-text"
type="symbol"
@@ -1876,18 +1996,7 @@ export function WarMap() {
</Source>
{/* 真主党势力范围标注 */}
<Source
id="hezbollah-label"
type="geojson"
data={{
type: 'Feature',
properties: { name: (hezbollahZone.properties as any).name },
geometry: {
type: 'Point',
coordinates: EXTENDED_WAR_ZONES.hezbollahLabelCenter,
},
}}
>
<Source id="hezbollah-label" type="geojson" data={hezbollahLabelData}>
<Layer
id="hezbollah-label-text"
type="symbol"
@@ -1906,18 +2015,7 @@ export function WarMap() {
</Source>
{/* 库尔德武装势力范围标注(参照真主党紫色区域标记) */}
<Source
id="kurdish-label"
type="geojson"
data={{
type: 'Feature',
properties: { name: '库尔德武装' },
geometry: {
type: 'Point',
coordinates: EXTENDED_WAR_ZONES.kurdishLabelCenter,
},
}}
>
<Source id="kurdish-label" type="geojson" data={kurdishLabelData}>
<Layer
id="kurdish-label-text"
type="symbol"

View File

@@ -91,6 +91,76 @@ export const EXTENDED_WAR_ZONES = {
// 真主党区域标注点(用于显示文字)
hezbollahLabelCenter: [35.7, 33.7] as [number, number],
// 苏丹武装势力范围(公开支持伊朗,轮廓用伊朗色标绘制整个苏丹国家)
sudanZone: {
type: 'Feature' as const,
properties: {
name: '苏丹武装',
status: 'IRAN-ALIGNED',
color: '#EF4444',
},
geometry: {
type: 'Polygon' as const,
coordinates: [
[
[31.0, 22.0],
[33.0, 22.0],
[34.5, 22.0],
[36.8, 22.0],
[37.3, 20.8],
[38.5, 18.0],
[37.9, 17.0],
[36.5, 14.3],
[35.0, 13.5],
[34.0, 11.5],
[32.5, 12.0],
[29.5, 9.5],
[27.0, 9.0],
[24.0, 8.5],
[23.5, 10.0],
[22.5, 13.5],
[22.5, 16.0],
[24.0, 20.0],
[25.0, 22.0],
[31.0, 22.0],
],
],
},
},
sudanLabelCenter: [30.5, 15.25] as [number, number],
/** 苏丹国境线(与 sudanZone 同轮廓LineString 供线图层绘制) */
sudanBorderLine: {
type: 'Feature' as const,
properties: { name: '苏丹国境线' },
geometry: {
type: 'LineString' as const,
coordinates: [
[31.0, 22.0],
[33.0, 22.0],
[34.5, 22.0],
[36.8, 22.0],
[37.3, 20.8],
[38.5, 18.0],
[37.9, 17.0],
[36.5, 14.3],
[35.0, 13.5],
[34.0, 11.5],
[32.5, 12.0],
[29.5, 9.5],
[27.0, 9.0],
[24.0, 8.5],
[23.5, 10.0],
[22.5, 13.5],
[22.5, 16.0],
[24.0, 20.0],
[25.0, 22.0],
[31.0, 22.0],
],
},
},
// 库尔德武装势力区域标注点(叙/土/伊三区紫色带中心附近)
kurdishLabelCenter: [43.5, 36.3] as [number, number],