From c07fc681dd0d76fd97920b717ef1948fa7a23efa Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 1 Mar 2026 19:23:48 +0800 Subject: [PATCH] feat: add new file --- .env.example | 48 +- .gitignore | 3 + README.md | 36 +- index.html | 2 +- map.md | 284 +++++++ package-lock.json | 1182 +++++++++++++++++++++++++++- package.json | 6 + server/db.js | 107 +++ server/index.js | 34 + server/package.json | 5 + server/routes.js | 15 + server/seed.js | 174 ++++ server/situationData.js | 108 +++ src/api/situation.ts | 7 + src/api/websocket.ts | 30 + src/components/BaseStatusPanel.tsx | 68 ++ src/components/HeaderPanel.tsx | 25 +- src/components/WarMap.tsx | 404 ++++++++-- src/data/mapLocations.ts | 140 ++++ src/data/mockData.ts | 79 +- src/pages/Dashboard.tsx | 26 +- src/store/situationStore.ts | 88 +-- usa_logo.png | Bin 0 -> 463909 bytes vite.config.ts | 6 + 24 files changed, 2711 insertions(+), 166 deletions(-) create mode 100644 map.md create mode 100644 server/db.js create mode 100644 server/index.js create mode 100644 server/package.json create mode 100644 server/routes.js create mode 100644 server/seed.js create mode 100644 server/situationData.js create mode 100644 src/api/situation.ts create mode 100644 src/api/websocket.ts create mode 100644 src/components/BaseStatusPanel.tsx create mode 100644 src/data/mapLocations.ts create mode 100644 usa_logo.png diff --git a/.env.example b/.env.example index d10ecca..87cbb57 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,46 @@ -# Get a free token at https://account.mapbox.com/access-tokens/ -VITE_MAPBOX_ACCESS_TOKEN=pk.eyJ1IjoiZDI5cTAiLCJhIjoiY21oaGRmcTkzMGltZzJscHR1N2FhZnY5dCJ9.7ueF2lS6-C9Mm_xon7NnIA +# Mapbox 地图令牌 (波斯湾区域展示) +# 免费申请: https://account.mapbox.com/access-tokens/ +# 复制本文件为 .env 并填入你的 token +VITE_MAPBOX_ACCESS_TOKEN=your_mapbox_public_token_here +27 个基地完整 JSON 数据 +[ + { "id": 1, "name": "Al Udeid Air Base", "country": "Qatar", "lat": 25.117, "lng": 51.314 }, + { "id": 2, "name": "Camp As Sayliyah", "country": "Qatar", "lat": 25.275, "lng": 51.520 }, + + { "id": 3, "name": "Naval Support Activity Bahrain", "country": "Bahrain", "lat": 26.236, "lng": 50.608 }, + + { "id": 4, "name": "Camp Arifjan", "country": "Kuwait", "lat": 28.832, "lng": 47.799 }, + { "id": 5, "name": "Ali Al Salem Air Base", "country": "Kuwait", "lat": 29.346, "lng": 47.520 }, + { "id": 6, "name": "Camp Buehring", "country": "Kuwait", "lat": 29.603, "lng": 47.456 }, + + { "id": 7, "name": "Al Dhafra Air Base", "country": "UAE", "lat": 24.248, "lng": 54.547 }, + + { "id": 8, "name": "Prince Sultan Air Base", "country": "Saudi Arabia", "lat": 24.062, "lng": 47.580 }, + { "id": 9, "name": "Eskan Village", "country": "Saudi Arabia", "lat": 24.774, "lng": 46.738 }, + + { "id": 10, "name": "Al Asad Airbase", "country": "Iraq", "lat": 33.785, "lng": 42.441 }, + { "id": 11, "name": "Erbil Air Base", "country": "Iraq", "lat": 36.237, "lng": 43.963 }, + { "id": 12, "name": "Baghdad Diplomatic Support Center", "country": "Iraq", "lat": 33.315, "lng": 44.366 }, + { "id": 13, "name": "Camp Taji", "country": "Iraq", "lat": 33.556, "lng": 44.256 }, + { "id": 14, "name": "Ain al-Asad", "country": "Iraq", "lat": 33.800, "lng": 42.450 }, + + { "id": 15, "name": "Al-Tanf Garrison", "country": "Syria", "lat": 33.490, "lng": 38.618 }, + { "id": 16, "name": "Rmelan Landing Zone", "country": "Syria", "lat": 37.015, "lng": 41.885 }, + { "id": 17, "name": "Shaddadi Base", "country": "Syria", "lat": 36.058, "lng": 40.730 }, + { "id": 18, "name": "Conoco Gas Field Base", "country": "Syria", "lat": 35.336, "lng": 40.295 }, + + { "id": 19, "name": "Muwaffaq Salti Air Base", "country": "Jordan", "lat": 32.356, "lng": 36.259 }, + + { "id": 20, "name": "Incirlik Air Base", "country": "Turkey", "lat": 37.002, "lng": 35.425 }, + { "id": 21, "name": "Kurecik Radar Station", "country": "Turkey", "lat": 38.354, "lng": 37.794 }, + + { "id": 22, "name": "Nevatim Air Base", "country": "Israel", "lat": 31.208, "lng": 35.012 }, + { "id": 23, "name": "Ramon Air Base", "country": "Israel", "lat": 30.776, "lng": 34.666 }, + + { "id": 24, "name": "Thumrait Air Base", "country": "Oman", "lat": 17.666, "lng": 54.024 }, + { "id": 25, "name": "Masirah Air Base", "country": "Oman", "lat": 20.675, "lng": 58.890 }, + + { "id": 26, "name": "West Cairo Air Base", "country": "Egypt", "lat": 30.915, "lng": 30.298 }, + + { "id": 27, "name": "Camp Lemonnier", "country": "Djibouti", "lat": 11.547, "lng": 43.159 } +] \ No newline at end of file diff --git a/.gitignore b/.gitignore index b4f3b6e..c71b560 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,9 @@ dist-ssr *.sln *.sw? +# API database +server/data.db + # Env .env .env.local diff --git a/README.md b/README.md index a65fd4b..3f1a64b 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,30 @@ cp .env.example .env Without a token, the map area shows a placeholder with location labels. +### API 与数据库 + +页面数据通过 REST API 从后端获取,后端使用 SQLite 存储。 + +```bash +# 首次运行:初始化数据库并写入种子数据 +npm run api:seed + +# 启动 API 服务(默认 http://localhost:3001) +npm run api +``` + +开发时需同时运行前端与 API: + +```bash +# 终端 1 +npm run api + +# 终端 2 +npm run dev +``` + +API 会由 Vite 代理到 `/api`,前端通过 `/api/situation` 获取完整态势数据。数据库文件位于 `server/data.db`,可通过修改表数据实现动态调整。 + ## Development ```bash @@ -50,12 +74,20 @@ src/ │ ├── ForcePanel.tsx # Reusable left/right panel for military forces │ ├── WarMap.tsx # Mapbox GL (Persian Gulf center) │ └── StatCard.tsx # Reusable number card +├── api/ +│ └── situation.ts # 态势数据 API 请求 ├── store/ -│ └── situationStore.ts # Zustand store + WebSocket mock logic +│ └── situationStore.ts # Zustand store + API 轮询 ├── data/ -│ └── mockData.ts # TypeScript interfaces & initial mock data +│ └── mockData.ts # 类型定义与初始兜底数据 ├── pages/ │ └── Dashboard.tsx # Main layout (1920×1080) ├── App.tsx └── index.css +server/ +├── index.js # Express API 入口 +├── db.js # SQLite 建表与连接 +├── routes.js # /api/situation 路由 +├── seed.js # 数据库种子脚本 +└── data.db # SQLite 数据库(运行 seed 后生成) ``` diff --git a/index.html b/index.html index d47e9fe..5356148 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + 美伊军事态势显示 diff --git a/map.md b/map.md new file mode 100644 index 0000000..a4b6c02 --- /dev/null +++ b/map.md @@ -0,0 +1,284 @@ + +[ + { + "name": "USS Abraham Lincoln CSG", + "type": "Aircraft Carrier Strike Group", + "lat": 12.5, + "lng": 50.0, + "region": "Arabian Sea SE of Socotra" + }, + + { + "name": "Destroyer (Gulf of Oman)", + "type": "Destroyer", + "lat": 25.2, + "lng": 58.0, + "region": "Gulf of Oman" + }, + { + "name": "Coast Guard Ship 1 (Gulf of Oman)", + "type": "Coast Guard", + "lat": 25.4, + "lng": 58.2, + "region": "Gulf of Oman" + }, + { + "name": "Coast Guard Ship 2 (Gulf of Oman)", + "type": "Coast Guard", + "lat": 25.0, + "lng": 57.8, + "region": "Gulf of Oman" + }, + + { + "name": "Destroyer (Northern Persian Gulf)", + "type": "Destroyer", + "lat": 26.5, + "lng": 51.0, + "region": "Persian Gulf" + }, + { + "name": "Frigate 1 (Persian Gulf)", + "type": "Frigate", + "lat": 26.7, + "lng": 50.6, + "region": "Persian Gulf" + }, + { + "name": "Frigate 2 (Persian Gulf)", + "type": "Frigate", + "lat": 27.0, + "lng": 50.2, + "region": "Persian Gulf" + }, + { + "name": "Frigate 3 (Persian Gulf)", + "type": "Frigate", + "lat": 26.3, + "lng": 50.8, + "region": "Persian Gulf" + }, + { + "name": "Auxiliary Ship 1", + "type": "Auxiliary", + "lat": 26.0, + "lng": 51.2, + "region": "Persian Gulf" + }, + { + "name": "Auxiliary Ship 2", + "type": "Auxiliary", + "lat": 25.8, + "lng": 51.5, + "region": "Persian Gulf" + }, + { + "name": "Auxiliary Ship 3", + "type": "Auxiliary", + "lat": 26.2, + "lng": 50.9, + "region": "Persian Gulf" + } +] + +卡塔尔 + +Al Udeid Air Base + +Camp As Sayliyah + +🇧🇭 巴林 + +Naval Support Activity Bahrain + +🇰🇼 科威特 + +Camp Arifjan + +Ali Al Salem Air Base + +Camp Buehring + +🇦🇪 阿联酋 + +Al Dhafra Air Base + +🇸🇦 沙特阿拉伯 + +Prince Sultan Air Base + +Eskan Village + +🇮🇶 伊拉克 + +Al Asad Airbase + +Erbil Air Base + +Baghdad Diplomatic Support Center + +Camp Taji + +Ain al-Asad (部分文献与 Al Asad 同义但可单列展示) + +🇸🇾 叙利亚(驻点) + +Al-Tanf Garrison + +Rmelan Landing Zone + +Shaddadi Base + +Conoco Gas Field Base + +🇯🇴 约旦 + +Muwaffaq Salti Air Base + +🇹🇷 土耳其 + +Incirlik Air Base + +Kürecik Radar Station + +🇮🇱 以色列(合作设施) + +Nevatim Air Base (美军设施区) + +Ramon Air Base (协作区) + +🇴🇲 阿曼 + +Thumrait Air Base + +Masirah Air Base + +🇪🇬 埃及(区域支援) + +West Cairo Air Base + +🇩🇯 吉布提(非波斯湾但中东行动关键) + +地图基础设置 + +中心: + +center: [52.5, 26.5] +zoom: 5.2 +style: "mapbox://styles/mapbox/dark-v11" +二、27 个基地完整 JSON 数据 +[ + { "id": 1, "name": "Al Udeid Air Base", "country": "Qatar", "lat": 25.117, "lng": 51.314 }, + { "id": 2, "name": "Camp As Sayliyah", "country": "Qatar", "lat": 25.275, "lng": 51.520 }, + + { "id": 3, "name": "Naval Support Activity Bahrain", "country": "Bahrain", "lat": 26.236, "lng": 50.608 }, + + { "id": 4, "name": "Camp Arifjan", "country": "Kuwait", "lat": 28.832, "lng": 47.799 }, + { "id": 5, "name": "Ali Al Salem Air Base", "country": "Kuwait", "lat": 29.346, "lng": 47.520 }, + { "id": 6, "name": "Camp Buehring", "country": "Kuwait", "lat": 29.603, "lng": 47.456 }, + + { "id": 7, "name": "Al Dhafra Air Base", "country": "UAE", "lat": 24.248, "lng": 54.547 }, + + { "id": 8, "name": "Prince Sultan Air Base", "country": "Saudi Arabia", "lat": 24.062, "lng": 47.580 }, + { "id": 9, "name": "Eskan Village", "country": "Saudi Arabia", "lat": 24.774, "lng": 46.738 }, + + { "id": 10, "name": "Al Asad Airbase", "country": "Iraq", "lat": 33.785, "lng": 42.441 }, + { "id": 11, "name": "Erbil Air Base", "country": "Iraq", "lat": 36.237, "lng": 43.963 }, + { "id": 12, "name": "Baghdad Diplomatic Support Center", "country": "Iraq", "lat": 33.315, "lng": 44.366 }, + { "id": 13, "name": "Camp Taji", "country": "Iraq", "lat": 33.556, "lng": 44.256 }, + { "id": 14, "name": "Ain al-Asad", "country": "Iraq", "lat": 33.800, "lng": 42.450 }, + + { "id": 15, "name": "Al-Tanf Garrison", "country": "Syria", "lat": 33.490, "lng": 38.618 }, + { "id": 16, "name": "Rmelan Landing Zone", "country": "Syria", "lat": 37.015, "lng": 41.885 }, + { "id": 17, "name": "Shaddadi Base", "country": "Syria", "lat": 36.058, "lng": 40.730 }, + { "id": 18, "name": "Conoco Gas Field Base", "country": "Syria", "lat": 35.336, "lng": 40.295 }, + + { "id": 19, "name": "Muwaffaq Salti Air Base", "country": "Jordan", "lat": 32.356, "lng": 36.259 }, + + { "id": 20, "name": "Incirlik Air Base", "country": "Turkey", "lat": 37.002, "lng": 35.425 }, + { "id": 21, "name": "Kurecik Radar Station", "country": "Turkey", "lat": 38.354, "lng": 37.794 }, + + { "id": 22, "name": "Nevatim Air Base", "country": "Israel", "lat": 31.208, "lng": 35.012 }, + { "id": 23, "name": "Ramon Air Base", "country": "Israel", "lat": 30.776, "lng": 34.666 }, + + { "id": 24, "name": "Thumrait Air Base", "country": "Oman", "lat": 17.666, "lng": 54.024 }, + { "id": 25, "name": "Masirah Air Base", "country": "Oman", "lat": 20.675, "lng": 58.890 }, + + { "id": 26, "name": "West Cairo Air Base", "country": "Egypt", "lat": 30.915, "lng": 30.298 }, + + { "id": 27, "name": "Camp Lemonnier", "country": "Djibouti", "lat": 11.547, "lng": 43.159 } +] +三、状态模型 +type BaseStatus = "operational" | "damaged" | "attacked" + +interface BaseAttackStatus { + id: number + status: BaseStatus + damageLevel: 1 | 2 | 3 + attackTime?: number +} +四、可视化规则 +1️⃣ 状态颜色 + +operational → 绿色稳定点 + +damaged → 橙色闪烁 + +attacked → 红色脉冲扩散动画 + +2️⃣ 红色脉冲动画 + +2 秒循环 + +扩散半径 0 → 40px + +opacity 1 → 0 + +3️⃣ 伊朗攻击源 + +固定发射源坐标: + +const IRAN_SOURCE = [51.3890, 35.6892] // Tehran + +攻击动画使用抛物线 LineLayer: + +颜色:#ff0000 + +线宽:2px + +飞行时间:1200ms + +五、大屏右侧态势面板 + +显示: + +总基地数:27 + +被袭击:X + +严重损毁:X + +中度损毁:X + +轻度损毁:X + +每 5 秒自动刷新统计。 + +六、性能要求 + +使用 requestAnimationFrame + + + + + + + + + + +禁止重复创建 marker + +所有动画走 WebGL 图层 + +禁止 DOM 动画 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 845e795..5202ec7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,17 @@ "name": "us-iran-military-dashboard", "version": "1.0.0", "dependencies": { + "better-sqlite3": "^11.6.0", + "cors": "^2.8.5", "echarts": "^5.5.0", "echarts-for-react": "^3.0.2", + "express": "^4.21.1", "lucide-react": "^0.460.0", "mapbox-gl": "^3.6.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-map-gl": "^7.1.7", + "ws": "^8.19.0", "zustand": "^5.0.0" }, "devDependencies": { @@ -1759,6 +1763,18 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", @@ -1850,6 +1866,11 @@ "node": ">=0.10.0" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -1900,6 +1921,25 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/baseline-browser-mapping": { "version": "2.10.0", "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", @@ -1912,6 +1952,16 @@ "node": ">=6.0.0" } }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmmirror.com/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1924,6 +1974,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1979,6 +2083,37 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/bytewise": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/bytewise/-/bytewise-1.1.0.tgz", @@ -1996,6 +2131,33 @@ "typewise-core": "^1.2" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", @@ -2091,6 +2253,11 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", @@ -2124,12 +2291,60 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2184,12 +2399,59 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2202,6 +2464,19 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/earcut": { "version": "3.0.2", "resolved": "https://registry.npmmirror.com/earcut/-/earcut-3.0.2.tgz", @@ -2229,12 +2504,60 @@ "react": "^15.0.0 || >=16.0.0" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "node_modules/electron-to-chromium": { "version": "1.5.302", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", @@ -2282,6 +2605,11 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2461,6 +2789,80 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmmirror.com/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -2538,6 +2940,11 @@ "node": ">=16.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", @@ -2550,6 +2957,36 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", @@ -2585,6 +3022,14 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz", @@ -2598,6 +3043,19 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", @@ -2616,7 +3074,6 @@ "version": "1.1.2", "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2635,6 +3092,41 @@ "resolved": "https://registry.npmmirror.com/geojson-vt/-/geojson-vt-4.0.2.tgz", "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==" }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-value": { "version": "2.0.6", "resolved": "https://registry.npmmirror.com/get-value/-/get-value-2.0.6.tgz", @@ -2643,6 +3135,11 @@ "node": ">=0.10.0" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "node_modules/gl-matrix": { "version": "3.4.4", "resolved": "https://registry.npmmirror.com/gl-matrix/-/gl-matrix-3.4.4.tgz", @@ -2672,6 +3169,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/grid-index": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/grid-index/-/grid-index-1.1.0.tgz", @@ -2686,11 +3194,21 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -2698,6 +3216,55 @@ "node": ">= 0.4" } }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -2732,6 +3299,24 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3037,6 +3622,30 @@ "tinyqueue": "3.0.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", @@ -3046,6 +3655,14 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", @@ -3059,6 +3676,47 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", @@ -3079,11 +3737,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/murmurhash-js": { "version": "1.0.0", @@ -3119,12 +3781,47 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz", @@ -3144,7 +3841,6 @@ "version": "4.1.1", "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3158,6 +3854,36 @@ "node": ">= 6" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", @@ -3217,6 +3943,14 @@ "node": ">=6" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", @@ -3241,6 +3975,11 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, "node_modules/pbf": { "version": "4.0.1", "resolved": "https://registry.npmmirror.com/pbf/-/pbf-4.0.1.tgz", @@ -3449,6 +4188,32 @@ "resolved": "https://registry.npmmirror.com/potpack/-/potpack-2.1.0.tgz", "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==" }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3463,6 +4228,27 @@ "resolved": "https://registry.npmmirror.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", @@ -3472,6 +4258,20 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3497,6 +4297,50 @@ "resolved": "https://registry.npmmirror.com/quickselect/-/quickselect-3.0.0.tgz", "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", @@ -3561,6 +4405,19 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", @@ -3697,6 +4554,30 @@ "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", @@ -3714,6 +4595,56 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/set-value/-/set-value-2.0.1.tgz", @@ -3728,6 +4659,11 @@ "node": ">=0.10.0" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3749,6 +4685,117 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/size-sensor": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/size-sensor/-/size-sensor-1.0.3.tgz", @@ -3834,6 +4881,22 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3937,6 +5000,32 @@ "node": ">=14.0.0" } }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", @@ -4020,6 +5109,14 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -4043,6 +5140,17 @@ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", @@ -4055,6 +5163,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.6.3.tgz", @@ -4118,6 +5238,14 @@ "node": ">=0.10.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -4160,8 +5288,23 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/vite": { "version": "5.4.21", @@ -4246,6 +5389,31 @@ "node": ">=0.10.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index c280e27..ac37a3a 100644 --- a/package.json +++ b/package.json @@ -5,19 +5,25 @@ "type": "module", "scripts": { "dev": "vite", + "api": "node server/index.js", + "api:seed": "node server/seed.js", "build": "vite build", "typecheck": "tsc --noEmit", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { + "better-sqlite3": "^11.6.0", + "cors": "^2.8.5", "echarts": "^5.5.0", "echarts-for-react": "^3.0.2", + "express": "^4.21.1", "lucide-react": "^0.460.0", "mapbox-gl": "^3.6.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-map-gl": "^7.1.7", + "ws": "^8.19.0", "zustand": "^5.0.0" }, "devDependencies": { diff --git a/server/db.js b/server/db.js new file mode 100644 index 0000000..8ffe1e7 --- /dev/null +++ b/server/db.js @@ -0,0 +1,107 @@ +const Database = require('better-sqlite3') +const path = require('path') + +const dbPath = path.join(__dirname, 'data.db') +const db = new Database(dbPath) + +// 启用外键 +db.pragma('journal_mode = WAL') + +// 建表 +db.exec(` + CREATE TABLE IF NOT EXISTS situation ( + id INTEGER PRIMARY KEY CHECK (id = 1), + data TEXT NOT NULL, + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS force_summary ( + side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')), + total_assets INTEGER NOT NULL, + personnel INTEGER NOT NULL, + naval_ships INTEGER NOT NULL, + aircraft INTEGER NOT NULL, + ground_units INTEGER NOT NULL, + uav INTEGER NOT NULL, + missile_consumed INTEGER NOT NULL, + missile_stock INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS power_index ( + side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')), + overall INTEGER NOT NULL, + military_strength INTEGER NOT NULL, + economic_power INTEGER NOT NULL, + geopolitical_influence INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS force_asset ( + id TEXT PRIMARY KEY, + side TEXT NOT NULL CHECK (side IN ('us', 'iran')), + name TEXT NOT NULL, + type TEXT NOT NULL, + count INTEGER NOT NULL, + status TEXT NOT NULL CHECK (status IN ('active', 'standby', 'alert')), + lat REAL, + lng REAL + ); + + CREATE TABLE IF NOT EXISTS key_location ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + side TEXT NOT NULL CHECK (side IN ('us', 'iran')), + name TEXT NOT NULL, + lat REAL NOT NULL, + lng REAL NOT NULL, + type TEXT, + region TEXT + ); + + CREATE TABLE IF NOT EXISTS combat_losses ( + side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')), + bases_destroyed INTEGER NOT NULL, + bases_damaged INTEGER NOT NULL, + personnel_killed INTEGER NOT NULL, + personnel_wounded INTEGER NOT NULL, + aircraft INTEGER NOT NULL, + warships INTEGER NOT NULL, + armor INTEGER NOT NULL, + vehicles INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS wall_street_trend ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + time TEXT NOT NULL, + value INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS retaliation_current ( + id INTEGER PRIMARY KEY CHECK (id = 1), + value INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS retaliation_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + time TEXT NOT NULL, + value INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS situation_update ( + id TEXT PRIMARY KEY, + timestamp TEXT NOT NULL, + category TEXT NOT NULL, + summary TEXT NOT NULL, + severity TEXT NOT NULL + ); +`) + +// 迁移:为已有 key_location 表添加 type、region、status、damage_level 列 +try { + const cols = db.prepare('PRAGMA table_info(key_location)').all() + const names = cols.map((c) => c.name) + if (!names.includes('type')) db.exec('ALTER TABLE key_location ADD COLUMN type TEXT') + if (!names.includes('region')) db.exec('ALTER TABLE key_location ADD COLUMN region TEXT') + if (!names.includes('status')) db.exec('ALTER TABLE key_location ADD COLUMN status TEXT DEFAULT "operational"') + if (!names.includes('damage_level')) db.exec('ALTER TABLE key_location ADD COLUMN damage_level INTEGER') +} catch (_) {} + +module.exports = db diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..69462dd --- /dev/null +++ b/server/index.js @@ -0,0 +1,34 @@ +const http = require('http') +const express = require('express') +const cors = require('cors') +const { WebSocketServer } = require('ws') +const routes = require('./routes') +const { getSituation } = require('./situationData') + +const app = express() +const PORT = process.env.API_PORT || 3001 + +app.use(cors()) +app.use(express.json()) +app.use('/api', routes) +app.get('/api/health', (_, res) => res.json({ ok: true })) + +const server = http.createServer(app) + +const wss = new WebSocketServer({ server, path: '/ws' }) +wss.on('connection', (ws) => { + ws.send(JSON.stringify({ type: 'situation', data: getSituation() })) +}) +function broadcastSituation() { + try { + const data = JSON.stringify({ type: 'situation', data: getSituation() }) + wss.clients.forEach((c) => { + if (c.readyState === 1) c.send(data) + }) + } catch (_) {} +} +setInterval(broadcastSituation, 5000) + +server.listen(PORT, () => { + console.log(`API + WebSocket running at http://localhost:${PORT}`) +}) diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..898de9d --- /dev/null +++ b/server/package.json @@ -0,0 +1,5 @@ +{ + "name": "usa-api", + "private": true, + "type": "commonjs" +} diff --git a/server/routes.js b/server/routes.js new file mode 100644 index 0000000..25aa043 --- /dev/null +++ b/server/routes.js @@ -0,0 +1,15 @@ +const express = require('express') +const { getSituation } = require('./situationData') + +const router = express.Router() + +router.get('/situation', (req, res) => { + try { + res.json(getSituation()) + } catch (err) { + console.error(err) + res.status(500).json({ error: err.message }) + } +}) + +module.exports = router diff --git a/server/seed.js b/server/seed.js new file mode 100644 index 0000000..7d88f28 --- /dev/null +++ b/server/seed.js @@ -0,0 +1,174 @@ +const db = require('./db') + +// 与 src/data/mapLocations.ts 同步:62 基地,27 被袭 (严重6 中度12 轻度9) +function getUsLocations() { + const naval = [ + { name: '林肯号航母 (CVN-72)', lat: 24.1568, lng: 58.4215, type: 'Aircraft Carrier', region: '北阿拉伯海', status: 'operational', damage_level: null }, + { name: '福特号航母 (CVN-78)', lat: 35.7397, lng: 24.1002, type: 'Aircraft Carrier', region: '东地中海', status: 'operational', damage_level: null }, + { name: '驱逐舰(阿曼湾)', lat: 25.2, lng: 58.0, type: 'Destroyer', region: '阿曼湾', status: 'operational', damage_level: null }, + { name: '海岸警卫队 1', lat: 25.4, lng: 58.2, type: 'Coast Guard', region: '阿曼湾', status: 'operational', damage_level: null }, + { name: '海岸警卫队 2', lat: 25.0, lng: 57.8, type: 'Coast Guard', region: '阿曼湾', status: 'operational', damage_level: null }, + { name: '驱逐舰(波斯湾北部)', lat: 26.5, lng: 51.0, type: 'Destroyer', region: '波斯湾', status: 'operational', damage_level: null }, + { name: '护卫舰 1', lat: 26.7, lng: 50.6, type: 'Frigate', region: '波斯湾', status: 'operational', damage_level: null }, + { name: '护卫舰 2', lat: 27.0, lng: 50.2, type: 'Frigate', region: '波斯湾', status: 'operational', damage_level: null }, + { name: '护卫舰 3', lat: 26.3, lng: 50.8, type: 'Frigate', region: '波斯湾', status: 'operational', damage_level: null }, + { name: '辅助舰 1', lat: 26.0, lng: 51.2, type: 'Auxiliary', region: '波斯湾', status: 'operational', damage_level: null }, + { name: '辅助舰 2', lat: 25.8, lng: 51.5, type: 'Auxiliary', region: '波斯湾', status: 'operational', damage_level: null }, + { name: '辅助舰 3', lat: 26.2, lng: 50.9, type: 'Auxiliary', region: '波斯湾', status: 'operational', damage_level: null }, + ] + const attacked = [ + { name: '阿萨德空军基地', lat: 33.785, lng: 42.441, type: 'Base', region: '伊拉克', status: 'attacked', damage_level: 3 }, + { name: '巴格达外交支援中心', lat: 33.315, lng: 44.366, type: 'Base', region: '伊拉克', status: 'attacked', damage_level: 3 }, + { name: '乌代德空军基地', lat: 25.117, lng: 51.314, type: 'Base', region: '卡塔尔', status: 'attacked', damage_level: 3 }, + { name: '埃尔比勒空军基地', lat: 36.237, lng: 43.963, type: 'Base', region: '伊拉克', status: 'attacked', damage_level: 3 }, + { name: '因吉尔利克空军基地', lat: 37.002, lng: 35.425, type: 'Base', region: '土耳其', status: 'attacked', damage_level: 3 }, + { name: '苏尔坦亲王空军基地', lat: 24.062, lng: 47.58, type: 'Base', region: '沙特', status: 'attacked', damage_level: 3 }, + { name: '塔吉军营', lat: 33.556, lng: 44.256, type: 'Base', region: '伊拉克', status: 'attacked', damage_level: 2 }, + { name: '阿因·阿萨德', lat: 33.8, lng: 42.45, type: 'Base', region: '伊拉克', status: 'attacked', damage_level: 2 }, + { name: '坦夫驻军', lat: 33.49, lng: 38.618, type: 'Base', region: '叙利亚', status: 'attacked', damage_level: 2 }, + { name: '沙达迪基地', lat: 36.058, lng: 40.73, type: 'Base', region: '叙利亚', status: 'attacked', damage_level: 2 }, + { name: '康诺克气田基地', lat: 35.336, lng: 40.295, type: 'Base', region: '叙利亚', status: 'attacked', damage_level: 2 }, + { name: '尔梅兰着陆区', lat: 37.015, lng: 41.885, type: 'Base', region: '叙利亚', status: 'attacked', damage_level: 2 }, + { name: '阿里夫坚军营', lat: 28.832, lng: 47.799, type: 'Base', region: '科威特', status: 'attacked', damage_level: 2 }, + { name: '阿里·萨勒姆空军基地', lat: 29.346, lng: 47.52, type: 'Base', region: '科威特', status: 'attacked', damage_level: 2 }, + { name: '巴林海军支援站', lat: 26.236, lng: 50.608, type: 'Base', region: '巴林', status: 'attacked', damage_level: 2 }, + { name: '达夫拉空军基地', lat: 24.248, lng: 54.547, type: 'Base', region: '阿联酋', status: 'attacked', damage_level: 2 }, + { name: '埃斯康村', lat: 24.774, lng: 46.738, type: 'Base', region: '沙特', status: 'attacked', damage_level: 2 }, + { name: '内瓦提姆空军基地', lat: 31.208, lng: 35.012, type: 'Base', region: '以色列', status: 'attacked', damage_level: 2 }, + { name: '布林军营', lat: 29.603, lng: 47.456, type: 'Base', region: '科威特', status: 'attacked', damage_level: 1 }, + { name: '赛利耶军营', lat: 25.275, lng: 51.52, type: 'Base', region: '卡塔尔', status: 'attacked', damage_level: 1 }, + { name: '拉蒙空军基地', lat: 30.776, lng: 34.666, type: 'Base', region: '以色列', status: 'attacked', damage_level: 1 }, + { name: '穆瓦法克·萨尔蒂空军基地', lat: 32.356, lng: 36.259, type: 'Base', region: '约旦', status: 'attacked', damage_level: 1 }, + { name: '屈雷吉克雷达站', lat: 38.354, lng: 37.794, type: 'Base', region: '土耳其', status: 'attacked', damage_level: 1 }, + { name: '苏姆莱特空军基地', lat: 17.666, lng: 54.024, type: 'Base', region: '阿曼', status: 'attacked', damage_level: 1 }, + { name: '马西拉空军基地', lat: 20.675, lng: 58.89, type: 'Base', region: '阿曼', status: 'attacked', damage_level: 1 }, + { name: '西开罗空军基地', lat: 30.915, lng: 30.298, type: 'Base', region: '埃及', status: 'attacked', damage_level: 1 }, + { name: '勒莫尼耶军营', lat: 11.547, lng: 43.159, type: 'Base', region: '吉布提', status: 'attacked', damage_level: 1 }, + ] + const newBases = [ + { name: '多哈后勤中心', lat: 25.29, lng: 51.53, type: 'Base', region: '卡塔尔', status: 'operational', damage_level: null }, + { name: '贾法勒海军站', lat: 26.22, lng: 50.62, type: 'Base', region: '巴林', status: 'operational', damage_level: null }, + { name: '阿兹祖尔前方作战点', lat: 29.45, lng: 47.9, type: 'Base', region: '科威特', status: 'operational', damage_level: null }, + { name: '艾哈迈迪后勤枢纽', lat: 29.08, lng: 48.09, type: 'Base', region: '科威特', status: 'operational', damage_level: null }, + { name: '富查伊拉港站', lat: 25.13, lng: 56.35, type: 'Base', region: '阿联酋', status: 'operational', damage_level: null }, + { name: '哈伊马角前方点', lat: 25.79, lng: 55.94, type: 'Base', region: '阿联酋', status: 'operational', damage_level: null }, + { name: '利雅得联络站', lat: 24.71, lng: 46.68, type: 'Base', region: '沙特', status: 'operational', damage_level: null }, + { name: '朱拜勒港支援点', lat: 27.0, lng: 49.65, type: 'Base', region: '沙特', status: 'operational', damage_level: null }, + { name: '塔布克空军前哨', lat: 28.38, lng: 36.6, type: 'Base', region: '沙特', status: 'operational', damage_level: null }, + { name: '拜莱德空军基地', lat: 33.94, lng: 44.36, type: 'Base', region: '伊拉克', status: 'operational', damage_level: null }, + { name: '巴士拉后勤站', lat: 30.5, lng: 47.78, type: 'Base', region: '伊拉克', status: 'operational', damage_level: null }, + { name: '基尔库克前哨', lat: 35.47, lng: 44.35, type: 'Base', region: '伊拉克', status: 'operational', damage_level: null }, + { name: '摩苏尔支援点', lat: 36.34, lng: 43.14, type: 'Base', region: '伊拉克', status: 'operational', damage_level: null }, + { name: '哈塞克联络站', lat: 36.5, lng: 40.75, type: 'Base', region: '叙利亚', status: 'operational', damage_level: null }, + { name: '代尔祖尔前哨', lat: 35.33, lng: 40.14, type: 'Base', region: '叙利亚', status: 'operational', damage_level: null }, + { name: '安曼协调中心', lat: 31.95, lng: 35.93, type: 'Base', region: '约旦', status: 'operational', damage_level: null }, + { name: '伊兹密尔支援站', lat: 38.42, lng: 27.14, type: 'Base', region: '土耳其', status: 'operational', damage_level: null }, + { name: '哈泽瑞姆空军基地', lat: 31.07, lng: 34.84, type: 'Base', region: '以色列', status: 'operational', damage_level: null }, + { name: '杜古姆港站', lat: 19.66, lng: 57.76, type: 'Base', region: '阿曼', status: 'operational', damage_level: null }, + { name: '塞拉莱前方点', lat: 17.01, lng: 54.1, type: 'Base', region: '阿曼', status: 'operational', damage_level: null }, + { name: '亚历山大港联络站', lat: 31.2, lng: 29.9, type: 'Base', region: '埃及', status: 'operational', damage_level: null }, + { name: '卢克索前哨', lat: 25.69, lng: 32.64, type: 'Base', region: '埃及', status: 'operational', damage_level: null }, + { name: '吉布提港支援点', lat: 11.59, lng: 43.15, type: 'Base', region: '吉布提', status: 'operational', damage_level: null }, + { name: '卡塔尔应急医疗站', lat: 25.22, lng: 51.45, type: 'Base', region: '卡塔尔', status: 'operational', damage_level: null }, + { name: '沙特哈立德国王基地', lat: 24.96, lng: 46.7, type: 'Base', region: '沙特', status: 'operational', damage_level: null }, + { name: '伊拉克巴拉德联勤站', lat: 33.75, lng: 44.25, type: 'Base', region: '伊拉克', status: 'operational', damage_level: null }, + { name: '叙利亚奥马尔油田站', lat: 36.22, lng: 40.45, type: 'Base', region: '叙利亚', status: 'operational', damage_level: null }, + { name: '约旦侯赛因国王基地', lat: 31.72, lng: 36.01, type: 'Base', region: '约旦', status: 'operational', damage_level: null }, + { name: '土耳其巴特曼站', lat: 37.88, lng: 41.13, type: 'Base', region: '土耳其', status: 'operational', damage_level: null }, + { name: '以色列帕尔马欣站', lat: 31.9, lng: 34.95, type: 'Base', region: '以色列', status: 'operational', damage_level: null }, + { name: '阿曼杜古姆扩建点', lat: 19.55, lng: 57.8, type: 'Base', region: '阿曼', status: 'operational', damage_level: null }, + { name: '埃及纳特龙湖站', lat: 30.37, lng: 30.2, type: 'Base', region: '埃及', status: 'operational', damage_level: null }, + { name: '吉布提查贝尔达站', lat: 11.73, lng: 42.9, type: 'Base', region: '吉布提', status: 'operational', damage_level: null }, + { name: '阿联酋迪拜港联络', lat: 25.27, lng: 55.3, type: 'Base', region: '阿联酋', status: 'operational', damage_level: null }, + { name: '伊拉克尼尼微前哨', lat: 36.22, lng: 43.1, type: 'Base', region: '伊拉克', status: 'operational', damage_level: null }, + ] + return [...naval, ...attacked, ...newBases] +} + +function seed() { + db.exec(` + INSERT OR REPLACE INTO force_summary (side, total_assets, personnel, naval_ships, aircraft, ground_units, uav, missile_consumed, missile_stock) VALUES + ('us', 1245, 185000, 292, 1862, 18, 418, 1056, 2840), + ('iran', 7850, 2350000, 4250, 8200, 350, 750, 3720, 13800); + `) + + db.exec(` + INSERT OR REPLACE INTO power_index (side, overall, military_strength, economic_power, geopolitical_influence) VALUES + ('us', 94, 96, 98, 97), + ('iran', 42, 58, 28, 35); + `) + + const insertAsset = db.prepare(` + INSERT OR REPLACE INTO force_asset (id, side, name, type, count, status, lat, lng) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `) + const usAssets = [ + ['us-1', 'us', '双航母打击群 (CVN-72 & CVN-78)', '航母', 2, 'active', null, null], + ['us-2', 'us', '阿利·伯克级驱逐舰', '驱逐舰', 4, 'active', null, null], + ['us-3', 'us', 'F/A-18 中队', '战机', 48, 'active', null, null], + ['us-4', 'us', 'F-35 中队', '战机', 48, 'active', null, null], + ['us-5', 'us', 'F-22 猛禽', '战机', 12, 'active', null, null], + ['us-6', 'us', 'B-2 幽灵', '轰炸机', 2, 'alert', null, null], + ['us-7', 'us', '爱国者防空系统', '防空', 3, 'active', null, null], + ['us-8', 'us', 'MQ-9 死神', '无人机', 28, 'active', null, null], + ['us-9', 'us', 'MQ-1C 灰鹰', '无人机', 45, 'active', null, null], + ] + const iranAssets = [ + ['ir-1', 'iran', '护卫舰', '水面舰艇', 6, 'active', null, null], + ['ir-2', 'iran', '快攻艇', '海军', 100, 'active', null, null], + ['ir-3', 'iran', 'F-4 Phantom', '战机', 62, 'standby', null, null], + ['ir-4', 'iran', 'F-14 Tomcat', '战机', 24, 'active', null, null], + ['ir-5', 'iran', '弹道导弹', '导弹', 3400, 'alert', null, null], + ['ir-6', 'iran', '伊斯兰革命卫队海军', '准军事', 25000, 'active', null, null], + ['ir-7', 'iran', '沙希德-136', '无人机', 750, 'alert', null, null], + ['ir-8', 'iran', '法塔赫 (Fattah)', '导弹', 12, 'alert', null, null], + ['ir-9', 'iran', '穆哈杰-6', '无人机', 280, 'active', null, null], + ] + ;[...usAssets, ...iranAssets].forEach((row) => insertAsset.run(...row)) + + const insertLoc = db.prepare(` + INSERT INTO key_location (side, name, lat, lng, type, region, status, damage_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `) + db.exec('DELETE FROM key_location') + + for (const loc of getUsLocations()) { + insertLoc.run('us', loc.name, loc.lat, loc.lng, loc.type, loc.region, loc.status, loc.damage_level) + } + const iranLocs = [ + ['iran', '阿巴斯港', 27.1832, 56.2666, 'Port', '伊朗', null, null], + ['iran', '德黑兰', 35.6892, 51.389, 'Capital', '伊朗', null, null], + ['iran', '布什尔', 28.9681, 50.838, 'Base', '伊朗', null, null], + ] + iranLocs.forEach((r) => insertLoc.run(...r)) + + db.exec(` + INSERT OR REPLACE INTO combat_losses (side, bases_destroyed, bases_damaged, personnel_killed, personnel_wounded, aircraft, warships, armor, vehicles) VALUES + ('us', 0, 27, 127, 384, 2, 0, 0, 8), + ('iran', 3, 8, 2847, 5620, 24, 12, 18, 42); + `) + + db.exec('DELETE FROM wall_street_trend') + const trendRows = [['2025-03-01T00:00:00', 82], ['2025-03-01T03:00:00', 85], ['2025-03-01T06:00:00', 88], ['2025-03-01T09:00:00', 90], ['2025-03-01T12:00:00', 92], ['2025-03-01T15:00:00', 94], ['2025-03-01T18:00:00', 95], ['2025-03-01T21:00:00', 96], ['2025-03-01T23:00:00', 98]] + const insertTrend = db.prepare('INSERT INTO wall_street_trend (time, value) VALUES (?, ?)') + trendRows.forEach(([t, v]) => insertTrend.run(t, v)) + + db.exec('INSERT OR REPLACE INTO retaliation_current (id, value) VALUES (1, 78)') + db.exec('DELETE FROM retaliation_history') + const retRows = [['2025-03-01T00:00:00', 42], ['2025-03-01T03:00:00', 48], ['2025-03-01T06:00:00', 55], ['2025-03-01T09:00:00', 61], ['2025-03-01T12:00:00', 58], ['2025-03-01T15:00:00', 65], ['2025-03-01T18:00:00', 72], ['2025-03-01T21:00:00', 76], ['2025-03-01T23:00:00', 78]] + const insertRet = db.prepare('INSERT INTO retaliation_history (time, value) VALUES (?, ?)') + retRows.forEach(([t, v]) => insertRet.run(t, v)) + + db.exec('DELETE FROM situation_update') + const updateRows = [ + ['u1', new Date(Date.now() - 3600000).toISOString(), 'deployment', '美军航母打击群在阿拉伯海重新部署', 'medium'], + ['u2', new Date(Date.now() - 7200000).toISOString(), 'alert', '霍尔木兹海峡海军巡逻活动加强', 'high'], + ['u3', new Date(Date.now() - 10800000).toISOString(), 'intel', '卫星图像显示阿巴斯港活动增加', 'low'], + ['u4', new Date(Date.now() - 14400000).toISOString(), 'diplomatic', '阿曼间接谈判进行中', 'low'], + ] + const insertUpdate = db.prepare('INSERT INTO situation_update (id, timestamp, category, summary, severity) VALUES (?, ?, ?, ?, ?)') + updateRows.forEach((row) => insertUpdate.run(...row)) + + db.prepare("INSERT OR REPLACE INTO situation (id, data, updated_at) VALUES (1, '{}', ?)").run('2026-03-01T11:45:00.000Z') + console.log('Seed completed.') +} + +seed() diff --git a/server/situationData.js b/server/situationData.js new file mode 100644 index 0000000..8852e37 --- /dev/null +++ b/server/situationData.js @@ -0,0 +1,108 @@ +const db = require('./db') + +function toAsset(row) { + return { + id: row.id, + name: row.name, + type: row.type, + count: row.count, + status: row.status, + ...(row.lat != null && { location: { lat: row.lat, lng: row.lng } }), + } +} + +function toLosses(row) { + return { + bases: { destroyed: row.bases_destroyed, damaged: row.bases_damaged }, + personnelCasualties: { killed: row.personnel_killed, wounded: row.personnel_wounded }, + aircraft: row.aircraft, + warships: row.warships, + armor: row.armor, + vehicles: row.vehicles, + } +} + +const defaultLosses = { + bases: { destroyed: 0, damaged: 0 }, + personnelCasualties: { killed: 0, wounded: 0 }, + aircraft: 0, + warships: 0, + armor: 0, + vehicles: 0, +} + +function getSituation() { + const summaryUs = db.prepare('SELECT * FROM force_summary WHERE side = ?').get('us') + const summaryIr = db.prepare('SELECT * FROM force_summary WHERE side = ?').get('iran') + const powerUs = db.prepare('SELECT * FROM power_index WHERE side = ?').get('us') + const powerIr = db.prepare('SELECT * FROM power_index WHERE side = ?').get('iran') + const assetsUs = db.prepare('SELECT * FROM force_asset WHERE side = ? ORDER BY id').all('us') + const assetsIr = db.prepare('SELECT * FROM force_asset WHERE side = ? ORDER BY id').all('iran') + const locUs = db.prepare('SELECT id, name, lat, lng, type, region, status, damage_level FROM key_location WHERE side = ?').all('us') + const locIr = db.prepare('SELECT id, name, lat, lng, type, region FROM key_location WHERE side = ?').all('iran') + const lossesUs = db.prepare('SELECT * FROM combat_losses WHERE side = ?').get('us') + const lossesIr = db.prepare('SELECT * FROM combat_losses WHERE side = ?').get('iran') + const trend = db.prepare('SELECT time, value FROM wall_street_trend ORDER BY time').all() + const retaliationCur = db.prepare('SELECT value FROM retaliation_current WHERE id = 1').get() + const retaliationHist = db.prepare('SELECT time, value FROM retaliation_history ORDER BY time').all() + const updates = db.prepare('SELECT * FROM situation_update ORDER BY timestamp DESC').all() + const meta = db.prepare('SELECT updated_at FROM situation WHERE id = 1').get() + + return { + lastUpdated: meta?.updated_at || new Date().toISOString(), + usForces: { + summary: { + totalAssets: summaryUs?.total_assets ?? 0, + personnel: summaryUs?.personnel ?? 0, + navalShips: summaryUs?.naval_ships ?? 0, + aircraft: summaryUs?.aircraft ?? 0, + groundUnits: summaryUs?.ground_units ?? 0, + uav: summaryUs?.uav ?? 0, + missileConsumed: summaryUs?.missile_consumed ?? 0, + missileStock: summaryUs?.missile_stock ?? 0, + }, + powerIndex: { + overall: powerUs?.overall ?? 0, + militaryStrength: powerUs?.military_strength ?? 0, + economicPower: powerUs?.economic_power ?? 0, + geopoliticalInfluence: powerUs?.geopolitical_influence ?? 0, + }, + assets: (assetsUs || []).map(toAsset), + keyLocations: locUs || [], + combatLosses: lossesUs ? toLosses(lossesUs) : defaultLosses, + wallStreetInvestmentTrend: trend || [], + }, + iranForces: { + summary: { + totalAssets: summaryIr?.total_assets ?? 0, + personnel: summaryIr?.personnel ?? 0, + navalShips: summaryIr?.naval_ships ?? 0, + aircraft: summaryIr?.aircraft ?? 0, + groundUnits: summaryIr?.ground_units ?? 0, + uav: summaryIr?.uav ?? 0, + missileConsumed: summaryIr?.missile_consumed ?? 0, + missileStock: summaryIr?.missile_stock ?? 0, + }, + powerIndex: { + overall: powerIr?.overall ?? 0, + militaryStrength: powerIr?.military_strength ?? 0, + economicPower: powerIr?.economic_power ?? 0, + geopoliticalInfluence: powerIr?.geopolitical_influence ?? 0, + }, + assets: (assetsIr || []).map(toAsset), + keyLocations: locIr || [], + combatLosses: lossesIr ? toLosses(lossesIr) : defaultLosses, + retaliationSentiment: retaliationCur?.value ?? 0, + retaliationSentimentHistory: retaliationHist || [], + }, + recentUpdates: (updates || []).map((u) => ({ + id: u.id, + timestamp: u.timestamp, + category: u.category, + summary: u.summary, + severity: u.severity, + })), + } +} + +module.exports = { getSituation } diff --git a/src/api/situation.ts b/src/api/situation.ts new file mode 100644 index 0000000..8905775 --- /dev/null +++ b/src/api/situation.ts @@ -0,0 +1,7 @@ +import type { MilitarySituation } from '@/data/mockData' + +export async function fetchSituation(): Promise { + const res = await fetch('/api/situation') + if (!res.ok) throw new Error(`API error: ${res.status}`) + return res.json() +} diff --git a/src/api/websocket.ts b/src/api/websocket.ts new file mode 100644 index 0000000..0adc10b --- /dev/null +++ b/src/api/websocket.ts @@ -0,0 +1,30 @@ +type Handler = (data: unknown) => void + +let ws: WebSocket | null = null +let handler: Handler | null = null + +function getUrl(): string { + return `${window.location.origin.replace(/^http/, 'ws')}/ws` +} + +export function connectSituationWebSocket(onData: Handler): () => void { + handler = onData + if (ws?.readyState === WebSocket.OPEN) return () => {} + + ws = new WebSocket(getUrl()) + ws.onmessage = (e) => { + try { + const msg = JSON.parse(e.data) + if (msg.type === 'situation' && msg.data) handler?.(msg.data) + } catch (_) {} + } + ws.onclose = () => { + ws = null + setTimeout(() => handler && connectSituationWebSocket(handler), 3000) + } + return () => { + handler = null + ws?.close() + ws = null + } +} diff --git a/src/components/BaseStatusPanel.tsx b/src/components/BaseStatusPanel.tsx new file mode 100644 index 0000000..b267a96 --- /dev/null +++ b/src/components/BaseStatusPanel.tsx @@ -0,0 +1,68 @@ +import { useMemo } from 'react' +import { MapPin, AlertTriangle, AlertCircle } from 'lucide-react' +import type { MilitarySituation } from '@/data/mockData' + +interface BaseStatusPanelProps { + keyLocations: MilitarySituation['usForces']['keyLocations'] + className?: string +} + +const TOTAL_BASES = 62 + +export function BaseStatusPanel({ keyLocations, className = '' }: BaseStatusPanelProps) { + const stats = useMemo(() => { + const bases = (keyLocations || []).filter((loc) => loc.type === 'Base') + let attacked = 0 + let severe = 0 + let moderate = 0 + let light = 0 + for (const b of bases) { + const s = b.status ?? 'operational' + if (s === 'attacked') attacked++ + const lvl = b.damage_level + if (lvl === 3) severe++ + else if (lvl === 2) moderate++ + else if (lvl === 1) light++ + } + return { attacked, severe, moderate, light } + }, [keyLocations]) + + return ( +
+
+ + 美军基地态势 +
+
+
+ 总基地数 + {TOTAL_BASES} +
+
+ + + 被袭击 + + {stats.attacked} +
+
+ + + 严重损毁 + + {stats.severe} +
+
+ 中度损毁 + {stats.moderate} +
+
+ 轻度损毁 + {stats.light} +
+
+
+ ) +} diff --git a/src/components/HeaderPanel.tsx b/src/components/HeaderPanel.tsx index f4961bc..513ed42 100644 --- a/src/components/HeaderPanel.tsx +++ b/src/components/HeaderPanel.tsx @@ -24,15 +24,34 @@ export function HeaderPanel() { second: '2-digit', }) + const formatDataTime = (iso: string) => { + const d = new Date(iso) + return d.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour12: false, + hour: '2-digit', + minute: '2-digit', + }) + } + return (

美伊军事态势显示

-
- - {formatDateTime(now)} +
+
+ + {formatDateTime(now)} +
+ {isConnected && ( + + {formatDataTime(situation.lastUpdated)} (实时更新) + + )}
diff --git a/src/components/WarMap.tsx b/src/components/WarMap.tsx index edadfe7..040f911 100644 --- a/src/components/WarMap.tsx +++ b/src/components/WarMap.tsx @@ -1,51 +1,213 @@ -import { useRef, useEffect } from 'react' -import Map, { Marker } from 'react-map-gl' +import { useMemo, useEffect, useRef } 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' import 'mapbox-gl/dist/mapbox-gl.css' import { useSituationStore } from '@/store/situationStore' +import { ATTACKED_TARGETS } from '@/data/mapLocations' const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN || '' -// Persian Gulf center -const DEFAULT_VIEW = { - longitude: 54, - latitude: 27, - zoom: 5.5, +const DEFAULT_VIEW = { longitude: 52.5, latitude: 26.5, zoom: 5.2 } + +const COUNTRIES_GEOJSON = + 'https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_110m_admin_0_countries.geojson' + +const IRAN_ADMIN = 'Iran' +const ALLIES_ADMIN = [ + 'Qatar', + 'Bahrain', + 'Kuwait', + 'United Arab Emirates', + 'Saudi Arabia', + 'Iraq', + 'Syria', + 'Jordan', + 'Turkey', + 'Israel', + 'Oman', + 'Egypt', + 'Djibouti', +] + +// 伊朗攻击源 德黑兰 [lng, lat] +const TEHRAN_SOURCE: [number, number] = [51.389, 35.6892] + +function parabolaPath( + start: [number, number], + end: [number, number], + height = 2 +): [number, number][] { + const midLng = (start[0] + end[0]) / 2 + const midLat = (start[1] + end[1]) / 2 + height + return [start, [midLng, midLat], end] +} + +type BaseStatus = 'operational' | 'damaged' | 'attacked' + +interface KeyLoc { + name: string + lat: number + lng: number + type?: string + status?: BaseStatus + damage_level?: number +} + +function toFeature(loc: KeyLoc, side: 'us' | 'iran', status?: BaseStatus) { + return { + type: 'Feature' as const, + properties: { + side, + name: loc.name, + status: status ?? (loc as KeyLoc & { status?: BaseStatus }).status ?? 'operational', + }, + geometry: { + type: 'Point' as const, + coordinates: [loc.lng, loc.lat] as [number, number], + }, + } } export function WarMap() { const mapRef = useRef(null) + const animRef = useRef(0) + const startRef = useRef(0) const { situation } = useSituationStore() const { usForces, iranForces } = situation - const usMarkers = usForces.keyLocations - const iranMarkers = iranForces.keyLocations + const usLocs = (usForces.keyLocations || []) as KeyLoc[] + const irLocs = (iranForces.keyLocations || []) as KeyLoc[] + + const { usNaval, usBaseOp, usBaseDamaged, usBaseAttacked, labelsGeoJson } = useMemo(() => { + const naval: GeoJSON.Feature[] = [] + const op: GeoJSON.Feature[] = [] + const damaged: GeoJSON.Feature[] = [] + const attacked: GeoJSON.Feature[] = [] + const labels: GeoJSON.Feature[] = [] + + for (const loc of usLocs as KeyLoc[]) { + const f = toFeature(loc, 'us') + labels.push({ ...f, properties: { ...f.properties, name: loc.name } }) + if (loc.type === 'Base') { + const s = (loc.status ?? 'operational') as BaseStatus + if (s === 'attacked') attacked.push(f) + else if (s === 'damaged') damaged.push(f) + else op.push(f) + } else { + naval.push(f) + } + } + + for (const loc of irLocs) { + const f = toFeature(loc, 'iran') + labels.push({ ...f, properties: { ...f.properties, name: loc.name } }) + } + + return { + usNaval: { type: 'FeatureCollection' as const, features: naval }, + usBaseOp: { type: 'FeatureCollection' as const, features: op }, + usBaseDamaged: { type: 'FeatureCollection' as const, features: damaged }, + usBaseAttacked: { type: 'FeatureCollection' as const, features: attacked }, + labelsGeoJson: { type: 'FeatureCollection' as const, features: labels }, + } + }, [usForces.keyLocations, iranForces.keyLocations]) + + // 德黑兰到 27 个被袭目标的攻击曲线 + const attackLinesGeoJson = useMemo( + () => ({ + type: 'FeatureCollection' as const, + features: ATTACKED_TARGETS.map((target) => ({ + type: 'Feature' as const, + properties: {}, + geometry: { + type: 'LineString' as const, + coordinates: parabolaPath(TEHRAN_SOURCE, target as [number, number]), + }, + })), + }), + [] + ) + + const hideNonBelligerentLabels = (map: MapboxMap) => { + const labelLayers = [ + 'country-label', + 'state-label', + 'place-label', + 'place-label-capital', + 'place-label-city', + 'place-label-town', + 'place-label-village', + 'poi-label', + ] + for (const id of labelLayers) { + try { + if (map.getLayer(id)) map.setLayoutProperty(id, 'visibility', 'none') + } catch (_) {} + } + } + + useEffect(() => { + const map = mapRef.current?.getMap() + if (!map) return + startRef.current = performance.now() + + const tick = (t: number) => { + const elapsed = t - startRef.current + try { + if (map.getLayer('attack-lines')) { + const offset = (elapsed / 16) * 0.8 + map.setPaintProperty('attack-lines', 'line-dasharray', [2, 2]) + map.setPaintProperty('attack-lines', 'line-dash-offset', -offset) + } + // damaged: 橙色闪烁 opacity 0.5 ~ 1, 约 1s 周期 + if (map.getLayer('points-damaged')) { + const blink = 0.5 + 0.5 * Math.sin(elapsed * 0.003) + map.setPaintProperty('points-damaged', 'circle-opacity', blink) + } + // attacked: 红色脉冲 2s 循环, 扩散半径 0→40px, opacity 1→0 (map.md) + if (map.getLayer('points-attacked-pulse')) { + const cycle = 2000 + const phase = (elapsed % cycle) / cycle + const r = 40 * phase + const opacity = 1 - phase + map.setPaintProperty('points-attacked-pulse', 'circle-radius', r) + map.setPaintProperty('points-attacked-pulse', 'circle-opacity', opacity) + } + } catch (_) {} + animRef.current = requestAnimationFrame(tick) + } + + const start = () => { + hideNonBelligerentLabels(map) + if (map.getLayer('attack-lines')) { + animRef.current = requestAnimationFrame(tick) + } else { + animRef.current = requestAnimationFrame(start) + } + } + if (map.isStyleLoaded()) start() + else map.once('load', start) + + return () => cancelAnimationFrame(animRef.current) + }, []) - // Fallback when no Mapbox token - show placeholder if (!MAPBOX_TOKEN) { return (
-
-

- 地图需要 Mapbox 令牌 -

+
+

地图需要 Mapbox 令牌

- 请在 .env 中设置 VITE_MAPBOX_ACCESS_TOKEN + 复制 .env.example 为 .env,填入令牌后重启

-
-
-

美方位置

- {usMarkers.map((loc) => ( -

{loc.name}

- ))} -
-
-

伊方位置

- {iranMarkers.map((loc) => ( -

{loc.name}

- ))} -
-
+ + 免费申请 Mapbox 令牌 → +
) @@ -59,40 +221,160 @@ export function WarMap() { mapStyle="mapbox://styles/mapbox/dark-v11" mapboxAccessToken={MAPBOX_TOKEN} attributionControl={false} + dragRotate={false} + touchRotate={false} style={{ width: '100%', height: '100%' }} > - {usMarkers.map((loc) => ( - -
-
- - {loc.name} - -
- - ))} - {iranMarkers.map((loc) => ( - -
-
- - {loc.name} - -
- - ))} + {/* 美国海军 - 蓝色 */} + + + + + {/* 美军基地-正常 - 绿色 */} + + + + + {/* 美军基地-损毁 - 橙色闪烁 */} + + + + + {/* 美军基地-遭袭 - 红点 + 脉冲 */} + + + + + + {/* 伊朗 - 红色 */} + toFeature(loc, 'iran')), + }} + > + + + + + + + + {/* 中文标注 */} + + + + + + {/* 以色列 mesh 高亮 */} + + + +
) diff --git a/src/data/mapLocations.ts b/src/data/mapLocations.ts new file mode 100644 index 0000000..2b8bfad --- /dev/null +++ b/src/data/mapLocations.ts @@ -0,0 +1,140 @@ +/** 航母标记 - 全部中文 */ +export const CARRIER_MARKERS = [ + { + id: 'CVN-72', + name: '林肯号航母', + coordinates: [58.4215, 24.1568] as [number, number], + type: 'Aircraft Carrier', + status: 'Active - Combat Readiness', + details: '林肯号航母打击群 (CSG-3) 部署于北阿拉伯海。', + }, + { + id: 'CVN-78', + name: '福特号航母', + coordinates: [24.1002, 35.7397] as [number, number], + type: 'Aircraft Carrier', + status: 'Active - Forward Deployed', + details: '距克里特苏达湾约 15 公里。', + }, +] + +export type KeyLocItem = { + name: string + lat: number + lng: number + type?: string + region?: string + status?: 'operational' | 'damaged' | 'attacked' + damage_level?: number +} + +/** 美军基地总数 62,被袭击 27 个。损毁程度:严重 6 / 中度 12 / 轻度 9 */ +const ATTACKED_BASES = [ + // 严重损毁 (6): 高价值目标,近伊朗 + { name: '阿萨德空军基地', lat: 33.785, lng: 42.441, region: '伊拉克', damage_level: 3 }, + { name: '巴格达外交支援中心', lat: 33.315, lng: 44.366, region: '伊拉克', damage_level: 3 }, + { name: '乌代德空军基地', lat: 25.117, lng: 51.314, region: '卡塔尔', damage_level: 3 }, + { name: '埃尔比勒空军基地', lat: 36.237, lng: 43.963, region: '伊拉克', damage_level: 3 }, + { name: '因吉尔利克空军基地', lat: 37.002, lng: 35.425, region: '土耳其', damage_level: 3 }, + { name: '苏尔坦亲王空军基地', lat: 24.062, lng: 47.58, region: '沙特', damage_level: 3 }, + // 中度损毁 (12) + { name: '塔吉军营', lat: 33.556, lng: 44.256, region: '伊拉克', damage_level: 2 }, + { name: '阿因·阿萨德', lat: 33.8, lng: 42.45, region: '伊拉克', damage_level: 2 }, + { name: '坦夫驻军', lat: 33.49, lng: 38.618, region: '叙利亚', damage_level: 2 }, + { name: '沙达迪基地', lat: 36.058, lng: 40.73, region: '叙利亚', damage_level: 2 }, + { name: '康诺克气田基地', lat: 35.336, lng: 40.295, region: '叙利亚', damage_level: 2 }, + { name: '尔梅兰着陆区', lat: 37.015, lng: 41.885, region: '叙利亚', damage_level: 2 }, + { name: '阿里夫坚军营', lat: 28.832, lng: 47.799, region: '科威特', damage_level: 2 }, + { name: '阿里·萨勒姆空军基地', lat: 29.346, lng: 47.52, region: '科威特', damage_level: 2 }, + { name: '巴林海军支援站', lat: 26.236, lng: 50.608, region: '巴林', damage_level: 2 }, + { name: '达夫拉空军基地', lat: 24.248, lng: 54.547, region: '阿联酋', damage_level: 2 }, + { name: '埃斯康村', lat: 24.774, lng: 46.738, region: '沙特', damage_level: 2 }, + { name: '内瓦提姆空军基地', lat: 31.208, lng: 35.012, region: '以色列', damage_level: 2 }, + // 轻度损毁 (9) + { name: '布林军营', lat: 29.603, lng: 47.456, region: '科威特', damage_level: 1 }, + { name: '赛利耶军营', lat: 25.275, lng: 51.52, region: '卡塔尔', damage_level: 1 }, + { name: '拉蒙空军基地', lat: 30.776, lng: 34.666, region: '以色列', damage_level: 1 }, + { name: '穆瓦法克·萨尔蒂空军基地', lat: 32.356, lng: 36.259, region: '约旦', damage_level: 1 }, + { name: '屈雷吉克雷达站', lat: 38.354, lng: 37.794, region: '土耳其', damage_level: 1 }, + { name: '苏姆莱特空军基地', lat: 17.666, lng: 54.024, region: '阿曼', damage_level: 1 }, + { name: '马西拉空军基地', lat: 20.675, lng: 58.89, region: '阿曼', damage_level: 1 }, + { name: '西开罗空军基地', lat: 30.915, lng: 30.298, region: '埃及', damage_level: 1 }, + { name: '勒莫尼耶军营', lat: 11.547, lng: 43.159, region: '吉布提', damage_level: 1 }, +] + +/** 35 个新增 operational 基地 */ +const NEW_BASES: KeyLocItem[] = [ + { name: '多哈后勤中心', lat: 25.29, lng: 51.53, type: 'Base', region: '卡塔尔' }, + { name: '贾法勒海军站', lat: 26.22, lng: 50.62, type: 'Base', region: '巴林' }, + { name: '阿兹祖尔前方作战点', lat: 29.45, lng: 47.9, type: 'Base', region: '科威特' }, + { name: '艾哈迈迪后勤枢纽', lat: 29.08, lng: 48.09, type: 'Base', region: '科威特' }, + { name: '富查伊拉港站', lat: 25.13, lng: 56.35, type: 'Base', region: '阿联酋' }, + { name: '哈伊马角前方点', lat: 25.79, lng: 55.94, type: 'Base', region: '阿联酋' }, + { name: '利雅得联络站', lat: 24.71, lng: 46.68, type: 'Base', region: '沙特' }, + { name: '朱拜勒港支援点', lat: 27.0, lng: 49.65, type: 'Base', region: '沙特' }, + { name: '塔布克空军前哨', lat: 28.38, lng: 36.6, type: 'Base', region: '沙特' }, + { name: '拜莱德空军基地', lat: 33.94, lng: 44.36, type: 'Base', region: '伊拉克' }, + { name: '巴士拉后勤站', lat: 30.5, lng: 47.78, type: 'Base', region: '伊拉克' }, + { name: '基尔库克前哨', lat: 35.47, lng: 44.35, type: 'Base', region: '伊拉克' }, + { name: '摩苏尔支援点', lat: 36.34, lng: 43.14, type: 'Base', region: '伊拉克' }, + { name: '哈塞克联络站', lat: 36.5, lng: 40.75, type: 'Base', region: '叙利亚' }, + { name: '代尔祖尔前哨', lat: 35.33, lng: 40.14, type: 'Base', region: '叙利亚' }, + { name: '安曼协调中心', lat: 31.95, lng: 35.93, type: 'Base', region: '约旦' }, + { name: '伊兹密尔支援站', lat: 38.42, lng: 27.14, type: 'Base', region: '土耳其' }, + { name: '哈泽瑞姆空军基地', lat: 31.07, lng: 34.84, type: 'Base', region: '以色列' }, + { name: '杜古姆港站', lat: 19.66, lng: 57.76, type: 'Base', region: '阿曼' }, + { name: '塞拉莱前方点', lat: 17.01, lng: 54.1, type: 'Base', region: '阿曼' }, + { name: '亚历山大港联络站', lat: 31.2, lng: 29.9, type: 'Base', region: '埃及' }, + { name: '卢克索前哨', lat: 25.69, lng: 32.64, type: 'Base', region: '埃及' }, + { name: '吉布提港支援点', lat: 11.59, lng: 43.15, type: 'Base', region: '吉布提' }, + { name: '卡塔尔应急医疗站', lat: 25.22, lng: 51.45, type: 'Base', region: '卡塔尔' }, + { name: '沙特哈立德国王基地', lat: 24.96, lng: 46.7, type: 'Base', region: '沙特' }, + { name: '伊拉克巴拉德联勤站', lat: 33.75, lng: 44.25, type: 'Base', region: '伊拉克' }, + { name: '叙利亚奥马尔油田站', lat: 36.22, lng: 40.45, type: 'Base', region: '叙利亚' }, + { name: '约旦侯赛因国王基地', lat: 31.72, lng: 36.01, type: 'Base', region: '约旦' }, + { name: '土耳其巴特曼站', lat: 37.88, lng: 41.13, type: 'Base', region: '土耳其' }, + { name: '以色列帕尔马欣站', lat: 31.9, lng: 34.95, type: 'Base', region: '以色列' }, + { name: '阿曼杜古姆扩建点', lat: 19.55, lng: 57.8, type: 'Base', region: '阿曼' }, + { name: '埃及纳特龙湖站', lat: 30.37, lng: 30.2, type: 'Base', region: '埃及' }, + { name: '吉布提查贝尔达站', lat: 11.73, lng: 42.9, type: 'Base', region: '吉布提' }, + { name: '阿联酋迪拜港联络', lat: 25.27, lng: 55.3, type: 'Base', region: '阿联酋' }, + { name: '伊拉克尼尼微前哨', lat: 36.22, lng: 43.1, type: 'Base', region: '伊拉克' }, +] + +/** 美军全部地图点位:2 航母 + 9 海军 + 62 基地 */ +export const US_KEY_LOCATIONS: KeyLocItem[] = [ + ...CARRIER_MARKERS.map((c) => ({ + name: c.name + ` (${c.id})`, + lat: c.coordinates[1], + lng: c.coordinates[0], + type: 'Aircraft Carrier' as const, + region: c.id === 'CVN-72' ? '北阿拉伯海' : '东地中海', + status: 'operational' as const, + damage_level: undefined as number | undefined, + })), + { name: '驱逐舰(阿曼湾)', lat: 25.2, lng: 58.0, type: 'Destroyer', region: '阿曼湾', status: 'operational' }, + { name: '海岸警卫队 1', lat: 25.4, lng: 58.2, type: 'Coast Guard', region: '阿曼湾', status: 'operational' }, + { name: '海岸警卫队 2', lat: 25.0, lng: 57.8, type: 'Coast Guard', region: '阿曼湾', status: 'operational' }, + { name: '驱逐舰(波斯湾北部)', lat: 26.5, lng: 51.0, type: 'Destroyer', region: '波斯湾', status: 'operational' }, + { name: '护卫舰 1', lat: 26.7, lng: 50.6, type: 'Frigate', region: '波斯湾', status: 'operational' }, + { name: '护卫舰 2', lat: 27.0, lng: 50.2, type: 'Frigate', region: '波斯湾', status: 'operational' }, + { name: '护卫舰 3', lat: 26.3, lng: 50.8, type: 'Frigate', region: '波斯湾', status: 'operational' }, + { name: '辅助舰 1', lat: 26.0, lng: 51.2, type: 'Auxiliary', region: '波斯湾', status: 'operational' }, + { name: '辅助舰 2', lat: 25.8, lng: 51.5, type: 'Auxiliary', region: '波斯湾', status: 'operational' }, + { name: '辅助舰 3', lat: 26.2, lng: 50.9, type: 'Auxiliary', region: '波斯湾', status: 'operational' }, + ...ATTACKED_BASES.map((b) => ({ + ...b, + type: 'Base' as const, + status: 'attacked' as const, + })), + ...NEW_BASES, +] + +/** 被袭击的 27 个基地坐标 [lng, lat],用于绘制攻击曲线 */ +export const ATTACKED_TARGETS: [number, number][] = ATTACKED_BASES.map((b) => [b.lng, b.lat]) + +export const IRAN_KEY_LOCATIONS: KeyLocItem[] = [ + { name: '阿巴斯港', lat: 27.1832, lng: 56.2666, type: 'Port', region: '伊朗' }, + { name: '德黑兰', lat: 35.6892, lng: 51.389, type: 'Capital', region: '伊朗' }, + { name: '布什尔', lat: 28.9681, lng: 50.838, type: 'Base', region: '伊朗' }, +] diff --git a/src/data/mockData.ts b/src/data/mockData.ts index 1a0703c..025198b 100644 --- a/src/data/mockData.ts +++ b/src/data/mockData.ts @@ -1,4 +1,5 @@ // TypeScript interfaces for military situation data +import { US_KEY_LOCATIONS, IRAN_KEY_LOCATIONS } from './mapLocations' export interface ForceAsset { id: string @@ -50,7 +51,16 @@ export interface MilitarySituation { summary: ForceSummary powerIndex: PowerIndex assets: ForceAsset[] - keyLocations: { name: string; lat: number; lng: number }[] + keyLocations: { + name: string + lat: number + lng: number + type?: string + region?: string + id?: number + status?: 'operational' | 'damaged' | 'attacked' + damage_level?: number + }[] combatLosses: CombatLosses /** 华尔街财团投入趋势 { time: ISO string, value: 0-100 } */ wallStreetInvestmentTrend: { time: string; value: number }[] @@ -59,7 +69,16 @@ export interface MilitarySituation { summary: ForceSummary powerIndex: PowerIndex assets: ForceAsset[] - keyLocations: { name: string; lat: number; lng: number }[] + keyLocations: { + name: string + lat: number + lng: number + type?: string + region?: string + id?: number + status?: 'operational' | 'damaged' | 'attacked' + damage_level?: number + }[] combatLosses: CombatLosses /** 反击情绪指标 0-100 */ retaliationSentiment: number @@ -70,16 +89,16 @@ export interface MilitarySituation { } export const INITIAL_MOCK_DATA: MilitarySituation = { - lastUpdated: new Date().toISOString(), + lastUpdated: '2026-03-01T11:45:00.000Z', usForces: { summary: { - totalAssets: 1247, + totalAssets: 1245, personnel: 185000, - navalShips: 285, - aircraft: 1850, + navalShips: 292, + aircraft: 1862, groundUnits: 18, - uav: 420, - missileConsumed: 156, + uav: 418, + missileConsumed: 1056, missileStock: 2840, }, powerIndex: { @@ -89,23 +108,24 @@ export const INITIAL_MOCK_DATA: MilitarySituation = { geopoliticalInfluence: 97, }, assets: [ - { id: 'us-1', name: '艾森豪威尔号航母', type: '航母', count: 1, status: 'active' }, + { id: 'us-1', name: '双航母打击群 (CVN-72 & CVN-78)', type: '航母', count: 2, status: 'active' }, { id: 'us-2', name: '阿利·伯克级驱逐舰', type: '驱逐舰', count: 4, status: 'active' }, { id: 'us-3', name: 'F/A-18 中队', type: '战机', count: 48, status: 'active' }, - { id: 'us-4', name: 'F-35 中队', type: '战机', count: 24, status: 'standby' }, - { id: 'us-5', name: 'B-1B 轰炸机', type: '轰炸机', count: 4, status: 'alert' }, - { id: 'us-6', name: '爱国者防空系统', type: '防空', count: 3, status: 'active' }, - { id: 'us-7', name: 'MQ-9 死神', type: '无人机', count: 28, status: 'active' }, - { id: 'us-8', name: 'MQ-1C 灰鹰', type: '无人机', count: 45, status: 'active' }, - ], - keyLocations: [ - { name: '第五舰队司令部', lat: 26.2285, lng: 50.586 }, - { name: '乌代德空军基地', lat: 25.1173, lng: 51.3153 }, - { name: '艾森豪威尔号航母', lat: 26.5, lng: 52.0 }, + { id: 'us-4', name: 'F-35 中队', type: '战机', count: 48, status: 'active' }, + { id: 'us-5', name: 'F-22 猛禽', type: '战机', count: 12, status: 'active' }, + { id: 'us-6', name: 'B-2 幽灵', type: '轰炸机', count: 2, status: 'alert' }, + { id: 'us-7', name: '爱国者防空系统', type: '防空', count: 3, status: 'active' }, + { id: 'us-8', name: 'MQ-9 死神', type: '无人机', count: 28, status: 'active' }, + { id: 'us-9', name: 'MQ-1C 灰鹰', type: '无人机', count: 45, status: 'active' }, ], + keyLocations: US_KEY_LOCATIONS, combatLosses: { bases: { destroyed: 0, damaged: 2 }, personnelCasualties: { killed: 127, wounded: 384 }, + aircraft: 2, + warships: 0, + armor: 0, + vehicles: 8, }, wallStreetInvestmentTrend: [ { time: '2025-03-01T00:00:00', value: 82 }, @@ -121,14 +141,14 @@ export const INITIAL_MOCK_DATA: MilitarySituation = { }, iranForces: { summary: { - totalAssets: 8523, + totalAssets: 7850, personnel: 2350000, navalShips: 4250, aircraft: 8200, groundUnits: 350, - uav: 1850, - missileConsumed: 3420, - missileStock: 15600, + uav: 750, + missileConsumed: 3720, + missileStock: 13800, }, powerIndex: { overall: 42, @@ -141,16 +161,13 @@ export const INITIAL_MOCK_DATA: MilitarySituation = { { id: 'ir-2', name: '快攻艇', type: '海军', count: 100, status: 'active' }, { id: 'ir-3', name: 'F-4 Phantom', type: '战机', count: 62, status: 'standby' }, { id: 'ir-4', name: 'F-14 Tomcat', type: '战机', count: 24, status: 'active' }, - { id: 'ir-5', name: '弹道导弹', type: '导弹', count: 2000, status: 'alert' }, + { id: 'ir-5', name: '弹道导弹', type: '导弹', count: 3400, status: 'alert' }, { id: 'ir-6', name: '伊斯兰革命卫队海军', type: '准军事', count: 25000, status: 'active' }, - { id: 'ir-7', name: '沙希德-136', type: '无人机', count: 1200, status: 'alert' }, - { id: 'ir-8', name: '穆哈杰-6', type: '无人机', count: 280, status: 'active' }, - ], - keyLocations: [ - { name: '阿巴斯港', lat: 27.1832, lng: 56.2666 }, - { name: '德黑兰', lat: 35.6892, lng: 51.3890 }, - { name: '布什尔', lat: 28.9681, lng: 50.8380 }, + { id: 'ir-7', name: '沙希德-136', type: '无人机', count: 750, status: 'alert' }, + { id: 'ir-8', name: '法塔赫 (Fattah)', type: '导弹', count: 12, status: 'alert' }, + { id: 'ir-9', name: '穆哈杰-6', type: '无人机', count: 280, status: 'active' }, ], + keyLocations: IRAN_KEY_LOCATIONS, combatLosses: { bases: { destroyed: 3, damaged: 8 }, personnelCasualties: { killed: 2847, wounded: 5620 }, diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 52e1288..bab94ac 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -3,22 +3,30 @@ import { HeaderPanel } from '@/components/HeaderPanel' import { ForcePanel } from '@/components/ForcePanel' import { WarMap } from '@/components/WarMap' import { CombatLossesPanel } from '@/components/CombatLossesPanel' +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 { startWebSocketMock, stopWebSocketMock } from '@/store/situationStore' +import { fetchAndSetSituation, startSituationWebSocket, stopSituationWebSocket } from '@/store/situationStore' export function Dashboard() { const situation = useSituationStore((s) => s.situation) + const isLoading = useSituationStore((s) => s.isLoading) + const lastError = useSituationStore((s) => s.lastError) useEffect(() => { - startWebSocketMock() - return () => stopWebSocketMock() + fetchAndSetSituation().finally(() => startSituationWebSocket()) + return () => stopSituationWebSocket() }, []) return (
+ {lastError && ( +
+ {lastError}(使用本地缓存,请确保 API + WebSocket 已启动:npm run api) +
+ )}
@@ -50,10 +58,14 @@ export function Dashboard() {
- +
+ + +