diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css
index 93045ed..8b52050 100644
--- a/manual_trading_hub/static/app.css
+++ b/manual_trading_hub/static/app.css
@@ -1164,6 +1164,123 @@ html[data-theme="light"] .host-metric-bar {
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 {
padding-bottom: 0;
}
diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js
index ec785ce..e54d496 100644
--- a/manual_trading_hub/static/app.js
+++ b/manual_trading_hub/static/app.js
@@ -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 = `正常 ${ok}·关注 ${warn}·异常 ${err}`;
}
- /** 监控卡片列数:桌面 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 ? "" : '
无已启用账户
');
+ 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 =
+ `` +
+ `
${leftHtml}
` +
+ `
${rightHtml}
` +
+ `
`;
+ } else {
+ cardsHtml =
+ displayRows
+ .map((r) => (mobileTiles ? renderMonitorTile(r) : renderMonitorCard(r)))
+ .join("") || (showStatsCard ? "" : '无已启用账户
');
+ }
box.innerHTML = statsHtml + cardsHtml;
if (showStatsCard && !cardsHtml && !statsHtml) {
box.innerHTML = '无已启用账户
';
@@ -2219,7 +2242,8 @@
box.innerHTML = `监控区渲染失败:${esc(String(err && err.message ? err.message : err))}
`;
}
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 = '';
+ html += '
永续账户
';
+ html += '
';
+ html += renderAccountStatRow(row, ag);
+ html += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday);
+ html += "
";
+ return html;
+ }
+
+ function renderOptionsSectionBody(row) {
const opt = row.options || {};
- let html = '';
- html += '
期权账户
';
+ let html = "";
if (opt.enabled === false) {
html += renderStatRow(null, null, null);
html += '
期权持仓
';
html += '
期权未启用(OKX_OPTIONS_ENABLED)
';
- html += "
";
- return html;
- }
- if (opt.ok === false) {
+ } else if (opt.ok === false) {
html += renderStatRow(null, null, null);
html += '期权持仓
';
html += `${esc(opt.msg || "期权数据不可用")}
`;
- html += "";
- return html;
- }
- const pos = Array.isArray(opt.positions) ? opt.positions : [];
- const bal = optionsBalanceFields(opt);
- html += renderStatRow(bal.funding, bal.trading, bal.upl);
- html += `期权持仓 · ${pos.length} 仓
`;
- html += renderOptionsPositionsTable(pos);
- if (row.flask_url_browser || row.flask_url) {
- html += ``;
+ } else {
+ const pos = Array.isArray(opt.positions) ? opt.positions : [];
+ const bal = optionsBalanceFields(opt);
+ html += renderStatRow(bal.funding, bal.trading, bal.upl);
+ html += `期权持仓 · ${pos.length} 仓
`;
+ html += renderOptionsPositionsTable(pos);
+ if (row.flask_url_browser || row.flask_url) {
+ html += ``;
+ }
}
+ return html;
+ }
+
+ function renderOptionsInnerCard(row) {
+ let html = '';
+ html += '
期权账户
';
+ html += '
';
+ html += renderOptionsSectionBody(row);
+ html += "
";
+ return html;
+ }
+
+ function renderOptionsMonitorSection(row) {
+ if (!rowHasOptionsLayout(row)) return "";
+ let html = '';
+ html += '
期权账户
';
+ html += renderOptionsSectionBody(row);
html += "
";
return html;
}
@@ -3523,12 +3573,19 @@
const intraday = isIntradayDisciplineRow(row);
let inner = "";
if (rowHasOptionsLayout(row)) {
- inner += '';
- inner += '
永续账户
';
- inner += renderAccountStatRow(row, ag);
- inner += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday);
- inner += "
";
- inner += renderOptionsMonitorSection(row);
+ if (monitorGridOptionsSplit) {
+ inner += '';
+ inner += renderPerpetualInnerCard(row, ag, pos, orders, trends, tickMap, intraday);
+ inner += renderOptionsInnerCard(row);
+ inner += "
";
+ } else {
+ inner += '';
+ inner += '
永续账户
';
+ inner += renderAccountStatRow(row, ag);
+ inner += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday);
+ inner += "
";
+ inner += renderOptionsMonitorSection(row);
+ }
} else {
inner += renderAccountStatRow(row, ag);
inner += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday);
@@ -3878,7 +3935,8 @@
`;
}
- 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 `
+ const layoutCls = opts.okxSplit
+ ? " card-monitor-okx-split"
+ : opts.splitSide
+ ? " card-monitor-split-side"
+ : "";
+ return `
diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html
index 56307fa..d470d2c 100644
--- a/manual_trading_hub/static/index.html
+++ b/manual_trading_hub/static/index.html
@@ -15,7 +15,7 @@
-
+
@@ -1151,6 +1151,6 @@
-
+