a
This commit is contained in:
parent
18257b7d8a
commit
8ff8cd4ebc
117
frontend/src/components/AdminDashboard.css
Normal file
117
frontend/src/components/AdminDashboard.css
Normal 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;
|
||||
}
|
||||
98
frontend/src/components/AdminDashboard.jsx
Normal file
98
frontend/src/components/AdminDashboard.jsx
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user