fix: 优化数据
This commit is contained in:
@@ -68,6 +68,7 @@ export function Dashboard() {
|
||||
usLosses={situation.usForces.combatLosses}
|
||||
iranLosses={situation.iranForces.combatLosses}
|
||||
conflictStats={situation.conflictStats}
|
||||
civilianTotal={situation.civilianCasualtiesTotal}
|
||||
className="min-w-0 flex-1 py-1"
|
||||
/>
|
||||
<EventTimelinePanel updates={situation.recentUpdates} conflictEvents={situation.conflictEvents} className="min-w-0 shrink-0 min-h-[80px] overflow-hidden lg:min-w-[240px]" />
|
||||
|
||||
161
src/pages/DbDashboard.tsx
Normal file
161
src/pages/DbDashboard.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Database, Table, ArrowLeft, RefreshCw } from 'lucide-react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
interface TableData {
|
||||
[table: string]: Record<string, unknown>[] | { error: string }
|
||||
}
|
||||
|
||||
export function DbDashboard() {
|
||||
const [data, setData] = useState<TableData | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [expanded, setExpanded] = useState<Set<string>>(new Set(['situation_update', 'combat_losses', 'conflict_stats']))
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
const t = setInterval(fetchData, 30000)
|
||||
return () => clearInterval(t)
|
||||
}, [])
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const res = await fetch('/api/db/dashboard')
|
||||
if (!res.ok) throw new Error(res.statusText)
|
||||
const json = await res.json()
|
||||
setData(json)
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : '加载失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const toggle = (name: string) => {
|
||||
setExpanded((s) => {
|
||||
const next = new Set(s)
|
||||
if (next.has(name)) next.delete(name)
|
||||
else next.add(name)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
if (loading && !data) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-military-dark text-military-text-secondary">
|
||||
<RefreshCw className="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-military-dark font-db text-military-text-primary">
|
||||
<header className="sticky top-0 z-10 flex items-center justify-between border-b border-military-border bg-military-panel/95 px-4 py-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center gap-2 rounded border border-military-border px-3 py-1.5 text-sm text-military-text-secondary hover:bg-military-border/30 hover:text-military-text-primary"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
返回主面板
|
||||
</Link>
|
||||
<span className="flex items-center gap-2 text-lg">
|
||||
<Database className="h-5 w-5 text-cyan-400" />
|
||||
数据库内容
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={fetchData}
|
||||
disabled={loading}
|
||||
className="flex items-center gap-2 rounded border border-military-border px-3 py-1.5 text-sm text-military-text-secondary hover:bg-military-border/30 disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
|
||||
刷新
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{error && (
|
||||
<div className="mx-4 mt-4 rounded border border-amber-600/50 bg-amber-950/30 px-4 py-2 text-amber-400">
|
||||
{error}(请确保 API 已启动:npm run api)
|
||||
</div>
|
||||
)}
|
||||
|
||||
<main className="max-w-6xl space-y-4 p-4">
|
||||
{data &&
|
||||
Object.entries(data).map(([name, rows]) => {
|
||||
const isExpanded = expanded.has(name)
|
||||
const isError = rows && typeof rows === 'object' && 'error' in rows
|
||||
const arr = Array.isArray(rows) ? rows : []
|
||||
return (
|
||||
<section
|
||||
key={name}
|
||||
className="rounded border border-military-border bg-military-panel/80 overflow-hidden"
|
||||
>
|
||||
<button
|
||||
onClick={() => toggle(name)}
|
||||
className="flex w-full items-center justify-between px-4 py-3 text-left hover:bg-military-border/20"
|
||||
>
|
||||
<span className="flex items-center gap-2 font-medium">
|
||||
<Table className="h-4 w-4 text-cyan-400" />
|
||||
{name}
|
||||
<span className="text-military-text-secondary font-normal">
|
||||
{isError ? '(错误)' : `(${arr.length} 条)`}
|
||||
</span>
|
||||
</span>
|
||||
<span className="text-military-text-secondary">{isExpanded ? '▼' : '▶'}</span>
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<div className="border-t border-military-border overflow-x-auto">
|
||||
{isError ? (
|
||||
<pre className="p-4 text-sm text-amber-400">{(rows as { error: string }).error}</pre>
|
||||
) : arr.length === 0 ? (
|
||||
<p className="p-4 text-sm text-military-text-secondary">无数据</p>
|
||||
) : (
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-military-border bg-military-dark/50">
|
||||
{Object.keys(arr[0] as object).map((col) => (
|
||||
<th
|
||||
key={col}
|
||||
className="whitespace-nowrap px-3 py-2 font-medium text-cyan-400"
|
||||
>
|
||||
{col}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{arr.map((row, i) => (
|
||||
<tr
|
||||
key={i}
|
||||
className="border-b border-military-border/50 hover:bg-military-border/10"
|
||||
>
|
||||
{Object.values(row as object).map((v, j) => (
|
||||
<td
|
||||
key={j}
|
||||
className="max-w-xs truncate px-3 py-2 text-military-text-secondary"
|
||||
title={String(v ?? '')}
|
||||
>
|
||||
{v === null || v === undefined
|
||||
? '—'
|
||||
: typeof v === 'object'
|
||||
? JSON.stringify(v)
|
||||
: String(v)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
})}
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user