Refactor processing logic and enhance error handling

- Cleaned up code in processing.py by removing inline semicolons and improving readability.
- Updated upsert_file_relation calls to ensure consistent handling of file relations.
- Enhanced query_file_relations in db_schema.py to support filtering by file existence.
- Improved API error handling in index.ts with user-friendly messages for 401 and 403 errors.
- Added online/offline status tracking in Layout.vue.
- Implemented debounced search functionality across multiple views to optimize performance.
- Introduced loading skeletons in Dashboard.vue for better user experience during data fetching.
- Enhanced file preview cleanup logic in Images.vue, Orders.vue, and Tables.vue to prevent memory leaks.
- Updated global styles to include new loading and notification animations.
This commit is contained in:
2026-05-12 18:37:23 +08:00
parent 81bafaf557
commit e441ac82a8
20 changed files with 455 additions and 76 deletions
+27 -10
View File
@@ -3,6 +3,19 @@
<!-- Top stats row -->
<div class="stats-row animate-in">
<div
v-if="statsLoading"
v-for="n in 4"
:key="'sk'+n"
class="stat-card"
>
<div class="skeleton skeleton-circle" style="width:44px;height:44px;border-radius:12px"></div>
<div class="stat-info" style="gap:4px">
<div class="skeleton skeleton-text" style="width:48px;height:24px"></div>
<div class="skeleton skeleton-text short" style="width:64px;height:14px"></div>
</div>
</div>
<div
v-if="!statsLoading"
class="stat-card"
v-for="stat in stats"
:key="stat.label"
@@ -183,6 +196,7 @@ const uploadingName = ref('')
const processing = ref(false)
const fileInput = ref<HTMLInputElement>()
const logBox = ref<HTMLElement>()
const statsLoading = ref(true)
const detailedStats = ref({
input_images: 0,
@@ -260,12 +274,6 @@ const stats = computed(() => [
},
])
function fmtSize(b: number): string {
if (b < 1024) return b + ' B'
if (b < 1048576) return (b / 1024).toFixed(1) + ' KB'
return (b / 1048576).toFixed(1) + ' MB'
}
function fmtTime(i: number): string {
const d = new Date()
d.setSeconds(d.getSeconds() - (logs.value.length - i))
@@ -283,11 +291,14 @@ function clearLogs(): void {
}
async function refreshStats(): Promise<void> {
statsLoading.value = true
try {
const res = await api.get('/files/stats/detailed')
detailedStats.value = res.data
} catch {
// silent
} finally {
statsLoading.value = false
}
}
@@ -328,6 +339,7 @@ async function upload(files: File[]): Promise<void> {
uploading.value = true
uploadPct.value = 0
const uploadedFiles: { name: string; type: string }[] = []
const failedFiles: string[] = []
for (let i = 0; i < files.length; i++) {
const file = files[i]
uploadingName.value = file.name
@@ -344,9 +356,8 @@ async function upload(files: File[]): Promise<void> {
})
const typeLabel = getFileTypeLabel(file.name)
uploadedFiles.push({ name: file.name, type: typeLabel })
ElMessage.success(`${file.name}${typeLabel === 'OCR' ? '全流程处理队列' : 'Excel处理队列'}`)
} catch (err: any) {
ElMessage.error(`上传失败: ${file.name}`)
failedFiles.push(file.name)
}
}
uploading.value = false
@@ -354,15 +365,21 @@ async function upload(files: File[]): Promise<void> {
uploadPct.value = 0
refreshStats()
// Show upload results
if (uploadedFiles.length > 0) {
ElMessage.success(`${uploadedFiles.length} 个文件上传成功`)
}
if (failedFiles.length > 0) {
ElMessage.error(`${failedFiles.length} 个文件上传失败: ${failedFiles.join(', ')}`)
}
// Auto-process: pipeline for images, excel for Excel files
if (uploadedFiles.length > 0) {
const hasImages = uploadedFiles.some(f => f.type === 'OCR')
const hasExcel = uploadedFiles.some(f => f.type === 'Excel')
if (hasImages) {
ElMessage.info('自动启动一键全流程...')
await doAction('/processing/pipeline')
} else if (hasExcel) {
ElMessage.info('自动启动Excel处理...')
await doAction('/processing/excel')
}
}