feat: auto-sync file list + clear processing cache

- Auto-sync file_relations on every query (files appear immediately)
- Add POST /api/files/reset-cache endpoint to delete output/result
  files and reset status to pending for reprocessing
- Add "清除处理缓存" button to all 3 file views

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-12 22:18:47 +08:00
parent ec8d0d7db6
commit 13ef605481
5 changed files with 144 additions and 1 deletions
+22 -1
View File
@@ -15,7 +15,7 @@ from ..config import MAX_UPLOAD_SIZE, ALLOWED_EXTENSIONS
from ..services.db_schema import (
insert_file_metadata, query_file_history, query_file_stats,
query_file_relations, delete_file_relations, sync_file_relations,
query_file_relations_stats,
query_file_relations_stats, reset_file_cache,
)
logger = logging.getLogger(__name__)
@@ -301,9 +301,12 @@ async def get_file_relations(
page_size: int = Query(50, ge=1, le=200),
sort_by: Optional[str] = None,
sort_order: str = "desc",
sync: bool = Query(True, description="Auto-sync file relations before querying"),
current_user: dict = Depends(get_current_user),
):
"""Query file relations with optional view filter."""
if sync:
sync_file_relations()
items, total = query_file_relations(view=view, status=status, page=page, page_size=page_size,
sort_by=sort_by, sort_order=sort_order)
return {"items": items, "total": total}
@@ -326,6 +329,24 @@ async def sync_relations(
return {"message": "文件关系表已重建"}
class ResetCacheRequest(BaseModel):
files: list[dict] # [{input_image, output_excel, result_purchase}, ...]
@router.post("/reset-cache")
async def reset_cache(
req: ResetCacheRequest,
current_user: dict = Depends(get_current_user),
):
"""Delete output/result files and reset status to pending for reprocessing.
Each item in files should have: {input_image?, output_excel?, result_purchase?}
The corresponding files on disk are deleted, and the relation status is reset.
"""
result = reset_file_cache(req.files)
return result
@router.delete("/relations")
async def delete_relations(
body: RelationDeleteRequest,
+65
View File
@@ -653,6 +653,71 @@ def sync_file_relations():
conn.close()
def reset_file_cache(files: list[dict]) -> dict:
"""Delete output/result files and reset relation status to pending.
Each item: {input_image?, output_excel?, result_purchase?}
Deletes the corresponding files from disk and resets status.
"""
project_root = Path(__file__).resolve().parent.parent.parent.parent
output_dir = project_root / 'data' / 'output'
result_dir = project_root / 'data' / 'result'
deleted_files = 0
reset_count = 0
errors = []
conn = sqlite3.connect(_db_path)
conn.row_factory = sqlite3.Row
try:
for item in files:
input_image = item.get('input_image')
output_excel = item.get('output_excel')
result_purchase = item.get('result_purchase')
# Delete output file from disk
if output_excel:
out_path = output_dir / output_excel
if out_path.exists():
try:
out_path.unlink()
deleted_files += 1
except Exception as e:
errors.append(f"{output_excel}: {e}")
# Delete result file from disk
if result_purchase:
res_path = result_dir / result_purchase
if res_path.exists():
try:
res_path.unlink()
deleted_files += 1
except Exception as e:
errors.append(f"{result_purchase}: {e}")
# Reset relation status to pending
if input_image:
conn.execute(
"UPDATE file_relations SET output_excel = NULL, result_purchase = NULL, "
"status = 'pending', updated_at = ? WHERE input_image = ?",
(datetime.now().isoformat(), input_image),
)
reset_count += conn.total_changes
elif output_excel:
conn.execute(
"UPDATE file_relations SET output_excel = NULL, result_purchase = NULL, "
"status = 'pending', updated_at = ? WHERE output_excel = ?",
(datetime.now().isoformat(), output_excel),
)
reset_count += conn.total_changes
conn.commit()
finally:
conn.close()
return {"deleted_files": deleted_files, "reset_relations": reset_count, "errors": errors}
def query_file_relations_stats() -> dict:
"""Get detailed file statistics for Dashboard.
+19
View File
@@ -19,6 +19,9 @@
<el-button type="danger" :disabled="!selected.length" @click="batchDelete">
批量删除
</el-button>
<el-button :disabled="!selected.length" @click="resetCache">
清除处理缓存
</el-button>
<input
ref="uploadInput"
type="file"
@@ -287,6 +290,22 @@ async function batchDelete() {
}
}
async function resetCache() {
try {
await ElMessageBox.confirm(`确定清除选中的 ${selected.value.length} 个文件的处理缓存?删除后可重新处理。`, '确认')
const files = selected.value.map(r => ({
input_image: r.input_image,
output_excel: r.output_excel,
result_purchase: r.result_purchase,
}))
const res = await api.post('/files/reset-cache', { files })
ElMessage.success(`已清除 ${res.data.deleted_files} 个缓存文件`)
loadData()
} catch (err: any) {
if (err !== 'cancel') ElMessage.error('清除缓存失败')
}
}
onMounted(loadData)
</script>
+19
View File
@@ -15,6 +15,9 @@
<el-button type="danger" :disabled="!selected.length" @click="batchDelete">
批量删除
</el-button>
<el-button :disabled="!selected.length" @click="resetCache">
清除处理缓存
</el-button>
</div>
</div>
@@ -238,6 +241,22 @@ async function batchDelete() {
}
}
async function resetCache() {
try {
await ElMessageBox.confirm(`确定清除选中的 ${selected.value.length} 个文件的处理缓存?删除后可重新处理。`, '确认')
const files = selected.value.map(r => ({
input_image: r.input_image,
output_excel: r.output_excel,
result_purchase: r.result_purchase,
}))
const res = await api.post('/files/reset-cache', { files })
ElMessage.success(`已清除 ${res.data.deleted_files} 个缓存文件`)
loadData()
} catch (err: any) {
if (err !== 'cancel') ElMessage.error('清除缓存失败')
}
}
onMounted(loadData)
</script>
+19
View File
@@ -13,6 +13,9 @@
<el-button :disabled="!selected.length" @click="batchDelete">
批量删除
</el-button>
<el-button :disabled="!selected.length" @click="resetCache">
清除处理缓存
</el-button>
<el-button type="danger" @click="clearAll">
删除全部
</el-button>
@@ -246,6 +249,22 @@ async function batchDelete() {
}
}
async function resetCache() {
try {
await ElMessageBox.confirm(`确定清除选中的 ${selected.value.length} 个文件的处理缓存?删除后可重新处理。`, '确认')
const files = selected.value.map(r => ({
input_image: r.input_image,
output_excel: r.output_excel,
result_purchase: r.result_purchase,
}))
const res = await api.post('/files/reset-cache', { files })
ElMessage.success(`已清除 ${res.data.deleted_files} 个缓存文件`)
loadData()
} catch (err: any) {
if (err !== 'cancel') ElMessage.error('清除缓存失败')
}
}
async function clearAll() {
try {
await ElMessageBox.confirm('确定清空所有 Excel 处理文件?此操作不可恢复。', '确认')