feat(accounts): 增加全选所有页功能并重构批量操作API

- 前端增加全选所有页横幅和状态管理
- 后端批量API支持select_all参数和筛选条件传递
- 统一批量操作逻辑,支持全选和筛选条件组合
This commit is contained in:
cnlimiter
2026-03-16 18:54:49 +08:00
parent 19eb172eee
commit 46f390a984
3 changed files with 245 additions and 64 deletions

View File

@@ -60,7 +60,11 @@ class AccountUpdateRequest(BaseModel):
class BatchDeleteRequest(BaseModel):
"""批量删除请求"""
ids: List[int]
ids: List[int] = []
select_all: bool = False
status_filter: Optional[str] = None
email_service_filter: Optional[str] = None
search_filter: Optional[str] = None
class BatchUpdateRequest(BaseModel):
@@ -71,6 +75,30 @@ class BatchUpdateRequest(BaseModel):
# ============== Helper Functions ==============
def resolve_account_ids(
db,
ids: List[int],
select_all: bool = False,
status_filter: Optional[str] = None,
email_service_filter: Optional[str] = None,
search_filter: Optional[str] = None,
) -> List[int]:
"""当 select_all=True 时查询全部符合条件的 ID否则直接返回传入的 ids"""
if not select_all:
return ids
query = db.query(Account.id)
if status_filter:
query = query.filter(Account.status == status_filter)
if email_service_filter:
query = query.filter(Account.email_service == email_service_filter)
if search_filter:
pattern = f"%{search_filter}%"
query = query.filter(
(Account.email.ilike(pattern)) | (Account.account_id.ilike(pattern))
)
return [row[0] for row in query.all()]
def account_to_response(account: Account) -> AccountResponse:
"""转换 Account 模型为响应模型"""
return AccountResponse(
@@ -162,9 +190,9 @@ async def get_account_tokens(account_id: int):
return {
"id": account.id,
"email": account.email,
"access_token": account.access_token[:50] + "..." if account.access_token else None,
"refresh_token": account.refresh_token[:50] + "..." if account.refresh_token else None,
"id_token": account.id_token[:50] + "..." if account.id_token else None,
"access_token": account.access_token,
"refresh_token": account.refresh_token,
"id_token": account.id_token,
"has_tokens": bool(account.access_token and account.refresh_token),
}
@@ -208,10 +236,14 @@ async def delete_account(account_id: int):
async def batch_delete_accounts(request: BatchDeleteRequest):
"""批量删除账号"""
with get_db() as db:
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
deleted_count = 0
errors = []
for account_id in request.ids:
for account_id in ids:
try:
account = crud.get_account_by_id(db, account_id)
if account:
@@ -255,14 +287,22 @@ async def batch_update_accounts(request: BatchUpdateRequest):
class BatchExportRequest(BaseModel):
"""批量导出请求"""
ids: List[int]
ids: List[int] = []
select_all: bool = False
status_filter: Optional[str] = None
email_service_filter: Optional[str] = None
search_filter: Optional[str] = None
@router.post("/export/json")
async def export_accounts_json(request: BatchExportRequest):
"""导出账号为 JSON 格式"""
with get_db() as db:
accounts = db.query(Account).filter(Account.id.in_(request.ids)).all()
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
accounts = db.query(Account).filter(Account.id.in_(ids)).all()
export_data = []
for acc in accounts:
@@ -304,7 +344,11 @@ async def export_accounts_csv(request: BatchExportRequest):
import io
with get_db() as db:
accounts = db.query(Account).filter(Account.id.in_(request.ids)).all()
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
accounts = db.query(Account).filter(Account.id.in_(ids)).all()
# 创建 CSV 内容
output = io.StringIO()
@@ -357,7 +401,11 @@ async def export_accounts_cpa(request: BatchExportRequest):
from ...core.cpa_upload import generate_token_json
with get_db() as db:
accounts = db.query(Account).filter(Account.id.in_(request.ids)).all()
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
accounts = db.query(Account).filter(Account.id.in_(ids)).all()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
@@ -427,8 +475,12 @@ class TokenRefreshRequest(BaseModel):
class BatchRefreshRequest(BaseModel):
"""批量刷新请求"""
ids: List[int]
ids: List[int] = []
proxy: Optional[str] = None
select_all: bool = False
status_filter: Optional[str] = None
email_service_filter: Optional[str] = None
search_filter: Optional[str] = None
class TokenValidateRequest(BaseModel):
@@ -438,8 +490,12 @@ class TokenValidateRequest(BaseModel):
class BatchValidateRequest(BaseModel):
"""批量验证请求"""
ids: List[int]
ids: List[int] = []
proxy: Optional[str] = None
select_all: bool = False
status_filter: Optional[str] = None
email_service_filter: Optional[str] = None
search_filter: Optional[str] = None
@router.post("/{account_id}/refresh")
@@ -478,7 +534,13 @@ async def batch_refresh_tokens(request: BatchRefreshRequest, background_tasks: B
"errors": []
}
for account_id in request.ids:
with get_db() as db:
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
for account_id in ids:
try:
result = do_refresh(account_id, proxy)
if result.success:
@@ -523,7 +585,13 @@ async def batch_validate_tokens(request: BatchValidateRequest):
"details": []
}
for account_id in request.ids:
with get_db() as db:
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
for account_id in ids:
try:
is_valid, error = do_validate(account_id, proxy)
results["details"].append({
@@ -555,8 +623,12 @@ class CPAUploadRequest(BaseModel):
class BatchCPAUploadRequest(BaseModel):
"""批量 CPA 上传请求"""
ids: List[int]
ids: List[int] = []
proxy: Optional[str] = None
select_all: bool = False
status_filter: Optional[str] = None
email_service_filter: Optional[str] = None
search_filter: Optional[str] = None
@router.post("/{account_id}/upload-cpa")
@@ -609,6 +681,12 @@ async def batch_upload_accounts_to_cpa(request: BatchCPAUploadRequest):
# 使用传入的代理或全局代理配置
proxy = request.proxy if request.proxy else get_settings().proxy_url
results = batch_upload_to_cpa(request.ids, proxy)
with get_db() as db:
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
results = batch_upload_to_cpa(ids, proxy)
return results

View File

@@ -12,6 +12,7 @@ from pydantic import BaseModel
from ...database.session import get_db
from ...database.models import Account
from ...config.settings import get_settings
from .accounts import resolve_account_ids
from ...core.payment import (
generate_plus_link,
generate_team_link,
@@ -48,8 +49,12 @@ class MarkSubscriptionRequest(BaseModel):
class BatchCheckSubscriptionRequest(BaseModel):
ids: List[int]
ids: List[int] = []
proxy: Optional[str] = None
select_all: bool = False
status_filter: Optional[str] = None
email_service_filter: Optional[str] = None
search_filter: Optional[str] = None
class UploadTMRequest(BaseModel):
@@ -57,7 +62,11 @@ class UploadTMRequest(BaseModel):
class BatchUploadTMRequest(BaseModel):
ids: List[int]
ids: List[int] = []
select_all: bool = False
status_filter: Optional[str] = None
email_service_filter: Optional[str] = None
search_filter: Optional[str] = None
# ============== 支付链接生成 ==============
@@ -143,7 +152,11 @@ def batch_check_subscription(request: BatchCheckSubscriptionRequest):
results = {"success_count": 0, "failed_count": 0, "details": []}
with get_db() as db:
for account_id in request.ids:
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
for account_id in ids:
account = db.query(Account).filter(Account.id == account_id).first()
if not account:
results["failed_count"] += 1
@@ -201,5 +214,11 @@ def batch_upload_tm(request: BatchUploadTMRequest):
api_url = settings.tm_api_url
api_key = settings.tm_api_key.get_secret_value() if settings.tm_api_key else ""
results = batch_upload_to_team_manager(request.ids, api_url, api_key)
with get_db() as db:
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
results = batch_upload_to_team_manager(ids, api_url, api_key)
return results