feat: market scenes tab - upload/install with dependency check

This commit is contained in:
2026-07-26 21:37:24 +08:00
parent e6344b1916
commit 47341f1f76
2 changed files with 136 additions and 14 deletions
+49 -13
View File
@@ -1,6 +1,7 @@
const PageMarket = {
_el: null,
_marketToken: null,
_tab: 'dashboards',
init() {
this._el = document.getElementById('page-market');
@@ -18,24 +19,29 @@ const PageMarket = {
const grid = document.getElementById('market-grid');
if (!grid) return;
grid.innerHTML = '<div style="text-align:center;padding:40px;color:var(--text-tertiary);">加载中...</div>';
const isScene = this._tab === 'scenes';
const endpoint = isScene ? '/api/market/scenes' : '/api/market/dashboards';
try {
const res = await fetch('/api/market/dashboards');
const res = await fetch(endpoint);
const data = await res.json();
const items = data.items || [];
if (!items.length) { grid.innerHTML = this._emptyHtml('市场暂无仪表盘'); return; }
if (!items.length) { grid.innerHTML = this._emptyHtml(isScene ? '暂无场景' : '暂无仪表盘'); return; }
grid.innerHTML = items.map(d => {
const icon = d.icon || '📦';
const icon = isScene ? '🎬' : (d.icon || '📦');
const btnLabel = isScene ? '安装场景' : '安装';
const btnClass = isScene ? 'btn-m-install-scene' : 'btn-m-install';
return `<div class="glass-card theme-card">
<div class="theme-card-preview">${icon}</div>
<div class="theme-card-info">
<div class="theme-card-name">${d.name||'Unnamed'}</div>
<div class="theme-card-meta">
<span>${d.category||''}</span><span>v${d.version||'1.0'}</span>
<span>${isScene ? (d.game_id||'') : (d.category||'')}</span><span>${isScene ? '' : 'v'+(d.version||'1.0')}</span>
</div>
<div class="theme-card-meta" style="margin-top:4px">
<span>${d.author||''}</span><span style="color:var(--text-tertiary)">${d.downloads||0} 次</span>
</div>
<button class="btn btn-sm btn-primary btn-m-install" data-id="${d.id}" data-file="${d.file||''}" style="margin-top:8px;width:100%">安装</button>
<button class="btn btn-sm btn-primary ${btnClass}" data-id="${d.id}" data-file="${d.file||''}" style="margin-top:8px;width:100%">${btnLabel}</button>
</div></div>`;
}).join('');
} catch (e) { grid.innerHTML = '<div style="text-align:center;padding:40px;color:var(--danger);">加载失败</div>'; }
@@ -45,14 +51,39 @@ const PageMarket = {
document.getElementById('btn-m-login')?.addEventListener('click', () => this._showLogin());
document.getElementById('btn-m-upload')?.addEventListener('click', () => this._upload());
document.getElementById('market-grid')?.addEventListener('click', async (e) => {
const btn = e.target.closest('.btn-m-install');
if (!btn) return;
const btnInstall = e.target.closest('.btn-m-install');
const btnScene = e.target.closest('.btn-m-install-scene');
const id = (btnInstall || btnScene)?.dataset.id;
const file = (btnInstall || btnScene)?.dataset.file;
if (!id || !file) return;
const isScene = !!btnScene;
const url = isScene ? `/api/market/scenes/install?id=${encodeURIComponent(id)}&filename=${encodeURIComponent(file)}`
: `/api/market/install?id=${encodeURIComponent(id)}&filename=${encodeURIComponent(file)}`;
try {
const res = await fetch(`/api/market/install?id=${encodeURIComponent(btn.dataset.id)}&filename=${encodeURIComponent(btn.dataset.file)}`);
const res = await fetch(url);
const data = await res.json();
Toast.show(data.ok ? '安装成功' : '安装失败: ' + (data.error||''), data.ok ? 'success' : 'error');
if (data.ok) {
let msg = '安装成功';
if (data.missing_dashboards && data.missing_dashboards.length > 0)
msg += ',缺少 ' + data.missing_dashboards.length + ' 个依赖仪表盘';
Toast.show(msg, 'success');
} else {
Toast.show('安装失败: ' + (data.error||''), 'error');
}
} catch (e) { Toast.show('安装失败', 'error'); }
});
document.getElementById('tab-dashboards')?.addEventListener('click', () => {
this._tab = 'dashboards';
document.getElementById('tab-dashboards').classList.add('active');
document.getElementById('tab-scenes').classList.remove('active');
this._loadItems();
});
document.getElementById('tab-scenes')?.addEventListener('click', () => {
this._tab = 'scenes';
document.getElementById('tab-scenes').classList.add('active');
document.getElementById('tab-dashboards').classList.remove('active');
this._loadItems();
});
},
_updateUI() {
@@ -74,7 +105,8 @@ const PageMarket = {
},
_upload() {
const input = document.createElement('input'); input.type = 'file'; input.accept = '.tsd';
const input = document.createElement('input'); input.type = 'file'; input.accept = this._tab === 'scenes' ? '.tss' : '.tsd';
const endpoint = this._tab === 'scenes' ? '/api/market/scenes/upload' : '/api/market/upload';
input.onchange = async (e) => {
const file = e.target.files[0]; if (!file) return;
const fd = new FormData(); fd.append('file', file);
@@ -152,16 +184,20 @@ const PageMarket = {
_template() {
return `<div class="animated">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:10px">
<div>
<h2 style="font-size:24px;font-weight:700">仪表盘市场</h2>
<p style="color:var(--text-secondary);font-size:14px;margin-top:4px">浏览并安装社区分享的仪表盘 <span id="m-status" style="color:var(--text-tertiary)"> 未登录</span></p>
<p style="color:var(--text-secondary);font-size:14px;margin-top:4px">浏览并安装社区分享的仪表盘和场景 <span id="m-status" style="color:var(--text-tertiary)"> 未登录</span></p>
</div>
<div style="display:flex;gap:8px">
<button id="btn-m-upload" class="btn btn-sm btn-secondary" style="display:none">发布到市场</button>
<button id="btn-m-upload" class="btn btn-sm btn-secondary" style="display:none">发布仪表盘</button>
<button id="btn-m-login" class="btn btn-sm btn-primary">登录</button>
</div>
</div>
<div style="display:flex;gap:0;margin-bottom:16px;border-bottom:1px solid var(--border-subtle)">
<button id="tab-dashboards" class="btn btn-sm active" style="border-radius:0;border-bottom:2px solid var(--accent);color:var(--accent);background:transparent;padding:8px 16px">仪表盘</button>
<button id="tab-scenes" class="btn btn-sm" style="border-radius:0;border-bottom:2px solid transparent;color:var(--text-secondary);background:transparent;padding:8px 16px">场景</button>
</div>
<div id="market-grid" class="theme-grid"></div>
</div>`;
}