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:
@@ -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,
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 处理文件?此操作不可恢复。', '确认')
|
||||
|
||||
Reference in New Issue
Block a user