diff --git a/apps/desktop/src-tauri/src/business/services/draft_parser.rs b/apps/desktop/src-tauri/src/business/services/draft_parser.rs index 40cfa8b..1223a5d 100644 --- a/apps/desktop/src-tauri/src/business/services/draft_parser.rs +++ b/apps/desktop/src-tauri/src/business/services/draft_parser.rs @@ -201,9 +201,11 @@ impl DraftContentParser { material.template_id = template.id.clone(); } + // 构建素材映射表:素材ID -> original_id (用于片段关联) + // 片段的 material_id 对应素材的 id,需要映射到 original_id let material_map: HashMap = materials .iter() - .map(|m| (m.original_id.clone(), m.id.clone())) + .map(|m| (m.id.clone(), m.original_id.clone())) // 键是素材ID,值是original_id .collect(); for material in materials { @@ -330,21 +332,20 @@ impl DraftContentParser { missing_files.push(video.path.clone()); } + // original_id 使用 local_material_id,如果没有则使用空字符串 + let original_id = video.local_material_id.clone() + .filter(|id| !id.is_empty()) + .unwrap_or_default(); + let mut material = TemplateMaterial::new( - String::new(), // template_id 稍后设置 - video.id.clone(), // 主键使用 id + video.id.clone(), // 使用素材的真实ID + String::new(), // template_id 稍后设置 + original_id, // original_id 对应 local_material_id name, TemplateMaterialType::Video, video.path.clone(), ); - // 设置 original_id 为 local_material_id - if let Some(local_material_id) = &video.local_material_id { - if !local_material_id.is_empty() { - material.original_id = local_material_id.clone(); - } - } - material.duration = video.duration; material.width = video.width; material.height = video.height; @@ -367,14 +368,15 @@ impl DraftContentParser { missing_files.push(audio.path.clone()); } - // 使用 local_material_id 作为主键,如果没有则使用 id - let material_id = audio.local_material_id.clone() + // original_id 使用 local_material_id,如果没有则使用空字符串 + let original_id = audio.local_material_id.clone() .filter(|id| !id.is_empty()) - .unwrap_or_else(|| audio.id.clone()); + .unwrap_or_default(); let mut material = TemplateMaterial::new( - String::new(), // template_id 稍后设置 - material_id, // 使用 local_material_id 作为主键 + audio.id.clone(), // 使用素材的真实ID + String::new(), // template_id 稍后设置 + original_id, // original_id 对应 local_material_id audio.name.clone(), TemplateMaterialType::Audio, audio.path.clone(), @@ -413,9 +415,15 @@ impl DraftContentParser { missing_files.push(path.clone()); } + // original_id 使用 local_material_id,如果没有则使用空字符串 + let original_id = image.local_material_id.clone() + .filter(|id| !id.is_empty()) + .unwrap_or_default(); + let mut material = TemplateMaterial::new( - String::new(), // template_id 稍后设置 - image.id.clone(), + image.id.clone(), // 使用素材的真实ID + String::new(), // template_id 稍后设置 + original_id, // original_id 对应 local_material_id name, TemplateMaterialType::Image, path, @@ -439,11 +447,12 @@ impl DraftContentParser { let name = text.content.clone().unwrap_or_else(|| "文本".to_string()); let mut material = TemplateMaterial::new( - String::new(), // template_id 稍后设置 - text.id.clone(), + text.id.clone(), // 使用素材的真实ID + String::new(), // template_id 稍后设置 + String::new(), // 文字素材没有 local_material_id name, TemplateMaterialType::Text, - String::new(), // 文本素材没有文件路径 + String::new(), // 文本素材没有文件路径 ); // 将文本属性存储为元数据 @@ -472,8 +481,9 @@ impl DraftContentParser { } let mut material = TemplateMaterial::new( - String::new(), // template_id 稍后设置 - sticker.id.clone(), + sticker.id.clone(), // 使用素材的真实ID + String::new(), // template_id 稍后设置 + String::new(), // 贴纸素材没有 local_material_id name, TemplateMaterialType::Sticker, path, @@ -503,8 +513,9 @@ impl DraftContentParser { } let mut material = TemplateMaterial::new( - String::new(), // template_id 稍后设置 - effect.id.clone(), + effect.id.clone(), // 使用素材的真实ID + String::new(), // template_id 稍后设置 + String::new(), // 特效素材没有 local_material_id name, TemplateMaterialType::Effect, path, @@ -525,11 +536,12 @@ impl DraftContentParser { let name = "画布".to_string(); let mut material = TemplateMaterial::new( - String::new(), // template_id 稍后设置 - canvas.id.clone(), + canvas.id.clone(), // 使用素材的真实ID + String::new(), // template_id 稍后设置 + String::new(), // 画布素材没有 local_material_id name, TemplateMaterialType::Canvas, - String::new(), // 画布素材没有文件路径 + String::new(), // 画布素材没有文件路径 ); // 将画布属性存储为元数据 @@ -562,6 +574,7 @@ impl DraftContentParser { let track_name = format!("轨道{} ({})", track_index + 1, track_raw.track_type); let mut track = Track::new( + track_raw.id.clone(), // 使用轨道的真实ID template_id.to_string(), track_name, track_type, @@ -603,6 +616,7 @@ impl DraftContentParser { let segment_name = format!("片段{}", segment_index + 1); let mut segment = TrackSegment::new( + segment_raw.id.clone(), // 使用片段的真实ID track_id.to_string(), segment_name, start_time, diff --git a/apps/desktop/src-tauri/src/business/services/template_import_service.rs b/apps/desktop/src-tauri/src/business/services/template_import_service.rs index 148f144..829f286 100644 --- a/apps/desktop/src-tauri/src/business/services/template_import_service.rs +++ b/apps/desktop/src-tauri/src/business/services/template_import_service.rs @@ -187,7 +187,6 @@ impl TemplateImportService { info!( template_id = %template.id, template_name = %template.name, - project_id = ?template.project_id, "开始保存模板到数据库" ); template.update_import_status(ImportStatus::Processing); @@ -311,12 +310,8 @@ impl TemplateImportService { return Err(anyhow!("模板验证失败: {}", validation_errors.join(", "))); } - // 设置项目关联 - let mut template = parse_result.template.clone(); - template.project_id = request.project_id.clone(); - Ok(ParseResult { - template, + template: parse_result.template, missing_files: parse_result.missing_files, warnings: parse_result.warnings, }) diff --git a/apps/desktop/src-tauri/src/business/services/template_service.rs b/apps/desktop/src-tauri/src/business/services/template_service.rs index 4a6b537..9820c3f 100644 --- a/apps/desktop/src-tauri/src/business/services/template_service.rs +++ b/apps/desktop/src-tauri/src/business/services/template_service.rs @@ -71,19 +71,14 @@ impl TemplateService { // 保存模板基本信息 tx.execute( "INSERT OR REPLACE INTO templates ( - id, name, description, project_id, canvas_width, canvas_height, + id, name, description, canvas_width, canvas_height, canvas_ratio, duration, fps, import_status, source_file_path, created_at, updated_at, is_active - ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)", + ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)", params![ template.id, template.name, template.description, - if template.project_id.as_ref().map_or(true, |id| id.is_empty()) { - None:: - } else { - template.project_id.clone() - }, template.canvas_config.width, template.canvas_config.height, template.canvas_config.ratio, @@ -183,7 +178,7 @@ impl TemplateService { // 查询模板基本信息 let template_result = conn.query_row( - "SELECT id, name, description, project_id, canvas_width, canvas_height, + "SELECT id, name, description, canvas_width, canvas_height, canvas_ratio, duration, fps, import_status, source_file_path, created_at, updated_at, is_active FROM templates WHERE id = ?1", @@ -333,7 +328,7 @@ impl TemplateService { |row| Ok(row.get::<_, i64>(0)? as u32) )?; - let mut sql = "SELECT id, name, description, project_id, canvas_width, canvas_height, + let mut sql = "SELECT id, name, description, canvas_width, canvas_height, canvas_ratio, duration, fps, import_status, source_file_path, created_at, updated_at, is_active FROM templates WHERE is_active = 1 ORDER BY created_at DESC".to_string(); @@ -402,7 +397,6 @@ impl TemplateService { id: row.get("id")?, name: row.get("name")?, description: row.get("description")?, - project_id: row.get("project_id")?, canvas_config, duration: row.get::<_, i64>("duration")? as u64, fps: row.get("fps")?, diff --git a/apps/desktop/src-tauri/src/data/models/template.rs b/apps/desktop/src-tauri/src/data/models/template.rs index d6cdff7..5b5ec9a 100644 --- a/apps/desktop/src-tauri/src/data/models/template.rs +++ b/apps/desktop/src-tauri/src/data/models/template.rs @@ -8,7 +8,6 @@ pub struct Template { pub id: String, pub name: String, pub description: Option, - pub project_id: Option, // 关联的项目ID pub canvas_config: CanvasConfig, pub duration: u64, // 模板总时长(微秒) pub fps: f64, @@ -179,7 +178,6 @@ impl Template { id: uuid::Uuid::new_v4().to_string(), name, description: None, - project_id: None, canvas_config, duration, fps, @@ -237,17 +235,18 @@ impl Template { impl TemplateMaterial { /// 创建新的模板素材实例 pub fn new( + id: String, // 素材的真实ID(来自剪映草稿) template_id: String, - original_id: String, + original_id: String, // local_material_id name: String, material_type: TemplateMaterialType, original_path: String, ) -> Self { let now = Utc::now(); Self { - id: original_id.clone(), // 使用原始ID作为主键,保持与剪映草稿的一致性 + id, // 使用传入的真实ID template_id, - original_id, + original_id, // original_id 对应 local_material_id,可以重复 name, material_type, original_path, @@ -298,6 +297,7 @@ impl TemplateMaterial { impl Track { /// 创建新的轨道实例 pub fn new( + id: String, // 轨道的真实ID(来自剪映草稿) template_id: String, name: String, track_type: TrackType, @@ -305,7 +305,7 @@ impl Track { ) -> Self { let now = Utc::now(); Self { - id: uuid::Uuid::new_v4().to_string(), + id, // 使用传入的真实ID template_id, name, track_type, @@ -335,6 +335,7 @@ impl Track { impl TrackSegment { /// 创建新的轨道片段实例 pub fn new( + id: String, // 片段的真实ID(来自剪映草稿) track_id: String, name: String, start_time: u64, @@ -343,7 +344,7 @@ impl TrackSegment { ) -> Self { let now = Utc::now(); Self { - id: uuid::Uuid::new_v4().to_string(), + id, // 使用传入的真实ID track_id, template_material_id: None, name, diff --git a/apps/desktop/src-tauri/src/infrastructure/database.rs b/apps/desktop/src-tauri/src/infrastructure/database.rs index f0c6952..678b8d8 100644 --- a/apps/desktop/src-tauri/src/infrastructure/database.rs +++ b/apps/desktop/src-tauri/src/infrastructure/database.rs @@ -265,7 +265,6 @@ impl Database { id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, - project_id TEXT, canvas_width INTEGER NOT NULL, canvas_height INTEGER NOT NULL, canvas_ratio TEXT NOT NULL, @@ -275,8 +274,7 @@ impl Database { source_file_path TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - is_active BOOLEAN DEFAULT 1, - FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE SET NULL + is_active BOOLEAN DEFAULT 1 )", [], )?; @@ -380,11 +378,6 @@ impl Database { )?; // 创建模板表索引 - conn.execute( - "CREATE INDEX IF NOT EXISTS idx_templates_project_id ON templates (project_id)", - [], - )?; - conn.execute( "CREATE INDEX IF NOT EXISTS idx_templates_import_status ON templates (import_status)", [], @@ -406,6 +399,41 @@ impl Database { [], ); + // 移除模板表的 project_id 字段(如果存在) + // SQLite 不支持 DROP COLUMN,需要重建表 + let _ = conn.execute( + "CREATE TABLE IF NOT EXISTS templates_new ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + description TEXT, + canvas_width INTEGER NOT NULL, + canvas_height INTEGER NOT NULL, + canvas_ratio TEXT NOT NULL, + duration INTEGER NOT NULL, + fps REAL NOT NULL, + import_status TEXT NOT NULL DEFAULT 'Pending', + source_file_path TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + is_active BOOLEAN DEFAULT 1 + )", + [], + ); + + // 迁移数据(排除 project_id) + let _ = conn.execute( + "INSERT OR IGNORE INTO templates_new + SELECT id, name, description, canvas_width, canvas_height, canvas_ratio, + duration, fps, import_status, source_file_path, created_at, updated_at, is_active + FROM templates", + [], + ); + + // 删除旧表并重命名新表 + let _ = conn.execute("DROP TABLE IF EXISTS templates_old", []); + let _ = conn.execute("ALTER TABLE templates RENAME TO templates_old", []); + let _ = conn.execute("ALTER TABLE templates_new RENAME TO templates", []); + // 创建模板素材表索引 conn.execute( "CREATE INDEX IF NOT EXISTS idx_template_materials_template_id ON template_materials (template_id)",