feat: add batch-delete API endpoint, replace N+1 frontend calls
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -169,6 +169,34 @@ async def delete_file(
|
|||||||
return {"message": f"已删除 {filename}"}
|
return {"message": f"已删除 {filename}"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/batch-delete")
|
||||||
|
async def batch_delete_files(
|
||||||
|
req: BatchDeleteRequest,
|
||||||
|
current_user: dict = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Batch delete files from disk and clean up relation records."""
|
||||||
|
dir_map = {"input": _input_dir, "output": _output_dir, "result": _result_dir}
|
||||||
|
deleted = 0
|
||||||
|
errors = []
|
||||||
|
for item in req.files:
|
||||||
|
d = item.get("directory", "")
|
||||||
|
fname = item.get("filename", "")
|
||||||
|
if d not in dir_map or not fname:
|
||||||
|
errors.append(f"无效参数: {d}/{fname}")
|
||||||
|
continue
|
||||||
|
file_path = dir_map[d] / fname
|
||||||
|
try:
|
||||||
|
if file_path.exists():
|
||||||
|
size = file_path.stat().st_size
|
||||||
|
file_path.unlink()
|
||||||
|
deleted += 1
|
||||||
|
_record_file_action(fname, d, size, "delete", current_user.get("username"))
|
||||||
|
_cleanup_relation_for_deleted_file(d, fname)
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"{fname}: {str(e)}")
|
||||||
|
return {"deleted": deleted, "errors": errors}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/clear/{directory}")
|
@router.post("/clear/{directory}")
|
||||||
async def clear_directory(
|
async def clear_directory(
|
||||||
directory: str,
|
directory: str,
|
||||||
@@ -224,6 +252,10 @@ class RelationDeleteRequest(BaseModel):
|
|||||||
ids: List[int]
|
ids: List[int]
|
||||||
|
|
||||||
|
|
||||||
|
class BatchDeleteRequest(BaseModel):
|
||||||
|
files: list[dict]
|
||||||
|
|
||||||
|
|
||||||
def _cleanup_relation_for_deleted_file(directory: str, filename: str):
|
def _cleanup_relation_for_deleted_file(directory: str, filename: str):
|
||||||
"""Clean up relation table when a file is deleted."""
|
"""Clean up relation table when a file is deleted."""
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|||||||
@@ -272,15 +272,15 @@ async function batchDownload() {
|
|||||||
async function batchDelete() {
|
async function batchDelete() {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${selected.value.length} 个文件?`, '确认')
|
await ElMessageBox.confirm(`确定删除选中的 ${selected.value.length} 个文件?`, '确认')
|
||||||
for (const row of selected.value) {
|
const files = selected.value
|
||||||
if (row.input_image) {
|
.filter(r => r.input_image)
|
||||||
await api.delete(`/files/input/${encodeURIComponent(row.input_image)}`)
|
.map(r => ({ directory: 'input', filename: r.input_image }))
|
||||||
}
|
const res = await api.post('/files/batch-delete', { files })
|
||||||
if (row.id) {
|
if (res.data.errors?.length) {
|
||||||
await api.delete('/files/relations', { data: { ids: [row.id] } })
|
ElMessage.warning(`删除完成,${res.data.errors.length} 个文件失败`)
|
||||||
}
|
} else {
|
||||||
|
ElMessage.success('批量删除完成')
|
||||||
}
|
}
|
||||||
ElMessage.success('批量删除完成')
|
|
||||||
loadData()
|
loadData()
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err !== 'cancel') ElMessage.error('批量删除失败')
|
if (err !== 'cancel') ElMessage.error('批量删除失败')
|
||||||
|
|||||||
@@ -223,15 +223,15 @@ async function batchDownload() {
|
|||||||
async function batchDelete() {
|
async function batchDelete() {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${selected.value.length} 个文件?`, '确认')
|
await ElMessageBox.confirm(`确定删除选中的 ${selected.value.length} 个文件?`, '确认')
|
||||||
for (const row of selected.value) {
|
const files = selected.value
|
||||||
if (row.result_purchase) {
|
.filter(r => r.result_purchase)
|
||||||
await api.delete(`/files/result/${encodeURIComponent(row.result_purchase)}`)
|
.map(r => ({ directory: 'result', filename: r.result_purchase }))
|
||||||
}
|
const res = await api.post('/files/batch-delete', { files })
|
||||||
if (row.id) {
|
if (res.data.errors?.length) {
|
||||||
await api.delete('/files/relations', { data: { ids: [row.id] } })
|
ElMessage.warning(`删除完成,${res.data.errors.length} 个文件失败`)
|
||||||
}
|
} else {
|
||||||
|
ElMessage.success('批量删除完成')
|
||||||
}
|
}
|
||||||
ElMessage.success('批量删除完成')
|
|
||||||
loadData()
|
loadData()
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err !== 'cancel') ElMessage.error('批量删除失败')
|
if (err !== 'cancel') ElMessage.error('批量删除失败')
|
||||||
|
|||||||
@@ -231,15 +231,15 @@ async function batchProcess() {
|
|||||||
async function batchDelete() {
|
async function batchDelete() {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${selected.value.length} 个文件?`, '确认')
|
await ElMessageBox.confirm(`确定删除选中的 ${selected.value.length} 个文件?`, '确认')
|
||||||
for (const row of selected.value) {
|
const files = selected.value
|
||||||
if (row.output_excel) {
|
.filter(r => r.output_excel)
|
||||||
await api.delete(`/files/output/${encodeURIComponent(row.output_excel)}`)
|
.map(r => ({ directory: 'output', filename: r.output_excel }))
|
||||||
}
|
const res = await api.post('/files/batch-delete', { files })
|
||||||
if (row.id) {
|
if (res.data.errors?.length) {
|
||||||
await api.delete('/files/relations', { data: { ids: [row.id] } })
|
ElMessage.warning(`删除完成,${res.data.errors.length} 个文件失败`)
|
||||||
}
|
} else {
|
||||||
|
ElMessage.success('批量删除完成')
|
||||||
}
|
}
|
||||||
ElMessage.success('批量删除完成')
|
|
||||||
loadData()
|
loadData()
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err !== 'cancel') ElMessage.error('批量删除失败')
|
if (err !== 'cancel') ElMessage.error('批量删除失败')
|
||||||
|
|||||||
Reference in New Issue
Block a user