refactor: standalone dashboard folders with index.html, zip-based import/export (.tsd/.tss/.tsp)

- Each dashboard is now a self-contained folder: manifest.json + index.html
- Auto-scan dashboards/ and data/dashboards/ on startup
- Export: .tsd (dashboards), .tss (scenes), .tsp (game plugins) - all zip format
- Dashboard index.html is complete standalone page (WS + data binding)
- Aspect ratio constraints handled in each dashboard's own JS
- Removed template-based dashboard rendering in favor of static serve
- Import via file upload endpoints, export via direct file download
This commit is contained in:
2026-07-25 15:35:08 +08:00
parent d61bb61a83
commit 63e3c8d607
21 changed files with 752 additions and 380 deletions
+39 -2
View File
@@ -83,7 +83,33 @@ const PageDashboard = {
const themeId = card.dataset.themeId;
this._openDashboard(themeId);
});
gridEl.addEventListener('contextmenu', (e) => {
const card = e.target.closest('.theme-card');
if (!card) return;
e.preventDefault();
const themeId = card.dataset.themeId;
this._exportDashboard(themeId);
});
}
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 formData = new FormData();
formData.append('file', file);
try {
const res = await fetch('/api/dashboards/import', { method: 'POST', body: formData });
const data = await res.json();
if (data.ok) { Toast.show('仪表盘导入成功', 'success'); this._loadThemes(); }
else Toast.show('导入失败', 'error');
} catch (err) { Toast.show('导入失败', 'error'); }
};
input.click();
});
},
_openDashboard(themeId) {
@@ -96,9 +122,17 @@ const PageDashboard = {
});
},
_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.config?.icon || '📊';
const aspect = theme.config?.aspect_ratio || 'auto';
const icon = theme.icon || '📊';
const aspect = theme.aspect_ratio || 'auto';
return `
<div class="glass-card theme-card" data-theme-id="${theme.id}">
<div class="theme-card-preview">${icon}</div>
@@ -121,6 +155,9 @@ const PageDashboard = {
<div class="dashboard-layout">
<div class="dashboard-sub-sidebar">
<div id="dash-category-list"></div>
<div style="padding:8px 12px;margin-top:auto;">
<button id="btn-import-dashboard" class="btn btn-sm btn-secondary" style="width:100%;">导入 .tsd</button>
</div>
</div>
<div class="dashboard-main">
<div id="dash-theme-grid" class="theme-grid"></div>