const App = { navItems: [ { id: 'dashboard', label: '控制台', icon: 'fa-chart-simple', roles: ['owner','admin','player'] }, { id: 'tickets-create', label: '提交工单', icon: 'fa-plus-circle', roles: ['player'] }, { id: 'bans', label: '封禁列表', icon: 'fa-ban', roles: ['owner','admin','player'] }, { id: 'unclaimed', label: '待处理工单', icon: 'fa-inbox', roles: ['owner','admin'] }, { id: 'users', label: '用户管理', icon: 'fa-users', roles: ['owner'] }, { id: 'servers', label: '服务器管理', icon: 'fa-server', roles: ['owner'] }, { id: 'polls', label: '投票', icon: 'fa-poll', roles: ['owner'] }, { id: 'features', label: '更新内容', icon: 'fa-lightbulb', roles: ['owner'] }, { id: 'notifications', label: '通知配置', icon: 'fa-bell', roles: ['owner'] }, { id: 'templates', label: '邮件模板', icon: 'fa-envelope', roles: ['owner'] }, { id: 'settings', label: '系统设置', icon: 'fa-cog', roles: ['owner'] }, { id: 'logs', label: '系统日志', icon: 'fa-clipboard-list', roles: ['owner','admin'] }, { id: 'export', label: '数据导出', icon: 'fa-download', roles: ['owner'] }, ], async init() { if (window.__SITE_NAME__) { document.getElementById('site-title').textContent = window.__SITE_NAME__; document.getElementById('sidebar-title').textContent = window.__SITE_NAME__ || '举报系统'; } try { const s = await API.get('/install/status'); if (!s.installed) location.hash = '#/install'; } catch { location.hash = '#/install'; } this._footerLoaded = false; window.addEventListener('hashchange', () => this.route()); window.onerror = (msg, url, line, col, err) => { console.error('JS Error:', msg, url, line, col); const el = document.getElementById('sidebar-nav'); if (el) el.insertAdjacentHTML('beforeend', '
Error: ' + String(msg).substring(0,80) + '
'); }; document.getElementById('modal-close-btn').onclick = () => this.closeModal(); document.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; const parts = btn.dataset.action.split(':'); const target = window[parts[0]]; if (target && target[parts[1]]) target[parts[1]](...parts.slice(2)); }); this.route(); }, async loadFooter() { if (!Auth.isAdmin() || this._footerLoaded) return; this._footerLoaded = true; try { const s = await API.get('/settings/settings'); const cr = document.getElementById('footer-copyright'); const icp = document.getElementById('footer-icp'); if (cr && s.copyright) cr.innerHTML = s.copyright.value; if (icp && s.icp && s.icp.value) icp.innerHTML = `${s.icp.value}`; } catch {} }, afterLogin() { location.hash = '#/dashboard'; }, logout() { Auth.logout(); }, closeModal() { document.getElementById('modal-overlay').classList.add('hidden'); }, navigate(page, param) { location.hash = `#/${page}` + (param ? `/${param}` : ''); }, route() { const hash = location.hash || '#/home'; const parts = hash.replace('#/', '').split('/'); const page = parts[0]; const param = parts.slice(1).join('/'); if (page === 'verify') { this.showPublic('verify'); return; } 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; } this.showLayout(); const sidebarPage = (page === 'tickets' && param.startsWith('create')) ? 'tickets-create' : page; this.renderSidebar(sidebarPage); switch (page) { case 'dashboard': this.renderMain('控制台', Dashboard, param); break; case 'unclaimed': this.renderMain('待处理工单', UnclaimedPage, param); break; case 'tickets-create': App.navigate('tickets','create'); break; case 'tickets': if (param.startsWith('create')) { const q = new URLSearchParams((location.hash.split('?')[1] || '')); document.getElementById('page-title').textContent = '创建工单'; document.getElementById('page-actions').innerHTML = ''; TicketCreate.render(document.getElementById('page-content'), { type: q.get('type') || 'report', prefill: { game_name: Auth.user()?.game_name, game_uid: Auth.user()?.game_uid }, backLink: "App.navigate('tickets')" }); } else this.renderMain('工单列表', TicketsPage, param); break; case 'ticket': this.renderMain('工单详情', TicketDetail, param); break; case 'tracking': this.renderMain('追踪工单', TrackingPage, param); break; case 'users': this.renderMain('用户管理', UsersPage, param); break; case 'notifications': this.renderMain('通知配置', NotificationsPage, param); break; case 'templates': this.renderMain('邮件模板', TemplatesPage, param); break; case 'settings': this.renderMain('系统设置', SettingsPage, param); break; case 'export': this.renderMain('数据导出', ExportPage, param); break; case 'polls': this.renderMain('投票', PollsPage, param); break; case 'features': this.renderMain('更新内容', FeaturesPage, param); break; case 'feature': this.renderMain('更新详情', FeatureDetail, param); break; case 'servers': this.renderMain('服务器管理', ServersPage, param); break; case 'bans': this.renderMain('封禁列表', BansPage, param); break; case 'logs': this.renderMain('系统日志', LogsPage, param); break; default: this.renderMain('控制台', Dashboard, param); } }, showPublic(page, param) { document.getElementById('main-layout').classList.add('hidden'); const pub = document.getElementById('public-page'); pub.classList.remove('hidden'); document.getElementById('top-bar').classList.remove('hidden'); this.updateTopAuth(); const comp = page === 'login' ? LoginPage : page === 'register' ? RegisterPage : page === 'install' ? InstallPage : page === 'forgot' ? ForgotPage : page === 'reset' ? ResetPage : page === 'verify' ? VerifyPage : HomePage; comp.render(param).then(html => { pub.innerHTML = html; if (comp.mount) comp.mount(param); }).catch(() => { pub.innerHTML = '
'; }); }, updateTopAuth() { const el = document.getElementById('top-auth'); if (Auth.logged()) { const u = Auth.user(); el.innerHTML = `${U.esc(u.game_name||u.username)} ${Auth.isAdmin() ? ` 控制台` : ''} ${Auth.isAdmin() ? `待处理` : ''} ${Auth.isOwner() ? `` : ''} `; } else { el.innerHTML = ` 登录 注册`; el.style.display = 'flex'; } }, showLayout() { document.getElementById('top-bar').classList.add('hidden'); document.getElementById('public-page').classList.add('hidden'); document.getElementById('main-layout').classList.remove('hidden'); const u = Auth.user(); document.getElementById('user-info').innerHTML = `
${U.esc(u.game_name||u.username)}
`; document.getElementById('sidebar-logout').onclick = () => App.logout(); this.loadFooter(); }, renderSidebar(current) { const u = Auth.user(); const nav = document.getElementById('sidebar-nav'); nav.innerHTML = this.navItems .filter(i => i.roles.includes(u.role)) .map(i => ``).join(''); nav.querySelectorAll('.nav-item').forEach(el => { el.addEventListener('click', () => App.navigate(el.dataset.nav)); }); }, async renderMain(title, page, param) { document.getElementById('page-title').textContent = title; document.getElementById('page-actions').innerHTML = ''; const ct = document.getElementById('page-content'); ct.innerHTML = await page.render(param); if (page.mount) await page.mount(param); } }; document.addEventListener('DOMContentLoaded', () => App.init()); window.App = App; window.Dashboard = Dashboard; window.TicketsPage = TicketsPage; window.TicketDetail = TicketDetail; window.TicketCreate = TicketCreate; window.TrackingPage = TrackingPage; window.UnclaimedPage = UnclaimedPage; window.UsersPage = UsersPage; window.TemplatesPage = TemplatesPage; window.NotificationsPage = NotificationsPage; window.SettingsPage = SettingsPage; window.ExportPage = ExportPage; window.PollsPage = PollsPage; window.FeaturesPage = FeaturesPage; window.FeatureDetail = FeatureDetail; window.ServersPage = ServersPage; window.BansPage = BansPage; window.LogsPage = LogsPage; window.LoginPage = LoginPage; window.RegisterPage = RegisterPage; window.HomePage = HomePage; window.InstallPage = InstallPage; window.ForgotPage = ForgotPage; window.ResetPage = ResetPage; window.VerifyPage = VerifyPage; window.U = U; window.Auth = Auth; window.API = API; window.App = App;