diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 2261877..e8f0cea 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -4035,6 +4035,38 @@ body.login-page { } } +/* —— 可折叠条:桌面/平板始终展开(隐藏 summary) —— */ +.hub-m-fold { + margin: 0 0 12px; + border: none; + background: transparent; + padding: 0; +} + +.hub-m-fold > .hub-m-fold-summary { + display: none; + list-style: none; +} + +.hub-m-fold > .hub-m-fold-summary::-webkit-details-marker { + display: none; +} + +.calc-mobile-tabs { + display: none; +} + +.card-stat-chip.card-stat-options { + border-color: rgba(0, 212, 255, 0.35); + color: var(--accent, #6ea8ff); + background: rgba(0, 212, 255, 0.08); +} + +.hub-tile-opt { + color: var(--accent, #6ea8ff) !important; + font-weight: 500; +} + /* —— 手机壳:底栏四件套(仅 ≤720px;桌面/平板完全不进此规则) —— */ .hub-mobile-tabbar, .hub-mobile-more { @@ -4370,6 +4402,125 @@ body.login-page { font-size: 13px; } + /* 手机折叠条 */ + body.hub-phone .hub-m-fold { + margin: 0 0 10px; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--panel); + overflow: hidden; + } + + body.hub-phone .hub-m-fold > .hub-m-fold-summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 10px 12px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + color: var(--text); + -webkit-tap-highlight-color: transparent; + } + + body.hub-phone .hub-m-fold > .hub-m-fold-summary::after { + content: "▾"; + color: var(--muted); + font-size: 12px; + transition: transform 0.15s; + } + + body.hub-phone .hub-m-fold[open] > .hub-m-fold-summary::after { + transform: rotate(180deg); + } + + body.hub-phone .hub-m-fold-meta { + flex: 1 1 auto; + min-width: 0; + text-align: right; + font-size: 10px; + font-weight: 500; + font-family: var(--mono); + color: var(--muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + body.hub-phone .hub-m-fold .toolbar, + body.hub-phone .hub-m-fold .market-toolbar { + margin-bottom: 0; + border: none; + border-top: 1px solid var(--border-soft); + border-radius: 0; + box-shadow: none; + } + + /* 计算器手机 Tab */ + body.hub-phone .calc-mobile-tabs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; + margin: 0 0 10px; + } + + body.hub-phone .calc-m-tab { + min-height: 38px; + border: 1px solid var(--border-soft); + border-radius: 10px; + background: var(--nav-bg, rgba(255, 255, 255, 0.03)); + color: var(--muted); + font: inherit; + font-size: 13px; + font-weight: 600; + cursor: pointer; + } + + body.hub-phone .calc-m-tab.is-active { + color: var(--accent, #6ea8ff); + border-color: color-mix(in srgb, var(--accent, #6ea8ff) 45%, var(--border-soft)); + background: var(--accent-dim, rgba(110, 168, 255, 0.12)); + } + + body.hub-phone .calc-layout[data-calc-tab="trend"] [data-calc-pane="roll"], + body.hub-phone .calc-layout[data-calc-tab="roll"] [data-calc-pane="trend"] { + display: none; + } + + body.hub-phone .calc-layout { + grid-template-columns: 1fr; + } + + body.hub-phone #page-calculator .calc-card h2 { + display: none; + } + + /* 行情全屏:竖屏提示转横;横屏吃满 */ + @media (orientation: portrait) { + body.hub-phone.market-chart-fs-open .market-chart-wrap.is-fullscreen::before { + content: "全屏看图请横持手机"; + position: absolute; + top: max(10px, env(safe-area-inset-top)); + left: 50%; + transform: translateX(-50%); + z-index: 5; + padding: 6px 12px; + border-radius: 999px; + background: rgba(0, 0, 0, 0.72); + color: #fff; + font-size: 12px; + pointer-events: none; + } + } + + @media (orientation: landscape) { + body.hub-phone.market-chart-fs-open .app-header, + body.hub-phone.market-chart-fs-open .hub-mobile-tabbar { + display: none !important; + } + } + .hub-mobile-tabbar { display: flex; position: fixed; diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 1c5930c..ffcd258 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -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 `