修复账号删除报错+添加批量删除功能

- 修复单个删除报错:删除账号前先删除关联的LoginTask(外键约束)
- 新增批量删除接口 DELETE /api/accounts/batch/delete
- 路由顺序:/batch/delete 在 /{account_id} 前注册避免路径冲突
- 前端添加批量删除按钮(带确认弹窗)
- 前端添加 batchDelete API
This commit is contained in:
yml2213
2026-06-23 01:29:03 +08:00
parent d47b8e24fa
commit 5a4805814f
3 changed files with 67 additions and 0 deletions
+31
View File
@@ -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)
+2
View File
@@ -53,6 +53,8 @@ export const accountApi = {
api.put<any, any>('/accounts/batch-tag', { account_ids, tag }),
listTags: () => api.get<any, string[]>('/accounts/tags/list'),
delete: (id: number) => api.delete<any, any>(`/accounts/${id}`),
batchDelete: (account_ids: number[]) =>
api.delete<any, any>('/accounts/batch/delete', { params: { account_ids: account_ids.join(',') } }),
};
export const loginApi = {
+34
View File
@@ -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() {
>
</Button>
{canDelete && (
<Popconfirm
title={`确认删除选中的 ${selectedRowKeys.length} 个账号?`}
description="将同时删除关联的登录任务"
onConfirm={handleBatchDelete}
okText="删除"
okButtonProps={{ danger: true }}
cancelText="取消"
>
<Button
danger
disabled={selectedRowKeys.length === 0}
icon={<DeleteOutlined />}
>
</Button>
</Popconfirm>
)}
<Button type="primary" icon={<ImportOutlined />} onClick={() => setImportOpen(true)}>
</Button>