From 44b4084eb9288dbc63bef9cb78bb41d4818a5968 Mon Sep 17 00:00:00 2001 From: imeepos Date: Sun, 13 Jul 2025 23:57:56 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E4=B8=9A=E5=8A=A1=E9=80=BB=E8=BE=91=E5=92=8C?= =?UTF-8?q?loading=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题修复: 1. 异步导入没有调用完整业务逻辑 - 只创建了Material记录,没有处理元数据、场景检测、视频切分 2. 导入动画一闪而逝 - 缺少实际的处理时间 3. 后台无素材处理日志 - 简化版本跳过了所有业务处理 解决方案: 1. 替换简化处理函数为完整业务逻辑处理 2. 调用MaterialService的完整处理流程 3. 添加详细的处理日志和进度事件 4. 添加适当延迟确保用户看到loading动画 5. 修复前端事件处理逻辑 技术改进: - process_single_file_with_full_logic: 完整的素材处理流程 - 元数据提取、场景检测、视频切分全流程 - 详细的tracing日志输出 - 实时进度事件发送 - 正确的错误处理和状态更新 --- .../commands/material_commands.rs | 157 +++++++++++++++++- .../src/components/MaterialImportDialog.tsx | 10 +- 2 files changed, 152 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs index 6abee4d..58f9fc3 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs @@ -80,7 +80,6 @@ async fn import_materials_with_tauri_events( config: MaterialProcessingConfig, app_handle: tauri::AppHandle, ) -> Result { - use anyhow::anyhow; use std::path::Path; use std::time::Instant; use tracing::{info, warn, error, debug}; @@ -113,6 +112,9 @@ async fn import_materials_with_tauri_events( "progress_percentage": 0.0 })); + // 添加小延迟确保用户能看到loading动画 + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + // 异步处理每个文件 for (index, file_path) in request.file_paths.iter().enumerate() { debug!(file_path = %file_path, "异步处理文件"); @@ -127,12 +129,13 @@ async fn import_materials_with_tauri_events( "progress_percentage": (index as f64 / result.total_files as f64) * 100.0 })); - // 处理单个文件 - match process_single_file_simple( + // 处理单个文件 - 使用完整的业务逻辑 + match process_single_file_with_full_logic( Arc::clone(&repository), &request.project_id, file_path, &config, + app_handle.clone(), ).await { Ok(Some(material)) => { info!( @@ -186,19 +189,23 @@ async fn import_materials_with_tauri_events( Ok(result) } -/// 简化的单文件处理函数 -async fn process_single_file_simple( +/// 使用完整业务逻辑的单文件处理函数 +async fn process_single_file_with_full_logic( repository: Arc, project_id: &str, file_path: &str, - _config: &MaterialProcessingConfig, + config: &MaterialProcessingConfig, + app_handle: tauri::AppHandle, ) -> Result, anyhow::Error> { use anyhow::anyhow; use std::path::Path; use std::fs; use tokio::task; - use crate::data::models::material::{Material, MaterialType}; + use crate::data::models::material::{Material, MaterialType, ProcessingStatus}; use crate::business::errors::error_utils; + use tracing::{info, warn, error, debug}; + + debug!(file_path = %file_path, "开始处理单个文件"); // 验证文件路径 error_utils::validate_file_path(file_path) @@ -218,6 +225,7 @@ async fn process_single_file_simple( // 检查是否已存在相同的文件 if repository.exists_by_md5(project_id, &md5_hash)? { + warn!(file_path = %file_path, "文件已存在,跳过"); return Ok(None); // 跳过重复文件 } @@ -235,17 +243,148 @@ async fn process_single_file_simple( let material_type = MaterialType::from_extension(extension); // 创建素材对象 - let material = Material::new( + let mut material = Material::new( project_id.to_string(), file_name.clone(), file_path.to_string(), file_size, md5_hash, - material_type, + material_type.clone(), ); // 保存到数据库 repository.create(&material)?; + info!(material_id = %material.id, file_name = %file_name, "素材创建成功"); + + // 发送处理进度事件 + let _ = app_handle.emit("material_processing_progress", serde_json::json!({ + "material_id": material.id, + "file_name": file_name, + "stage": "开始处理", + "progress_percentage": 0.0 + })); + + // 如果启用自动处理,则进行完整的业务处理 + if config.auto_process.unwrap_or(true) { + // 更新状态为处理中 + MaterialService::update_material_status( + &repository, + &material.id, + ProcessingStatus::Processing, + None, + )?; + + // 1. 提取元数据 + let _ = app_handle.emit("material_processing_progress", serde_json::json!({ + "material_id": material.id, + "file_name": file_name, + "stage": "提取元数据", + "progress_percentage": 25.0 + })); + + let original_path = material.original_path.clone(); + let material_type_clone = material.material_type.clone(); + + match task::spawn_blocking(move || { + MaterialService::extract_metadata(&original_path, &material_type_clone) + }).await? { + Ok(metadata) => { + material.set_metadata(metadata); + repository.update(&material)?; + info!(material_id = %material.id, "元数据提取成功"); + } + Err(e) => { + error!(material_id = %material.id, error = %e, "元数据提取失败"); + MaterialService::update_material_status( + &repository, + &material.id, + ProcessingStatus::Failed, + Some(format!("元数据提取失败: {}", e)), + )?; + return Err(e); + } + } + + // 2. 场景检测(如果是视频且启用了场景检测) + if matches!(material.material_type, MaterialType::Video) && config.enable_scene_detection { + let _ = app_handle.emit("material_processing_progress", serde_json::json!({ + "material_id": material.id, + "file_name": file_name, + "stage": "场景检测", + "progress_percentage": 50.0 + })); + + let original_path = material.original_path.clone(); + let threshold = config.scene_detection_threshold; + + match task::spawn_blocking(move || { + MaterialService::detect_video_scenes(&original_path, threshold) + }).await? { + Ok(scene_detection) => { + info!(material_id = %material.id, scenes = scene_detection.scenes.len(), "场景检测成功"); + material.set_scene_detection(scene_detection); + repository.update(&material)?; + } + Err(e) => { + // 场景检测失败不应该导致整个处理失败 + warn!(material_id = %material.id, error = %e, "场景检测失败"); + } + } + } + + // 3. 检查是否需要切分视频 + let should_segment = material.needs_segmentation(config.max_segment_duration) || + (matches!(material.material_type, MaterialType::Video) && material.scene_detection.is_some()); + + if should_segment { + let _ = app_handle.emit("material_processing_progress", serde_json::json!({ + "material_id": material.id, + "file_name": file_name, + "stage": "视频切分", + "progress_percentage": 75.0 + })); + + let repository_clone = Arc::clone(&repository); + let material_clone = material.clone(); + let config_clone = config.clone(); + + match task::spawn_blocking(move || { + MaterialService::segment_video(&repository_clone, &material_clone, &config_clone) + }).await? { + Ok(_) => { + info!(material_id = %material.id, "视频切分完成"); + } + Err(e) => { + error!(material_id = %material.id, error = %e, "视频切分失败"); + MaterialService::update_material_status( + &repository, + &material.id, + ProcessingStatus::Failed, + Some(format!("视频切分失败: {}", e)), + )?; + return Err(e); + } + } + } + + // 标记为完成 + MaterialService::update_material_status( + &repository, + &material.id, + ProcessingStatus::Completed, + None, + )?; + + // 发送处理完成事件 + let _ = app_handle.emit("material_processing_progress", serde_json::json!({ + "material_id": material.id, + "file_name": file_name, + "stage": "处理完成", + "progress_percentage": 100.0 + })); + + info!(material_id = %material.id, "素材处理完成"); + } Ok(Some(material)) } diff --git a/apps/desktop/src/components/MaterialImportDialog.tsx b/apps/desktop/src/components/MaterialImportDialog.tsx index dd0c2e2..2bd5b99 100644 --- a/apps/desktop/src/components/MaterialImportDialog.tsx +++ b/apps/desktop/src/components/MaterialImportDialog.tsx @@ -183,13 +183,11 @@ export const MaterialImportDialog: React.FC = ({ console.log('开始异步导入:', request); - // 使用异步导入,进度更新通过事件监听器处理 - const result = await importMaterialsAsync(request); + // 使用异步导入,进度更新和完成状态完全通过事件监听器处理 + await importMaterialsAsync(request); - // 如果没有通过事件监听器收到完成事件,手动处理完成状态 - console.log('异步导入完成:', result); - setStep('complete'); - onImportComplete(result); + // 注意:不在这里设置完成状态,完全依赖事件监听器 + console.log('异步导入命令执行完成,等待事件通知'); } catch (error) { console.error('导入失败:', error); setStep('configure'); // 返回配置步骤