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 = {
+
`;