修复账号删除报错+添加批量删除功能
- 修复单个删除报错:删除账号前先删除关联的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]]
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user