fix(hub): keep mobile PWA AI navigation visible

Fix keyboard detection in standalone app so top nav and AI tabs stay visible; recognize PWA display mode for mobile layout.
This commit is contained in:
dekun
2026-06-11 11:35:44 +08:00
parent 08ae171e48
commit 51252d5dda
3 changed files with 45 additions and 16 deletions
+29 -8
View File
@@ -3884,7 +3884,7 @@ body.hub-page-ai #page-ai {
} }
/* 手机 AI:须在 .ai-layout 双列定义之后,避免被覆盖成半屏 */ /* 手机 AI:须在 .ai-layout 双列定义之后,避免被覆盖成半屏 */
@media (max-width: 720px) { @media (max-width: 720px), ((display-mode: standalone) and (max-width: 960px)) {
html:has(body.hub-page-ai) { html:has(body.hub-page-ai) {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
@@ -3922,19 +3922,35 @@ body.hub-page-ai #page-ai {
font-size: 11px; font-size: 11px;
} }
body.hub-page-ai.hub-ai-keyboard-open .app-header, body.hub-page-ai .app-header .brand {
body.hub-page-ai.hub-ai-keyboard-open #page-ai .page-head {
display: none; display: none;
} }
body.hub-page-ai.hub-ai-keyboard-open .ai-mobile-tabs, body.hub-page-ai .header-right {
body.hub-page-ai.hub-ai-keyboard-open .ai-chat-topbar, grid-template-columns: 1fr auto auto;
body.hub-page-ai.hub-ai-keyboard-open .ai-chat-session-head { grid-template-rows: auto;
}
body.hub-page-ai .header-right .top-nav {
grid-column: 1 / -1;
order: 2;
}
body.hub-page-ai.hub-ai-keyboard-open .app-header .theme-toggle,
body.hub-page-ai.hub-ai-keyboard-open .app-header .sys-pill,
body.hub-page-ai.hub-ai-keyboard-open .app-header #btn-logout {
display: none; display: none;
} }
body.hub-page-ai.hub-ai-keyboard-open .ai-mobile-tabs { body.hub-page-ai.hub-ai-keyboard-open .app-header {
margin-bottom: 4px; padding: 4px 0;
margin-bottom: 0;
}
body.hub-page-ai.hub-ai-keyboard-open .top-nav a {
min-height: 30px;
padding: 4px 8px;
font-size: 10px;
} }
body.hub-page-ai #page-ai { body.hub-page-ai #page-ai {
@@ -3950,6 +3966,11 @@ body.hub-page-ai #page-ai {
margin-bottom: 6px; margin-bottom: 6px;
flex-shrink: 0; flex-shrink: 0;
width: 100%; width: 100%;
position: sticky;
top: 0;
z-index: 12;
padding: 4px 0 2px;
background: var(--bg);
} }
body.hub-page-ai .ai-mobile-tab { body.hub-page-ai .ai-mobile-tab {
+13 -5
View File
@@ -900,7 +900,10 @@
} }
function isMobileLayout() { function isMobileLayout() {
return window.matchMedia("(max-width: 720px)").matches; if (window.matchMedia("(max-width: 720px)").matches) return true;
if (window.matchMedia("(display-mode: standalone)").matches) return true;
if (window.navigator && window.navigator.standalone === true) return true;
return false;
} }
function positionHasContracts(p) { function positionHasContracts(p) {
@@ -1107,7 +1110,6 @@
let syncHubAiMobileViewport = () => {}; let syncHubAiMobileViewport = () => {};
function initHubAiMobileViewport() { function initHubAiMobileViewport() {
const mq = window.matchMedia("(max-width: 720px)");
const shell = document.querySelector(".app-shell"); const shell = document.querySelector(".app-shell");
const chatInput = document.getElementById("ai-chat-input"); const chatInput = document.getElementById("ai-chat-input");
if (!shell || !window.visualViewport) { if (!shell || !window.visualViewport) {
@@ -1115,6 +1117,8 @@
return; return;
} }
let baselineInnerH = Math.max(window.innerHeight, window.visualViewport.height || 0);
const scrollChatToEnd = () => { const scrollChatToEnd = () => {
const box = document.getElementById("ai-chat-messages"); const box = document.getElementById("ai-chat-messages");
if (box) requestAnimationFrame(() => { box.scrollTop = box.scrollHeight; }); if (box) requestAnimationFrame(() => { box.scrollTop = box.scrollHeight; });
@@ -1122,7 +1126,7 @@
syncHubAiMobileViewport = () => { syncHubAiMobileViewport = () => {
const onAi = document.body.classList.contains("hub-page-ai"); const onAi = document.body.classList.contains("hub-page-ai");
if (!onAi || !mq.matches) { if (!onAi || !isMobileLayout()) {
shell.style.removeProperty("height"); shell.style.removeProperty("height");
shell.style.removeProperty("max-height"); shell.style.removeProperty("max-height");
shell.style.removeProperty("width"); shell.style.removeProperty("width");
@@ -1135,14 +1139,18 @@
const h = Math.max(240, Math.round(vv.height)); const h = Math.max(240, Math.round(vv.height));
const top = Math.round(vv.offsetTop || 0); const top = Math.round(vv.offsetTop || 0);
const left = Math.round(vv.offsetLeft || 0); const left = Math.round(vv.offsetLeft || 0);
const inputFocused = !!(chatInput && document.activeElement === chatInput);
if (!inputFocused) {
baselineInnerH = Math.max(baselineInnerH, window.innerHeight, h);
}
document.documentElement.style.setProperty("--hub-vvh", `${h}px`); document.documentElement.style.setProperty("--hub-vvh", `${h}px`);
shell.style.height = `${h}px`; shell.style.height = `${h}px`;
shell.style.maxHeight = `${h}px`; shell.style.maxHeight = `${h}px`;
shell.style.width = `${Math.round(vv.width)}px`; shell.style.width = `${Math.round(vv.width)}px`;
shell.style.transform = shell.style.transform =
top > 0 || left > 0 ? `translate(${left}px, ${top}px)` : ""; top > 0 || left > 0 ? `translate(${left}px, ${top}px)` : "";
const keyboardLikely = const viewportShrunk = h < baselineInnerH * 0.72;
top > 0 || h < window.innerHeight * 0.82 || document.activeElement === chatInput; const keyboardLikely = inputFocused && (viewportShrunk || top > 48);
document.body.classList.toggle("hub-ai-keyboard-open", keyboardLikely); document.body.classList.toggle("hub-ai-keyboard-open", keyboardLikely);
}; };
+3 -3
View File
@@ -15,7 +15,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" /> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript> <noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
<link rel="stylesheet" href="/assets/app.css?v=20260612-hub-ai-one-screen" /> <link rel="stylesheet" href="/assets/app.css?v=20260612-hub-ai-pwa-nav" />
<link rel="stylesheet" href="/assets/dashboard.css?v=20260612-dash-monitor-count" /> <link rel="stylesheet" href="/assets/dashboard.css?v=20260612-dash-monitor-count" />
</head> </head>
<body> <body>
@@ -449,7 +449,7 @@
<button type="button" class="ai-mobile-tab" data-ai-tab="history" role="tab" aria-selected="false">历史</button> <button type="button" class="ai-mobile-tab" data-ai-tab="history" role="tab" aria-selected="false">历史</button>
<button type="button" class="ai-mobile-tab ai-mobile-tab-action" data-ai-tab="new" role="tab" aria-selected="false" title="新开对话">新开</button> <button type="button" class="ai-mobile-tab ai-mobile-tab-action" data-ai-tab="new" role="tab" aria-selected="false" title="新开对话">新开</button>
</div> </div>
<div class="ai-layout" data-ai-mobile-tab="chat"> <div class="ai-layout" data-ai-mobile-tab="trading">
<section class="ai-panel ai-chat-panel" data-ai-panel="chat"> <section class="ai-panel ai-chat-panel" data-ai-panel="chat">
<div class="ai-chat-topbar"> <div class="ai-chat-topbar">
<div class="ai-bot-bar" role="tablist" aria-label="聊天机器人"> <div class="ai-bot-bar" role="tablist" aria-label="聊天机器人">
@@ -557,6 +557,6 @@
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script> <script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script> <script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
<script src="/assets/ai_review_render.js?v=2"></script> <script src="/assets/ai_review_render.js?v=2"></script>
<script src="/assets/app.js?v=20260612-hub-monitor-expand"></script> <script src="/assets/app.js?v=20260612-hub-ai-pwa-nav"></script>
</body> </body>
</html> </html>