Hub monitor: OKX left column with dual inner cards when options enabled.
Use a stats bar plus left OKX / right Binance+Gate split layout with equal-height inner perpetual and options cards and no in-card scrolling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1164,6 +1164,123 @@ html[data-theme="light"] .host-metric-bar {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-monitor.grid-monitor-options-split {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-monitor.grid-monitor-options-split .monitor-stats-card {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-monitor.grid-monitor-options-split .monitor-stats-grid {
|
||||||
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-split-body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
align-items: stretch;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-split-left,
|
||||||
|
.monitor-split-right {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-split-left > .card,
|
||||||
|
.monitor-split-right > .card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-split-right {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-monitor-okx-split,
|
||||||
|
.card-monitor-split-side {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-monitor-okx-split .card-body,
|
||||||
|
.card-monitor-split-side .card-body {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: visible;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-inner-cards {
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-inner-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: color-mix(in srgb, var(--inset-surface) 92%, transparent);
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-inner-card-head {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--text);
|
||||||
|
border-bottom: 1px solid var(--border-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-inner-card-options .hub-inner-card-head {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-inner-card-body {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 12px;
|
||||||
|
overflow: visible;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-monitor-options-split .hub-options-table-wrap,
|
||||||
|
.grid-monitor-options-split .pos-table-wrap,
|
||||||
|
.grid-monitor-options-split .table-scroll {
|
||||||
|
overflow: visible;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.grid-monitor.grid-monitor-options-split .monitor-stats-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.monitor-split-body {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-split-right {
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.monitor-stats-card .card-head {
|
.monitor-stats-card .card-head {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,7 @@
|
|||||||
}
|
}
|
||||||
let tpslPending = null;
|
let tpslPending = null;
|
||||||
let lastMonitorRows = [];
|
let lastMonitorRows = [];
|
||||||
|
let monitorGridOptionsSplit = false;
|
||||||
let lastMonitorTotals = null;
|
let lastMonitorTotals = null;
|
||||||
let expandedExchangeId = sessionStorage.getItem("hub_expanded_ex") || "";
|
let expandedExchangeId = sessionStorage.getItem("hub_expanded_ex") || "";
|
||||||
const HUB_MONITOR_BOARD_CACHE_KEY = "hub_monitor_board_v1";
|
const HUB_MONITOR_BOARD_CACHE_KEY = "hub_monitor_board_v1";
|
||||||
@@ -1632,10 +1633,14 @@
|
|||||||
el.innerHTML = `<span class="mas-item mas-ok">正常 ${ok}</span><span class="mas-sep">·</span><span class="mas-item mas-warn">关注 ${warn}</span><span class="mas-sep">·</span><span class="mas-item mas-err">异常 ${err}</span>`;
|
el.innerHTML = `<span class="mas-item mas-ok">正常 ${ok}</span><span class="mas-sep">·</span><span class="mas-item mas-warn">关注 ${warn}</span><span class="mas-sep">·</span><span class="mas-item mas-err">异常 ${err}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监控卡片列数:桌面 2×2(统计+三所);手机 2 列瓦片 */
|
/** 监控卡片列数:桌面 2×2(统计+三所);期权分栏时由 CSS 控制 */
|
||||||
function syncMonitorGridColumns(gridEl, itemCount, opts) {
|
function syncMonitorGridColumns(gridEl, itemCount, opts) {
|
||||||
if (!gridEl) return;
|
if (!gridEl) return;
|
||||||
const options = opts || {};
|
const options = opts || {};
|
||||||
|
if (options.optionsSplit) {
|
||||||
|
gridEl.style.gridTemplateColumns = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isMobileLayout()) {
|
if (isMobileLayout()) {
|
||||||
gridEl.style.gridTemplateColumns = options.statsFirst
|
gridEl.style.gridTemplateColumns = options.statsFirst
|
||||||
? "1fr"
|
? "1fr"
|
||||||
@@ -1837,8 +1842,10 @@
|
|||||||
wasMobile = nowMobile;
|
wasMobile = nowMobile;
|
||||||
const box = document.getElementById("monitor-grid");
|
const box = document.getElementById("monitor-grid");
|
||||||
if (box && lastMonitorRows.length) {
|
if (box && lastMonitorRows.length) {
|
||||||
|
const split = monitorOptionsSplitActive(lastMonitorRows);
|
||||||
syncMonitorGridColumns(box, lastMonitorRows.length + (lastMonitorTotals ? 1 : 0), {
|
syncMonitorGridColumns(box, lastMonitorRows.length + (lastMonitorTotals ? 1 : 0), {
|
||||||
statsFirst: !!lastMonitorTotals,
|
statsFirst: !!lastMonitorTotals && !split,
|
||||||
|
optionsSplit: split,
|
||||||
});
|
});
|
||||||
updateMonitorAlertSummary(lastMonitorRows);
|
updateMonitorAlertSummary(lastMonitorRows);
|
||||||
}
|
}
|
||||||
@@ -2200,16 +2207,32 @@
|
|||||||
}
|
}
|
||||||
const mobileTiles = isMobileLayout() && !expandedExchangeId;
|
const mobileTiles = isMobileLayout() && !expandedExchangeId;
|
||||||
const displayRows = mobileTiles ? sortRowsForMobileDashboard(rows) : rows;
|
const displayRows = mobileTiles ? sortRowsForMobileDashboard(rows) : rows;
|
||||||
|
const optionsSplit = monitorOptionsSplitActive(displayRows);
|
||||||
|
monitorGridOptionsSplit = optionsSplit;
|
||||||
const showStatsCard = !expandedExchangeId;
|
const showStatsCard = !expandedExchangeId;
|
||||||
box.classList.toggle("grid-monitor-tiles", mobileTiles);
|
box.classList.toggle("grid-monitor-tiles", mobileTiles);
|
||||||
box.classList.toggle("grid-monitor-2x2", showStatsCard && !mobileTiles);
|
box.classList.toggle("grid-monitor-2x2", showStatsCard && !mobileTiles && !optionsSplit);
|
||||||
box.classList.toggle("grid-monitor-with-stats", showStatsCard && mobileTiles);
|
box.classList.toggle("grid-monitor-with-stats", showStatsCard && mobileTiles);
|
||||||
|
box.classList.toggle("grid-monitor-options-split", optionsSplit && showStatsCard);
|
||||||
try {
|
try {
|
||||||
const statsHtml = showStatsCard ? renderMonitorStatsCard(lastMonitorTotals) : "";
|
const statsHtml = showStatsCard ? renderMonitorStatsCard(lastMonitorTotals) : "";
|
||||||
const cardsHtml =
|
let cardsHtml = "";
|
||||||
displayRows
|
if (optionsSplit) {
|
||||||
.map((r) => (mobileTiles ? renderMonitorTile(r) : renderMonitorCard(r)))
|
const okxRow = displayRows.find((r) => rowHasOptionsLayout(r));
|
||||||
.join("") || (showStatsCard ? "" : '<div class="err">无已启用账户</div>');
|
const otherRows = displayRows.filter((r) => !rowHasOptionsLayout(r));
|
||||||
|
const leftHtml = okxRow ? renderMonitorCard(okxRow, { okxSplit: true }) : "";
|
||||||
|
const rightHtml = otherRows.map((r) => renderMonitorCard(r, { splitSide: true })).join("");
|
||||||
|
cardsHtml =
|
||||||
|
`<div class="monitor-split-body">` +
|
||||||
|
`<div class="monitor-split-left">${leftHtml}</div>` +
|
||||||
|
`<div class="monitor-split-right">${rightHtml}</div>` +
|
||||||
|
`</div>`;
|
||||||
|
} else {
|
||||||
|
cardsHtml =
|
||||||
|
displayRows
|
||||||
|
.map((r) => (mobileTiles ? renderMonitorTile(r) : renderMonitorCard(r)))
|
||||||
|
.join("") || (showStatsCard ? "" : '<div class="err">无已启用账户</div>');
|
||||||
|
}
|
||||||
box.innerHTML = statsHtml + cardsHtml;
|
box.innerHTML = statsHtml + cardsHtml;
|
||||||
if (showStatsCard && !cardsHtml && !statsHtml) {
|
if (showStatsCard && !cardsHtml && !statsHtml) {
|
||||||
box.innerHTML = '<div class="err">无已启用账户</div>';
|
box.innerHTML = '<div class="err">无已启用账户</div>';
|
||||||
@@ -2219,7 +2242,8 @@
|
|||||||
box.innerHTML = `<div class="err">监控区渲染失败:${esc(String(err && err.message ? err.message : err))}</div>`;
|
box.innerHTML = `<div class="err">监控区渲染失败:${esc(String(err && err.message ? err.message : err))}</div>`;
|
||||||
}
|
}
|
||||||
syncMonitorGridColumns(box, displayRows.length + (showStatsCard ? 1 : 0), {
|
syncMonitorGridColumns(box, displayRows.length + (showStatsCard ? 1 : 0), {
|
||||||
statsFirst: showStatsCard,
|
statsFirst: showStatsCard && !optionsSplit,
|
||||||
|
optionsSplit: optionsSplit && showStatsCard,
|
||||||
});
|
});
|
||||||
bindMonitorInteractions(box);
|
bindMonitorInteractions(box);
|
||||||
if (window.TimeCloseUI && TimeCloseUI.tickLocalCountdowns) {
|
if (window.TimeCloseUI && TimeCloseUI.tickLocalCountdowns) {
|
||||||
@@ -3468,33 +3492,59 @@
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOptionsMonitorSection(row) {
|
function monitorOptionsSplitActive(rows) {
|
||||||
if (!rowHasOptionsLayout(row)) return "";
|
if (isMobileLayout() || expandedExchangeId) return false;
|
||||||
|
return (rows || []).some((r) => rowHasOptionsLayout(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPerpetualInnerCard(row, ag, pos, orders, trends, tickMap, intraday) {
|
||||||
|
let html = '<div class="hub-inner-card hub-inner-card-perp">';
|
||||||
|
html += '<div class="hub-inner-card-head">永续账户</div>';
|
||||||
|
html += '<div class="hub-inner-card-body">';
|
||||||
|
html += renderAccountStatRow(row, ag);
|
||||||
|
html += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday);
|
||||||
|
html += "</div></div>";
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderOptionsSectionBody(row) {
|
||||||
const opt = row.options || {};
|
const opt = row.options || {};
|
||||||
let html = '<div class="hub-monitor-block hub-monitor-options">';
|
let html = "";
|
||||||
html += '<div class="hub-monitor-block-label">期权账户</div>';
|
|
||||||
if (opt.enabled === false) {
|
if (opt.enabled === false) {
|
||||||
html += renderStatRow(null, null, null);
|
html += renderStatRow(null, null, null);
|
||||||
html += '<div class="section-title hub-options-title">期权持仓</div>';
|
html += '<div class="section-title hub-options-title">期权持仓</div>';
|
||||||
html += '<div class="empty-hint">期权未启用(OKX_OPTIONS_ENABLED)</div>';
|
html += '<div class="empty-hint">期权未启用(OKX_OPTIONS_ENABLED)</div>';
|
||||||
html += "</div>";
|
} else if (opt.ok === false) {
|
||||||
return html;
|
|
||||||
}
|
|
||||||
if (opt.ok === false) {
|
|
||||||
html += renderStatRow(null, null, null);
|
html += renderStatRow(null, null, null);
|
||||||
html += '<div class="section-title hub-options-title">期权持仓</div>';
|
html += '<div class="section-title hub-options-title">期权持仓</div>';
|
||||||
html += `<div class="err">${esc(opt.msg || "期权数据不可用")}</div>`;
|
html += `<div class="err">${esc(opt.msg || "期权数据不可用")}</div>`;
|
||||||
html += "</div>";
|
} else {
|
||||||
return html;
|
const pos = Array.isArray(opt.positions) ? opt.positions : [];
|
||||||
}
|
const bal = optionsBalanceFields(opt);
|
||||||
const pos = Array.isArray(opt.positions) ? opt.positions : [];
|
html += renderStatRow(bal.funding, bal.trading, bal.upl);
|
||||||
const bal = optionsBalanceFields(opt);
|
html += `<div class="section-title hub-options-title">期权持仓 · ${pos.length} 仓</div>`;
|
||||||
html += renderStatRow(bal.funding, bal.trading, bal.upl);
|
html += renderOptionsPositionsTable(pos);
|
||||||
html += `<div class="section-title hub-options-title">期权持仓 · ${pos.length} 仓</div>`;
|
if (row.flask_url_browser || row.flask_url) {
|
||||||
html += renderOptionsPositionsTable(pos);
|
html += `<div class="hub-options-actions"><a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/options">打开期权页</a></div>`;
|
||||||
if (row.flask_url_browser || row.flask_url) {
|
}
|
||||||
html += `<div class="hub-options-actions"><a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/options">打开期权页</a></div>`;
|
|
||||||
}
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderOptionsInnerCard(row) {
|
||||||
|
let html = '<div class="hub-inner-card hub-inner-card-options">';
|
||||||
|
html += '<div class="hub-inner-card-head">期权账户</div>';
|
||||||
|
html += '<div class="hub-inner-card-body">';
|
||||||
|
html += renderOptionsSectionBody(row);
|
||||||
|
html += "</div></div>";
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderOptionsMonitorSection(row) {
|
||||||
|
if (!rowHasOptionsLayout(row)) return "";
|
||||||
|
let html = '<div class="hub-monitor-block hub-monitor-options">';
|
||||||
|
html += '<div class="hub-monitor-block-label">期权账户</div>';
|
||||||
|
html += renderOptionsSectionBody(row);
|
||||||
html += "</div>";
|
html += "</div>";
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
@@ -3523,12 +3573,19 @@
|
|||||||
const intraday = isIntradayDisciplineRow(row);
|
const intraday = isIntradayDisciplineRow(row);
|
||||||
let inner = "";
|
let inner = "";
|
||||||
if (rowHasOptionsLayout(row)) {
|
if (rowHasOptionsLayout(row)) {
|
||||||
inner += '<div class="hub-monitor-block hub-monitor-perp">';
|
if (monitorGridOptionsSplit) {
|
||||||
inner += '<div class="hub-monitor-block-label">永续账户</div>';
|
inner += '<div class="hub-inner-cards">';
|
||||||
inner += renderAccountStatRow(row, ag);
|
inner += renderPerpetualInnerCard(row, ag, pos, orders, trends, tickMap, intraday);
|
||||||
inner += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday);
|
inner += renderOptionsInnerCard(row);
|
||||||
inner += "</div>";
|
inner += "</div>";
|
||||||
inner += renderOptionsMonitorSection(row);
|
} else {
|
||||||
|
inner += '<div class="hub-monitor-block hub-monitor-perp">';
|
||||||
|
inner += '<div class="hub-monitor-block-label">永续账户</div>';
|
||||||
|
inner += renderAccountStatRow(row, ag);
|
||||||
|
inner += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday);
|
||||||
|
inner += "</div>";
|
||||||
|
inner += renderOptionsMonitorSection(row);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
inner += renderAccountStatRow(row, ag);
|
inner += renderAccountStatRow(row, ag);
|
||||||
inner += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday);
|
inner += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday);
|
||||||
@@ -3878,7 +3935,8 @@
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMonitorCard(row) {
|
function renderMonitorCard(row, layoutOpts) {
|
||||||
|
const opts = layoutOpts || {};
|
||||||
const ag = row.agent || {};
|
const ag = row.agent || {};
|
||||||
const pos = Array.isArray(ag.positions) ? ag.positions : [];
|
const pos = Array.isArray(ag.positions) ? ag.positions : [];
|
||||||
const hm = row.hub_monitor || {};
|
const hm = row.hub_monitor || {};
|
||||||
@@ -3924,7 +3982,12 @@
|
|||||||
: "";
|
: "";
|
||||||
const intraday = isIntradayDisciplineRow(row);
|
const intraday = isIntradayDisciplineRow(row);
|
||||||
const fcHeadBadge = intraday ? forceCloseHeadBadgeHtml(row.force_close) : "";
|
const fcHeadBadge = intraday ? forceCloseHeadBadgeHtml(row.force_close) : "";
|
||||||
return `<div class="card ${cardCls}" data-ex-id="${esc(row.id)}">
|
const layoutCls = opts.okxSplit
|
||||||
|
? " card-monitor-okx-split"
|
||||||
|
: opts.splitSide
|
||||||
|
? " card-monitor-split-side"
|
||||||
|
: "";
|
||||||
|
return `<div class="card ${cardCls}${layoutCls}" data-ex-id="${esc(row.id)}">
|
||||||
<div class="card-head card-expand-zone" title="点击放大全屏">
|
<div class="card-head card-expand-zone" title="点击放大全屏">
|
||||||
<div>
|
<div>
|
||||||
<div class="card-title-row">
|
<div class="card-title-row">
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<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=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
||||||
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
||||||
<link rel="stylesheet" href="/assets/app.css?v=20260707-hub-options-split" />
|
<link rel="stylesheet" href="/assets/app.css?v=20260707-monitor-options-split" />
|
||||||
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=3" />
|
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=3" />
|
||||||
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
||||||
<script src="/assets/account_risk_badge.js?v=4"></script>
|
<script src="/assets/account_risk_badge.js?v=4"></script>
|
||||||
@@ -1151,6 +1151,6 @@
|
|||||||
<script src="/assets/ai_review_render.js?v=3"></script>
|
<script src="/assets/ai_review_render.js?v=3"></script>
|
||||||
<script src="/assets/time_close_ui.js?v=3"></script>
|
<script src="/assets/time_close_ui.js?v=3"></script>
|
||||||
<script src="/assets/backup.js?v=1"></script>
|
<script src="/assets/backup.js?v=1"></script>
|
||||||
<script src="/assets/app.js?v=20260707-hub-options-split"></script>
|
<script src="/assets/app.js?v=20260707-monitor-options-split"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user