修复账号删除报错+添加批量删除功能
- 修复单个删除报错:删除账号前先删除关联的LoginTask(外键约束)
- 新增批量删除接口 DELETE /api/accounts/batch/delete
- 路由顺序:/batch/delete 在 /{account_id} 前注册避免路径冲突
- 前端添加批量删除按钮(带确认弹窗)
- 前端添加 batchDelete API
This commit is contained in:
@@ -288,6 +288,34 @@ def list_tags(
|
|||||||
return [t[0] for t in tags if t[0]]
|
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}")
|
@router.delete("/{account_id}")
|
||||||
def delete_account(
|
def delete_account(
|
||||||
account_id: int,
|
account_id: int,
|
||||||
@@ -298,6 +326,9 @@ def delete_account(
|
|||||||
if not acc:
|
if not acc:
|
||||||
raise HTTPException(status_code=404, detail="账号不存在")
|
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,
|
db.add(AuditLog(user_id=current.id, username=current.username,
|
||||||
action="account:delete", target=acc.username))
|
action="account:delete", target=acc.username))
|
||||||
db.delete(acc)
|
db.delete(acc)
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ export const accountApi = {
|
|||||||
api.put<any, any>('/accounts/batch-tag', { account_ids, tag }),
|
api.put<any, any>('/accounts/batch-tag', { account_ids, tag }),
|
||||||
listTags: () => api.get<any, string[]>('/accounts/tags/list'),
|
listTags: () => api.get<any, string[]>('/accounts/tags/list'),
|
||||||
delete: (id: number) => api.delete<any, any>(`/accounts/${id}`),
|
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 = {
|
export const loginApi = {
|
||||||
|
|||||||
@@ -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[] = [
|
const columns: any[] = [
|
||||||
{ title: 'ID', dataIndex: 'id', width: 60 },
|
{ title: 'ID', dataIndex: 'id', width: 60 },
|
||||||
{ title: '用户名', dataIndex: 'username' },
|
{ title: '用户名', dataIndex: 'username' },
|
||||||
@@ -263,6 +279,24 @@ export default function AccountsPage() {
|
|||||||
>
|
>
|
||||||
批量打标签
|
批量打标签
|
||||||
</Button>
|
</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 type="primary" icon={<ImportOutlined />} onClick={() => setImportOpen(true)}>
|
||||||
批量导入
|
批量导入
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user