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:
@@ -120,6 +120,7 @@
|
||||
}
|
||||
let tpslPending = null;
|
||||
let lastMonitorRows = [];
|
||||
let monitorGridOptionsSplit = false;
|
||||
let lastMonitorTotals = null;
|
||||
let expandedExchangeId = sessionStorage.getItem("hub_expanded_ex") || "";
|
||||
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>`;
|
||||
}
|
||||
|
||||
/** 监控卡片列数:桌面 2×2(统计+三所);手机 2 列瓦片 */
|
||||
/** 监控卡片列数:桌面 2×2(统计+三所);期权分栏时由 CSS 控制 */
|
||||
function syncMonitorGridColumns(gridEl, itemCount, opts) {
|
||||
if (!gridEl) return;
|
||||
const options = opts || {};
|
||||
if (options.optionsSplit) {
|
||||
gridEl.style.gridTemplateColumns = "";
|
||||
return;
|
||||
}
|
||||
if (isMobileLayout()) {
|
||||
gridEl.style.gridTemplateColumns = options.statsFirst
|
||||
? "1fr"
|
||||
@@ -1837,8 +1842,10 @@
|
||||
wasMobile = nowMobile;
|
||||
const box = document.getElementById("monitor-grid");
|
||||
if (box && lastMonitorRows.length) {
|
||||
const split = monitorOptionsSplitActive(lastMonitorRows);
|
||||
syncMonitorGridColumns(box, lastMonitorRows.length + (lastMonitorTotals ? 1 : 0), {
|
||||
statsFirst: !!lastMonitorTotals,
|
||||
statsFirst: !!lastMonitorTotals && !split,
|
||||
optionsSplit: split,
|
||||
});
|
||||
updateMonitorAlertSummary(lastMonitorRows);
|
||||
}
|
||||
@@ -2200,16 +2207,32 @@
|
||||
}
|
||||
const mobileTiles = isMobileLayout() && !expandedExchangeId;
|
||||
const displayRows = mobileTiles ? sortRowsForMobileDashboard(rows) : rows;
|
||||
const optionsSplit = monitorOptionsSplitActive(displayRows);
|
||||
monitorGridOptionsSplit = optionsSplit;
|
||||
const showStatsCard = !expandedExchangeId;
|
||||
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-options-split", optionsSplit && showStatsCard);
|
||||
try {
|
||||
const statsHtml = showStatsCard ? renderMonitorStatsCard(lastMonitorTotals) : "";
|
||||
const cardsHtml =
|
||||
displayRows
|
||||
.map((r) => (mobileTiles ? renderMonitorTile(r) : renderMonitorCard(r)))
|
||||
.join("") || (showStatsCard ? "" : '<div class="err">无已启用账户</div>');
|
||||
let cardsHtml = "";
|
||||
if (optionsSplit) {
|
||||
const okxRow = displayRows.find((r) => rowHasOptionsLayout(r));
|
||||
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;
|
||||
if (showStatsCard && !cardsHtml && !statsHtml) {
|
||||
box.innerHTML = '<div class="err">无已启用账户</div>';
|
||||
@@ -2219,7 +2242,8 @@
|
||||
box.innerHTML = `<div class="err">监控区渲染失败:${esc(String(err && err.message ? err.message : err))}</div>`;
|
||||
}
|
||||
syncMonitorGridColumns(box, displayRows.length + (showStatsCard ? 1 : 0), {
|
||||
statsFirst: showStatsCard,
|
||||
statsFirst: showStatsCard && !optionsSplit,
|
||||
optionsSplit: optionsSplit && showStatsCard,
|
||||
});
|
||||
bindMonitorInteractions(box);
|
||||
if (window.TimeCloseUI && TimeCloseUI.tickLocalCountdowns) {
|
||||
@@ -3468,33 +3492,59 @@
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderOptionsMonitorSection(row) {
|
||||
if (!rowHasOptionsLayout(row)) return "";
|
||||
function monitorOptionsSplitActive(rows) {
|
||||
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 || {};
|
||||
let html = '<div class="hub-monitor-block hub-monitor-options">';
|
||||
html += '<div class="hub-monitor-block-label">期权账户</div>';
|
||||
let html = "";
|
||||
if (opt.enabled === false) {
|
||||
html += renderStatRow(null, null, null);
|
||||
html += '<div class="section-title hub-options-title">期权持仓</div>';
|
||||
html += '<div class="empty-hint">期权未启用(OKX_OPTIONS_ENABLED)</div>';
|
||||
html += "</div>";
|
||||
return html;
|
||||
}
|
||||
if (opt.ok === false) {
|
||||
} else if (opt.ok === false) {
|
||||
html += renderStatRow(null, null, null);
|
||||
html += '<div class="section-title hub-options-title">期权持仓</div>';
|
||||
html += `<div class="err">${esc(opt.msg || "期权数据不可用")}</div>`;
|
||||
html += "</div>";
|
||||
return html;
|
||||
}
|
||||
const pos = Array.isArray(opt.positions) ? opt.positions : [];
|
||||
const bal = optionsBalanceFields(opt);
|
||||
html += renderStatRow(bal.funding, bal.trading, bal.upl);
|
||||
html += `<div class="section-title hub-options-title">期权持仓 · ${pos.length} 仓</div>`;
|
||||
html += renderOptionsPositionsTable(pos);
|
||||
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>`;
|
||||
} else {
|
||||
const pos = Array.isArray(opt.positions) ? opt.positions : [];
|
||||
const bal = optionsBalanceFields(opt);
|
||||
html += renderStatRow(bal.funding, bal.trading, bal.upl);
|
||||
html += `<div class="section-title hub-options-title">期权持仓 · ${pos.length} 仓</div>`;
|
||||
html += renderOptionsPositionsTable(pos);
|
||||
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>";
|
||||
return html;
|
||||
}
|
||||
@@ -3523,12 +3573,19 @@
|
||||
const intraday = isIntradayDisciplineRow(row);
|
||||
let inner = "";
|
||||
if (rowHasOptionsLayout(row)) {
|
||||
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);
|
||||
if (monitorGridOptionsSplit) {
|
||||
inner += '<div class="hub-inner-cards">';
|
||||
inner += renderPerpetualInnerCard(row, ag, pos, orders, trends, tickMap, intraday);
|
||||
inner += renderOptionsInnerCard(row);
|
||||
inner += "</div>";
|
||||
} 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 {
|
||||
inner += renderAccountStatRow(row, ag);
|
||||
inner += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday);
|
||||
@@ -3878,7 +3935,8 @@
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderMonitorCard(row) {
|
||||
function renderMonitorCard(row, layoutOpts) {
|
||||
const opts = layoutOpts || {};
|
||||
const ag = row.agent || {};
|
||||
const pos = Array.isArray(ag.positions) ? ag.positions : [];
|
||||
const hm = row.hub_monitor || {};
|
||||
@@ -3924,7 +3982,12 @@
|
||||
: "";
|
||||
const intraday = isIntradayDisciplineRow(row);
|
||||
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>
|
||||
<div class="card-title-row">
|
||||
|
||||
Reference in New Issue
Block a user