(function (global) { "use strict"; const ICONS = { "chevron-left": '', "chevron-right": '', "sun": '', "moon": '', "bar-chart-3": '', "wand-2": '', "notebook-pen": '', "message-square": '', "settings": '', "inbox": '', "activity": '', "trending-up": '', "trending-down": '', "zap": '', "history": '', "chart-line": '', "layers": '', "refresh-cw": '', "gavel": '', "book-open": '', "flame": '', "crown": '', "filter": '', "target": '', "bot": '', "star": '', "scroll-text": '', "calendar-check": '', "sticky-note": '', "bell": '', "user": '', "lock": '', "gem": '', "sliders-horizontal": '', "users": '' }; function icon(name, size) { const body = ICONS[name] || ""; return '"; } function escapeHtml(value) { return String(value == null ? "" : value).replace(/[&<>"']/g, function (ch) { return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch]; }); } const DEFAULT_HASH = "#/hub/market"; const stack = []; let internalNav = 0; let authMode = "login"; const VIEW_MOTION_CLASSES = ["m-motion-fade-in", "m-motion-push-in", "m-motion-pop-in", "m-motion-boot-in"]; function applyViewMotion(motion) { const view = document.getElementById("m-view"); VIEW_MOTION_CLASSES.forEach(function (cls) { view.classList.remove(cls); }); void view.offsetWidth; view.classList.add(motion || "m-motion-fade-in"); } function currentHash() { let hash = window.location.hash || ""; if (!hash || hash === "#" || hash === "#/") return DEFAULT_HASH; if (hash.charAt(0) !== "#") hash = "#" + hash; return hash; } function routeOf(hash) { const path = hash.replace(/^#\/?/, ""); const parts = path.split("/").filter(Boolean); return { name: parts[0] || "home", params: parts.slice(1) }; } function currentRoute() { 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) { internalNav++; window.location.hash = hash; } function navigate(hash) { stack.push(hash); setHash(hash); render("m-motion-push-in"); } function replace(hash, motion) { if (stack.length) stack[stack.length - 1] = hash; else stack.push(hash); setHash(hash); render(motion); } function resetStack(hash) { stack.length = 0; stack.push(hash); setHash(hash); render(); } function parentRoute(route) { if (route.name === "feature") return "#/hub/" + route.params[0]; return null; } function back() { if (stack.length > 1) { stack.pop(); setHash(stack[stack.length - 1]); render("m-motion-pop-in"); return; } const parent = parentRoute(currentRoute()); if (parent) replace(parent, "m-motion-pop-in"); } 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) { document.getElementById("m-title").textContent = options.title || "小白复盘"; document.getElementById("m-back").hidden = !options.back; document.getElementById("m-actions").innerHTML = options.actions || ""; } function placeholderHtml(title, subtitle) { return '
' + '' + icon("inbox", 26) + "" + "

" + escapeHtml(title) + "

" + "

" + escapeHtml(subtitle || "该页面将在后续批次实现,返回即可继续浏览。") + "

" + "
"; } function findFeatureLabel(key) { const hubs = global.MobileNav.hubs; for (const hubKey in hubs) { const items = hubs[hubKey].items || []; for (const item of items) { if (item.key === key) return item.label; } } return null; } function themeToggleSection() { const dark = document.getElementById("m-app").dataset.theme === "dark"; return '
' + '
' + '' + icon(dark ? "moon" : "sun") + "" + '外观主题' + (dark ? "当前:夜间模式" : "当前:日间模式") + "" + '' + "
"; } function syncThemeToggleUI() { const dark = document.getElementById("m-app").dataset.theme === "dark"; const toggle = document.querySelector("[data-theme-toggle]"); if (toggle) toggle.setAttribute("aria-checked", dark ? "true" : "false"); const rowIcon = document.querySelector(".m-theme-row-icon"); if (rowIcon) rowIcon.innerHTML = icon(dark ? "moon" : "sun"); const rowBodySmall = document.querySelector(".m-theme-row-body small"); if (rowBodySmall) rowBodySmall.textContent = dark ? "当前:夜间模式" : "当前:日间模式"; } function visibleHubItems(hub) { const items = hub && hub.items ? hub.items : []; return items.filter(function (item) { return !item.adminOnly || global.MobileSession.isAdmin(); }); } function renderHub(key) { const hub = global.MobileNav.hubs[key]; if (!hub) { replace(DEFAULT_HASH); return; } const items = visibleHubItems(hub); updateHeader({ title: hub.title, back: false }); const section = key === "system" ? themeToggleSection() : ""; document.getElementById("m-view").innerHTML = section + '
    ' + items.map(function (item) { return '
  • ' + '
  • "; }).join("") + "
"; } function renderFeature(key) { if (global.MobilePages && global.MobilePages.has(key)) { global.MobilePages.render(key); return; } const title = findFeatureLabel(key) || key; updateHeader({ title: title, back: true }); document.getElementById("m-view").innerHTML = placeholderHtml(title, "该功能页将在后续批次实现。"); } function renderAssistant() { updateHeader({ title: "复盘助手", back: false }); document.getElementById("m-view").innerHTML = placeholderHtml("复盘助手", "聊天工作台将在后续批次实现。"); } function renderAuth() { updateHeader({ title: "小白复盘", back: false, actions: "" }); document.getElementById("m-view").innerHTML = [ '
', '
', '', '

小白复盘

', '

登录后进入你的复盘空间

', "
", '
', '', '', "
", '
', '', '', '', '', '', "
", "
" ].join(""); authMode = "login"; bindAuth(); } function setAuthMode(mode) { authMode = mode === "register" ? "register" : "login"; document.querySelectorAll("[data-auth-mode]").forEach(function (button) { button.classList.toggle("active", button.dataset.authMode === authMode); }); document.getElementById("m-auth-confirm-field").hidden = authMode !== "register"; document.getElementById("m-auth-confirm").required = authMode === "register"; document.getElementById("m-auth-password").autocomplete = authMode === "register" ? "new-password" : "current-password"; const submitLabel = document.querySelector("#m-auth-submit .m-btn-label"); if (submitLabel) submitLabel.textContent = authMode === "register" ? "注册并进入" : "登录"; document.getElementById("m-auth-error").hidden = true; } function bindAuth() { document.querySelectorAll("[data-auth-mode]").forEach(function (button) { button.addEventListener("click", function () { setAuthMode(button.dataset.authMode); }); }); document.getElementById("m-auth-form").addEventListener("submit", submitAuth); } function showAuthError(element, message) { element.textContent = message; element.classList.remove("m-motion-fade-in"); void element.offsetWidth; element.classList.add("m-motion-fade-in"); element.hidden = false; } async function submitAuth(event) { event.preventDefault(); const username = document.getElementById("m-auth-username").value.trim(); const password = document.getElementById("m-auth-password").value; const errorElement = document.getElementById("m-auth-error"); if (authMode === "register" && password !== document.getElementById("m-auth-confirm").value) { showAuthError(errorElement, "两次输入的密码不一致。"); return; } const submit = document.getElementById("m-auth-submit"); const spinner = submit.querySelector(".m-btn-spinner"); const label = submit.querySelector(".m-btn-label"); submit.disabled = true; spinner.hidden = false; label.textContent = authMode === "register" ? "注册中…" : "登录中…"; try { if (authMode === "register") { await global.MobileSession.register(username, password); } else { await global.MobileSession.login(username, password); } replace(DEFAULT_HASH); } catch (error) { showAuthError(errorElement, error.message || "账号操作失败"); } finally { submit.disabled = false; spinner.hidden = true; label.textContent = authMode === "register" ? "注册并进入" : "登录"; } } function buildTabbar() { document.getElementById("m-tabbar").innerHTML = global.MobileNav.entries.map(function (entry) { return '"; }).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(motion) { const route = currentRoute(); document.getElementById("m-view").classList.remove("m-view-feature"); 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]); else if (route.name === "feature") renderFeature(route.params.join("/")); else if (route.name === "assistant") renderAssistant(); else if (route.name === "auth") renderAuth(); applyViewMotion(motion); } function bindGlobalEvents() { document.getElementById("m-view").addEventListener("click", function (event) { const entry = event.target.closest("[data-route]"); if (entry) { 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.addEventListener("click", function (event) { const toggle = event.target.closest("[data-theme-toggle]"); if (!toggle) return; global.MobileTheme.toggle(); syncThemeToggleUI(); }); window.addEventListener("hashchange", function () { if (internalNav > 0) { internalNav--; return; } const hash = currentHash(); if (stack.length > 1 && hash === stack[stack.length - 2]) { stack.pop(); render("m-motion-pop-in"); } else if (hash !== stack[stack.length - 1]) { stack.push(hash); render("m-motion-push-in"); } }); } function init() { bindGlobalEvents(); buildTabbar(); stack.length = 0; const raw = window.location.hash || ""; if (!raw || raw === "#" || raw === "#/") { stack.push(DEFAULT_HASH); setHash(DEFAULT_HASH); } else { stack.push(currentHash()); } render("m-motion-boot-in"); } global.MobileRouter = { init: init, navigate: navigate, replace: replace, back: back, render: render, currentRoute: currentRoute, updateHeader: updateHeader }; })(window);