diff --git a/web/backend/routers/files.py b/web/backend/routers/files.py index e97dbc7..b36bc3d 100644 --- a/web/backend/routers/files.py +++ b/web/backend/routers/files.py @@ -169,6 +169,34 @@ async def delete_file( 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}") async def clear_directory( directory: str, @@ -224,6 +252,10 @@ class RelationDeleteRequest(BaseModel): ids: List[int] +class BatchDeleteRequest(BaseModel): + files: list[dict] + + def _cleanup_relation_for_deleted_file(directory: str, filename: str): """Clean up relation table when a file is deleted.""" import sqlite3 diff --git a/web/frontend/src/views/files/Images.vue b/web/frontend/src/views/files/Images.vue index 5f48149..e2b6d4f 100644 --- a/web/frontend/src/views/files/Images.vue +++ b/web/frontend/src/views/files/Images.vue @@ -272,15 +272,15 @@ async function batchDownload() { async function batchDelete() { try { await ElMessageBox.confirm(`确定删除选中的 ${selected.value.length} 个文件?`, '确认') - for (const row of selected.value) { - if (row.input_image) { - await api.delete(`/files/input/${encodeURIComponent(row.input_image)}`) - } - if (row.id) { - await api.delete('/files/relations', { data: { ids: [row.id] } }) - } + const files = selected.value + .filter(r => r.input_image) + .map(r => ({ directory: 'input', filename: r.input_image })) + const res = await api.post('/files/batch-delete', { files }) + if (res.data.errors?.length) { + ElMessage.warning(`删除完成,${res.data.errors.length} 个文件失败`) + } else { + ElMessage.success('批量删除完成') } - ElMessage.success('批量删除完成') loadData() } catch (err: any) { if (err !== 'cancel') ElMessage.error('批量删除失败') diff --git a/web/frontend/src/views/files/Orders.vue b/web/frontend/src/views/files/Orders.vue index ec060fd..be290a4 100644 --- a/web/frontend/src/views/files/Orders.vue +++ b/web/frontend/src/views/files/Orders.vue @@ -223,15 +223,15 @@ async function batchDownload() { async function batchDelete() { try { await ElMessageBox.confirm(`确定删除选中的 ${selected.value.length} 个文件?`, '确认') - for (const row of selected.value) { - if (row.result_purchase) { - await api.delete(`/files/result/${encodeURIComponent(row.result_purchase)}`) - } - if (row.id) { - await api.delete('/files/relations', { data: { ids: [row.id] } }) - } + const files = selected.value + .filter(r => r.result_purchase) + .map(r => ({ directory: 'result', filename: r.result_purchase })) + const res = await api.post('/files/batch-delete', { files }) + if (res.data.errors?.length) { + ElMessage.warning(`删除完成,${res.data.errors.length} 个文件失败`) + } else { + ElMessage.success('批量删除完成') } - ElMessage.success('批量删除完成') loadData() } catch (err: any) { if (err !== 'cancel') ElMessage.error('批量删除失败') diff --git a/web/frontend/src/views/files/Tables.vue b/web/frontend/src/views/files/Tables.vue index 68e7206..56e6d2b 100644 --- a/web/frontend/src/views/files/Tables.vue +++ b/web/frontend/src/views/files/Tables.vue @@ -231,15 +231,15 @@ async function batchProcess() { async function batchDelete() { try { await ElMessageBox.confirm(`确定删除选中的 ${selected.value.length} 个文件?`, '确认') - for (const row of selected.value) { - if (row.output_excel) { - await api.delete(`/files/output/${encodeURIComponent(row.output_excel)}`) - } - if (row.id) { - await api.delete('/files/relations', { data: { ids: [row.id] } }) - } + const files = selected.value + .filter(r => r.output_excel) + .map(r => ({ directory: 'output', filename: r.output_excel })) + const res = await api.post('/files/batch-delete', { files }) + if (res.data.errors?.length) { + ElMessage.warning(`删除完成,${res.data.errors.length} 个文件失败`) + } else { + ElMessage.success('批量删除完成') } - ElMessage.success('批量删除完成') loadData() } catch (err: any) { if (err !== 'cancel') ElMessage.error('批量删除失败')