feat(HEL-72): 手机端底部五入口导航栏改版
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
+137
-62
@@ -74,68 +74,8 @@ body {
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-entry-list {
|
#m-app[data-tabbar="true"] .m-view {
|
||||||
list-style: none;
|
padding-bottom: calc(var(--mobile-tabbar-height) + var(--mobile-safe-bottom) + var(--mobile-page-padding));
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-entry {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 64px;
|
|
||||||
padding: 0 12px;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--surface);
|
|
||||||
color: var(--text-primary);
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: var(--elevation-card);
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-entry:active {
|
|
||||||
background: var(--surface-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-entry-icon {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
margin-right: 12px;
|
|
||||||
color: var(--action);
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-entry-body {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-entry-body strong {
|
|
||||||
font-size: var(--font-size-card-title);
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-entry-body small {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-entry-chevron {
|
|
||||||
display: inline-flex;
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-placeholder {
|
.m-placeholder {
|
||||||
@@ -337,3 +277,138 @@ body {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-tabbar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: var(--m-keyboard-inset);
|
||||||
|
z-index: 60;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
height: calc(var(--mobile-tabbar-height) + var(--mobile-safe-bottom));
|
||||||
|
padding-bottom: var(--mobile-safe-bottom);
|
||||||
|
background: var(--surface);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-tabbar-item {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: var(--mobile-tabbar-height);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 3px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-tabbar-item:active {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-tabbar-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-tabbar-label {
|
||||||
|
font-size: var(--mobile-tabbar-label-size);
|
||||||
|
line-height: 1.2;
|
||||||
|
color: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-tabbar-item.active {
|
||||||
|
color: var(--action);
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-theme-section {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-theme-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--surface);
|
||||||
|
box-shadow: var(--elevation-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-theme-row-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--action-soft);
|
||||||
|
color: var(--action);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-theme-row-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-theme-row-body strong {
|
||||||
|
font-size: var(--font-size-card-title);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-theme-row-body small {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-theme-switch {
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 48px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--border-strong);
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-theme-switch[aria-checked="true"] {
|
||||||
|
background: var(--action);
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-theme-switch-thumb {
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--text-inverse);
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-theme-switch[aria-checked="true"] .m-theme-switch-thumb {
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,9 @@
|
|||||||
--mobile-icon-cell: 78px;
|
--mobile-icon-cell: 78px;
|
||||||
--mobile-sheet-radius: 12px;
|
--mobile-sheet-radius: 12px;
|
||||||
--mobile-min-width: 320px;
|
--mobile-min-width: 320px;
|
||||||
|
--mobile-tabbar-height: 56px;
|
||||||
|
--mobile-tabbar-label-size: 11px;
|
||||||
|
--m-keyboard-inset: 0px;
|
||||||
|
|
||||||
--font-size-aux: 11.5px;
|
--font-size-aux: 11.5px;
|
||||||
--font-size-caption: 12.5px;
|
--font-size-caption: 12.5px;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
<div id="m-actions" class="m-actions"></div>
|
<div id="m-actions" class="m-actions"></div>
|
||||||
</header>
|
</header>
|
||||||
<main id="m-view" class="m-view"></main>
|
<main id="m-view" class="m-view"></main>
|
||||||
|
<nav id="m-tabbar" class="m-tabbar" aria-label="主导航" hidden></nav>
|
||||||
</div>
|
</div>
|
||||||
<script src="config/nav.config.js"></script>
|
<script src="config/nav.config.js"></script>
|
||||||
<script src="js/api.js"></script>
|
<script src="js/api.js"></script>
|
||||||
|
|||||||
+22
-1
@@ -32,8 +32,29 @@
|
|||||||
|
|
||||||
global.MobileTheme = { readTheme: readTheme, applyTheme: applyTheme, toggle: toggle };
|
global.MobileTheme = { readTheme: readTheme, applyTheme: applyTheme, toggle: toggle };
|
||||||
|
|
||||||
|
function syncKeyboardInset() {
|
||||||
|
const app = document.getElementById("m-app");
|
||||||
|
const visual = global.visualViewport;
|
||||||
|
if (!visual) {
|
||||||
|
app.style.setProperty("--m-keyboard-inset", "0px");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const inset = Math.max(0, global.innerHeight - visual.height - visual.offsetTop);
|
||||||
|
app.style.setProperty("--m-keyboard-inset", inset + "px");
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindViewport() {
|
||||||
|
const visual = global.visualViewport;
|
||||||
|
if (visual) {
|
||||||
|
visual.addEventListener("resize", syncKeyboardInset);
|
||||||
|
visual.addEventListener("scroll", syncKeyboardInset);
|
||||||
|
}
|
||||||
|
syncKeyboardInset();
|
||||||
|
}
|
||||||
|
|
||||||
async function boot() {
|
async function boot() {
|
||||||
applyTheme(readTheme());
|
applyTheme(readTheme());
|
||||||
|
bindViewport();
|
||||||
let session = { authenticated: false };
|
let session = { authenticated: false };
|
||||||
try {
|
try {
|
||||||
session = await global.MobileSession.me();
|
session = await global.MobileSession.me();
|
||||||
@@ -44,7 +65,7 @@
|
|||||||
if (!session.authenticated) {
|
if (!session.authenticated) {
|
||||||
if (!/^#\/?auth/.test(hash)) global.location.replace("#/auth");
|
if (!/^#\/?auth/.test(hash)) global.location.replace("#/auth");
|
||||||
} else if (/^#\/?auth/.test(hash)) {
|
} else if (/^#\/?auth/.test(hash)) {
|
||||||
global.location.replace("#/home");
|
global.location.replace("#/hub/market");
|
||||||
}
|
}
|
||||||
global.MobileRouter.init();
|
global.MobileRouter.init();
|
||||||
}
|
}
|
||||||
|
|||||||
+93
-32
@@ -10,7 +10,7 @@
|
|||||||
"wand-2": '<path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z"/><path d="m14 7 3 3"/><path d="M5 6v4"/><path d="M19 14v4"/><path d="M10 2v2"/><path d="M7 8H3"/><path d="M21 16h-4"/><path d="M11 3H9"/>',
|
"wand-2": '<path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z"/><path d="m14 7 3 3"/><path d="M5 6v4"/><path d="M19 14v4"/><path d="M10 2v2"/><path d="M7 8H3"/><path d="M21 16h-4"/><path d="M11 3H9"/>',
|
||||||
"notebook-pen": '<path d="M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4"/><path d="M2 6h4"/><path d="M2 10h4"/><path d="M2 14h4"/><path d="M2 18h4"/><path d="m21.38 5.63-3-3a1 1 0 0 0-1.42 0l-5.01 5.01a2 2 0 0 0-.5.85l-.84 2.87a.5.5 0 0 0 .62.62l2.87-.84a2 2 0 0 0 .85-.5z"/>',
|
"notebook-pen": '<path d="M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4"/><path d="M2 6h4"/><path d="M2 10h4"/><path d="M2 14h4"/><path d="M2 18h4"/><path d="m21.38 5.63-3-3a1 1 0 0 0-1.42 0l-5.01 5.01a2 2 0 0 0-.5.85l-.84 2.87a.5.5 0 0 0 .62.62l2.87-.84a2 2 0 0 0 .85-.5z"/>',
|
||||||
"message-square": '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
"message-square": '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
||||||
"settings": '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
|
"settings": '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
|
||||||
"inbox": '<path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>',
|
"inbox": '<path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>',
|
||||||
"activity": '<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',
|
"activity": '<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',
|
||||||
"trending-up": '<polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/>',
|
"trending-up": '<polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/>',
|
||||||
@@ -50,13 +50,15 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_HASH = "#/hub/market";
|
||||||
|
|
||||||
const stack = [];
|
const stack = [];
|
||||||
let internalNav = 0;
|
let internalNav = 0;
|
||||||
let authMode = "login";
|
let authMode = "login";
|
||||||
|
|
||||||
function currentHash() {
|
function currentHash() {
|
||||||
let hash = window.location.hash || "";
|
let hash = window.location.hash || "";
|
||||||
if (!hash || hash === "#" || hash === "#/") return "#/home";
|
if (!hash || hash === "#" || hash === "#/") return DEFAULT_HASH;
|
||||||
if (hash.charAt(0) !== "#") hash = "#" + hash;
|
if (hash.charAt(0) !== "#") hash = "#" + hash;
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
@@ -71,6 +73,17 @@
|
|||||||
return routeOf(stack.length ? stack[stack.length - 1] : currentHash());
|
return routeOf(stack.length ? stack[stack.length - 1] : currentHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function entryKeyOfRoute(route) {
|
||||||
|
if (route.name === "hub") return route.params[0];
|
||||||
|
if (route.name === "feature") return route.params[0];
|
||||||
|
if (route.name === "assistant") return "assistant";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function entryRoute(key) {
|
||||||
|
return key === "assistant" ? "#/assistant/chat" : "#/hub/" + key;
|
||||||
|
}
|
||||||
|
|
||||||
function setHash(hash) {
|
function setHash(hash) {
|
||||||
internalNav++;
|
internalNav++;
|
||||||
window.location.hash = hash;
|
window.location.hash = hash;
|
||||||
@@ -89,10 +102,15 @@
|
|||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetStack(hash) {
|
||||||
|
stack.length = 0;
|
||||||
|
stack.push(hash);
|
||||||
|
setHash(hash);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
function parentRoute(route) {
|
function parentRoute(route) {
|
||||||
if (route.name === "hub") return "#/home";
|
|
||||||
if (route.name === "feature") return "#/hub/" + route.params[0];
|
if (route.name === "feature") return "#/hub/" + route.params[0];
|
||||||
if (route.name === "assistant") return "#/home";
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +122,19 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parent = parentRoute(currentRoute());
|
const parent = parentRoute(currentRoute());
|
||||||
replace(parent || "#/home");
|
if (parent) replace(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchEntry(key) {
|
||||||
|
const route = currentRoute();
|
||||||
|
const current = entryKeyOfRoute(route);
|
||||||
|
if (current === key) {
|
||||||
|
if (route.name === "feature") {
|
||||||
|
resetStack(entryRoute(key));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resetStack(entryRoute(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHeader(options) {
|
function updateHeader(options) {
|
||||||
@@ -113,12 +143,6 @@
|
|||||||
document.getElementById("m-actions").innerHTML = options.actions || "";
|
document.getElementById("m-actions").innerHTML = options.actions || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function themeToggleHtml() {
|
|
||||||
const dark = document.getElementById("m-app").dataset.theme === "dark";
|
|
||||||
const label = dark ? "切换到日间模式" : "切换到夜间模式";
|
|
||||||
return '<button class="m-header-btn" type="button" data-theme-toggle aria-label="' + label + '" aria-pressed="' + (dark ? "true" : "false") + '">' + icon(dark ? "sun" : "moon") + "</button>";
|
|
||||||
}
|
|
||||||
|
|
||||||
function placeholderHtml(title, subtitle) {
|
function placeholderHtml(title, subtitle) {
|
||||||
return '<div class="m-placeholder">' +
|
return '<div class="m-placeholder">' +
|
||||||
'<span class="m-placeholder-icon">' + icon("inbox", 26) + "</span>" +
|
'<span class="m-placeholder-icon">' + icon("inbox", 26) + "</span>" +
|
||||||
@@ -138,18 +162,14 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderHome() {
|
function themeToggleSection() {
|
||||||
updateHeader({ title: "小白复盘", back: false, actions: themeToggleHtml() });
|
const dark = document.getElementById("m-app").dataset.theme === "dark";
|
||||||
const items = global.MobileNav.entries.map(function (entry) {
|
return '<div class="m-theme-section">' +
|
||||||
const route = entry.key === "assistant" ? "#/assistant/chat" : "#/hub/" + entry.key;
|
'<div class="m-theme-row">' +
|
||||||
return '<li>' +
|
'<span class="m-theme-row-icon">' + icon(dark ? "moon" : "sun") + "</span>" +
|
||||||
'<button class="m-entry" type="button" data-route="' + route + '">' +
|
'<span class="m-theme-row-body"><strong>外观主题</strong><small>' + (dark ? "当前:夜间模式" : "当前:日间模式") + "</small></span>" +
|
||||||
'<span class="m-entry-icon">' + icon(entry.icon) + "</span>" +
|
'<button class="m-theme-switch" type="button" data-theme-toggle role="switch" aria-checked="' + (dark ? "true" : "false") + '" aria-label="切换日间/夜间模式"><span class="m-theme-switch-thumb"></span></button>' +
|
||||||
'<span class="m-entry-body"><strong>' + escapeHtml(entry.title) + "</strong><small>" + escapeHtml(entry.subtitle) + "</small></span>" +
|
"</div></div>";
|
||||||
'<span class="m-entry-chevron">' + icon("chevron-right", 20) + "</span>" +
|
|
||||||
"</button></li>";
|
|
||||||
}).join("");
|
|
||||||
document.getElementById("m-view").innerHTML = '<ul class="m-entry-list">' + items + "</ul>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function visibleHubItems(hub) {
|
function visibleHubItems(hub) {
|
||||||
@@ -162,12 +182,14 @@
|
|||||||
function renderHub(key) {
|
function renderHub(key) {
|
||||||
const hub = global.MobileNav.hubs[key];
|
const hub = global.MobileNav.hubs[key];
|
||||||
if (!hub) {
|
if (!hub) {
|
||||||
renderHome();
|
replace(DEFAULT_HASH);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const items = visibleHubItems(hub);
|
const items = visibleHubItems(hub);
|
||||||
updateHeader({ title: hub.title, back: true });
|
updateHeader({ title: hub.title, back: false });
|
||||||
|
const section = key === "system" ? themeToggleSection() : "";
|
||||||
document.getElementById("m-view").innerHTML =
|
document.getElementById("m-view").innerHTML =
|
||||||
|
section +
|
||||||
'<ul class="m-hub-grid">' +
|
'<ul class="m-hub-grid">' +
|
||||||
items.map(function (item) {
|
items.map(function (item) {
|
||||||
return '<li>' +
|
return '<li>' +
|
||||||
@@ -186,7 +208,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderAssistant() {
|
function renderAssistant() {
|
||||||
updateHeader({ title: "复盘助手", back: true });
|
updateHeader({ title: "复盘助手", back: false });
|
||||||
document.getElementById("m-view").innerHTML = placeholderHtml("复盘助手", "聊天工作台将在后续批次实现。");
|
document.getElementById("m-view").innerHTML = placeholderHtml("复盘助手", "聊天工作台将在后续批次实现。");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +275,7 @@
|
|||||||
} else {
|
} else {
|
||||||
await global.MobileSession.login(username, password);
|
await global.MobileSession.login(username, password);
|
||||||
}
|
}
|
||||||
replace("#/home");
|
replace(DEFAULT_HASH);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorElement.textContent = error.message || "账号操作失败";
|
errorElement.textContent = error.message || "账号操作失败";
|
||||||
errorElement.hidden = false;
|
errorElement.hidden = false;
|
||||||
@@ -262,13 +284,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildTabbar() {
|
||||||
|
document.getElementById("m-tabbar").innerHTML = global.MobileNav.entries.map(function (entry) {
|
||||||
|
return '<button class="m-tabbar-item" type="button" data-tab="' + entry.key + '">' +
|
||||||
|
'<span class="m-tabbar-icon">' + icon(entry.icon, 24) + "</span>" +
|
||||||
|
'<span class="m-tabbar-label">' + escapeHtml(entry.title) + "</span>" +
|
||||||
|
"</button>";
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTabbar(activeKey) {
|
||||||
|
document.querySelectorAll("#m-tabbar .m-tabbar-item").forEach(function (button) {
|
||||||
|
const active = button.dataset.tab === activeKey;
|
||||||
|
button.classList.toggle("active", active);
|
||||||
|
if (active) button.setAttribute("aria-current", "page");
|
||||||
|
else button.removeAttribute("aria-current");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
const route = currentRoute();
|
const route = currentRoute();
|
||||||
|
if (route.name === "home") {
|
||||||
|
replace(DEFAULT_HASH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tabbar = document.getElementById("m-tabbar");
|
||||||
|
if (route.name === "auth") {
|
||||||
|
tabbar.hidden = true;
|
||||||
|
document.getElementById("m-app").dataset.tabbar = "false";
|
||||||
|
} else {
|
||||||
|
tabbar.hidden = false;
|
||||||
|
document.getElementById("m-app").dataset.tabbar = "true";
|
||||||
|
updateTabbar(entryKeyOfRoute(route));
|
||||||
|
}
|
||||||
if (route.name === "hub") renderHub(route.params[0]);
|
if (route.name === "hub") renderHub(route.params[0]);
|
||||||
else if (route.name === "feature") renderFeature(route.params.join("/"));
|
else if (route.name === "feature") renderFeature(route.params.join("/"));
|
||||||
else if (route.name === "assistant") renderAssistant();
|
else if (route.name === "assistant") renderAssistant();
|
||||||
else if (route.name === "auth") renderAuth();
|
else if (route.name === "auth") renderAuth();
|
||||||
else renderHome();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindGlobalEvents() {
|
function bindGlobalEvents() {
|
||||||
@@ -278,14 +330,16 @@
|
|||||||
navigate(entry.dataset.route);
|
navigate(entry.dataset.route);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
document.getElementById("m-tabbar").addEventListener("click", function (event) {
|
||||||
|
const item = event.target.closest("[data-tab]");
|
||||||
|
if (item) switchEntry(item.dataset.tab);
|
||||||
|
});
|
||||||
document.getElementById("m-back").addEventListener("click", back);
|
document.getElementById("m-back").addEventListener("click", back);
|
||||||
document.addEventListener("click", function (event) {
|
document.addEventListener("click", function (event) {
|
||||||
const toggle = event.target.closest("[data-theme-toggle]");
|
const toggle = event.target.closest("[data-theme-toggle]");
|
||||||
if (!toggle) return;
|
if (!toggle) return;
|
||||||
global.MobileTheme.toggle();
|
global.MobileTheme.toggle();
|
||||||
if (currentRoute().name === "home") {
|
render();
|
||||||
document.getElementById("m-actions").innerHTML = themeToggleHtml();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
window.addEventListener("hashchange", function () {
|
window.addEventListener("hashchange", function () {
|
||||||
if (internalNav > 0) {
|
if (internalNav > 0) {
|
||||||
@@ -304,8 +358,15 @@
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
bindGlobalEvents();
|
bindGlobalEvents();
|
||||||
|
buildTabbar();
|
||||||
stack.length = 0;
|
stack.length = 0;
|
||||||
stack.push(currentHash());
|
const raw = window.location.hash || "";
|
||||||
|
if (!raw || raw === "#" || raw === "#/") {
|
||||||
|
stack.push(DEFAULT_HASH);
|
||||||
|
setHash(DEFAULT_HASH);
|
||||||
|
} else {
|
||||||
|
stack.push(currentHash());
|
||||||
|
}
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user