feat: 添加批量 Deep Scan 功能
功能: - 在 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)..." - 完成后显示成功/失败统计
This commit is contained in:
@@ -99,6 +99,9 @@
|
||||
<div v-if="completedFiles.length && showCompletedSection" class="ms-fm-section">
|
||||
<div class="ms-fm-section-title">
|
||||
✅ {{ t('library.status.completed') }} ({{ completedFiles.length }})
|
||||
<button v-if="selectedRegisteredFiles.length > 0" class="ms-batch-scan-btn" @click="batchDeepScan">
|
||||
🔍 Deep Scan ({{ selectedRegisteredFiles.length }})
|
||||
</button>
|
||||
<button v-if="selectedRegisteredFiles.length > 0" class="ms-batch-unregister-btn" @click="batchUnregister">
|
||||
🗑️ {{ t('library.context.unregister') }} ({{ selectedRegisteredFiles.length }})
|
||||
</button>
|
||||
@@ -145,6 +148,9 @@
|
||||
<div v-if="showRegisteredSection" class="ms-fm-section">
|
||||
<div class="ms-fm-section-title">
|
||||
📋 {{ t('library.status.registered') }} ({{ registeredFiles.length }})
|
||||
<button v-if="selectedRegisteredFiles.length > 0" class="ms-batch-scan-btn" @click="batchDeepScan">
|
||||
🔍 Deep Scan ({{ selectedRegisteredFiles.length }})
|
||||
</button>
|
||||
<button v-if="selectedRegisteredFiles.length > 0" class="ms-batch-unregister-btn" @click="batchUnregister">
|
||||
🗑️ {{ t('library.context.unregister') }} ({{ selectedRegisteredFiles.length }})
|
||||
</button>
|
||||
@@ -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); }
|
||||
|
||||
Reference in New Issue
Block a user