feat: dashboard manifest supports 'supported_games' field, frontend filters by selected game

- DashboardTheme model adds supported_games (default: 'all')
- Built-in dashboards declare game compatibility:
  speedometer/tachometer/gear_indicator -> all
  lap_timer -> forza_horizon_5,forza_horizon_4,forza_motorsport,f1_24
- Dashboard browser filters themes by selected game
- Card shows '全' (all) or '限' (limited) badge
- Updated DASHBOARD_DEV.md with supported_games spec
This commit is contained in:
2026-07-25 15:41:55 +08:00
parent 5e177f4514
commit 2177e65fb4
7 changed files with 46 additions and 10 deletions
+35 -8
View File
@@ -1,12 +1,15 @@
const PageDashboard = {
_el: null,
_currentCategory: 'all',
_selectedGameId: null,
init() {
this._el = document.getElementById('page-dashboard');
},
async render() {
const cfg = await API.getConfig();
this._selectedGameId = cfg.selected_game_id;
this._el.innerHTML = this._template();
await this._loadCategories();
await this._loadThemes();
@@ -44,14 +47,18 @@ const PageDashboard = {
gridEl.innerHTML = '<div style="text-align:center;padding:40px;color:var(--text-tertiary);">加载中...</div>';
try {
const themes = await API.getDashboards(this._currentCategory);
let themes = await API.getDashboards(this._currentCategory);
if (!themes || themes.length === 0) {
gridEl.innerHTML = `
<div class="empty-state" style="grid-column:1/-1;">
<svg viewBox="0 0 24 24" width="64" height="64"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-12c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z" fill="currentColor"/></svg>
<h4>暂无仪表盘主题</h4>
<p>创建或导入你的第一个仪表盘主题</p>
</div>`;
gridEl.innerHTML = this._emptyHtml();
return;
}
if (this._selectedGameId) {
themes = themes.filter(t => this._isGameSupported(t));
}
if (themes.length === 0) {
gridEl.innerHTML = this._emptyHtml('当前游戏没有兼容的仪表盘');
return;
}
@@ -62,6 +69,22 @@ const PageDashboard = {
}
},
_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 `
<div class="empty-state" style="grid-column:1/-1;">
<svg viewBox="0 0 24 24" width="64" height="64"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-12c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z" fill="currentColor"/></svg>
<h4>${msg || '暂无仪表盘主题'}</h4>
<p>创建或导入你的仪表盘主题</p>
</div>`;
},
_bindEvents() {
const categoryList = document.getElementById('dash-category-list');
if (categoryList) {
@@ -133,6 +156,10 @@ const PageDashboard = {
_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';
return `
<div class="glass-card theme-card" data-theme-id="${theme.id}">
<div class="theme-card-preview">${icon}</div>
@@ -144,7 +171,7 @@ const PageDashboard = {
</div>
<div class="theme-card-meta" style="margin-top:4px;">
<span>${theme.author || ''}</span>
<span class="badge ${theme.is_builtin ? 'badge-info' : 'badge-success'}">${theme.is_builtin ? '内置' : '自定义'}</span>
<span class="badge ${compatClass}">${compatLabel}</span>
</div>
</div>
</div>`;