Add status column with blinking dots, widen shell, risk/open format.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-02 12:07:42 +08:00
parent 46a861d311
commit ec3156021a
2 changed files with 80 additions and 28 deletions
+18 -9
View File
@@ -106,7 +106,7 @@ type RiskLines = {
lossPct: string | null; lossPct: string | null;
exit: string; exit: string;
openRatio: string | null; openRatio: string | null;
/** 表格「风险/开仓」列:以损显示风险%;手动显示名义比 */ /** 表格「风险/开仓」列:如 5%/1:2;手动为 —/名义比 */
riskOrOpen: string; riskOrOpen: string;
leverage: string; leverage: string;
}; };
@@ -144,7 +144,7 @@ function riskLines(strat: Record<string, unknown>): RiskLines {
const openRatio = riskBased const openRatio = riskBased
? `${unitLabel(strat.risk_perp_unit)}:${unitLabel(strat.risk_option_unit)}` ? `${unitLabel(strat.risk_perp_unit)}:${unitLabel(strat.risk_option_unit)}`
: `${unitLabel(strat.perp_qty_eth)}:${unitLabel(strat.option_qty_eth)}`; : `${unitLabel(strat.perp_qty_eth)}:${unitLabel(strat.option_qty_eth)}`;
const riskOrOpen = riskBased ? lossPct || "—" : openRatio; const riskOrOpen = `${lossPct || "—"}/${openRatio}`;
return { return {
riskBased, riskBased,
sizing: riskBased ? "以损定仓" : "手动仓位", sizing: riskBased ? "以损定仓" : "手动仓位",
@@ -516,6 +516,7 @@ export default function MonitorPage() {
<th className="col-check"> <th className="col-check">
<span className="sr-only"></span> <span className="sr-only"></span>
</th> </th>
<th className="col-status"></th>
<th></th> <th></th>
<th></th> <th></th>
<th></th> <th></th>
@@ -550,30 +551,38 @@ export default function MonitorPage() {
aria-label={`选择 ${n.name}`} aria-label={`选择 ${n.name}`}
/> />
</td> </td>
<td> <td className="col-status">
<div className="machine-cell"> <div
className="status-dots"
title={`在线:${n.online ? "是" : "否"} · ${tokenDotTitle(n)} · ${running ? "运行中" : "已停"}`}
>
<span <span
className={`dot ${n.online ? "ok" : "bad"}`} className={`dot pulse d1 ${n.online ? "ok" : "bad"}`}
title={n.online ? "在线" : "离线"} title={n.online ? "在线" : "离线"}
/> />
<span <span
className={tokenDotClass(n)} className={`dot pulse d2 ${tokenDotClass(n).replace("dot ", "")}`}
title={tokenDotTitle(n)} title={tokenDotTitle(n)}
/> />
<span <span
className={`run-dot ${running ? "ok" : "bad"}`} className={`dot pulse d3 ${running ? "ok" : "bad"}`}
title={running ? "运行中" : "已停"} title={running ? "运行中" : "已停"}
/> />
<strong className="machine-name">{n.name}</strong>
</div> </div>
</td> </td>
<td>
<strong className="machine-name">{n.name}</strong>
</td>
<td className="mono">{modeLabel(s.mode, s.strat)}</td> <td className="mono">{modeLabel(s.mode, s.strat)}</td>
<td className="mono">{s.exchange}</td> <td className="mono">{s.exchange}</td>
<td className={inPos ? "pnl-pos" : "pnl-neg"}> <td className={inPos ? "pnl-pos" : "pnl-neg"}>
{inPos ? "持仓中" : "无持仓"} {inPos ? "持仓中" : "无持仓"}
</td> </td>
<td className="mono">{s.rounds ?? "—"}</td> <td className="mono">{s.rounds ?? "—"}</td>
<td className="mono" title={risk.riskBased ? "风险比例" : "开仓比例"}> <td
className="mono"
title="风险比例 / 开仓比例"
>
{risk.riskOrOpen} {risk.riskOrOpen}
</td> </td>
<td className="mono">{risk.exit}</td> <td className="mono">{risk.exit}</td>
+62 -19
View File
@@ -25,9 +25,11 @@ body {
} }
.shell { .shell {
max-width: 1200px; width: 100%;
max-width: min(1920px, 100%);
margin: 0 auto; margin: 0 auto;
padding: 16px 20px 40px; padding: 16px 24px 40px;
box-sizing: border-box;
} }
.header { .header {
@@ -211,17 +213,20 @@ input {
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: var(--radius); border-radius: var(--radius);
background: var(--panel); background: var(--panel);
width: 100%;
} }
.monitor-table { .monitor-table {
width: 100%; width: 100%;
min-width: 1100px;
border-collapse: collapse; border-collapse: collapse;
font-size: 0.88rem; font-size: 0.9rem;
table-layout: auto;
} }
.monitor-table th, .monitor-table th,
.monitor-table td { .monitor-table td {
padding: 10px 12px; padding: 12px 14px;
border-bottom: 1px solid var(--line); border-bottom: 1px solid var(--line);
text-align: left; text-align: left;
white-space: nowrap; white-space: nowrap;
@@ -254,53 +259,91 @@ input {
} }
.monitor-table .col-check { .monitor-table .col-check {
width: 36px; width: 40px;
text-align: center; text-align: center;
} }
.monitor-table .col-status {
width: 88px;
}
.monitor-table .col-actions { .monitor-table .col-actions {
white-space: normal; white-space: normal;
} }
.machine-cell { .status-dots {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 6px; gap: 8px;
min-width: 0;
} }
.machine-name { .machine-name {
font-weight: 650; font-weight: 650;
font-size: 0.95rem;
} }
.dot, .dot {
.run-dot { width: 9px;
width: 8px; height: 9px;
height: 8px;
border-radius: 50%; border-radius: 50%;
flex: 0 0 auto; flex: 0 0 auto;
background: var(--muted); background: var(--muted);
} }
.dot.ok, .dot.ok {
.run-dot.ok {
background: var(--ok, #3cb371); background: var(--ok, #3cb371);
box-shadow: 0 0 0 2px rgba(60, 179, 113, 0.2); box-shadow: 0 0 0 2px rgba(60, 179, 113, 0.25);
} }
.dot.bad, .dot.bad {
.run-dot.bad {
background: var(--bad, #e35d5d); background: var(--bad, #e35d5d);
box-shadow: 0 0 0 2px rgba(227, 93, 93, 0.2); box-shadow: 0 0 0 2px rgba(227, 93, 93, 0.25);
} }
.dot.warn { .dot.warn {
background: #e6a23c; background: #e6a23c;
box-shadow: 0 0 0 2px rgba(230, 162, 60, 0.25); box-shadow: 0 0 0 2px rgba(230, 162, 60, 0.3);
} }
.dot.muted { .dot.muted {
background: #5a6570; background: #5a6570;
box-shadow: 0 0 0 2px rgba(90, 101, 112, 0.25);
}
.dot.pulse {
animation: status-blink 1.4s ease-in-out infinite;
}
.dot.pulse.d1 {
animation-delay: 0s;
}
.dot.pulse.d2 {
animation-delay: 0.25s;
}
.dot.pulse.d3 {
animation-delay: 0.5s;
}
@keyframes status-blink {
0%,
100% {
opacity: 1;
transform: scale(1);
filter: brightness(1.15);
}
50% {
opacity: 0.35;
transform: scale(0.85);
filter: brightness(0.85);
}
}
@media (prefers-reduced-motion: reduce) {
.dot.pulse {
animation: none;
}
} }
.row-actions { .row-actions {