const PageDashboard = { _el: null, _currentCategory: 'all', _selectedGameId: null, _marketMode: false, _marketToken: null, init() { this._el = document.getElementById('page-dashboard'); this._marketToken = localStorage.getItem('turbosu-market-token'); }, async render() { const cfg = await API.getConfig(); this._selectedGameId = cfg.selected_game_id; this._el.innerHTML = this._template(); await this._loadCategories(); await this._loadThemes(); this._bindEvents(); }, async _loadCategories() { try { const cats = await API.getCategories(); const listEl = document.getElementById('dash-category-list'); if (!listEl) return; const allItem = document.createElement('div'); allItem.className = 'category-item active'; allItem.textContent = '全部'; allItem.dataset.cat = 'all'; listEl.appendChild(allItem); cats.forEach(cat => { const item = document.createElement('div'); item.className = 'category-item'; item.textContent = cat; item.dataset.cat = cat; listEl.appendChild(item); }); } catch (e) { console.error('Failed to load categories:', e); } }, async _loadThemes() { const gridEl = document.getElementById('dash-theme-grid'); if (!gridEl) return; gridEl.innerHTML = '
加载中...
'; if (this._marketMode) { await this._loadMarket(gridEl); return; } try { let themes = await API.getDashboards(this._currentCategory); if (!themes || themes.length === 0) { gridEl.innerHTML = this._emptyHtml(); return; } if (this._selectedGameId) { themes = themes.filter(t => this._isGameSupported(t)); } const searchQuery = document.getElementById('dash-search')?.value?.toLowerCase() || ''; if (searchQuery) { themes = themes.filter(t => (t.name||'').toLowerCase().includes(searchQuery) || (t.description||'').toLowerCase().includes(searchQuery) || (t.author||'').toLowerCase().includes(searchQuery)); } const sortBy = document.getElementById('dash-sort')?.value || 'name'; themes.sort((a, b) => (a[sortBy]||'').localeCompare(b[sortBy]||'')); if (themes.length === 0) { gridEl.innerHTML = this._emptyHtml('当前游戏没有兼容的仪表盘'); return; } gridEl.innerHTML = themes.map(t => this._themeCardHtml(t)).join(''); } catch (e) { gridEl.innerHTML = '
加载失败
'; } }, async _loadMarket(gridEl) { try { const res = await fetch('/api/market/dashboards'); const data = await res.json(); const items = data.items || []; if (!items.length) { gridEl.innerHTML = this._emptyHtml('市场暂无仪表盘'); return; } gridEl.innerHTML = items.map(d => { const icon = d.icon || '📦'; return `
${icon}
${d.name||'Unnamed'}
${d.category||''}v${d.version||'1.0'}
${d.author||''}${d.downloads||0} 次下载
`; }).join(''); } catch (e) { gridEl.innerHTML = '
市场加载失败
'; } }, _isGameSupported(theme) { const supported = theme.supported_games || 'all'; if (supported === 'all') return true; const ids = supported.split(',').map(s => s.trim()); return ids.includes(this._selectedGameId); }, _emptyHtml(msg) { return `

${msg || '暂无仪表盘主题'}

创建或导入你的仪表盘主题

`; }, _bindEvents() { const categoryList = document.getElementById('dash-category-list'); if (categoryList) { categoryList.addEventListener('click', (e) => { const item = e.target.closest('.category-item'); if (!item) return; categoryList.querySelectorAll('.category-item').forEach(el => el.classList.remove('active')); item.classList.add('active'); this._currentCategory = item.dataset.cat; this._loadThemes(); }); } const gridEl = document.getElementById('dash-theme-grid'); if (gridEl) { gridEl.addEventListener('click', (e) => { const card = e.target.closest('.theme-card'); if (!card) return; const themeId = card.dataset.themeId; if (themeId) this._openDashboard(themeId); const installBtn = e.target.closest('.btn-market-install'); if (installBtn) this._installFromMarket(installBtn.dataset.id, installBtn.dataset.file); }); gridEl.addEventListener('contextmenu', (e) => { const card = e.target.closest('.theme-card'); if (!card || !card.dataset.themeId) return; e.preventDefault(); this._exportDashboard(card.dataset.themeId); }); } document.getElementById('dash-search')?.addEventListener('input', () => this._loadThemes()); document.getElementById('dash-sort')?.addEventListener('change', () => this._loadThemes()); document.getElementById('btn-new-dashboard')?.addEventListener('click', () => this._createNew()); document.getElementById('btn-import-dashboard')?.addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.tsd'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const fd = new FormData(); fd.append('file', file); const res = await fetch('/api/dashboards/import', { method: 'POST', body: fd }); const data = await res.json(); if (data.ok) { Toast.show('导入成功', 'success'); this._loadThemes(); } else Toast.show('导入失败', 'error'); }; input.click(); }); document.getElementById('btn-market-upload')?.addEventListener('click', () => this._showMarketUpload()); document.getElementById('btn-market-login')?.addEventListener('click', () => this._showMarketLogin()); document.getElementById('btn-market-tab')?.addEventListener('click', () => { document.getElementById('dash-category-list').querySelectorAll('.category-item').forEach(el => el.classList.remove('active')); document.getElementById('btn-market-tab').classList.add('active'); this._marketMode = true; document.getElementById('dash-search-box').style.display = 'none'; this._loadThemes(); }); this._updateMarketUI(); }, async _installFromMarket(id, filename) { try { const res = await fetch(`/api/market/install?id=${id}&filename=${filename}`); const data = await res.json(); if (data.ok) { Toast.show('安装成功', 'success'); this._loadThemes(); } else Toast.show('安装失败', 'error'); } catch (e) { Toast.show('安装失败', 'error'); } }, _showMarketUpload() { if (!this._marketToken) { Toast.show('请先登录市场账户', 'info'); this._showMarketLogin(); return; } const input = document.createElement('input'); input.type = 'file'; input.accept = '.tsd'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const fd = new FormData(); fd.append('file', file); fd.append('name', file.name.replace('.tsd','')); fd.append('category', 'community'); fd.append('author', 'TurboSu User'); fd.append('token', this._marketToken); try { const res = await fetch('/api/market/upload', { method: 'POST', body: fd }); const data = await res.json(); Toast.show(data.error ? '上传失败: ' + data.error : '上传成功', data.error ? 'error' : 'success'); } catch (e) { Toast.show('上传失败', 'error'); } }; input.click(); }, _showMarketLogin() { const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.innerHTML = ``; document.body.appendChild(overlay); if (this._marketToken) { overlay.querySelector('#ml-login').style.display = 'none'; overlay.querySelector('#ml-register').style.display = 'none'; overlay.querySelector('#ml-logout').style.display = ''; overlay.querySelector('#ml-logout').addEventListener('click', () => { this._marketToken = null; localStorage.removeItem('turbosu-market-token'); overlay.remove(); this._updateMarketUI(); Toast.show('已退出', 'info'); }); } overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); }); overlay.querySelector('#ml-login').addEventListener('click', async () => { const email = document.getElementById('ml-email').value; const pass = document.getElementById('ml-pass').value; const res = await fetch('/api/market/auth', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({email, password}) }); const data = await res.json(); if (data.token) { this._marketToken = data.token; localStorage.setItem('turbosu-market-token', data.token); Toast.show('登录成功', 'success'); overlay.remove(); this._updateMarketUI(); } else { Toast.show(data.message || '登录失败', 'error'); } }); overlay.querySelector('#ml-register').addEventListener('click', async () => { const email = document.getElementById('ml-email').value; const pass = document.getElementById('ml-pass').value; const res = await fetch('/api/market/register', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({email, password: pass, passwordConfirm: pass}) }); const data = await res.json(); Toast.show(data.error ? '注册失败: ' + data.error : '注册成功,请查收验证邮件', data.error ? 'error' : 'success'); }); }, _updateMarketUI() { const loginBtn = document.getElementById('btn-market-login'); const uploadBtn = document.getElementById('btn-market-upload'); if (!loginBtn || !uploadBtn) return; if (this._marketToken) { loginBtn.textContent = '已登录'; loginBtn.className = 'btn btn-sm btn-secondary'; uploadBtn.style.display = ''; } else { loginBtn.textContent = '登录'; loginBtn.className = 'btn btn-sm btn-primary'; uploadBtn.style.display = 'none'; } }, _openDashboard(themeId) { const gameParam = this._selectedGameId ? `?game=${this._selectedGameId}` : ''; const url = `${location.origin}/dashboard/${themeId}${gameParam}`; window.open(url, '_blank'); navigator.clipboard.writeText(url).then(() => { Toast.show('链接已复制,可在局域网设备访问', 'success'); }).catch(() => { Toast.show(`打开地址: ${url}`, 'info'); }); }, _exportDashboard(themeId) { const a = document.createElement('a'); a.href = `/api/dashboards/${themeId}/export`; a.download = `${themeId}.tsd`; a.click(); Toast.show('正在下载 .tsd 文件...', 'info'); }, _themeCardHtml(theme) { const icon = theme.icon || '📊'; const aspect = theme.aspect_ratio || 'auto'; const supported = theme.supported_games || 'all'; const isAll = supported === 'all'; const compatLabel = isAll ? '全' : '限'; const compatClass = isAll ? 'badge-info' : 'badge-warning'; const preview = `/dashboard/${theme.id}/preview.jpg`; return `
${icon}
${theme.name || 'Unnamed'}
${theme.category || 'basic'} ${aspect}
${theme.author || ''} ${compatLabel}
`; }, async _createNew() { const name = prompt('仪表盘名称:', 'My Dashboard'); if (!name) return; try { const html = 'TurboSu - ' + name + '
0