This commit is contained in:
薇薇安 2026-02-01 22:37:27 +08:00
parent 18257b7d8a
commit 8ff8cd4ebc
2 changed files with 215 additions and 0 deletions

View File

@ -0,0 +1,117 @@
.admin-dashboard {
padding: 24px;
max-width: 1200px;
margin: 0 auto;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.refresh-btn {
padding: 8px 16px;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
.refresh-btn:hover {
background-color: #e0e0e0;
}
.summary-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
margin-bottom: 32px;
}
.card {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card-title {
color: #666;
font-size: 14px;
margin-bottom: 8px;
}
.card-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.card-value.profit {
color: #2e7d32;
}
.card-value.loss {
color: #c62828;
}
.accounts-section {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.accounts-section h3 {
margin-top: 0;
margin-bottom: 16px;
}
.table-container {
overflow-x: auto;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #eee;
}
.data-table th {
background-color: #f8f9fa;
font-weight: 600;
color: #555;
}
.status-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-badge.active {
background-color: #e8f5e9;
color: #2e7d32;
}
.status-badge.stopped {
background-color: #ffebee;
color: #c62828;
}
.profit {
color: #2e7d32;
}
.loss {
color: #c62828;
}

View File

@ -0,0 +1,98 @@
import React, { useEffect, useState } from 'react'
import { api } from '../services/api'
import './AdminDashboard.css'
const AdminDashboard = () => {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const loadData = async () => {
try {
setLoading(true)
const res = await api.getAdminDashboard()
setData(res)
setError(null)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
useEffect(() => {
loadData()
const timer = setInterval(loadData, 30000) // 30
return () => clearInterval(timer)
}, [])
if (loading && !data) return <div className="loading">加载中...</div>
if (error) return <div className="error">加载失败: {error}</div>
if (!data) return null
const { summary, accounts } = data
return (
<div className="admin-dashboard">
<div className="dashboard-header">
<h2>全局交易监控看板</h2>
<button className="refresh-btn" onClick={loadData}>刷新</button>
</div>
<div className="summary-cards">
<div className="card">
<div className="card-title">总资产 (USDT)</div>
<div className="card-value">{summary.total_assets_usdt.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</div>
</div>
<div className="card">
<div className="card-title">总盈亏 (USDT)</div>
<div className={`card-value ${summary.total_pnl_usdt >= 0 ? 'profit' : 'loss'}`}>
{summary.total_pnl_usdt > 0 ? '+' : ''}{summary.total_pnl_usdt.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>
</div>
<div className="card">
<div className="card-title">活跃账户数</div>
<div className="card-value">{summary.active_accounts} / {summary.total_accounts}</div>
</div>
</div>
<div className="accounts-section">
<h3>账户列表</h3>
<div className="table-container">
<table className="data-table">
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>状态</th>
<th>总资产</th>
<th>总盈亏</th>
<th>持仓数</th>
</tr>
</thead>
<tbody>
{accounts.map(acc => (
<tr key={acc.id}>
<td>{acc.id}</td>
<td>{acc.name}</td>
<td>
<span className={`status-badge ${acc.status}`}>
{acc.status === 'active' ? '运行中' : '停止'}
</span>
</td>
<td>{Number(acc.total_balance).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</td>
<td className={Number(acc.total_pnl) >= 0 ? 'profit' : 'loss'}>
{Number(acc.total_pnl) > 0 ? '+' : ''}{Number(acc.total_pnl).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</td>
<td>{acc.open_positions}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)
}
export default AdminDashboard