From 5a4805814f4a31f8da07f6661bf3b15ada8ee213 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 23 Jun 2026 01:29:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B4=A6=E5=8F=B7=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=8A=A5=E9=94=99+=E6=B7=BB=E5=8A=A0=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复单个删除报错:删除账号前先删除关联的LoginTask(外键约束) - 新增批量删除接口 DELETE /api/accounts/batch/delete - 路由顺序:/batch/delete 在 /{account_id} 前注册避免路径冲突 - 前端添加批量删除按钮(带确认弹窗) - 前端添加 batchDelete API --- web/backend/routers/accounts.py | 31 ++++++++++++++++++++++ web/frontend/src/api/modules.ts | 2 ++ web/frontend/src/pages/AccountsPage.tsx | 34 +++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/web/backend/routers/accounts.py b/web/backend/routers/accounts.py index 5e03738..dff849c 100644 --- a/web/backend/routers/accounts.py +++ b/web/backend/routers/accounts.py @@ -288,6 +288,34 @@ def list_tags( return [t[0] for t in tags if t[0]] +@router.delete("/batch/delete") +def batch_delete_accounts( + account_ids: str = Query(..., description="逗号分隔的账号ID"), + db: Session = Depends(get_db), + current: User = Depends(require_permission("account:delete")), +): + """批量删除账号及其关联的登录任务。""" + if not account_ids: + raise HTTPException(status_code=400, detail="请指定账号ID") + + ids = [int(x) for x in account_ids.split(",") if x.strip().isdigit()] + if not ids: + raise HTTPException(status_code=400, detail="无效的账号ID") + + # 先删除关联的登录任务 + db.query(LoginTask).filter(LoginTask.account_id.in_(ids)).delete(synchronize_session=False) + + # 删除账号 + deleted = db.query(Account).filter(Account.id.in_(ids)).delete(synchronize_session=False) + + db.add(AuditLog( + user_id=current.id, username=current.username, + action="account:delete", target=f"批量删除{deleted}个账号" + )) + db.commit() + return {"message": f"已删除 {deleted} 个账号", "deleted": deleted, "success": True} + + @router.delete("/{account_id}") def delete_account( account_id: int, @@ -298,6 +326,9 @@ def delete_account( if not acc: raise HTTPException(status_code=404, detail="账号不存在") + # 先删除关联的登录任务,避免外键约束失败 + db.query(LoginTask).filter(LoginTask.account_id == account_id).delete(synchronize_session=False) + db.add(AuditLog(user_id=current.id, username=current.username, action="account:delete", target=acc.username)) db.delete(acc) diff --git a/web/frontend/src/api/modules.ts b/web/frontend/src/api/modules.ts index 29987ff..2dd979e 100644 --- a/web/frontend/src/api/modules.ts +++ b/web/frontend/src/api/modules.ts @@ -53,6 +53,8 @@ export const accountApi = { api.put('/accounts/batch-tag', { account_ids, tag }), listTags: () => api.get('/accounts/tags/list'), delete: (id: number) => api.delete(`/accounts/${id}`), + batchDelete: (account_ids: number[]) => + api.delete('/accounts/batch/delete', { params: { account_ids: account_ids.join(',') } }), }; export const loginApi = { diff --git a/web/frontend/src/pages/AccountsPage.tsx b/web/frontend/src/pages/AccountsPage.tsx index 371fc20..db2712d 100644 --- a/web/frontend/src/pages/AccountsPage.tsx +++ b/web/frontend/src/pages/AccountsPage.tsx @@ -150,6 +150,22 @@ export default function AccountsPage() { } }; + const handleBatchDelete = async () => { + if (selectedRowKeys.length === 0) { + message.warning('请先选择账号'); + return; + } + try { + const result = await accountApi.batchDelete(selectedRowKeys as number[]); + message.success(result.message); + setSelectedRowKeys([]); + loadAccounts(); + loadTags(); + } catch (e: any) { + message.error(e.message); + } + }; + const columns: any[] = [ { title: 'ID', dataIndex: 'id', width: 60 }, { title: '用户名', dataIndex: 'username' }, @@ -263,6 +279,24 @@ export default function AccountsPage() { > 批量打标签 + {canDelete && ( + + + + )}