diff --git a/server/api.py b/server/api.py index 8d36a73..593b738 100644 --- a/server/api.py +++ b/server/api.py @@ -705,3 +705,41 @@ async def api_market_install_scene(id: str = "", filename: str = ""): return {"ok": ok, "missing_dashboards": missing} except Exception as e: return {"ok": False, "error": str(e)} + + +@router.get("/market/mine") +async def api_market_mine(token: str = ""): + import httpx + cfg = get_config() + url = cfg.get("market_url", "http://127.0.0.1:5301") + result = {"dashboards": [], "scenes": []} + try: + async with httpx.AsyncClient(timeout=10) as client: + r = await client.get(f"{url}/api/collections/dashboards/records?sort=-created&perPage=100", + headers={"Authorization": f"Bearer {token}"}) + result["dashboards"] = r.json().get("items", []) + r = await client.get(f"{url}/api/collections/scenes/records?sort=-created&perPage=100", + headers={"Authorization": f"Bearer {token}"}) + result["scenes"] = r.json().get("items", []) + except Exception: + pass + return result + + +@router.post("/market/delete") +async def api_market_delete(data: dict): + import httpx + cfg = get_config() + url = cfg.get("market_url", "http://127.0.0.1:5301") + try: + async with httpx.AsyncClient(timeout=10) as client: + collection = data.get("collection", "dashboards") + record_id = data.get("id", "") + token = data.get("token", "") + resp = await client.delete( + f"{url}/api/collections/{collection}/records/{record_id}", + headers={"Authorization": f"Bearer {token}"} + ) + return {"ok": resp.status_code < 400} + except Exception as e: + return {"ok": False, "error": str(e)} diff --git a/static/js/pages/market.js b/static/js/pages/market.js index 0f0d88c..65ac124 100644 --- a/static/js/pages/market.js +++ b/static/js/pages/market.js @@ -20,6 +20,14 @@ const PageMarket = { if (!grid) return; grid.innerHTML = '
加载中...
'; + if (this._tab === 'mine') { + if (!this._marketToken) { + grid.innerHTML = this._emptyHtml('请先登录'); + return; + } + return this._loadMine(grid); + } + const isScene = this._tab === 'scenes'; const endpoint = isScene ? '/api/market/scenes' : '/api/market/dashboards'; try { @@ -27,32 +35,92 @@ const PageMarket = { const data = await res.json(); const items = data.items || []; if (!items.length) { grid.innerHTML = this._emptyHtml(isScene ? '暂无场景' : '暂无仪表盘'); return; } - grid.innerHTML = items.map(d => { - const icon = isScene ? '🎬' : (d.icon || '📦'); - const btnLabel = isScene ? '安装场景' : '安装'; - const btnClass = isScene ? 'btn-m-install-scene' : 'btn-m-install'; - return `
-
${icon}
-
-
${d.name||'Unnamed'}
-
- ${isScene ? (d.game_id||'') : (d.category||'')}${isScene ? '' : 'v'+(d.version||'1.0')} -
-
- ${d.author||''}${d.downloads||0} 次 -
- -
`; - }).join(''); + grid.innerHTML = items.map(d => this._cardHtml(d, isScene)).join(''); } catch (e) { grid.innerHTML = '
加载失败
'; } }, + async _loadMine(grid) { + try { + const res = await fetch('/api/market/mine?token=' + encodeURIComponent(this._marketToken)); + const data = await res.json(); + if (!data.dashboards?.length && !data.scenes?.length) { + grid.innerHTML = this._emptyHtml('你还没有上传任何内容'); + return; + } + let html = ''; + if (data.dashboards?.length) { + html += '

我的仪表盘

' + data.dashboards.map(d => this._myCardHtml(d, 'dashboards')).join(''); + } + if (data.scenes?.length) { + html += '

我的场景

' + data.scenes.map(d => this._myCardHtml(d, 'scenes')).join(''); + } + grid.innerHTML = html; + } catch (e) { grid.innerHTML = '
加载失败
'; } + }, + + _cardHtml(d, isScene) { + const icon = isScene ? '🎬' : (d.icon || '📦'); + const btnLabel = isScene ? '安装场景' : '安装'; + const btnClass = isScene ? 'btn-m-install-scene' : 'btn-m-install'; + return `
+
${icon}
+
+
${d.name||'Unnamed'}
+
+ ${isScene ? (d.game_id||'') : (d.category||'')}${isScene ? '' : 'v'+(d.version||'1.0')} +
+
+ ${d.author||''}${d.downloads||0} 次 +
+ +
`; + }, + + _myCardHtml(d, collection) { + const pending = d.dashb_pending ? '待审核' : '已通过'; + return `
+
+
${d.name||'Unnamed'}
+
${d.author||''} · ${d.downloads||0} 次下载 ${pending}
+
+
+ + +
`; + }, + _bindEvents() { 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 btnInstall = e.target.closest('.btn-m-install'); const btnScene = e.target.closest('.btn-m-install-scene'); + const btnUpdate = e.target.closest('.btn-my-update'); + const btnDelete = e.target.closest('.btn-my-delete'); + + if (btnUpdate) { + const input = document.createElement('input'); input.type = 'file'; input.accept = btnUpdate.dataset.type; + input.onchange = async (ev) => { + const file = ev.target.files[0]; if (!file) return; + const fd = new FormData(); fd.append('file', file); fd.append('token', this._marketToken); + const url = btnUpdate.dataset.coll === 'scenes' ? '/api/market/scenes/upload' : '/api/market/upload'; + const res = await fetch(url, { method: 'POST', body: fd }); + const data = await res.json(); + Toast.show(data.error ? '更新失败: ' + data.error : '上传成功(新记录)', data.error ? 'error' : 'success'); + if (!data.error) this._loadItems(); + }; input.click(); + return; + } + if (btnDelete) { + if (!confirm('确定删除?')) return; + try { + await fetch('/api/market/delete', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({id: btnDelete.dataset.id, collection: btnDelete.dataset.coll, token: this._marketToken}) }); + Toast.show('已删除', 'info'); + this._loadItems(); + } catch (e) { Toast.show('删除失败', 'error'); } + return; + } + const id = (btnInstall || btnScene)?.dataset.id; const file = (btnInstall || btnScene)?.dataset.file; if (!id || !file) return; @@ -82,6 +150,14 @@ const PageMarket = { this._tab = 'scenes'; document.getElementById('tab-scenes').style.cssText = 'border-radius:0;border-bottom:2px solid var(--accent);color:var(--accent);background:transparent;padding:8px 16px'; document.getElementById('tab-dashboards').style.cssText = 'border-radius:0;border-bottom:2px solid transparent;color:var(--text-secondary);background:transparent;padding:8px 16px'; + document.getElementById('tab-mine').style.cssText = 'border-radius:0;border-bottom:2px solid transparent;color:var(--text-secondary);background:transparent;padding:8px 16px'; + this._loadItems(); + }); + document.getElementById('tab-mine')?.addEventListener('click', () => { + this._tab = 'mine'; + document.getElementById('tab-mine').style.cssText = 'border-radius:0;border-bottom:2px solid var(--accent);color:var(--accent);background:transparent;padding:8px 16px'; + document.getElementById('tab-dashboards').style.cssText = 'border-radius:0;border-bottom:2px solid transparent;color:var(--text-secondary);background:transparent;padding:8px 16px'; + document.getElementById('tab-scenes').style.cssText = 'border-radius:0;border-bottom:2px solid transparent;color:var(--text-secondary);background:transparent;padding:8px 16px'; this._loadItems(); }); }, @@ -197,6 +273,7 @@ const PageMarket = {
+
`;