fix:优化整个大屏界面

This commit is contained in:
Daniel
2026-03-02 00:59:40 +08:00
parent 24d0593e12
commit 91d9e48e1e
13 changed files with 314 additions and 101 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -92,6 +92,26 @@ db.exec(`
summary TEXT NOT NULL,
severity TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS gdelt_events (
event_id TEXT PRIMARY KEY,
event_time TEXT NOT NULL,
title TEXT NOT NULL,
lat REAL NOT NULL,
lng REAL NOT NULL,
impact_score INTEGER NOT NULL,
url TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS conflict_stats (
id INTEGER PRIMARY KEY CHECK (id = 1),
total_events INTEGER NOT NULL DEFAULT 0,
high_impact_events INTEGER NOT NULL DEFAULT 0,
estimated_casualties INTEGER NOT NULL DEFAULT 0,
estimated_strike_count INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL
);
`)
// 迁移:为已有 key_location 表添加 type、region、status、damage_level 列
@@ -103,5 +123,12 @@ try {
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 (_) {}
// 迁移combat_losses 添加平民伤亡
try {
const lossCols = db.prepare('PRAGMA table_info(combat_losses)').all()
const lossNames = lossCols.map((c) => c.name)
if (!lossNames.includes('civilian_killed')) db.exec('ALTER TABLE combat_losses ADD COLUMN civilian_killed INTEGER NOT NULL DEFAULT 0')
if (!lossNames.includes('civilian_wounded')) db.exec('ALTER TABLE combat_losses ADD COLUMN civilian_wounded INTEGER NOT NULL DEFAULT 0')
} catch (_) {}
module.exports = db

View File

@@ -12,6 +12,10 @@ app.use(cors())
app.use(express.json())
app.use('/api', routes)
app.get('/api/health', (_, res) => res.json({ ok: true }))
app.post('/api/crawler/notify', (_, res) => {
notifyCrawlerUpdate()
res.json({ ok: true })
})
const server = http.createServer(app)
@@ -29,6 +33,15 @@ function broadcastSituation() {
}
setInterval(broadcastSituation, 5000)
// 供爬虫调用:更新 situation.updated_at 并立即广播
function notifyCrawlerUpdate() {
try {
const db = require('./db')
db.prepare("INSERT OR REPLACE INTO situation (id, data, updated_at) VALUES (1, '{}', ?)").run(new Date().toISOString())
broadcastSituation()
} catch (_) {}
}
server.listen(PORT, () => {
console.log(`API + WebSocket running at http://localhost:${PORT}`)
})

View File

@@ -12,4 +12,19 @@ router.get('/situation', (req, res) => {
}
})
router.get('/events', (req, res) => {
try {
const s = getSituation()
res.json({
updated_at: s.lastUpdated,
count: (s.conflictEvents || []).length,
events: s.conflictEvents || [],
conflict_stats: s.conflictStats || {},
})
} catch (err) {
console.error(err)
res.status(500).json({ error: err.message })
}
})
module.exports = router

View File

@@ -140,11 +140,19 @@ function seed() {
]
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);
`)
try {
db.exec(`
INSERT OR REPLACE INTO combat_losses (side, bases_destroyed, bases_damaged, personnel_killed, personnel_wounded, civilian_killed, civilian_wounded, aircraft, warships, armor, vehicles) VALUES
('us', 0, 27, 127, 384, 18, 52, 2, 0, 0, 8),
('iran', 3, 8, 2847, 5620, 412, 1203, 24, 12, 18, 42);
`)
} catch (_) {
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]]

View File

@@ -15,6 +15,7 @@ function toLosses(row) {
return {
bases: { destroyed: row.bases_destroyed, damaged: row.bases_damaged },
personnelCasualties: { killed: row.personnel_killed, wounded: row.personnel_wounded },
civilianCasualties: { killed: row.civilian_killed ?? 0, wounded: row.civilian_wounded ?? 0 },
aircraft: row.aircraft,
warships: row.warships,
armor: row.armor,
@@ -25,6 +26,7 @@ function toLosses(row) {
const defaultLosses = {
bases: { destroyed: 0, damaged: 0 },
personnelCasualties: { killed: 0, wounded: 0 },
civilianCasualties: { killed: 0, wounded: 0 },
aircraft: 0,
warships: 0,
armor: 0,
@@ -45,9 +47,30 @@ function getSituation() {
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 updates = db.prepare('SELECT * FROM situation_update ORDER BY timestamp DESC LIMIT 50').all()
const meta = db.prepare('SELECT updated_at FROM situation WHERE id = 1').get()
let conflictEvents = []
let conflictStats = { total_events: 0, high_impact_events: 0, estimated_casualties: 0, estimated_strike_count: 0 }
try {
conflictEvents = db.prepare('SELECT event_id, event_time, title, lat, lng, impact_score, url FROM gdelt_events ORDER BY event_time DESC LIMIT 30').all()
const statsRow = db.prepare('SELECT total_events, high_impact_events, estimated_casualties, estimated_strike_count FROM conflict_stats WHERE id = 1').get()
if (statsRow) conflictStats = statsRow
} catch (_) {}
// 根据爬虫 conflict_stats 实时合并平民伤亡估算GDELT 数据)
const usLossesBase = lossesUs ? toLosses(lossesUs) : defaultLosses
const irLossesBase = lossesIr ? toLosses(lossesIr) : defaultLosses
const est = conflictStats.estimated_casualties || 0
const mergeCivilian = (base, share) => {
if (est <= 0) return base.civilianCasualties || { killed: 0, wounded: 0 }
const gdeltKilled = Math.round(est * share)
const cur = base.civilianCasualties || { killed: 0, wounded: 0 }
return { killed: Math.max(cur.killed, gdeltKilled), wounded: cur.wounded }
}
const usLosses = { ...usLossesBase, civilianCasualties: mergeCivilian(usLossesBase, 0.35) }
const irLosses = { ...irLossesBase, civilianCasualties: mergeCivilian(irLossesBase, 0.65) }
return {
lastUpdated: meta?.updated_at || new Date().toISOString(),
usForces: {
@@ -69,7 +92,7 @@ function getSituation() {
},
assets: (assetsUs || []).map(toAsset),
keyLocations: locUs || [],
combatLosses: lossesUs ? toLosses(lossesUs) : defaultLosses,
combatLosses: usLosses,
wallStreetInvestmentTrend: trend || [],
},
iranForces: {
@@ -91,7 +114,7 @@ function getSituation() {
},
assets: (assetsIr || []).map(toAsset),
keyLocations: locIr || [],
combatLosses: lossesIr ? toLosses(lossesIr) : defaultLosses,
combatLosses: irLosses,
retaliationSentiment: retaliationCur?.value ?? 0,
retaliationSentimentHistory: retaliationHist || [],
},
@@ -102,6 +125,16 @@ function getSituation() {
summary: u.summary,
severity: u.severity,
})),
conflictEvents: conflictEvents.map((e) => ({
event_id: e.event_id,
event_time: e.event_time,
title: e.title,
lat: e.lat,
lng: e.lng,
impact_score: e.impact_score,
url: e.url,
})),
conflictStats,
}
}