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:
@@ -48,6 +48,24 @@ const PageScene = {
|
||||
|
||||
_bindEvents() {
|
||||
document.getElementById('btn-new-scene')?.addEventListener('click', () => this._showEditor());
|
||||
document.getElementById('btn-import-scene')?.addEventListener('click', () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.tss';
|
||||
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/scenes/import', { method: 'POST', body: formData });
|
||||
const data = await res.json();
|
||||
if (data.ok) { Toast.show('场景导入成功', 'success'); this._loadScenes(); }
|
||||
else Toast.show('导入失败', 'error');
|
||||
} catch (err) { Toast.show('导入失败', 'error'); }
|
||||
};
|
||||
input.click();
|
||||
});
|
||||
|
||||
const grid = document.getElementById('scene-grid');
|
||||
if (grid) {
|
||||
@@ -65,6 +83,9 @@ const PageScene = {
|
||||
} else if (action === 'delete') {
|
||||
e.stopPropagation();
|
||||
this._deleteScene(sceneId);
|
||||
} else if (action === 'export') {
|
||||
e.stopPropagation();
|
||||
this._exportScene(sceneId);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -122,6 +143,14 @@ const PageScene = {
|
||||
this._loadScenes();
|
||||
},
|
||||
|
||||
_exportScene(sceneId) {
|
||||
const a = document.createElement('a');
|
||||
a.href = `/api/scenes/${sceneId}/export`;
|
||||
a.download = `${sceneId}.tss`;
|
||||
a.click();
|
||||
Toast.show('正在下载 .tss 文件...', 'info');
|
||||
},
|
||||
|
||||
_sceneCardHtml(scene) {
|
||||
const canvasCount = (scene.canvases || []).length;
|
||||
return `
|
||||
@@ -138,6 +167,7 @@ const PageScene = {
|
||||
<div style="margin-top:12px;display:flex;gap:6px;">
|
||||
<button class="btn btn-sm btn-primary" data-scene-id="${scene.id}" data-action="render">渲染</button>
|
||||
<button class="btn btn-sm btn-secondary" data-scene-id="${scene.id}" data-action="edit">编辑</button>
|
||||
<button class="btn btn-sm btn-secondary" data-scene-id="${scene.id}" data-action="export">导出</button>
|
||||
<button class="btn btn-sm btn-danger" data-scene-id="${scene.id}" data-action="delete">删除</button>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -176,9 +206,12 @@ const PageScene = {
|
||||
${this._currentGameId ? `当前游戏: ${this._currentGameName}` : '请先在侧边栏选择一个游戏'}
|
||||
</p>
|
||||
</div>
|
||||
<button id="btn-new-scene" class="btn btn-primary" ${!this._currentGameId ? 'disabled' : ''}>
|
||||
+ 新建场景
|
||||
</button>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<button id="btn-import-scene" class="btn btn-secondary btn-sm">导入 .tss</button>
|
||||
<button id="btn-new-scene" class="btn btn-primary btn-sm" ${!this._currentGameId ? 'disabled' : ''}>
|
||||
+ 新建场景
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="scene-grid" class="scene-grid animated"></div>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user