From d3df1e54d0c972de575ace685ab24b0a397a5b9a Mon Sep 17 00:00:00 2001 From: Momentry Studio Date: Thu, 8 Oct 2026 00:50:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=B9=E9=87=8F=20?= =?UTF-8?q?Deep=20Scan=20=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 功能: - 在 Registered 和 Completed section 添加批量 Deep Scan 按钮 - 显示条件:当有已选择的 registered/completed 文件时 - 使用 p-limit 控制并发(maxConcurrent = 3) - 超过并发的文件自动排队等待 实现细节: - 添加 batchProcessing ref 跟踪批量处理状态 - 实现 batchDeepScan 函数,调用 re_scan_file API - 显示处理进度和队列信息 - 成功后更新文件状态为 'scanning' - 处理完成后清空选择并刷新文件列表 用户体验: - 点击按钮 → 确认对话框 → 显示进度 - 显示格式:"Processing 1/10 (7 in queue)..." - 完成后显示成功/失败统计 --- src/views/LibraryView.vue | 89 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/src/views/LibraryView.vue b/src/views/LibraryView.vue index 4cc9b90..2e90d2d 100644 --- a/src/views/LibraryView.vue +++ b/src/views/LibraryView.vue @@ -99,6 +99,9 @@
✅ {{ t('library.status.completed') }} ({{ completedFiles.length }}) + @@ -145,6 +148,9 @@
📋 {{ t('library.status.registered') }} ({{ registeredFiles.length }}) + @@ -1069,6 +1075,7 @@ async function playVideo(f: any) { const registering = ref(false) const processing = ref(false) const unregistering = ref(false) +const batchProcessing = ref(false) const reprocessing = ref(false) const confirmDialog = ref(false) const confirmTarget = ref<{ mode: 'register' | 'process' | 'reprocess'; file: any } | null>(null) @@ -1871,6 +1878,84 @@ async function batchRegister() { setTimeout(() => { actionMsg.value = '' }, 8000) } +async function batchDeepScan() { + if (batchProcessing.value) return + if (selectedRegisteredFiles.value.length === 0) return + + const filesToProcess = selectedRegisteredFiles.value + + if (!confirm(`Deep scan ${filesToProcess.length} files?\n\nThis will process all selected files with default settings.`)) return + + batchProcessing.value = true + actionMsg.value = `Starting deep scan for ${filesToProcess.length} files...` + + const maxConcurrent = 3 + const limit = pLimit(maxConcurrent) + + let successCount = 0 + let failCount = 0 + let queueCount = 0 + + const tasks = filesToProcess.map((f, index) => { + return limit(async () => { + const fileUuid = f.file_uuid + if (!fileUuid) { + failCount++ + return + } + + const position = index + 1 + if (position > maxConcurrent) { + queueCount++ + actionMsg.value = `Processing ${successCount + 1}/${filesToProcess.length} (${queueCount} in queue)...` + } else { + actionMsg.value = `Processing ${position}/${filesToProcess.length}...` + } + + try { + const file = filesCache.value.find((x: any) => x.file_uuid === fileUuid) + if (!file) { + failCount++ + return + } + + const result: any = await apiCall('re_scan_file', { + fileUuid: fileUuid, + autoPipeline: true + }) + + if (result.success) { + successCount++ + if (file) { + file.status = 'scanning' + fileStatusCache.value[fileUuid] = 'scanning' + await syncFileStatus(fileUuid) + } + } else { + failCount++ + } + } catch (e: any) { + failCount++ + console.error('[batchDeepScan] Failed:', f.file_name, e) + } + }) + }) + + await Promise.all(tasks) + + selectedFiles.value = [] + batchProcessing.value = false + + if (successCount > 0) { + actionMsg.value = `Deep scan started for ${successCount} files${failCount > 0 ? `, ${failCount} failed` : ''}` + } else { + actionMsg.value = `Failed to start deep scan for all ${failCount} files` + } + + await loadFiles() + setTimeout(() => { actionMsg.value = '' }, 8000) +} + async function batchUnregister() { if (unregistering.value) return if (selectedRegisteredFiles.value.length === 0) return @@ -2248,7 +2333,9 @@ function resetFilters() { .ms-fm-section-title { font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 12px; padding-left: 4px; display: flex; align-items: center; gap: 12px; } .ms-batch-register-btn { margin-left: auto; padding: 4px 12px; font-size: 12px; font-weight: 600; background: var(--primary-color); color: white; border: none; border-radius: 6px; cursor: pointer; } .ms-batch-register-btn:hover { background: var(--primary-hover); } -.ms-batch-unregister-btn { margin-left: auto; padding: 4px 12px; font-size: 12px; font-weight: 600; background: var(--danger-color, #dc3545); color: white; border: none; border-radius: 6px; cursor: pointer; } +.ms-batch-scan-btn { margin-left: 8px; padding: 4px 12px; font-size: 12px; font-weight: 600; background: var(--info-color, #17a2b8); color: white; border: none; border-radius: 6px; cursor: pointer; } +.ms-batch-scan-btn:hover { background: var(--info-hover, #138496); } +.ms-batch-unregister-btn { margin-left: 8px; padding: 4px 12px; font-size: 12px; font-weight: 600; background: var(--danger-color, #dc3545); color: white; border: none; border-radius: 6px; cursor: pointer; } .ms-batch-unregister-btn:hover { background: var(--danger-hover, #c82333); } .ms-fm-card-checkbox { position: absolute; bottom: 8px; right: 8px; width: 24px; height: 24px; border: 2px solid var(--border-color); border-radius: 4px; background: var(--card-background); cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; color: var(--primary-color); z-index: 10; } .ms-fm-card-checkbox:hover { border-color: var(--primary-color); }