From ee5a4c81cf9c9b546804e21b4b106975383c6881 Mon Sep 17 00:00:00 2001 From: gouki Date: Sun, 9 Aug 2026 00:13:16 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B9=B3=E5=8F=B0=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E3=80=8C=E5=85=B3=E8=81=94N=E4=B8=AA=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E3=80=8D=E7=82=B9=E5=87=BB=E8=B7=B3=E8=BD=AC=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E9=A1=B5=E5=B9=B6=E6=8C=89=E5=B9=B3=E5=8F=B0=E7=AD=9B=E9=80=89?= =?UTF-8?q?(=E5=90=8E=E7=AB=AF/api/assets=E6=96=B0=E5=A2=9Eprovider?= =?UTF-8?q?=E5=8F=82=E6=95=B0,=E8=B5=84=E4=BA=A7=E9=A1=B5=E5=8F=AF?= =?UTF-8?q?=E6=B8=85=E9=99=A4=E7=AD=9B=E9=80=89=E6=A0=87=E7=AD=BE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- app/routers/assets.py | 5 +++-- app/services/asset_service.py | 8 ++++++++ static/js/store.js | 15 +++++++++++++++ static/js/views/assets.js | 15 ++++++++++++++- static/js/views/providers.js | 4 ++-- 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index 42045ac..c2c0004 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.4 +0.3.5 diff --git a/app/routers/assets.py b/app/routers/assets.py index 01e5096..3405eab 100644 --- a/app/routers/assets.py +++ b/app/routers/assets.py @@ -20,15 +20,16 @@ router = APIRouter(prefix="/api/assets", tags=["assets"]) @router.get("", response_model=List[AssetRead], summary="资产列表") def list_assets( asset_type: Optional[AssetType] = Query(default=None, description="按类型筛选"), - status: Optional[AssetStatus] = Query(default=None, description="按状态筛选"), + status_filter: Optional[AssetStatus] = Query(default=None, alias="status", description="按状态筛选"), is_archived: Optional[bool] = Query(default=None, description="按归档标记筛选"), q: Optional[str] = Query(default=None, description="搜索名称/服务商"), + provider: Optional[str] = Query(default=None, description="按平台筛选(slug 或平台 ID)"), sort: str = Query(default="expiry_date", description="排序字段"), order: str = Query(default="asc", description="排序方向 asc/desc"), session: Session = Depends(get_session), ): return asset_service.list_assets( - session, asset_type, status, is_archived, q, sort, order + session, asset_type, status_filter, is_archived, q, provider, sort, order ) diff --git a/app/services/asset_service.py b/app/services/asset_service.py index df6eb92..f069f7c 100644 --- a/app/services/asset_service.py +++ b/app/services/asset_service.py @@ -7,6 +7,7 @@ from datetime import date, datetime, timedelta, timezone from typing import List, Optional from fastapi import HTTPException, status +from sqlalchemy import or_ from sqlmodel import Session, select from app.core import crypto @@ -358,6 +359,7 @@ def list_assets( asset_status: Optional[AssetStatus] = None, is_archived: Optional[bool] = None, q: Optional[str] = None, + provider: Optional[str] = None, sort: str = "expiry_date", order: str = "asc", ) -> List[AssetRead]: @@ -369,6 +371,12 @@ def list_assets( stmt = stmt.where(Asset.status == asset_status) if is_archived is not None: stmt = stmt.where(Asset.is_archived == is_archived) + if provider: + # 兼容两种关联方式:provider slug 文本或 provider_id 外键 + conds = [Asset.provider == provider] + if provider.isdigit(): + conds.append(Asset.provider_id == int(provider)) + stmt = stmt.where(or_(*conds)) if q: pattern = f"%{q}%" stmt = stmt.where(Asset.name.like(pattern) | Asset.provider.like(pattern)) diff --git a/static/js/store.js b/static/js/store.js index 5b5a215..1b9682a 100644 --- a/static/js/store.js +++ b/static/js/store.js @@ -17,6 +17,7 @@ const store = reactive({ search: '', filterType: '', filterStatus: '', + filterProvider: '', // 按平台筛选(slug),从平台页「关联 N 个资产」跳转而来 assetTab: '', // 资产页 TAB:'' 全部 / vps / domain / cloudflare / subscription(付费资产) / ai_agent monitorTab: '', // 监控中心 TAB:同上 dark: localStorage.getItem('vps_dark') === '1', @@ -76,6 +77,7 @@ async function loadAssets() { const params = new URLSearchParams(); if (store.filterType) params.set('asset_type', store.filterType); if (store.filterStatus) params.set('status', store.filterStatus); + if (store.filterProvider) params.set('provider', store.filterProvider); if (store.search) params.set('q', store.search); params.set('sort', 'expiry_date'); params.set('order', 'asc'); store.assets = await Api.get('/assets?' + params.toString()); @@ -197,6 +199,19 @@ async function deleteAccount(a) { catch (e) { store.error = e.message; } } +/* ---------------- 平台页跳转:按平台查看资产 ---------------- */ +function openAssetsByProvider(p) { + store.filterProvider = p.slug; + store.assetTab = ''; + store.filterType = ''; + if (window.location.hash !== '#assets') window.location.hash = '#assets'; + loadAssets(); +} +function clearProviderFilter() { + store.filterProvider = ''; + loadAssets(); +} + /* ---------------- 服务器监控 ---------------- */ async function viewServer(a) { try { diff --git a/static/js/views/assets.js b/static/js/views/assets.js index e577c19..5a016f2 100644 --- a/static/js/views/assets.js +++ b/static/js/views/assets.js @@ -14,6 +14,14 @@ const AssetsView = { + +
+ + 🏢 平台:{{ providerName(store.filterProvider) }} + + +
+