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) => (
-
-
-
- ))}
- {iranMarkers.map((loc) => (
-
-
-
- ))}
+ {/* 美国海军 - 蓝色 */}
+
+
+
+
+ {/* 美军基地-正常 - 绿色 */}
+
+
+
+
+ {/* 美军基地-损毁 - 橙色闪烁 */}
+
+
+
+
+ {/* 美军基地-遭袭 - 红点 + 脉冲 */}
+
+
+
+
+
+ {/* 伊朗 - 红色 */}
+
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() {
-
+
+
+
+