From 1b77c610f434f343b5d6b80fda6fec0d6f26ab97 Mon Sep 17 00:00:00 2001 From: aaaamoooo Date: Tue, 20 Oct 2026 15:09:44 +0800 Subject: [PATCH] fix: keep current page on refresh via hash routing (restore page from location.hash, back/forward supported) --- public/app.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/public/app.js b/public/app.js index e9cd803..5e99368 100644 --- a/public/app.js +++ b/public/app.js @@ -116,8 +116,12 @@ async function load(page = state.page) { } catch (error) { toast(error.message, true); } } +const PAGES = ['overview', 'servers', 'customers', 'chains', 'commands', 'operations']; +function pageFromHash() { const page = location.hash.replace(/^#/, ''); return PAGES.includes(page) ? page : 'overview'; } function setPage(page) { + if (!PAGES.includes(page)) page = 'overview'; state.page = page; state.search = ''; closeSidebar(); + if (location.hash !== `#${page}`) history.pushState(null, '', `#${page}`); $$('#nav button').forEach((button) => button.classList.toggle('active', button.dataset.page === page)); const titles = { overview:'概览', servers:'服务器', customers:'客户与订阅', chains:'转发与节点', commands:'安装与命令', operations:'设置与运维' }; $('#page-title').textContent = titles[page]; $('#breadcrumb').textContent = `NEXUSGATE / ${titles[page]}`; @@ -462,7 +466,7 @@ document.addEventListener('submit', async (event) => { if (form.id === 'login-form') { $('#login-error').textContent = ''; state.session = await api('/api/auth/login', { method:'POST', body:JSON.stringify({ username:data.get('username'), password:data.get('password') }) }); - showApp(); setPage('overview'); return; + showApp(); setPage(pageFromHash()); return; } if (form.id === 'server-form') { const resourceId = data.get('resourceId'); @@ -613,13 +617,16 @@ $('#sidebar-scrim').addEventListener('click', closeSidebar); $('#refresh').addEventListener('click', () => load()); $('#logout').addEventListener('click', async () => { try { await api('/api/auth/logout', { method:'POST', body:'{}' }); } finally { showLogin(); } }); +window.addEventListener('popstate', () => { const page = pageFromHash(); if (state.session && page !== state.page) setPage(page); }); +window.addEventListener('hashchange', () => { const page = pageFromHash(); if (state.session && page !== state.page) setPage(page); }); + (async function boot() { let preferred = 'light'; try { preferred = localStorage.getItem('nexusgate-theme') || 'light'; } catch { /* storage may be disabled */ } setTheme(preferred); try { const session = await api('/api/session'); if (!session.authenticated) return showLogin(); - state.session = session; showApp(); await load('overview'); + state.session = session; showApp(); setPage(pageFromHash()); } catch { showLogin(); } setInterval(() => { if (state.session && document.visibilityState === 'visible' && !$('#modal').open) load(); }, 45000); })();