(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";
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();
}
function replace(hash) {
if (stack.length) stack[stack.length - 1] = hash;
else stack.push(hash);
setHash(hash);
render();
}
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();
return;
}
const parent = parentRoute(currentRoute());
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) {
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) {
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";
document.getElementById("m-auth-submit").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);
}
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) {
errorElement.textContent = "两次输入的密码不一致。";
errorElement.hidden = false;
return;
}
const submit = document.getElementById("m-auth-submit");
submit.disabled = true;
try {
if (authMode === "register") {
await global.MobileSession.register(username, password);
} else {
await global.MobileSession.login(username, password);
}
replace(DEFAULT_HASH);
} catch (error) {
errorElement.textContent = error.message || "账号操作失败";
errorElement.hidden = false;
} finally {
submit.disabled = false;
}
}
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() {
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]);
else if (route.name === "feature") renderFeature(route.params.join("/"));
else if (route.name === "assistant") renderAssistant();
else if (route.name === "auth") renderAuth();
}
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();
} else if (hash !== stack[stack.length - 1]) {
stack.push(hash);
}
render();
});
}
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();
}
global.MobileRouter = {
init: init,
navigate: navigate,
replace: replace,
back: back,
render: render,
currentRoute: currentRoute
};
})(window);