diff --git a/public/js/app.js b/public/js/app.js index 3ff9938..e7826e4 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -37,12 +37,16 @@ const App = { ], async init() { - // 获取站点名称(无论是否安装),使用原生 fetch 避免携带 Bearer token + // 获取站点名称:先尝试带 token 的请求,失败则降级(安装阶段无需登录) try { - const r = await fetch('/api/install/status'); - const s = await r.json(); - if (!s.installed) { location.hash = '#/install'; return; } - } catch { location.hash = '#/install'; return; } + const r = await API.get('/install/status'); + if (!r.installed) { location.hash = '#/install'; return; } + } catch { + // 未安装或 API 不可用,尝试直接路由到安装页 + location.hash = '#/install'; + return; + } + // 安装完成后获取站点名称(需要 token,若未登录则忽略名称设置) try { const nameRes = await fetch('/api/settings/settings'); if (nameRes.ok) { @@ -50,7 +54,7 @@ const App = { if (settings.site_name && settings.site_name.value) window.__SITE_NAME__ = settings.site_name.value; } } catch {} - // 确保登录态同步:若 cookie 还在但 token 已过期,清除 + // 确保登录态与服务器同步:若本地有 token 但 site_name 未加载(可能 token 已过期) if (Auth.logged() && !window.__SITE_NAME__) Auth.reset(); if (window.__SITE_NAME__) { document.getElementById('site-title').textContent = window.__SITE_NAME__; @@ -91,6 +95,21 @@ const App = { closeModal() { document.getElementById('modal-overlay').classList.add('hidden'); }, navigate(page, param) { location.hash = `#/${page}` + (param ? `/${param}` : ''); }, + // 验证 token 有效性(轻量级,失败则清除登录态) + async validateAuth() { + if (!Auth.logged()) return false; + try { + await API.get('/me'); + return true; + } catch (e) { + if (e && e.message && e.message.includes('登录')) { + Auth.reset(); + return false; + } + throw e; + } + }, + route() { const hash = location.hash || '#/home'; const parts = hash.replace('#/', '').split('/'); @@ -105,8 +124,22 @@ const App = { if (page === 'install') { this.showPublic('install'); return; } if (page === 'home') { this.showPublic('home', param); return; } if (page === 'login' || page === 'register' || page === 'forgot' || page === 'reset') { this.showPublic(page); return; } - if (!Auth.logged()) { location.hash = '#/login'; return; } + // 访问受保护页面前先验证 token 有效性 + if (Auth.logged()) { + this.validateAuth().then(valid => { + if (!valid) { location.hash = '#/login'; return; } + this._doRoute(page, param); + }).catch(() => { + Auth.reset(); + location.hash = '#/login'; + }); + return; + } + location.hash = '#/login'; + }, + + _doRoute(page, param) { this.showLayout(); const sidebarPage = (page === 'tickets' && param.startsWith('create')) ? 'tickets-create' : page; this.renderSidebar(sidebarPage);