Phone hub folds: emergency ops, market toolbar, calc tabs, OKX options.

Collapse bulky monitor/market controls by default on phone, show OKX options on tiles, add calculator tab switch, and request landscape on market fullscreen.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-15 22:56:02 +08:00
parent f16dcac202
commit 8838f1f221
5 changed files with 387 additions and 72 deletions
+107 -21
View File
@@ -1493,15 +1493,18 @@
else pill.classList.remove("syncing");
}
const upd = document.getElementById("monitor-updated");
if (upd) {
const updSum = document.getElementById("monitor-updated-summary");
if (upd || updSum) {
const ts = tsRaw.replace("T", " ");
upd.textContent = options.stale
const txt = options.stale
? ts
? `缓存 ${ts} · 后台聚合中…`
: "后台聚合中…"
: ts
? `UPD ${ts}`
: "";
if (upd) upd.textContent = txt;
if (updSum) updSum.textContent = txt;
}
updateMonitorAlertSummary(rows || []);
void refreshMacroRiskBanner(rows || []);
@@ -1923,6 +1926,46 @@
syncHubAiMobileViewport();
}
function syncHubPhoneFolds() {
const phone = isMobileLayout();
["monitor-ops-fold", "market-toolbar-fold"].forEach((id) => {
const el = document.getElementById(id);
if (!el) return;
if (!phone) {
el.open = true;
return;
}
// 手机默认折叠;用户手动点开后本会话不再强关
if (el.dataset.userToggled === "1") return;
el.open = false;
});
}
function bindHubPhoneFolds() {
["monitor-ops-fold", "market-toolbar-fold"].forEach((id) => {
const el = document.getElementById(id);
if (!el || el.dataset.boundFold === "1") return;
el.dataset.boundFold = "1";
el.addEventListener("toggle", () => {
if (!isMobileLayout()) return;
el.dataset.userToggled = "1";
});
});
const sym = document.getElementById("market-symbol");
const tf = document.getElementById("market-timeframe");
const syncMarketFoldMeta = () => {
const meta = document.getElementById("market-fold-summary");
if (!meta) return;
const s = sym ? String(sym.value || "").trim() : "";
const t = tf ? String(tf.value || "").trim() : "";
meta.textContent = [s || "—", t || "—"].join(" · ");
};
if (sym) sym.addEventListener("change", syncMarketFoldMeta);
if (sym) sym.addEventListener("input", syncMarketFoldMeta);
if (tf) tf.addEventListener("change", syncMarketFoldMeta);
syncMarketFoldMeta();
}
function initHubMobileChrome() {
const moreBtn = document.getElementById("m-tab-more");
const backdrop = document.getElementById("hub-mobile-more-backdrop");
@@ -1940,7 +1983,9 @@
closeHubMobileMore();
}
});
bindHubPhoneFolds();
syncHubPhoneShellClass();
syncHubPhoneFolds();
}
function bindHubSpaNavLinks(selector) {
@@ -1972,6 +2017,7 @@
resizeTimer = setTimeout(() => {
const nowMobile = isMobileLayout();
syncHubPhoneShellClass();
syncHubPhoneFolds();
if (!nowMobile) closeHubMobileMore();
if (lastMonitorRows.length && nowMobile !== wasMobile) {
wasMobile = nowMobile;
@@ -3541,21 +3587,37 @@
}
function renderCardStrategyStats(row, hm, flaskOk) {
if (!flaskOk || !hm || typeof hm !== "object") return "";
const caps = row.capabilities || [];
const chips = [];
if (caps.includes("key")) {
const kc = countKeyMonitorsByBucket(hm.keys || []);
if (kc.breakout > 0) chips.push({ kind: "key-breakout", label: `突破 ${kc.breakout}` });
if (kc.fib > 0) chips.push({ kind: "key-breakout", label: `斐波 ${kc.fib}` });
if (kc.watch > 0) chips.push({ kind: "key-watch", label: `监控 ${kc.watch}` });
if (flaskOk && hm && typeof hm === "object") {
if (caps.includes("key")) {
const kc = countKeyMonitorsByBucket(hm.keys || []);
if (kc.breakout > 0) chips.push({ kind: "key-breakout", label: `突破 ${kc.breakout}` });
if (kc.fib > 0) chips.push({ kind: "key-breakout", label: `斐波 ${kc.fib}` });
if (kc.watch > 0) chips.push({ kind: "key-watch", label: `监控 ${kc.watch}` });
}
if (caps.includes("trend")) {
const trendN = Array.isArray(hm.trends) ? hm.trends.length : 0;
if (trendN > 0) chips.push({ kind: "trend", label: `趋势回调 ${trendN}` });
}
const rollN = Array.isArray(hm.rolls) ? hm.rolls.length : 0;
if (rollN > 0) chips.push({ kind: "roll", label: `顺势加仓 ${rollN}` });
}
if (caps.includes("trend")) {
const trendN = Array.isArray(hm.trends) ? hm.trends.length : 0;
if (trendN > 0) chips.push({ kind: "trend", label: `趋势回调 ${trendN}` });
// OKX/已勾选期权能力:磁贴始终展示期权入口态(含 0 仓)
if (caps.includes("options") || rowHasOptionsLayout(row)) {
const opt = row.options || {};
if (opt.enabled === false) {
chips.push({ kind: "options", label: "期权 · 未启用" });
} else if (opt.ok === false) {
chips.push({ kind: "options", label: "期权 · 异常" });
} else {
const n = Number(
opt.position_count != null ? opt.position_count : (opt.positions || []).length
);
const nSafe = Number.isFinite(n) ? n : 0;
chips.push({ kind: "options", label: nSafe > 0 ? `期权 ${nSafe}` : "期权" });
}
}
const rollN = Array.isArray(hm.rolls) ? hm.rolls.length : 0;
if (rollN > 0) chips.push({ kind: "roll", label: `顺势加仓 ${rollN}` });
if (!chips.length) return "";
return `<div class="card-strategy-stats">${chips
.map(
@@ -4110,12 +4172,33 @@
const posLine =
openCount > 0 ? `${openCount}仓 · ${alert.summary}` : alert.summary;
const opt = row.options || {};
const optCount =
opt.enabled !== false && opt.ok !== false
? Number(opt.position_count != null ? opt.position_count : (opt.positions || []).length)
: 0;
const posLineWithOpt =
optCount > 0 ? `${posLine} · 期权${optCount}` : posLine;
const hasOptCap = (row.capabilities || []).includes("options") || rowHasOptionsLayout(row);
let optLine = "";
let pnlShow = upnl;
let pnlSuffix = "";
if (hasOptCap) {
if (opt.enabled === false) {
optLine = "期权未启用";
} else if (opt.ok === false) {
optLine = "期权异常";
} else {
const optCount = Number(
opt.position_count != null ? opt.position_count : (opt.positions || []).length
);
const n = Number.isFinite(optCount) ? optCount : 0;
const bal = typeof optionsBalanceFields === "function" ? optionsBalanceFields(opt) : {};
const optUpl = bal && bal.upl != null ? bal.upl : null;
optLine = n > 0 ? `期权 ${n}` : "期权 空仓";
if (optUpl != null && Number.isFinite(Number(optUpl))) {
optLine += ` · 浮盈 ${fmt(optUpl, 2)}U`;
// 永续空仓时主数字优先展示期权浮盈,避免一直显示 0U
if (openCount === 0) {
pnlShow = optUpl;
pnlSuffix = "期权";
}
}
}
}
const hm = row.hub_monitor || {};
const flaskOk = row.flask_ok !== false && hm.ok !== false;
const strategyStats = renderCardStrategyStats(row, hm, flaskOk);
@@ -4128,10 +4211,13 @@
</div>
${
showAccountPnlPref()
? `<div class="hub-tile-pnl ${pnlCls(upnl)}">${fmt(upnl, 2)} <small>U</small></div>`
? `<div class="hub-tile-pnl ${pnlCls(pnlShow)}">${fmt(pnlShow, 2)} <small>U${
pnlSuffix ? " · " + pnlSuffix : ""
}</small></div>`
: ""
}
<div class="hub-tile-meta">${esc(posLineWithOpt)}</div>
<div class="hub-tile-meta">${esc(posLine)}</div>
${optLine ? `<div class="hub-tile-meta hub-tile-opt">${esc(optLine)}</div>` : ""}
${strategyStats}
<div class="hub-tile-foot">UPD ${esc(tsShort)}</div>
</div>