From bfb5e22be90acb036886035635015a264d65000a Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 15 Jul 2026 22:40:50 +0800 Subject: [PATCH] Add phone-only hub shell with four bottom tabs. Keep desktop/tablet top nav unchanged; at max-width 720px use monitor/market/calculator/AI plus a More sheet for secondary pages. Co-authored-by: Cursor --- manual_trading_hub/docs/help/02-hub-nav.md | 4 + manual_trading_hub/static/app.css | 259 +++++++++++++++++++++ manual_trading_hub/static/app.js | 144 +++++++++--- manual_trading_hub/static/index.html | 32 ++- 4 files changed, 405 insertions(+), 34 deletions(-) diff --git a/manual_trading_hub/docs/help/02-hub-nav.md b/manual_trading_hub/docs/help/02-hub-nav.md index 6209493..58bf545 100644 --- a/manual_trading_hub/docs/help/02-hub-nav.md +++ b/manual_trading_hub/docs/help/02-hub-nav.md @@ -17,6 +17,10 @@ | **系统日志** | 中控与三实例 PM2 日志(排错用,非部署说明) | | **系统设置** | 中控密码、导航显示、交易所地址、监控能力勾选 | +## 手机端(≤720px) + +底栏固定四项:**监控 / 行情 / 计算 / AI**;其余入口进 **更多**。电脑与平板仍用顶栏,布局不变。 + ## 默认首页 登录后默认进入 **监控区**(`/monitor`)。 diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index a23fdce..006d496 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -4034,6 +4034,265 @@ body.login-page { } } +/* —— 手机壳:底栏四件套(仅 ≤720px;桌面/平板完全不进此规则) —— */ +.hub-mobile-tabbar, +.hub-mobile-more { + display: none; +} + +@media (max-width: 720px) { + :root { + --hub-m-tabbar-h: 56px; + } + + body.hub-phone .app-header .top-nav { + display: none !important; + } + + body.hub-phone .header-right { + grid-template-columns: 1fr auto auto; + grid-template-rows: auto; + } + + body.hub-phone .app-shell { + padding-bottom: calc(var(--hub-m-tabbar-h) + max(10px, env(safe-area-inset-bottom))); + } + + body.hub-phone .page-head h1 { + font-size: 1.05rem; + } + + body.hub-phone .page-head .head-tag { + font-size: 0.62rem; + } + + /* 监控:工具条单列优先 */ + body.hub-phone #page-monitor .toolbar { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + align-items: center; + } + + body.hub-phone #page-monitor .toolbar .primary { + grid-column: 1; + } + + body.hub-phone #page-monitor .toolbar .danger { + grid-column: 2; + } + + body.hub-phone #page-monitor .toolbar .chk-label { + grid-column: 1 / -1; + font-size: 11px; + } + + body.hub-phone #page-monitor .toolbar .toolbar-spacer { + display: none; + } + + body.hub-phone #page-monitor .toolbar .toolbar-meta { + grid-column: 1 / -1; + } + + body.hub-phone #page-monitor .host-status-panel { + margin-bottom: 8px; + } + + /* 行情:减噪,图表吃高度 */ + body.hub-phone #page-market .page-head { + margin-bottom: 4px; + } + + body.hub-phone #page-market .market-chart-wrap { + min-height: min(52vh, 420px); + } + + body.hub-phone #page-market .market-ohlcv-title .market-day-split-opt { + font-size: 11px; + } + + /* 计算器:卡片间距收紧 */ + body.hub-phone #page-calculator .calc-layout { + gap: 12px; + } + + body.hub-phone #page-calculator .calc-card { + padding: 14px 12px; + } + + body.hub-phone #page-calculator .calc-hint { + font-size: 11px; + } + + .hub-mobile-tabbar { + display: flex; + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 80; + height: calc(var(--hub-m-tabbar-h) + env(safe-area-inset-bottom)); + padding: 0 max(8px, env(safe-area-inset-right)) env(safe-area-inset-bottom) + max(8px, env(safe-area-inset-left)); + align-items: stretch; + justify-content: space-around; + gap: 2px; + background: color-mix(in srgb, var(--panel, #12161f) 92%, transparent); + border-top: 1px solid var(--border-soft); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + box-sizing: border-box; + } + + .hub-mobile-tabbar .hub-m-tab.nav-hidden { + display: none !important; + } + + .hub-m-tab { + flex: 1 1 0; + min-width: 0; + display: inline-flex; + align-items: center; + justify-content: center; + margin: 6px 2px; + padding: 0 4px; + border: none; + border-radius: 10px; + background: transparent; + color: var(--nav-link-idle, var(--muted)); + font: inherit; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.04em; + text-decoration: none; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + } + + .hub-m-tab:hover, + .hub-m-tab:focus-visible { + color: var(--text); + background: var(--nav-link-hover-bg, rgba(255, 255, 255, 0.04)); + outline: none; + } + + .hub-m-tab.active { + color: var(--accent, #6ea8ff); + background: var(--accent-dim, rgba(110, 168, 255, 0.12)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, #6ea8ff) 35%, transparent); + } + + body.hub-phone #toast { + bottom: calc(var(--hub-m-tabbar-h) + max(12px, env(safe-area-inset-bottom))); + } + + /* 更多抽屉 */ + body.hub-mobile-more-open .hub-mobile-more { + display: block; + } + + .hub-mobile-more { + position: fixed; + inset: 0; + z-index: 90; + } + + .hub-mobile-more-backdrop { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.45); + } + + .hub-mobile-more-sheet { + position: absolute; + left: 0; + right: 0; + bottom: 0; + max-height: min(78vh, 560px); + overflow: auto; + padding: 10px 16px calc(16px + env(safe-area-inset-bottom)); + border-radius: 16px 16px 0 0; + background: var(--panel, #12161f); + border: 1px solid var(--border-soft); + border-bottom: none; + box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.35); + } + + .hub-mobile-more-handle { + width: 36px; + height: 4px; + margin: 2px auto 12px; + border-radius: 999px; + background: var(--border); + } + + .hub-mobile-more-title { + margin: 0 0 4px; + font-size: 1rem; + } + + .hub-mobile-more-hint { + margin: 0 0 14px; + font-size: 11px; + color: var(--muted); + } + + .hub-mobile-more-nav { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + } + + .hub-mobile-more-nav a { + display: flex; + align-items: center; + justify-content: center; + min-height: 44px; + padding: 10px 8px; + border-radius: 10px; + border: 1px solid var(--border-soft); + background: var(--nav-bg, rgba(255, 255, 255, 0.03)); + color: var(--text); + text-decoration: none; + font-size: 13px; + font-weight: 500; + } + + .hub-mobile-more-nav a.nav-hidden { + display: none !important; + } + + .hub-mobile-more-nav a.active { + border-color: color-mix(in srgb, var(--accent, #6ea8ff) 45%, var(--border-soft)); + background: var(--accent-dim, rgba(110, 168, 255, 0.12)); + color: var(--accent, #6ea8ff); + } + + .hub-mobile-more-close { + width: 100%; + margin-top: 14px; + min-height: 44px; + } + + /* AI 页为底栏让位;键盘打开时藏底栏 */ + body.hub-page-ai.hub-phone .app-shell { + padding-bottom: calc(var(--hub-m-tabbar-h) + max(8px, env(safe-area-inset-bottom))); + } + + body.hub-page-ai.hub-phone.hub-ai-keyboard-open .hub-mobile-tabbar { + display: none; + } + + body.hub-page-ai.hub-phone.hub-ai-keyboard-open .app-shell { + padding-bottom: max(8px, env(safe-area-inset-bottom)); + } + + body.hub-page-ai.hub-phone .app-header .top-nav { + display: none !important; + } +} + @media (max-width: 480px) { body { font-size: 12px; diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 31cce3d..cf6a547 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -51,24 +51,69 @@ function syncNavVisibility(data) { const d = (data && data.display) || {}; - const navFunds = document.getElementById("nav-funds"); - const navDash = document.getElementById("nav-dashboard"); - const navPlan = document.getElementById("nav-plan"); - const navArchive = document.getElementById("nav-archive"); - const navAi = document.getElementById("nav-ai"); - const navCalc = document.getElementById("nav-calculator"); - const navStrategy = document.getElementById("nav-strategy"); - const navHelp = document.getElementById("nav-help"); - const navLogs = document.getElementById("nav-logs"); - if (navFunds) navFunds.classList.toggle("nav-hidden", d.show_nav_funds === false); - if (navDash) navDash.classList.toggle("nav-hidden", d.show_nav_dashboard === false); - if (navPlan) navPlan.classList.toggle("nav-hidden", d.show_nav_plan === false); - if (navArchive) navArchive.classList.toggle("nav-hidden", d.show_nav_archive === false); - if (navAi) navAi.classList.toggle("nav-hidden", d.show_nav_ai === false); - if (navCalc) navCalc.classList.toggle("nav-hidden", d.show_nav_calculator === false); - if (navStrategy) navStrategy.classList.toggle("nav-hidden", d.show_nav_strategy === false); - if (navHelp) navHelp.classList.toggle("nav-hidden", d.show_nav_help === false); - if (navLogs) navLogs.classList.toggle("nav-hidden", d.show_nav_logs === false); + const pairs = [ + ["nav-funds", "m-nav-funds", d.show_nav_funds === false], + ["nav-dashboard", "m-nav-dashboard", d.show_nav_dashboard === false], + ["nav-plan", "m-nav-plan", d.show_nav_plan === false], + ["nav-archive", "m-nav-archive", d.show_nav_archive === false], + ["nav-ai", "m-tab-ai", d.show_nav_ai === false], + ["nav-calculator", "m-tab-calculator", d.show_nav_calculator === false], + ["nav-strategy", "m-nav-strategy", d.show_nav_strategy === false], + ["nav-help", "m-nav-help", d.show_nav_help === false], + ["nav-logs", "m-nav-logs", d.show_nav_logs === false], + ]; + pairs.forEach(([desktopId, mobileId, hide]) => { + const a = document.getElementById(desktopId); + const b = document.getElementById(mobileId); + if (a) a.classList.toggle("nav-hidden", hide); + if (b) b.classList.toggle("nav-hidden", hide); + }); + } + + const HUB_PHONE_PRIMARY = { monitor: 1, market: 1, calculator: 1, ai: 1 }; + + function syncHubPhoneShellClass() { + document.body.classList.toggle("hub-phone", isMobileLayout()); + } + + function closeHubMobileMore() { + document.body.classList.remove("hub-mobile-more-open"); + const more = document.getElementById("hub-mobile-more"); + const btn = document.getElementById("m-tab-more"); + if (more) more.setAttribute("aria-hidden", "true"); + if (btn) btn.setAttribute("aria-expanded", "false"); + syncHubMobileTabActive(currentPage()); + } + + function openHubMobileMore() { + if (!isMobileLayout()) return; + document.body.classList.add("hub-mobile-more-open"); + const more = document.getElementById("hub-mobile-more"); + const btn = document.getElementById("m-tab-more"); + if (more) more.setAttribute("aria-hidden", "false"); + if (btn) btn.setAttribute("aria-expanded", "true"); + syncHubMobileTabActive(currentPage()); + } + + function toggleHubMobileMore() { + if (document.body.classList.contains("hub-mobile-more-open")) closeHubMobileMore(); + else openHubMobileMore(); + } + + function syncHubMobileTabActive(page) { + const primary = !!HUB_PHONE_PRIMARY[page]; + const moreOpen = document.body.classList.contains("hub-mobile-more-open"); + document.querySelectorAll(".hub-mobile-tabbar .hub-m-tab").forEach((el) => { + const tab = el.getAttribute("data-hub-tab") || ""; + let on = false; + if (tab === "more") on = moreOpen || !primary; + else on = !moreOpen && tab === page; + el.classList.toggle("active", on); + }); + document.querySelectorAll(".hub-mobile-more-nav a").forEach((a) => { + const href = (a.getAttribute("href") || "").split("?")[0]; + a.classList.toggle("active", href === "/" + page); + }); } function pageNavAllowed(page) { @@ -1260,6 +1305,9 @@ document.body.classList.toggle("hub-page-ai", page === "ai"); document.body.classList.toggle("hub-page-funds", page === "funds"); document.body.classList.toggle("hub-page-dashboard", page === "dashboard"); + syncHubPhoneShellClass(); + if (HUB_PHONE_PRIMARY[page]) closeHubMobileMore(); + syncHubMobileTabActive(page); syncHubAiMobileViewport(); if (page === "monitor") startMonitorPoll(); else stopMonitorPoll(); @@ -1876,19 +1924,61 @@ syncHubAiMobileViewport(); } + function initHubMobileChrome() { + const moreBtn = document.getElementById("m-tab-more"); + const backdrop = document.getElementById("hub-mobile-more-backdrop"); + const closeBtn = document.getElementById("hub-mobile-more-close"); + if (moreBtn) { + moreBtn.addEventListener("click", (ev) => { + ev.preventDefault(); + toggleHubMobileMore(); + }); + } + if (backdrop) backdrop.addEventListener("click", closeHubMobileMore); + if (closeBtn) closeBtn.addEventListener("click", closeHubMobileMore); + document.addEventListener("keydown", (ev) => { + if (ev.key === "Escape" && document.body.classList.contains("hub-mobile-more-open")) { + closeHubMobileMore(); + } + }); + syncHubPhoneShellClass(); + } + + function bindHubSpaNavLinks(selector) { + document.querySelectorAll(selector).forEach((a) => { + a.addEventListener("click", (ev) => { + const href = a.getAttribute("href"); + if (!href || ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey) return; + ev.preventDefault(); + closeHubMobileMore(); + const path = href.split("?")[0]; + if (path === window.location.pathname) { + setActiveNav(); + return; + } + history.pushState({}, "", href); + setActiveNav(); + }); + }); + } + function initMobileLayout() { initAiMobileTabs(); initHubAiMobileViewport(); + initHubMobileChrome(); let resizeTimer = null; let wasMobile = isMobileLayout(); window.addEventListener("resize", () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { const nowMobile = isMobileLayout(); + syncHubPhoneShellClass(); + if (!nowMobile) closeHubMobileMore(); if (lastMonitorRows.length && nowMobile !== wasMobile) { wasMobile = nowMobile; renderMonitorGrid(lastMonitorRows); updateMonitorAlertSummary(lastMonitorRows); + syncHubMobileTabActive(currentPage()); return; } wasMobile = nowMobile; @@ -1901,6 +1991,7 @@ }); updateMonitorAlertSummary(lastMonitorRows); } + syncHubMobileTabActive(currentPage()); }, 120); }); } @@ -5601,20 +5692,9 @@ } function initShellNav() { - document.querySelectorAll(".top-nav a[href^='/']").forEach((a) => { - a.addEventListener("click", (ev) => { - const href = a.getAttribute("href"); - if (!href || ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey) return; - ev.preventDefault(); - const path = href.split("?")[0]; - if (path === window.location.pathname) { - setActiveNav(); - return; - } - history.pushState({}, "", href); - setActiveNav(); - }); - }); + bindHubSpaNavLinks(".top-nav a[href^='/']"); + bindHubSpaNavLinks(".hub-mobile-tabbar a.hub-m-tab[href^='/']"); + bindHubSpaNavLinks(".hub-mobile-more-nav a[href^='/']"); window.addEventListener("popstate", setActiveNav); } diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 27a2e24..b0f77bb 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -15,7 +15,7 @@ - + @@ -1206,6 +1206,34 @@ + + + +