This commit is contained in:
aaron 2026-04-22 11:03:24 +08:00
parent 491a1d29f1
commit b1e6215ae2
11 changed files with 1041 additions and 419 deletions

View File

@ -28,6 +28,162 @@ def _parse_signal_timestamp(value: Any) -> datetime | None:
return None return None
def _safe_float(value: Any, default: float = 0.0) -> float:
try:
if value is None or value == "":
return default
return float(value)
except (TypeError, ValueError):
return default
def _normalize_platform_position(platform: str, position: Dict[str, Any]) -> Dict[str, Any]:
side_raw = str(position.get("side") or "").lower()
side = "long" if side_raw in {"buy", "long"} else "short"
symbol = position.get("symbol") or position.get("coin") or "-"
if isinstance(symbol, str) and symbol.endswith("USDTUSDT"):
symbol = symbol.replace("USDTUSDT", "USDT")
entry_price = _safe_float(position.get("entry_price") or position.get("filled_price"))
mark_price = _safe_float(position.get("mark_price") or position.get("current_price"))
if mark_price <= 0:
mark_price = entry_price
size = abs(_safe_float(position.get("size") or position.get("quantity")))
leverage = _safe_float(position.get("leverage"))
margin = _safe_float(position.get("margin") or position.get("initialMargin") or position.get("initial_margin"))
unrealized_pnl = _safe_float(position.get("unrealized_pnl") or position.get("pnl_amount"))
pnl_percent = _safe_float(position.get("unrealized_pnl_pct") or position.get("pnl_percent") or position.get("percentage"))
return {
"platform": platform,
"symbol": symbol,
"side": side,
"size": size,
"entry_price": entry_price,
"mark_price": mark_price,
"leverage": leverage,
"margin": margin,
"unrealized_pnl": unrealized_pnl,
"pnl_percent": pnl_percent,
"take_profit": position.get("take_profit"),
"stop_loss": position.get("stop_loss"),
"liquidation_price": position.get("liquidation_price"),
"opened_at": position.get("opened_at") or position.get("created_at"),
}
def _normalize_platform_order(platform: str, order: Dict[str, Any]) -> Dict[str, Any]:
side_raw = str(order.get("side") or "").lower()
side = "long" if side_raw in {"buy", "long", "b"} else "short"
symbol = order.get("symbol") or order.get("coin") or "-"
if isinstance(symbol, str) and symbol.endswith("USDTUSDT"):
symbol = symbol.replace("USDTUSDT", "USDT")
price = _safe_float(order.get("price") or order.get("entry_price"))
size = abs(_safe_float(order.get("size") or order.get("quantity")))
order_type = str(order.get("order_type") or order.get("entry_type") or order.get("type") or "").lower()
status = str(order.get("status") or "").lower()
is_reduce_only = bool(order.get("is_reduce_only"))
category = "tp_sl" if is_reduce_only else "entry"
if platform == "paper" and status == "pending":
category = "entry"
return {
"platform": platform,
"symbol": symbol,
"side": side,
"category": category,
"price": price,
"size": size,
"leverage": _safe_float(order.get("leverage")),
"margin": _safe_float(order.get("margin")),
"order_type": order_type,
"status": status,
"stop_loss": order.get("stop_loss"),
"take_profit": order.get("take_profit"),
"signal_grade": order.get("signal_grade"),
"signal_type": order.get("signal_type"),
"confidence": _safe_float(order.get("confidence")),
"created_at": order.get("created_at") or order.get("timestamp"),
}
def _build_attention_items(
platform_halts: Dict[str, Any],
platforms: Dict[str, Any],
unified_positions: list[Dict[str, Any]],
unified_orders: list[Dict[str, Any]],
execution_events: list[Dict[str, Any]],
) -> list[Dict[str, Any]]:
items: list[Dict[str, Any]] = []
for platform, halt in (platform_halts or {}).items():
if halt and halt.get("halted"):
items.append({
"severity": "danger",
"title": f"{platform} 已停机",
"detail": halt.get("reason") or "平台已触发停机/熔断",
"timestamp": halt.get("halted_at"),
})
for platform_name, payload in (platforms or {}).items():
if payload.get("enabled") is False:
continue
risk = payload.get("risk") or {}
current_leverage = _safe_float(risk.get("current_leverage"))
max_leverage = _safe_float(risk.get("max_leverage"))
drawdown_pct = _safe_float(risk.get("drawdown_percent") or risk.get("drawdown"))
breaker_pct = _safe_float(risk.get("circuit_breaker_threshold"), 25.0)
if breaker_pct > 0 and drawdown_pct >= breaker_pct * 0.7:
items.append({
"severity": "warning",
"title": f"{platform_name} 回撤接近熔断",
"detail": f"当前回撤 {drawdown_pct:.1f}% / 阈值 {breaker_pct:.1f}%",
"timestamp": None,
})
if max_leverage > 0 and current_leverage >= max_leverage * 0.8:
items.append({
"severity": "warning",
"title": f"{platform_name} 杠杆占用偏高",
"detail": f"当前总杠杆 {current_leverage:.2f}x / 上限 {max_leverage:.2f}x",
"timestamp": None,
})
for pos in unified_positions:
if not pos.get("stop_loss") or not pos.get("take_profit"):
items.append({
"severity": "warning",
"title": f"{pos['platform']} {pos['symbol']} 风控不完整",
"detail": "持仓缺少止盈或止损,请确认执行层保护单状态",
"timestamp": pos.get("opened_at"),
})
pending_entry_count = sum(1 for order in unified_orders if order.get("category") == "entry")
if pending_entry_count > 0:
items.append({
"severity": "info",
"title": "存在待成交入场单",
"detail": f"当前共有 {pending_entry_count} 笔待成交入场单,建议关注价格接近度和资金占用。",
"timestamp": None,
})
for event in (execution_events or [])[:8]:
if event.get("status") in {"error", "warning"}:
items.append({
"severity": event.get("status"),
"title": f"{event.get('platform', '-')}: {event.get('event_type', '-')}",
"detail": event.get("reason") or "最近执行事件需要关注",
"timestamp": event.get("timestamp"),
})
return items[:12]
@router.get("/status", response_model=Dict[str, Any]) @router.get("/status", response_model=Dict[str, Any])
async def get_system_status(): async def get_system_status():
""" """
@ -221,19 +377,45 @@ async def get_console_snapshot():
if (_parse_signal_timestamp(signal.get("created_at")) or datetime.min) >= recent_cutoff if (_parse_signal_timestamp(signal.get("created_at")) or datetime.min) >= recent_cutoff
) )
return { paper_position_items = [
"status": "success", _normalize_platform_position("paper", pos)
"data": { for pos in paper_service.get_open_positions()[:12]
"generated_at": now.isoformat(), ]
"system": summary, paper_order_items = [
"crypto_agent": crypto_status, _normalize_platform_order("paper", order)
"execution_events": crypto_agent.get_recent_execution_events(limit=40), for order in paper_pending[:12]
"signals": { ]
"stats_7d": signal_stats,
"latest": latest_signals, bitget_position_items = [
"recent_30m_count": recent_signal_count, _normalize_platform_position("bitget", pos)
}, for pos in (bg_positions[:12] if bitget_service is not None else [])
"platforms": { ]
bitget_order_items = [
_normalize_platform_order("bitget", order)
for order in (bg_orders[:12] if bitget_service is not None else [])
]
hyperliquid_position_items = [
_normalize_platform_position("hyperliquid", pos)
for pos in (hl_positions[:12] if hyperliquid_service is not None else [])
]
hyperliquid_order_items = [
_normalize_platform_order("hyperliquid", order)
for order in (hl_orders[:12] if hyperliquid_service is not None else [])
]
unified_positions = sorted(
paper_position_items + bitget_position_items + hyperliquid_position_items,
key=lambda item: _parse_signal_timestamp(item.get("opened_at")) or datetime.min,
reverse=True,
)
unified_orders = sorted(
paper_order_items + bitget_order_items + hyperliquid_order_items,
key=lambda item: _parse_signal_timestamp(item.get("created_at")) or datetime.min,
reverse=True,
)
platforms_payload = {
"paper": { "paper": {
"enabled": True, "enabled": True,
"account": paper_account, "account": paper_account,
@ -253,9 +435,43 @@ async def get_console_snapshot():
"max_drawdown": paper_stats.get("max_drawdown", 0), "max_drawdown": paper_stats.get("max_drawdown", 0),
"by_grade": paper_stats.get("by_grade", {}), "by_grade": paper_stats.get("by_grade", {}),
}, },
"risk": {
"current_leverage": paper_account.get("current_total_leverage", 0),
"max_leverage": paper_account.get("max_total_leverage", 0),
"drawdown_percent": paper_account.get("max_drawdown", 0),
"circuit_breaker_threshold": 25,
},
}, },
"bitget": bitget_summary, "bitget": bitget_summary,
"hyperliquid": hyperliquid_summary, "hyperliquid": hyperliquid_summary,
}
execution_events = crypto_agent.get_recent_execution_events(limit=40)
attention_items = _build_attention_items(
crypto_status.get("platform_halts", {}),
platforms_payload,
unified_positions,
unified_orders,
execution_events,
)
return {
"status": "success",
"data": {
"generated_at": now.isoformat(),
"system": summary,
"crypto_agent": crypto_status,
"execution_events": execution_events,
"signals": {
"stats_7d": signal_stats,
"latest": latest_signals,
"recent_30m_count": recent_signal_count,
},
"platforms": platforms_payload,
"management": {
"positions": unified_positions[:18],
"orders": unified_orders[:24],
"attention_items": attention_items,
}, },
} }
} }

View File

@ -11,7 +11,7 @@
<!-- Fonts --> <!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<!-- Page-Specific Styles --> <!-- Page-Specific Styles -->
<style> <style>
@ -25,11 +25,13 @@
/* Sidebar */ /* Sidebar */
.admin-sidebar { .admin-sidebar {
width: 240px; width: 240px;
background: var(--bg-primary); background: var(--panel-strong);
border-right: 1px solid var(--border); border-right: 1px solid var(--border);
padding: 24px 0; padding: 24px 0;
position: fixed; position: fixed;
height: 100vh; height: 100vh;
backdrop-filter: blur(18px);
box-shadow: var(--shadow-soft);
} }
.admin-logo { .admin-logo {
@ -63,7 +65,7 @@
} }
.admin-nav-item.active { .admin-nav-item.active {
background: var(--primary-light); background: rgba(126, 200, 255, 0.12);
color: var(--primary); color: var(--primary);
border-left-color: var(--primary); border-left-color: var(--primary);
font-weight: 500; font-weight: 500;
@ -91,7 +93,7 @@
.logout-btn { .logout-btn {
padding: 10px 20px; padding: 10px 20px;
background: var(--bg-primary); background: rgba(126, 200, 255, 0.08);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: var(--text-secondary); color: var(--text-secondary);
@ -120,6 +122,8 @@
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: 24px; padding: 24px;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
} }
.stat-label { .stat-label {
@ -165,18 +169,19 @@
.search-btn { .search-btn {
padding: 12px 24px; padding: 12px 24px;
background: var(--primary); background: linear-gradient(135deg, #8fd7ff, #63e6be);
border: none; border: none;
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: white; color: #071018;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
box-shadow: 0 10px 24px rgba(49, 132, 189, 0.22);
} }
.search-btn:hover { .search-btn:hover {
background: var(--primary-dark); transform: translateY(-1px);
} }
/* Table */ /* Table */
@ -185,6 +190,8 @@
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
overflow: hidden; overflow: hidden;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
} }
table { table {
@ -249,7 +256,7 @@
.pagination button { .pagination button {
padding: 8px 16px; padding: 8px 16px;
background: var(--bg-primary); background: rgba(126, 200, 255, 0.08);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: var(--text-primary); color: var(--text-primary);
@ -259,7 +266,7 @@
} }
.pagination button:hover:not(:disabled) { .pagination button:hover:not(:disabled) {
background: var(--primary-light); background: rgba(126, 200, 255, 0.14);
border-color: var(--primary); border-color: var(--primary);
color: var(--primary); color: var(--primary);
} }
@ -281,20 +288,22 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: var(--bg-secondary); background: rgba(4, 9, 14, 0.82);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 1000; z-index: 1000;
backdrop-filter: blur(10px);
} }
.login-box { .login-box {
width: 100%; width: 100%;
max-width: 400px; max-width: 400px;
background: var(--bg-primary); background: var(--panel-strong);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: 48px; padding: 48px;
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
border: 1px solid var(--border);
} }
.login-box h2 { .login-box h2 {
@ -329,10 +338,10 @@
.login-box button { .login-box button {
width: 100%; width: 100%;
padding: 14px; padding: 14px;
background: var(--primary); background: linear-gradient(135deg, #8fd7ff, #63e6be);
border: none; border: none;
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: white; color: #071018;
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
@ -340,7 +349,7 @@
} }
.login-box button:hover { .login-box button:hover {
background: var(--primary-dark); transform: translateY(-1px);
} }
.error-message { .error-message {

View File

@ -723,6 +723,152 @@
font-family: "IBM Plex Mono", monospace; font-family: "IBM Plex Mono", monospace;
} }
.ops-grid {
display: grid;
gap: 18px;
grid-template-columns: 0.9fr 1.1fr;
margin-top: 18px;
}
.attention-list,
.unified-list {
display: grid;
gap: 10px;
}
.attention-item,
.unified-item {
padding: 14px 16px;
border-radius: 14px;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.05);
}
.attention-item.danger {
border-color: rgba(255, 111, 97, 0.2);
background: rgba(255, 111, 97, 0.08);
}
.attention-item.warning {
border-color: rgba(255, 184, 77, 0.2);
background: rgba(255, 184, 77, 0.08);
}
.attention-item.info {
border-color: rgba(126, 200, 255, 0.2);
background: rgba(126, 200, 255, 0.08);
}
.attention-title,
.unified-title {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: center;
margin-bottom: 6px;
}
.attention-title strong,
.unified-title strong {
font-size: 14px;
}
.attention-time,
.unified-time {
color: var(--muted);
font-size: 11px;
font-family: "IBM Plex Mono", monospace;
}
.attention-detail,
.unified-detail {
color: var(--muted);
font-size: 12px;
line-height: 1.6;
}
.table-toolbar {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.toolbar-chip {
display: inline-flex;
align-items: center;
padding: 6px 10px;
border-radius: 999px;
background: rgba(126, 200, 255, 0.08);
color: var(--cold);
border: 1px solid rgba(126, 200, 255, 0.16);
font-size: 11px;
font-family: "IBM Plex Mono", monospace;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.unified-section {
margin-top: 18px;
}
.unified-table {
overflow-x: auto;
border-radius: 16px;
border: 1px solid rgba(255,255,255,0.05);
background: rgba(255,255,255,0.02);
}
.unified-table table {
min-width: 980px;
}
.platform-pill {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 92px;
padding: 5px 10px;
border-radius: 999px;
font-size: 10px;
font-family: "IBM Plex Mono", monospace;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--cold);
background: rgba(126, 200, 255, 0.1);
border: 1px solid rgba(126, 200, 255, 0.2);
}
.side-pill {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 60px;
padding: 4px 10px;
border-radius: 999px;
font-size: 10px;
font-family: "IBM Plex Mono", monospace;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.side-pill.long {
color: var(--good);
background: rgba(48, 209, 88, 0.12);
border: 1px solid rgba(48, 209, 88, 0.2);
}
.side-pill.short {
color: var(--danger);
background: rgba(255, 111, 97, 0.12);
border: 1px solid rgba(255, 111, 97, 0.2);
}
.inline-mono {
font-family: "IBM Plex Mono", monospace;
font-size: 12px;
}
.loading, .loading,
.error-box, .error-box,
.empty-box { .empty-box {
@ -747,7 +893,8 @@
} }
.platform-grid, .platform-grid,
.signal-grid { .signal-grid,
.ops-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@ -838,6 +985,18 @@
</div> </div>
</section> </section>
<section class="panel">
<div class="panel-header">
<div>
<h2 class="panel-title">管理待处理事项</h2>
<div class="panel-sub">风险、停机、异常、待成交提醒</div>
</div>
</div>
<div class="attention-list" id="attentionList">
<div class="loading">正在汇总待处理事项...</div>
</div>
</section>
<section class="panel"> <section class="panel">
<div class="panel-header"> <div class="panel-header">
<div> <div>
@ -909,6 +1068,34 @@
</section> </section>
</div> </div>
</section> </section>
<section class="ops-grid">
<section class="panel unified-section">
<div class="panel-header">
<div>
<h2 class="panel-title">统一持仓视图</h2>
<div class="panel-sub">三端持仓合并,优先看风险与盈亏</div>
</div>
</div>
<div class="table-toolbar" id="positionsToolbar"></div>
<div class="unified-table" id="positionsTable">
<div class="loading">正在整理跨平台持仓...</div>
</div>
</section>
<section class="panel unified-section">
<div class="panel-header">
<div>
<h2 class="panel-title">统一挂单视图</h2>
<div class="panel-sub">入场单、保护单、资金占用一屏观察</div>
</div>
</div>
<div class="table-toolbar" id="ordersToolbar"></div>
<div class="unified-table" id="ordersTable">
<div class="loading">正在整理跨平台挂单...</div>
</div>
</section>
</section>
</div> </div>
<script> <script>
@ -1300,6 +1487,122 @@
`).join(''); `).join('');
} }
function renderAttentionItems(items) {
const container = document.getElementById('attentionList');
if (!items || items.length === 0) {
container.innerHTML = '<div class="empty-box">当前没有需要人工处理的事项</div>';
return;
}
container.innerHTML = items.map((item) => `
<div class="attention-item ${item.severity || 'info'}">
<div class="attention-title">
<strong>${item.title || '-'}</strong>
<span class="attention-time">${item.timestamp ? relativeTime(item.timestamp) : 'now'}</span>
</div>
<div class="attention-detail">${item.detail || '-'}</div>
</div>
`).join('');
}
function renderUnifiedPositions(positions) {
const toolbar = document.getElementById('positionsToolbar');
const container = document.getElementById('positionsTable');
const total = positions || [];
const platformCounts = ['paper', 'bitget', 'hyperliquid'].map((platform) => {
const count = total.filter((item) => item.platform === platform).length;
return `<span class="toolbar-chip">${platform}: ${count}</span>`;
}).join('');
toolbar.innerHTML = `${platformCounts}<span class="toolbar-chip">total: ${total.length}</span>`;
if (!total.length) {
container.innerHTML = '<div class="empty-box">当前没有跨平台持仓</div>';
return;
}
container.innerHTML = `
<table>
<thead>
<tr>
<th>平台</th>
<th>交易对</th>
<th>方向</th>
<th>入场 / 现价</th>
<th>仓位 / 杠杆</th>
<th>止盈 / 止损</th>
<th>未实现盈亏</th>
<th>盈亏比例</th>
<th>时间</th>
</tr>
</thead>
<tbody>
${total.map((item) => `
<tr>
<td><span class="platform-pill">${item.platform}</span></td>
<td><strong>${item.symbol || '-'}</strong></td>
<td><span class="side-pill ${item.side === 'long' ? 'long' : 'short'}">${item.side === 'long' ? 'long' : 'short'}</span></td>
<td class="inline-mono">${formatMoney(item.entry_price)} / ${formatMoney(item.mark_price)}</td>
<td class="inline-mono">${formatNumber(item.size, 4)} / ${formatNumber(item.leverage, 1)}x</td>
<td class="inline-mono">${item.take_profit ? formatMoney(item.take_profit) : '-'} / ${item.stop_loss ? formatMoney(item.stop_loss) : '-'}</td>
<td style="color:${(item.unrealized_pnl || 0) >= 0 ? 'var(--good)' : 'var(--danger)'}">${formatMoney(item.unrealized_pnl)}</td>
<td style="color:${(item.pnl_percent || 0) >= 0 ? 'var(--good)' : 'var(--danger)'}">${formatPercent(item.pnl_percent, 2)}</td>
<td class="inline-mono">${item.opened_at ? relativeTime(item.opened_at) : '-'}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
function renderUnifiedOrders(orders) {
const toolbar = document.getElementById('ordersToolbar');
const container = document.getElementById('ordersTable');
const total = orders || [];
const entryCount = total.filter((item) => item.category === 'entry').length;
const protectionCount = total.filter((item) => item.category === 'tp_sl').length;
toolbar.innerHTML = `
<span class="toolbar-chip">entry: ${entryCount}</span>
<span class="toolbar-chip">tp/sl: ${protectionCount}</span>
<span class="toolbar-chip">total: ${total.length}</span>
`;
if (!total.length) {
container.innerHTML = '<div class="empty-box">当前没有跨平台挂单</div>';
return;
}
container.innerHTML = `
<table>
<thead>
<tr>
<th>平台</th>
<th>交易对</th>
<th>方向</th>
<th>类别</th>
<th>价格</th>
<th>数量 / 杠杆</th>
<th>信号</th>
<th>时间</th>
</tr>
</thead>
<tbody>
${total.map((item) => `
<tr>
<td><span class="platform-pill">${item.platform}</span></td>
<td><strong>${item.symbol || '-'}</strong></td>
<td><span class="side-pill ${item.side === 'long' ? 'long' : 'short'}">${item.side === 'long' ? 'long' : 'short'}</span></td>
<td class="inline-mono">${item.category === 'tp_sl' ? 'TP/SL' : 'ENTRY'}</td>
<td class="inline-mono">${formatMoney(item.price)}</td>
<td class="inline-mono">${formatNumber(item.size, 4)} / ${item.leverage ? `${formatNumber(item.leverage, 1)}x` : '-'}</td>
<td class="inline-mono">${item.signal_grade || '-'} ${item.signal_type || ''} ${item.confidence ? `/ ${formatPercent(item.confidence, 1)}` : ''}</td>
<td class="inline-mono">${item.created_at ? relativeTime(item.created_at) : '-'}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
async function resumePlatform(platform, button) { async function resumePlatform(platform, button) {
const platformMap = { const platformMap = {
PaperTrading: 'PaperTrading', PaperTrading: 'PaperTrading',
@ -1351,6 +1654,9 @@
renderDecisionPreview(data.crypto_agent?.last_execution_preview || {}); renderDecisionPreview(data.crypto_agent?.last_execution_preview || {});
renderHalts(data.crypto_agent?.platform_halts || {}); renderHalts(data.crypto_agent?.platform_halts || {});
renderExecutionEvents(data.execution_events || []); renderExecutionEvents(data.execution_events || []);
renderAttentionItems(data.management?.attention_items || []);
renderUnifiedPositions(data.management?.positions || []);
renderUnifiedOrders(data.management?.orders || []);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
setFeedback(`总控台加载失败: ${error.message}`, true); setFeedback(`总控台加载失败: ${error.message}`, true);

View File

@ -1,47 +1,58 @@
/* ======================================== /* ========================================
TRADUS - 清新简洁设计系统 XClaw Console Theme
========================================
Design: Modern, Clean, Minimal
Color: Professional Blue & White
======================================== */ ======================================== */
/* CSS Variables - Clean Color Palette */ @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
:root { :root {
/* Primary Colors */ /* Console core palette */
--primary: #0066FF; --bg: #08111a;
--primary-light: #E8F0FF; --bg-elevated: #0d1823;
--primary-dark: #0052CC; --panel: rgba(11, 21, 32, 0.86);
--panel-strong: rgba(8, 18, 28, 0.96);
--panel-soft: rgba(16, 28, 41, 0.74);
--line: rgba(128, 169, 202, 0.18);
--line-strong: rgba(128, 169, 202, 0.32);
--text: #edf6ff;
--muted: #8ea6bc;
--cold: #7ec8ff;
--accent: #9cff57;
--warn: #ffb84d;
--danger: #ff6f61;
--good: #30d158;
--shadow: 0 24px 80px rgba(0, 0, 0, 0.38);
--shadow-soft: 0 16px 48px rgba(0, 0, 0, 0.24);
--glow-cold: 0 0 0 1px rgba(126, 200, 255, 0.2), 0 14px 38px rgba(18, 51, 78, 0.3);
/* Background Colors */ /* Compatibility mapping */
--bg-primary: #FFFFFF; --primary: var(--cold);
--bg-secondary: #F8FAFB; --primary-light: rgba(126, 200, 255, 0.14);
--bg-tertiary: #F1F5F9; --primary-dark: #4eaef2;
/* Text Colors */ --bg-primary: rgba(11, 21, 32, 0.86);
--text-primary: #1E293B; --bg-secondary: rgba(16, 28, 41, 0.72);
--text-secondary: #64748B; --bg-tertiary: rgba(26, 41, 58, 0.86);
--text-tertiary: #94A3B8;
/* Semantic Colors */ --text-primary: var(--text);
--success: #10B981; --text-secondary: var(--muted);
--success-light: #D1FAE5; --text-tertiary: #6f8598;
--error: #EF4444;
--error-light: #FEE2E2;
--warning: #F59E0B;
--warning-light: #FEF3C7;
--info: #3B82F6;
--info-light: #DBEAFE;
/* Borders */ --success: var(--good);
--border: #E2E8F0; --success-light: rgba(48, 209, 88, 0.14);
--border-light: #F1F5F9; --error: var(--danger);
--error-light: rgba(255, 111, 97, 0.14);
--warning: var(--warn);
--warning-light: rgba(255, 184, 77, 0.14);
--info: var(--cold);
--info-light: rgba(126, 200, 255, 0.14);
/* Shadows */ --border: var(--line);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08); --border-light: rgba(128, 169, 202, 0.1);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12); --shadow-sm: 0 10px 24px rgba(0, 0, 0, 0.18);
--shadow-md: var(--shadow-soft);
--shadow-lg: var(--shadow);
/* Spacing */
--space-xs: 4px; --space-xs: 4px;
--space-sm: 8px; --space-sm: 8px;
--space-md: 16px; --space-md: 16px;
@ -49,55 +60,78 @@
--space-xl: 32px; --space-xl: 32px;
--space-2xl: 48px; --space-2xl: 48px;
/* Border Radius */ --radius-sm: 10px;
--radius-sm: 4px; --radius-md: 16px;
--radius-md: 8px; --radius-lg: 22px;
--radius-lg: 12px; --radius-xl: 28px;
--radius-xl: 16px;
/* Typography */
--font-xs: 11px; --font-xs: 11px;
--font-sm: 13px; --font-sm: 13px;
--font-base: 14px; --font-base: 14px;
--font-md: 16px; --font-md: 16px;
--font-lg: 18px; --font-lg: 18px;
--font-xl: 24px; --font-xl: 24px;
--font-2xl: 28px; --font-2xl: 30px;
--font-3xl: 32px; --font-3xl: 38px;
} }
/* ========================================
RESET & BASE
======================================== */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
html, body { html,
height: 100%; body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; min-height: 100%;
background: var(--bg-secondary); }
html {
background:
radial-gradient(circle at top left, rgba(64, 127, 255, 0.16), transparent 28%),
radial-gradient(circle at top right, rgba(156, 255, 87, 0.12), transparent 22%),
linear-gradient(180deg, #071018 0%, #08111a 48%, #050b11 100%);
}
body {
font-family: "IBM Plex Sans", "PingFang SC", "Microsoft YaHei", sans-serif;
background: transparent;
color: var(--text-primary); color: var(--text-primary);
font-size: var(--font-base); font-size: var(--font-base);
line-height: 1.6; line-height: 1.6;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
position: relative;
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.018) 1px, transparent 1px);
background-size: 28px 28px;
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.38), rgba(0, 0, 0, 0.92));
} }
#app { #app {
min-height: 100vh; min-height: 100vh;
position: relative;
z-index: 1;
} }
/* Typography */ h1,
h1, h2, h3, h4, h5, h6 { h2,
h3,
h4,
h5,
h6 {
font-weight: 600; font-weight: 600;
line-height: 1.3; line-height: 1.2;
color: var(--text-primary); color: var(--text-primary);
letter-spacing: -0.02em;
} }
h1 { font-size: var(--font-3xl); } h1 { font-size: var(--font-3xl); }
@ -107,16 +141,25 @@ h3 { font-size: var(--font-xl); }
a { a {
color: var(--primary); color: var(--primary);
text-decoration: none; text-decoration: none;
transition: color 0.2s; transition: color 0.2s ease, opacity 0.2s ease;
} }
a:hover { a:hover {
color: var(--primary-dark); color: var(--primary-dark);
} }
/* ======================================== button,
UTILITY CLASSES input,
======================================== */ select,
textarea {
font: inherit;
}
code,
pre,
.mono {
font-family: "IBM Plex Mono", monospace;
}
.text-center { text-align: center; } .text-center { text-align: center; }
.text-right { text-align: right; } .text-right { text-align: right; }
@ -132,91 +175,122 @@ a:hover {
.font-bold { font-weight: 600; } .font-bold { font-weight: 600; }
.font-semibold { font-weight: 500; } .font-semibold { font-weight: 500; }
/* ========================================
COMPONENTS
======================================== */
/* Buttons */
.btn { .btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center;
gap: 8px; gap: 8px;
padding: 10px 20px; min-height: 42px;
border-radius: var(--radius-md); padding: 10px 18px;
font-size: 14px; border-radius: 14px;
font-weight: 600; border: 1px solid transparent;
background: rgba(126, 200, 255, 0.1);
color: var(--text-primary);
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
border: none; font-size: 13px;
font-family: inherit; font-weight: 600;
letter-spacing: 0.02em;
backdrop-filter: blur(14px);
}
.btn:hover:not(:disabled) {
transform: translateY(-1px);
border-color: rgba(126, 200, 255, 0.24);
box-shadow: var(--shadow-sm);
}
.btn:disabled {
opacity: 0.48;
cursor: not-allowed;
} }
.btn-primary { .btn-primary {
background: var(--primary); background: linear-gradient(135deg, #8fd7ff, #63e6be);
color: white; color: #071018;
border-color: rgba(126, 200, 255, 0.34);
box-shadow: 0 10px 30px rgba(49, 132, 189, 0.22);
} }
.btn-primary:hover { .btn-primary:hover:not(:disabled) {
background: var(--primary-dark); background: linear-gradient(135deg, #9ce0ff, #7aeecb);
} }
.btn-secondary { .btn-secondary {
background: var(--bg-primary); background: rgba(126, 200, 255, 0.08);
border-color: var(--border);
color: var(--primary); color: var(--primary);
border: 1px solid var(--border);
} }
.btn-secondary:hover { .btn-secondary:hover:not(:disabled) {
background: var(--primary-light); background: rgba(126, 200, 255, 0.14);
border-color: var(--primary);
} }
.btn-danger { .btn-danger {
background: var(--error-light); background: rgba(255, 111, 97, 0.12);
color: var(--error); color: var(--error);
border: 1px solid var(--error); border-color: rgba(255, 111, 97, 0.22);
} }
.btn-danger:hover { .btn-danger:hover:not(:disabled) {
background: var(--error); background: rgba(255, 111, 97, 0.18);
color: white;
} }
.btn-success { .btn-success {
background: var(--success-light); background: rgba(48, 209, 88, 0.12);
color: var(--success); color: var(--success);
border: 1px solid var(--success); border-color: rgba(48, 209, 88, 0.2);
} }
.btn-success:hover { .btn-success:hover:not(:disabled) {
background: var(--success); background: rgba(48, 209, 88, 0.18);
color: white;
} }
.btn-small { .btn-small {
min-height: 34px;
padding: 6px 12px; padding: 6px 12px;
font-size: 12px; font-size: 12px;
border-radius: 12px;
} }
/* Cards */ .card,
.card { .table-container {
position: relative;
overflow: hidden;
background: var(--bg-primary); background: var(--bg-primary);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
}
.card::after,
.table-container::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.04), transparent 22%, transparent 72%, rgba(158, 214, 255, 0.04));
}
.card {
padding: 20px; padding: 20px;
transition: all 0.2s; transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
} }
.card:hover { .card:hover {
border-color: var(--primary); transform: translateY(-1px);
box-shadow: var(--shadow-md); border-color: var(--line-strong);
box-shadow: var(--shadow);
} }
/* Badges */
.badge { .badge {
display: inline-block; display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 12px; padding: 4px 12px;
border-radius: var(--radius-sm); border-radius: 999px;
border: 1px solid transparent;
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
} }
@ -224,34 +298,31 @@ a:hover {
.badge-success { .badge-success {
background: var(--success-light); background: var(--success-light);
color: var(--success); color: var(--success);
border-color: rgba(48, 209, 88, 0.2);
} }
.badge-error { .badge-error {
background: var(--error-light); background: var(--error-light);
color: var(--error); color: var(--error);
border-color: rgba(255, 111, 97, 0.22);
} }
.badge-warning { .badge-warning {
background: var(--warning-light); background: var(--warning-light);
color: var(--warning); color: var(--warning);
border-color: rgba(255, 184, 77, 0.22);
} }
.badge-info { .badge-info {
background: var(--info-light); background: var(--info-light);
color: var(--info); color: var(--info);
border-color: rgba(126, 200, 255, 0.22);
} }
.badge-neutral { .badge-neutral {
background: var(--bg-tertiary); background: rgba(128, 169, 202, 0.12);
color: var(--text-secondary); color: var(--text-secondary);
} border-color: var(--border);
/* Tables */
.table-container {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: var(--radius-md);
overflow: hidden;
} }
table { table {
@ -260,15 +331,15 @@ table {
} }
th { th {
background: var(--bg-tertiary); background: rgba(20, 34, 48, 0.92);
padding: 14px 16px; padding: 14px 16px;
text-align: left; text-align: left;
font-size: 12px; font-size: 11px;
font-weight: 600; font-weight: 600;
color: var(--text-secondary); color: var(--text-secondary);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.08em;
} }
td { td {
@ -283,39 +354,49 @@ tr:last-child td {
} }
tr:hover { tr:hover {
background: var(--bg-secondary); background: rgba(126, 200, 255, 0.05);
} }
/* Input Fields */ .input,
.input { input[type="text"],
input[type="number"],
input[type="password"],
input[type="tel"],
input[type="email"],
input[type="search"],
select,
textarea {
width: 100%; width: 100%;
padding: 12px 16px; padding: 12px 16px;
background: var(--bg-secondary); background: rgba(10, 20, 31, 0.78);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: 14px;
font-size: 14px;
color: var(--text-primary); color: var(--text-primary);
font-family: inherit; transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
transition: all 0.2s; backdrop-filter: blur(10px);
} }
.input:focus { .input::placeholder,
outline: none; input::placeholder,
background: var(--bg-primary); textarea::placeholder {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
}
.input::placeholder {
color: var(--text-tertiary); color: var(--text-tertiary);
} }
/* Spinner */ .input:focus,
input:focus,
select:focus,
textarea:focus {
outline: none;
border-color: rgba(126, 200, 255, 0.34);
box-shadow: 0 0 0 3px rgba(126, 200, 255, 0.12);
background: rgba(12, 24, 36, 0.9);
}
.spinner { .spinner {
display: inline-block; display: inline-block;
width: 32px; width: 32px;
height: 32px; height: 32px;
border: 3px solid var(--bg-tertiary); border: 3px solid rgba(128, 169, 202, 0.14);
border-top-color: var(--primary); border-top-color: var(--primary);
border-radius: 50%; border-radius: 50%;
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
@ -325,105 +406,148 @@ tr:hover {
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }
/* ========================================
LAYOUT
======================================== */
.container { .container {
max-width: 1400px; width: min(1440px, calc(100vw - 32px));
margin: 0 auto; margin: 0 auto;
padding: 32px 24px; padding: 24px 0 40px;
position: relative;
z-index: 1;
} }
.page-header { .page-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 32px; gap: 16px;
margin-bottom: 24px;
padding: 22px 24px;
background: var(--panel-strong);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
backdrop-filter: blur(18px);
} }
.page-title { .page-title {
font-size: var(--font-2xl); display: flex;
align-items: center;
gap: 12px;
font-size: clamp(28px, 4vw, 40px);
font-weight: 700; font-weight: 700;
letter-spacing: -0.04em;
color: var(--text-primary); color: var(--text-primary);
} }
.page-subtitle { .page-subtitle {
font-size: var(--font-base); margin-top: 6px;
color: var(--text-secondary); color: var(--text-secondary);
margin-top: 4px; font-size: 14px;
line-height: 1.6;
} }
/* Grid Layout */
.grid { .grid {
display: grid; display: grid;
gap: 16px; gap: 16px;
} }
.grid-cols-2 { .grid-cols-2 {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.grid-cols-3 { .grid-cols-3 {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, minmax(0, 1fr));
} }
.grid-cols-4 { .grid-cols-4 {
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, minmax(0, 1fr));
} }
.grid-auto-fit { .grid-auto-fit {
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
} }
/* Tabs */
.tabs { .tabs {
display: flex; display: flex;
border-bottom: 1px solid var(--border); gap: 8px;
margin-bottom: 24px; margin-bottom: 24px;
padding: 8px;
background: rgba(10, 20, 31, 0.72);
border: 1px solid var(--border);
border-radius: 18px;
overflow-x: auto;
} }
.tab { .tab {
padding: 14px 24px; padding: 12px 18px;
background: transparent; background: transparent;
border: none; border: 1px solid transparent;
border-bottom: 2px solid transparent; border-radius: 12px;
color: var(--text-secondary); color: var(--text-secondary);
font-size: 14px; font-size: 13px;
font-weight: 500; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.2s; white-space: nowrap;
font-family: inherit; transition: all 0.2s ease;
} }
.tab:hover { .tab:hover {
color: var(--text-primary); color: var(--text-primary);
background: rgba(126, 200, 255, 0.08);
} }
.tab.active { .tab.active {
color: var(--primary); color: #071018;
border-bottom-color: var(--primary); background: linear-gradient(135deg, #8fd7ff, #63e6be);
border-color: rgba(126, 200, 255, 0.26);
box-shadow: 0 8px 20px rgba(49, 132, 189, 0.24);
} }
/* ======================================== ::-webkit-scrollbar {
RESPONSIVE width: 8px;
======================================== */ height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(8, 18, 28, 0.82);
}
::-webkit-scrollbar-thumb {
background: rgba(128, 169, 202, 0.26);
border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(128, 169, 202, 0.42);
}
::selection {
background: rgba(126, 200, 255, 0.22);
color: var(--text-primary);
}
:focus-visible {
outline: 2px solid rgba(126, 200, 255, 0.52);
outline-offset: 2px;
}
@media (max-width: 768px) { @media (max-width: 768px) {
:root { :root {
--font-xl: 20px; --font-xl: 20px;
--font-2xl: 24px; --font-2xl: 24px;
--font-3xl: 28px; --font-3xl: 30px;
--radius-md: 14px;
--radius-lg: 18px;
} }
.container { .container {
padding: 20px 16px; width: min(100vw - 20px, 100%);
padding: 16px 0 28px;
} }
.page-header { .page-header {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 16px; padding: 18px;
} }
.grid-cols-2, .grid-cols-2,
@ -432,63 +556,25 @@ tr:hover {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.tabs {
overflow-x: auto;
}
.tab {
padding: 10px 16px;
font-size: 13px;
white-space: nowrap;
}
.table-container { .table-container {
overflow-x: auto; overflow-x: auto;
} }
table { table {
min-width: 800px; min-width: 720px;
} }
} }
@media (max-width: 480px) { @media (max-width: 480px) {
:root { .container {
--font-xl: 18px; width: min(100vw - 16px, 100%);
--font-2xl: 20px;
--font-3xl: 24px;
}
} }
/* ======================================== .page-header {
SCROLLBAR border-radius: 16px;
======================================== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
} }
::-webkit-scrollbar-track { .page-title {
background: var(--bg-secondary); font-size: 26px;
} }
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-tertiary);
}
/* Selection */
::selection {
background: var(--primary-light);
color: var(--primary);
}
/* Focus */
:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
} }

View File

@ -7,7 +7,7 @@
<link rel="stylesheet" href="/static/css/style.css"> <link rel="stylesheet" href="/static/css/style.css">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
<style> <style>
html, body { html, body {
@ -21,19 +21,20 @@
.hyperliquid-page { .hyperliquid-page {
min-height: 100vh; min-height: 100vh;
background: var(--bg-secondary); background: transparent;
} }
.paper-badge { .paper-badge {
display: inline-block; display: inline-block;
background: var(--primary); background: linear-gradient(135deg, #8fd7ff, #63e6be);
color: white; color: #071018;
padding: 4px 12px; padding: 5px 12px;
border-radius: var(--radius-sm); border-radius: 999px;
font-size: 11px; font-size: 11px;
font-weight: 700; font-weight: 700;
letter-spacing: 1px; letter-spacing: 1px;
margin-left: 12px; margin-left: 12px;
box-shadow: 0 10px 24px rgba(49, 132, 189, 0.22);
} }
.header-actions { .header-actions {
@ -86,6 +87,8 @@
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: var(--space-lg); padding: var(--space-lg);
transition: all 0.2s; transition: all 0.2s;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
} }
.stat-card:hover { .stat-card:hover {
@ -107,7 +110,7 @@
font-weight: 700; font-weight: 700;
color: var(--text-primary); color: var(--text-primary);
margin-bottom: var(--space-xs); margin-bottom: var(--space-xs);
font-family: 'Inter', monospace; font-family: "IBM Plex Mono", monospace;
} }
.stat-value.positive { .stat-value.positive {
@ -139,6 +142,8 @@
border-radius: var(--radius-md); border-radius: var(--radius-md);
overflow: hidden; overflow: hidden;
transition: all 0.2s; transition: all 0.2s;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
} }
.panel:hover { .panel:hover {
@ -152,7 +157,7 @@
align-items: center; align-items: center;
padding: 20px 24px; padding: 20px 24px;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
background: var(--bg-secondary); background: rgba(10, 20, 31, 0.76);
} }
.panel-title { .panel-title {
@ -184,7 +189,7 @@
} }
.list-item:hover { .list-item:hover {
background: var(--bg-secondary); background: rgba(126, 200, 255, 0.05);
} }
.item-left { .item-left {
@ -325,7 +330,7 @@
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
color: var(--text-secondary); color: var(--text-secondary);
background: var(--bg-secondary); background: rgba(10, 20, 31, 0.76);
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
} }

View File

@ -11,7 +11,7 @@
<!-- Fonts --> <!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<!-- Marked.js for Markdown rendering --> <!-- Marked.js for Markdown rendering -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
@ -26,12 +26,14 @@
/* Header */ /* Header */
.header { .header {
background: var(--bg-primary); background: var(--panel-strong);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
padding: 16px 24px; padding: 16px 24px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
backdrop-filter: blur(18px);
box-shadow: var(--shadow-soft);
} }
.logo { .logo {
@ -58,8 +60,9 @@
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 8px 16px; padding: 8px 16px;
background: var(--bg-secondary); background: rgba(126, 200, 255, 0.08);
border-radius: 8px; border-radius: 14px;
border: 1px solid var(--border);
font-size: 13px; font-size: 13px;
color: var(--text-secondary); color: var(--text-secondary);
} }
@ -70,12 +73,13 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: var(--bg-secondary); background: rgba(126, 200, 255, 0.08);
border: none; border: none;
border-radius: 8px; border-radius: 12px;
color: var(--text-secondary); color: var(--text-secondary);
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
border: 1px solid var(--border);
} }
.logout-btn:hover { .logout-btn:hover {
@ -113,6 +117,7 @@
font-weight: 700; font-weight: 700;
color: var(--text-primary); color: var(--text-primary);
margin-bottom: 16px; margin-bottom: 16px;
letter-spacing: -0.04em;
} }
.welcome-subtitle { .welcome-subtitle {
@ -133,15 +138,16 @@
padding: 14px 20px; padding: 14px 20px;
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 14px;
font-size: 14px; font-size: 14px;
color: var(--text-primary); color: var(--text-primary);
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
box-shadow: var(--shadow-soft);
} }
.example-btn:hover { .example-btn:hover {
background: var(--primary-light); background: rgba(126, 200, 255, 0.14);
border-color: var(--primary); border-color: var(--primary);
color: var(--primary); color: var(--primary);
} }
@ -163,13 +169,16 @@
.message-content { .message-content {
padding: 16px 20px; padding: 16px 20px;
border-radius: 12px; border-radius: 18px;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(14px);
} }
.message.user .message-content { .message.user .message-content {
background: var(--primary); background: linear-gradient(135deg, rgba(126, 200, 255, 0.26), rgba(99, 230, 190, 0.18));
color: white; color: var(--text-primary);
margin-left: 48px; margin-left: 48px;
border: 1px solid rgba(126, 200, 255, 0.22);
} }
.message.assistant .message-content { .message.assistant .message-content {
@ -187,9 +196,9 @@
.action-btn { .action-btn {
padding: 6px 12px; padding: 6px 12px;
background: var(--bg-primary); background: rgba(126, 200, 255, 0.08);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 6px; border-radius: 12px;
font-size: 13px; font-size: 13px;
color: var(--text-secondary); color: var(--text-secondary);
cursor: pointer; cursor: pointer;
@ -200,7 +209,7 @@
} }
.action-btn:hover { .action-btn:hover {
background: var(--bg-tertiary); background: rgba(126, 200, 255, 0.14);
color: var(--primary); color: var(--primary);
border-color: var(--primary); border-color: var(--primary);
} }
@ -233,7 +242,8 @@
.input-container { .input-container {
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
padding: 20px 24px; padding: 20px 24px;
background: var(--bg-primary); background: var(--panel-strong);
backdrop-filter: blur(18px);
} }
.input-wrapper { .input-wrapper {
@ -247,9 +257,9 @@
textarea { textarea {
flex: 1; flex: 1;
padding: 12px 16px; padding: 12px 16px;
background: var(--bg-secondary); background: rgba(10, 20, 31, 0.78);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 16px;
font-size: 15px; font-size: 15px;
font-family: inherit; font-family: inherit;
color: var(--text-primary); color: var(--text-primary);
@ -259,7 +269,7 @@
textarea:focus { textarea:focus {
outline: none; outline: none;
background: var(--bg-primary); background: rgba(12, 24, 36, 0.92);
border-color: var(--primary); border-color: var(--primary);
} }
@ -270,20 +280,21 @@
.send-btn { .send-btn {
width: 40px; width: 40px;
height: 40px; height: 40px;
background: var(--primary); background: linear-gradient(135deg, #8fd7ff, #63e6be);
border: none; border: none;
border-radius: 8px; border-radius: 14px;
color: white; color: #071018;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.2s; transition: all 0.2s;
flex-shrink: 0; flex-shrink: 0;
box-shadow: 0 10px 24px rgba(49, 132, 189, 0.24);
} }
.send-btn:hover:not(:disabled) { .send-btn:hover:not(:disabled) {
background: var(--primary-dark); transform: translateY(-1px);
} }
.send-btn:disabled { .send-btn:disabled {
@ -315,19 +326,22 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(0, 0, 0, 0.8); background: rgba(4, 9, 14, 0.78);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 1000; z-index: 1000;
backdrop-filter: blur(10px);
} }
.image-modal-content, .contact-modal-content { .image-modal-content, .contact-modal-content {
background: var(--bg-primary); background: var(--panel-strong);
border-radius: 12px; border-radius: 20px;
padding: 32px; padding: 32px;
max-width: 90%; max-width: 90%;
position: relative; position: relative;
border: 1px solid var(--border);
box-shadow: var(--shadow);
} }
.modal-close-btn { .modal-close-btn {
@ -336,15 +350,16 @@
right: 16px; right: 16px;
width: 32px; width: 32px;
height: 32px; height: 32px;
background: var(--bg-secondary); background: rgba(126, 200, 255, 0.08);
border: none; border: none;
border-radius: 8px; border-radius: 12px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: var(--text-secondary); color: var(--text-secondary);
transition: all 0.2s; transition: all 0.2s;
border: 1px solid var(--border);
} }
.modal-close-btn:hover { .modal-close-btn:hover {
@ -372,7 +387,8 @@
gap: 12px; gap: 12px;
padding: 16px; padding: 16px;
background: var(--bg-secondary); background: var(--bg-secondary);
border-radius: 8px; border-radius: 16px;
border: 1px solid var(--border);
width: 100%; width: 100%;
max-width: 400px; max-width: 400px;
} }
@ -383,10 +399,10 @@
.copy-btn { .copy-btn {
padding: 12px 24px; padding: 12px 24px;
background: var(--primary); background: linear-gradient(135deg, #8fd7ff, #63e6be);
border: none; border: none;
border-radius: 8px; border-radius: 14px;
color: white; color: #071018;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
@ -397,11 +413,11 @@
} }
.copy-btn:hover { .copy-btn:hover {
background: var(--primary-dark); transform: translateY(-1px);
} }
.modal-image { .modal-image {
border-radius: 8px; border-radius: 16px;
max-width: 100%; max-width: 100%;
display: block; display: block;
} }
@ -425,11 +441,11 @@
} }
.markdown code { .markdown code {
background: var(--bg-tertiary); background: rgba(10, 20, 31, 0.82);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 4px; border-radius: 10px;
padding: 2px 8px; padding: 2px 8px;
font-family: 'Courier New', monospace; font-family: "IBM Plex Mono", monospace;
font-size: 13px; font-size: 13px;
} }
@ -437,14 +453,14 @@
margin-top: 16px; margin-top: 16px;
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 16px;
padding: 16px; padding: 16px;
} }
.chart { .chart {
width: 100%; width: 100%;
height: 400px; height: 400px;
border-radius: 4px; border-radius: 14px;
} }
/* Responsive */ /* Responsive */

View File

@ -11,7 +11,7 @@
<!-- Fonts --> <!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<!-- Page-Specific Styles --> <!-- Page-Specific Styles -->
<style> <style>
@ -26,11 +26,12 @@
.login-container { .login-container {
width: 100%; width: 100%;
max-width: 420px; max-width: 420px;
background: var(--bg-primary); background: var(--panel-strong);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: 48px 40px; padding: 48px 40px;
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
backdrop-filter: blur(18px);
} }
.login-header { .login-header {
@ -106,7 +107,7 @@
.send-code-btn { .send-code-btn {
padding: 12px 20px; padding: 12px 20px;
background: var(--bg-primary); background: rgba(126, 200, 255, 0.08);
border: 1px solid var(--primary); border: 1px solid var(--primary);
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: var(--primary); color: var(--primary);
@ -118,7 +119,7 @@
} }
.send-code-btn:hover:not(:disabled) { .send-code-btn:hover:not(:disabled) {
background: var(--primary-light); background: rgba(126, 200, 255, 0.14);
} }
.send-code-btn:disabled { .send-code-btn:disabled {
@ -129,18 +130,19 @@
.login-btn { .login-btn {
width: 100%; width: 100%;
padding: 14px; padding: 14px;
background: var(--primary); background: linear-gradient(135deg, #8fd7ff, #63e6be);
border: none; border: none;
border-radius: var(--radius-md); border-radius: var(--radius-md);
color: white; color: #071018;
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
box-shadow: 0 10px 24px rgba(49, 132, 189, 0.22);
} }
.login-btn:hover:not(:disabled) { .login-btn:hover:not(:disabled) {
background: var(--primary-dark); transform: translateY(-1px);
} }
.login-btn:disabled { .login-btn:disabled {

View File

@ -11,7 +11,7 @@
<!-- Fonts --> <!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<!-- Page-Specific Styles --> <!-- Page-Specific Styles -->
<style> <style>
@ -28,6 +28,12 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 32px; margin-bottom: 32px;
padding: 22px 24px;
background: var(--panel-strong);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
backdrop-filter: blur(18px);
} }
.trading-title { .trading-title {
@ -41,13 +47,14 @@
.live-badge { .live-badge {
display: inline-block; display: inline-block;
background: var(--error); background: rgba(255, 111, 97, 0.16);
color: white; color: var(--error);
padding: 4px 12px; padding: 5px 12px;
border-radius: 4px; border-radius: 999px;
font-size: 11px; font-size: 11px;
font-weight: 700; font-weight: 700;
letter-spacing: 1px; letter-spacing: 1px;
border: 1px solid rgba(255, 111, 97, 0.22);
} }
.header-actions { .header-actions {
@ -101,10 +108,10 @@
width: 18px; width: 18px;
left: 4px; left: 4px;
bottom: 3px; bottom: 3px;
background-color: white; background-color: var(--text-primary);
transition: 0.3s; transition: 0.3s;
border-radius: 50%; border-radius: 50%;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: 0 6px 14px rgba(0, 0, 0, 0.24);
} }
input:checked + .slider { input:checked + .slider {
@ -137,18 +144,19 @@
.refresh-btn { .refresh-btn {
padding: 10px 20px; padding: 10px 20px;
background: var(--bg-primary); background: rgba(126, 200, 255, 0.08);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: 14px;
color: var(--primary); color: var(--primary);
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
backdrop-filter: blur(14px);
} }
.refresh-btn:hover { .refresh-btn:hover {
background: var(--primary-light); background: rgba(126, 200, 255, 0.14);
border-color: var(--primary); border-color: var(--primary);
} }
@ -162,6 +170,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; gap: 16px;
box-shadow: var(--shadow-soft);
} }
.warning-banner.info { .warning-banner.info {
@ -198,6 +207,8 @@
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: 20px; padding: 20px;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
} }
.account-label { .account-label {
@ -262,6 +273,8 @@
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
overflow: hidden; overflow: hidden;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
} }
table { table {

View File

@ -11,7 +11,7 @@
<!-- Fonts --> <!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<!-- Page-Specific Styles --> <!-- Page-Specific Styles -->
<style> <style>
@ -28,6 +28,12 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 32px; margin-bottom: 32px;
padding: 22px 24px;
background: var(--panel-strong);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
backdrop-filter: blur(18px);
} }
.signals-title { .signals-title {
@ -54,18 +60,19 @@
.refresh-btn { .refresh-btn {
padding: 10px 20px; padding: 10px 20px;
background: var(--bg-primary); background: rgba(126, 200, 255, 0.08);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: 14px;
color: var(--primary); color: var(--primary);
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
backdrop-filter: blur(14px);
} }
.refresh-btn:hover { .refresh-btn:hover {
background: var(--primary-light); background: rgba(126, 200, 255, 0.14);
border-color: var(--primary); border-color: var(--primary);
} }
@ -82,6 +89,7 @@
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: 20px; padding: 20px;
box-shadow: var(--shadow-soft);
} }
.stat-label { .stat-label {
@ -125,6 +133,7 @@
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: 16px; padding: 16px;
box-shadow: var(--shadow-soft);
} }
.grade-stat-header { .grade-stat-header {
@ -186,6 +195,8 @@
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: 20px; padding: 20px;
transition: all 0.2s; transition: all 0.2s;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
} }
.signal-card:hover { .signal-card:hover {
@ -215,9 +226,10 @@
.signal-type-badge { .signal-type-badge {
font-size: 11px; font-size: 11px;
padding: 3px 8px; padding: 3px 8px;
background: var(--bg-tertiary); background: rgba(128, 169, 202, 0.12);
color: var(--text-secondary); color: var(--text-secondary);
border-radius: 4px; border-radius: 999px;
border: 1px solid var(--border);
} }
.signal-action-group { .signal-action-group {
@ -262,13 +274,13 @@
} }
.signal-grade.B { .signal-grade.B {
background: #E5E7EB; background: rgba(126, 200, 255, 0.12);
color: #6B7280; color: var(--primary);
} }
.signal-grade.C { .signal-grade.C {
background: #FED7AA; background: rgba(255, 184, 77, 0.16);
color: #EA580C; color: var(--warning);
} }
.signal-grade.D { .signal-grade.D {
@ -304,7 +316,7 @@
.confidence-fill { .confidence-fill {
height: 100%; height: 100%;
background: var(--primary); background: linear-gradient(90deg, #7ec8ff, #63e6be);
transition: width 0.3s; transition: width 0.3s;
} }
@ -332,7 +344,7 @@
.price-value { .price-value {
font-size: 14px; font-size: 14px;
color: var(--text-primary); color: var(--text-primary);
font-family: 'Courier New', monospace; font-family: "IBM Plex Mono", monospace;
} }
/* Signal Details */ /* Signal Details */
@ -358,10 +370,11 @@
/* Reason */ /* Reason */
.signal-reason { .signal-reason {
background: var(--bg-tertiary); background: rgba(10, 20, 31, 0.72);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
padding: 12px; padding: 12px;
margin-bottom: 12px; margin-bottom: 12px;
border: 1px solid var(--border);
} }
.reason-label { .reason-label {
@ -465,44 +478,6 @@
<button class="refresh-btn" @click="loadSignals">刷新</button> <button class="refresh-btn" @click="loadSignals">刷新</button>
</div> </div>
<!-- Stats Grid -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">加密货币信号</div>
<div class="stat-value">{{ stats.crypto.total }}</div>
<div class="stat-sub">24小时: {{ stats.crypto.recent_24h }}</div>
</div>
<div class="stat-card">
<div class="stat-label">美股信号</div>
<div class="stat-value">{{ stats.stock.total }}</div>
<div class="stat-sub">24小时: {{ stats.stock.recent_24h }}</div>
</div>
<div class="stat-card">
<div class="stat-label">总信号数</div>
<div class="stat-value">{{ stats.total }}</div>
</div>
<div class="stat-card">
<div class="stat-label">买入信号</div>
<div class="stat-value positive">{{ stats.crypto.buy + stats.stock.buy }}</div>
</div>
<div class="stat-card">
<div class="stat-label">卖出信号</div>
<div class="stat-value negative">{{ stats.crypto.sell + stats.stock.sell }}</div>
</div>
</div>
<!-- Grade Stats -->
<div class="grade-stats" v-if="Object.keys(stats.grades).length > 0">
<div class="grade-stat-card" v-for="(count, grade) in stats.grades" :key="grade">
<div class="grade-stat-header">
<span class="grade-stat-title">
<span class="signal-grade" :class="grade">{{ grade }}</span> 级信号
</span>
<span class="grade-stat-count">{{ count }}</span>
</div>
</div>
</div>
<!-- Tabs --> <!-- Tabs -->
<div class="tabs"> <div class="tabs">
<button class="tab" :class="{ active: currentTab === 'crypto' }" @click="switchTab('crypto')"> <button class="tab" :class="{ active: currentTab === 'crypto' }" @click="switchTab('crypto')">

View File

@ -24,13 +24,13 @@
.status-page { .status-page {
min-height: 100vh; min-height: 100vh;
background: var(--bg-primary); background: transparent;
padding: 20px; padding: 20px;
} }
.status-container { .status-container {
max-width: 1400px; max-width: 1400px;
min-width: 1200px; min-width: 0;
margin: 0 auto; margin: 0 auto;
} }
@ -39,8 +39,9 @@
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 100; z-index: 100;
background: var(--bg-primary); background: linear-gradient(180deg, rgba(5, 11, 17, 0.94), rgba(5, 11, 17, 0.78));
padding-bottom: 10px; padding-bottom: 10px;
backdrop-filter: blur(16px);
} }
.status-header { .status-header {
@ -48,15 +49,18 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
padding: 10px 0 20px 0; padding: 18px 20px;
border-bottom: 1px solid var(--border); border: 1px solid var(--border);
background: var(--bg-primary); border-radius: var(--radius-lg);
background: var(--panel-strong);
box-shadow: var(--shadow);
} }
.status-title { .status-title {
font-size: 24px; font-size: 24px;
font-weight: 300; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
letter-spacing: -0.03em;
} }
.status-title span { .status-title span {
@ -65,17 +69,20 @@
.refresh-btn { .refresh-btn {
padding: 8px 16px; padding: 8px 16px;
background: transparent; background: rgba(126, 200, 255, 0.08);
border: 1px solid var(--accent); border: 1px solid var(--border);
color: var(--accent); color: var(--primary);
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
border-radius: 14px;
backdrop-filter: blur(14px);
} }
.refresh-btn:hover { .refresh-btn:hover {
background: var(--accent); background: rgba(126, 200, 255, 0.14);
color: var(--bg-primary); border-color: var(--primary);
color: var(--text-primary);
} }
.last-update { .last-update {
@ -96,8 +103,10 @@
.stat-card { .stat-card {
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 4px; border-radius: var(--radius-md);
padding: 16px; padding: 16px;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
} }
.stat-label { .stat-label {
@ -108,8 +117,9 @@
.stat-value { .stat-value {
font-size: 24px; font-size: 24px;
font-weight: 300; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
font-family: "IBM Plex Mono", monospace;
} }
.stat-value.running { .stat-value.running {
@ -127,7 +137,7 @@
.section-title { .section-title {
font-size: 18px; font-size: 18px;
font-weight: 300; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
margin-bottom: 16px; margin-bottom: 16px;
padding-bottom: 10px; padding-bottom: 10px;
@ -143,14 +153,16 @@
.agent-card { .agent-card {
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 4px; border-radius: var(--radius-md);
padding: 20px; padding: 20px;
transition: all 0.3s ease; transition: all 0.3s ease;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
} }
.agent-card:hover { .agent-card:hover {
border-color: var(--accent); border-color: var(--line-strong);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow);
} }
.agent-card.status-running { .agent-card.status-running {
@ -249,7 +261,7 @@
background: rgba(255, 68, 68, 0.1); background: rgba(255, 68, 68, 0.1);
color: #ff4444; color: #ff4444;
padding: 12px 16px; padding: 12px 16px;
border-radius: 4px; border-radius: 14px;
margin-bottom: 16px; margin-bottom: 16px;
border: 1px solid #ff4444; border: 1px solid #ff4444;
} }
@ -282,11 +294,12 @@
} }
.symbol-tag { .symbol-tag {
background: var(--bg-primary); background: rgba(126, 200, 255, 0.08);
padding: 2px 8px; padding: 2px 8px;
border-radius: 3px; border-radius: 999px;
font-size: 11px; font-size: 11px;
color: var(--text-secondary); color: var(--text-secondary);
border: 1px solid var(--border);
} }
</style> </style>
</head> </head>

View File

@ -11,20 +11,21 @@
<!-- Fonts --> <!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<!-- Page-Specific Styles --> <!-- Page-Specific Styles -->
<style> <style>
.paper-badge { .paper-badge {
display: inline-block; display: inline-block;
background: var(--primary); background: linear-gradient(135deg, #8fd7ff, #63e6be);
color: white; color: #071018;
padding: 4px 12px; padding: 5px 12px;
border-radius: var(--radius-sm); border-radius: 999px;
font-size: 11px; font-size: 11px;
font-weight: 700; font-weight: 700;
letter-spacing: 1px; letter-spacing: 1px;
margin-left: 12px; margin-left: 12px;
box-shadow: 0 10px 24px rgba(49, 132, 189, 0.22);
} }
.metrics-grid { .metrics-grid {
@ -47,6 +48,8 @@
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: var(--space-lg); padding: var(--space-lg);
transition: all 0.2s; transition: all 0.2s;
box-shadow: var(--shadow-soft);
backdrop-filter: blur(16px);
} }
.metric-card:hover, .stat-card:hover { .metric-card:hover, .stat-card:hover {
@ -68,7 +71,7 @@
font-weight: 700; font-weight: 700;
color: var(--text-primary); color: var(--text-primary);
margin-bottom: var(--space-xs); margin-bottom: var(--space-xs);
font-family: 'Inter', monospace; font-family: "IBM Plex Mono", monospace;
} }
.metric-sub { .metric-sub {
@ -152,6 +155,7 @@
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
box-shadow: var(--shadow-soft);
} }
.price-list { .price-list {
@ -167,7 +171,7 @@
padding: 12px; padding: 12px;
background: var(--bg-primary); background: var(--bg-primary);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-sm); border-radius: 14px;
} }
.price-item .symbol { .price-item .symbol {
@ -195,6 +199,7 @@
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: 24px; padding: 24px;
text-align: center; text-align: center;
box-shadow: var(--shadow-soft);
} }
.core-metric-label { .core-metric-label {
@ -234,12 +239,13 @@
top: 100%; top: 100%;
right: 0; right: 0;
margin-top: 8px; margin-top: 8px;
background: var(--bg-primary); background: var(--panel-strong);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
min-width: 200px; min-width: 200px;
z-index: 1000; z-index: 1000;
backdrop-filter: blur(18px);
} }
.admin-menu-item { .admin-menu-item {
@ -303,7 +309,7 @@
align-items: center; align-items: center;
padding: 2px 8px; padding: 2px 8px;
border-radius: 999px; border-radius: 999px;
background: var(--bg-secondary); background: rgba(126, 200, 255, 0.08);
border: 1px solid var(--border); border: 1px solid var(--border);
font-size: 11px; font-size: 11px;
color: var(--text-secondary); color: var(--text-secondary);
@ -327,6 +333,7 @@
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: 14px 16px; padding: 14px 16px;
box-shadow: var(--shadow-soft);
} }
.platform-halt-card.halted { .platform-halt-card.halted {
@ -416,32 +423,6 @@
</div> </div>
</div> </div>
<div v-if="adminMode" class="platform-halts">
<div
v-for="(status, platform) in platformHalts"
:key="platform"
class="platform-halt-card"
:class="{ halted: status.halted }"
>
<div class="platform-halt-title">
{{ platform }} · {{ status.halted ? '已暂停' : '运行中' }}
</div>
<div class="platform-halt-text" v-if="status.halted">
{{ status.reason || '无暂停原因' }}
</div>
<div class="platform-halt-text" v-if="status.halted && status.halted_at">
暂停时间: {{ formatTime(status.halted_at) }}
</div>
<button
v-if="status.halted"
class="btn btn-secondary btn-small"
@click="resumePlatform(platform)"
>
恢复平台
</button>
</div>
</div>
<!-- Real-time Prices --> <!-- Real-time Prices -->
<div v-if="Object.keys(latestPrices).length > 0" class="price-section"> <div v-if="Object.keys(latestPrices).length > 0" class="price-section">
<div class="stat-label" style="margin-bottom: 8px;">实时价格</div> <div class="stat-label" style="margin-bottom: 8px;">实时价格</div>