From 60a1e7ad48fd3799bd19fb89b7959af4a75f741c Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 2 Aug 2026 10:10:31 +0800 Subject: [PATCH] Use horizontal tables for strategy and stats in centered fullscreen modal. Co-authored-by: Cursor --- control/frontend/src/pages/Monitor.tsx | 280 +++++++++++-------------- control/frontend/src/styles.css | 61 +----- 2 files changed, 135 insertions(+), 206 deletions(-) diff --git a/control/frontend/src/pages/Monitor.tsx b/control/frontend/src/pages/Monitor.tsx index 56e26ef..a2f5ec3 100644 --- a/control/frontend/src/pages/Monitor.tsx +++ b/control/frontend/src/pages/Monitor.tsx @@ -1,9 +1,4 @@ -import { - useCallback, - useEffect, - useState, - type ReactNode, -} from "react"; +import { useCallback, useEffect, useState } from "react"; import { apiFetch, clearSession, getToken, type NodeCard } from "../api"; function pickStrategy(n: NodeCard) { @@ -635,168 +630,143 @@ export default function MonitorPage() { -
-
-

策略详情

-
- - - {(() => { - const r = riskLines(detail.strat); - const rows: { k: string; v: ReactNode }[] = [ - { k: "定仓", v: r.sizing }, - ...(r.lossPct != null - ? [{ k: "风险比例", v: r.lossPct }] - : []), - { k: "出场", v: r.exit }, - ...(r.openRatio - ? [{ k: "开仓比例", v: r.openRatio }] - : []), - { k: "杠杆", v: r.leverage }, - { - k: "状态", - v: `${detailRunning ? "运行中" : "已停"} · ${detail.phase}`, - }, - { - k: "模式 / 交易所", - v: `${detail.mode} / ${detail.exchange}`, - }, - { k: "轮次", v: String(detail.rounds ?? "—") }, - { - k: "组 ID", - v: ( - - {String( - detail.strat.group_id || - detail.position.group_id || - "—", - )} - - ), - }, - { - k: "保证金", - v: String(detail.strat.perp_margin_mode || "—"), - }, - { - k: "名义 永续/期权", - v: `${fmt(detail.strat.perp_qty_eth, 4)} / ${fmt(detail.strat.option_qty_eth, 4)} ETH`, - }, - { - k: "出场目标", - v: `${fmt(detail.strat.exit_target_usdt, 2)} USDT`, - }, - { k: "指数", v: fmt(detail.index_px, 2) }, - { - k: "合约对", - v: ( - - {detail.pair - ? `${detail.pair.expiry_ymd || "?"} @ ${detail.pair.strike ?? "?"}` - : "—"} - - ), - }, - ]; - if (detail.strat.last_error) { - rows.push({ - k: "最近错误", - v: ( - - {String(detail.strat.last_error)} - - ), - }); - } - return rows.map((row) => ( - - - - - )); - })()} - -
{row.k}{row.v}
-
-
- -
-

整体统计

- {detailStatsLoading ? ( -

正在从策略机拉取统计…

- ) : null} - {detailStatsErr ? ( -

{detailStatsErr}

- ) : null} - {detailStats ? ( -
- +
+

策略详情

+ {(() => { + const r = riskLines(detail.strat); + return ( +
+
+ + + + {r.lossPct != null ? : null} + + {r.openRatio ? : null} + + + + + + + + + + + + - - + {r.lossPct != null ? : null} + + {r.openRatio ? : null} + + - - - - - - - + - - - - + - - - + + - - {(detailStats.show_slip ?? - detailStats.mode !== "LIVE") ? ( - - - - - ) : null} - - -
定仓风险比例出场开仓比例杠杆状态模式/交易所轮次组 ID保证金名义永续/期权出场目标指数合约对
组数 / 胜率 - {detailStats.groups} /{" "} - {(detailStats.win_rate * 100).toFixed(1)}% + {r.sizing}{r.lossPct}{r.exit}{r.openRatio}{r.leverage} + {detailRunning ? "运行中" : "已停"} · {detail.phase}
总盈亏 - {fmt(detailStats.total_pnl, 2)} USDT + + {detail.mode} / {detail.exchange}
永续手续费{detail.rounds ?? "—"} - {fmt(detailStats.fees_perp ?? 0, 4)} + {String( + detail.strat.group_id || + detail.position.group_id || + "—", + )}
期权手续费 - {fmt(detailStats.fees_option ?? 0, 4)} + {String(detail.strat.perp_margin_mode || "—")} + {fmt(detail.strat.perp_qty_eth, 4)} /{" "} + {fmt(detail.strat.option_qty_eth, 4)} ETH
手续费合计{fmt(detail.strat.exit_target_usdt, 2)} USDT{fmt(detail.index_px, 2)} - {fmt(detailStats.total_fees, 4)} -
滑点合计 - {fmt(detailStats.total_slip ?? 0, 4)} -
平仓原因 - {Object.keys(detailStats.close_reasons || {}) - .length === 0 - ? "—" - : Object.entries(detailStats.close_reasons) - .sort((a, b) => b[1] - a[1]) - .map(([k, n]) => ( -
- {closeReasonZh(k)} × {n} -
- ))} + {detail.pair + ? `${detail.pair.expiry_ymd || "?"} @ ${detail.pair.strike ?? "?"}` + : "—"}
- ) : !detailStatsLoading && !detailStatsErr ? ( -

暂无统计

- ) : null} -
-
+ ); + })()} + {detail.strat.last_error ? ( +

+ 最近错误:{String(detail.strat.last_error)} +

+ ) : null} + + +
+

整体统计

+ {detailStatsLoading ? ( +

正在从策略机拉取统计…

+ ) : null} + {detailStatsErr ? ( +

{detailStatsErr}

+ ) : null} + {detailStats ? ( +
+ + + + + + + + + {(detailStats.show_slip ?? + detailStats.mode !== "LIVE") ? ( + + ) : null} + + + + + + + + + + + {(detailStats.show_slip ?? + detailStats.mode !== "LIVE") ? ( + + ) : null} + + + +
组数/胜率总盈亏永续手续费期权手续费手续费合计滑点合计平仓原因
+ {detailStats.groups} /{" "} + {(detailStats.win_rate * 100).toFixed(1)}% + + {fmt(detailStats.total_pnl, 2)} USDT + + {fmt(detailStats.fees_perp ?? 0, 4)} + + {fmt(detailStats.fees_option ?? 0, 4)} + + {fmt(detailStats.total_fees, 4)} + + {fmt(detailStats.total_slip ?? 0, 4)} + + {Object.keys(detailStats.close_reasons || {}) + .length === 0 + ? "—" + : Object.entries(detailStats.close_reasons) + .sort((a, b) => b[1] - a[1]) + .map(([k, n]) => `${closeReasonZh(k)}×${n}`) + .join(" · ")} +
+
+ ) : !detailStatsLoading && !detailStatsErr ? ( +

暂无统计

+ ) : null} +

按组盈亏

diff --git a/control/frontend/src/styles.css b/control/frontend/src/styles.css index 65954f8..af1abc2 100644 --- a/control/frontend/src/styles.css +++ b/control/frontend/src/styles.css @@ -451,21 +451,26 @@ td { transform: translateX(20px); } +/* 全屏遮罩;带鱼屏两侧留黑,内容始终水平垂直居中 */ .modal-backdrop { position: fixed; inset: 0; - background: rgba(4, 8, 14, 0.88); + width: 100vw; + height: 100vh; + background: rgba(4, 8, 14, 0.9); display: flex; align-items: center; justify-content: center; - padding: 16px; + padding: 24px 16px; z-index: 50; overflow: auto; + box-sizing: border-box; } .modal-panel { width: min(920px, 100%); - max-height: calc(100vh - 32px); + max-width: 920px; + max-height: calc(100vh - 48px); overflow: auto; background: var(--panel); border: 1px solid var(--line); @@ -474,7 +479,8 @@ td { display: flex; flex-direction: column; gap: 12px; - margin: auto; + flex-shrink: 0; + box-sizing: border-box; } .modal-panel.running { @@ -482,18 +488,6 @@ td { box-shadow: 0 0 0 1px rgba(60, 179, 113, 0.2); } -.modal-grid-2 { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 12px; -} - -@media (max-width: 760px) { - .modal-grid-2 { - grid-template-columns: 1fr; - } -} - .pager { display: flex; align-items: center; @@ -530,41 +524,6 @@ td { font-size: 0.9rem; } -.kv-table-wrap { - border: 1px solid var(--line); - border-radius: 8px; - overflow: hidden; -} - -.kv-table { - width: 100%; - border-collapse: collapse; - font-size: 0.82rem; -} - -.kv-table th { - width: 38%; - background: rgba(255, 255, 255, 0.03); - color: var(--muted); - font-weight: 500; - text-align: left; - padding: 6px 10px; - border-bottom: 1px solid var(--line); - white-space: nowrap; -} - -.kv-table td { - padding: 6px 10px; - border-bottom: 1px solid var(--line); - text-align: left; - font-variant-numeric: tabular-nums; -} - -.kv-table tr:last-child th, -.kv-table tr:last-child td { - border-bottom: none; -} - .detail-kv { grid-template-columns: 1fr 1fr; }