From 6eba25ea10de851acb97f43bffdd301355801569 Mon Sep 17 00:00:00 2001 From: "fengchuanhn@gmail.com" Date: Mon, 25 May 2026 01:21:12 +0800 Subject: [PATCH] 11 --- src-tauri/src/avatar_db.rs | 11 ++----- src-tauri/src/avatar_store.rs | 7 ++++- src-tauri/src/commands/avatar.rs | 41 +++++++------------------ src/views/AvatarView.vue | 51 ++++++++++++++++++++++++-------- 4 files changed, 58 insertions(+), 52 deletions(-) diff --git a/src-tauri/src/avatar_db.rs b/src-tauri/src/avatar_db.rs index 9f04588..e487c8c 100644 --- a/src-tauri/src/avatar_db.rs +++ b/src-tauri/src/avatar_db.rs @@ -169,16 +169,11 @@ impl AvatarDb { Ok(()) } - /// 删除记录并返回文件路径(供调用方删除磁盘文件) - pub fn delete(&self, id: i64) -> Result, AvatarDbError> { - let row = self.get(id)?; - let Some(record) = row else { - return Ok(None); - }; + pub fn delete(&self, id: i64) -> Result { let conn = self.conn.lock().map_err(|_| { AvatarDbError::Message("形象数据库锁异常".into()) })?; - conn.execute("DELETE FROM avatars WHERE id = ?1", params![id])?; - Ok(Some(record.file_path)) + let n = conn.execute("DELETE FROM avatars WHERE id = ?1", params![id])?; + Ok(n > 0) } } diff --git a/src-tauri/src/avatar_store.rs b/src-tauri/src/avatar_store.rs index fa08938..713c656 100644 --- a/src-tauri/src/avatar_store.rs +++ b/src-tauri/src/avatar_store.rs @@ -68,7 +68,12 @@ impl AvatarStore { .await } - pub async fn delete(&self, id: i64) -> Result, String> { + pub async fn get(&self, id: i64) -> Result, String> { + self.with_db(move |db| db.get(id).map_err(|e| e.to_string())) + .await + } + + pub async fn delete(&self, id: i64) -> Result { self.with_db(move |db| db.delete(id).map_err(|e| e.to_string())) .await } diff --git a/src-tauri/src/commands/avatar.rs b/src-tauri/src/commands/avatar.rs index 463fca9..dcc6c51 100644 --- a/src-tauri/src/commands/avatar.rs +++ b/src-tauri/src/commands/avatar.rs @@ -7,6 +7,7 @@ use uuid::Uuid; use crate::avatar_db::AvatarRecord; use crate::avatar_store::AvatarStore; +use crate::commands::fs_util; fn normalize_path(path: &str) -> PathBuf { Path::new(path) @@ -24,15 +25,6 @@ fn avatars_dir(app: &AppHandle) -> Result { Ok(dir) } -fn is_under_avatars_dir(avatars_root: &Path, target: &Path) -> bool { - if let (Ok(root), Ok(canonical)) = (avatars_root.canonicalize(), target.canonicalize()) { - return canonical.starts_with(&root); - } - let root_s = avatars_root.to_string_lossy().to_lowercase(); - let target_s = target.to_string_lossy().to_lowercase(); - target_s.contains("avatars") && target_s.starts_with(&root_s) -} - #[tauri::command] pub async fn list_avatars(store: State<'_, AvatarStore>) -> Result, String> { store.list().await @@ -65,16 +57,16 @@ pub async fn avatar_name_exists( store.name_exists(&name, exclude_id).await } -/// 删除数据库记录并删除应用目录内的视频文件 +/// 删除应用目录内的视频文件及数据库记录 #[tauri::command] -pub async fn delete_avatar( - app: AppHandle, - store: State<'_, AvatarStore>, - id: i64, -) -> Result<(), String> { - let file_path = store.delete(id).await?; - if let Some(path) = file_path { - delete_avatar_file_if_allowed(&app, &path).await?; +pub async fn delete_avatar(store: State<'_, AvatarStore>, id: i64) -> Result<(), String> { + let record = store + .get(id) + .await? + .ok_or_else(|| "形象不存在".to_string())?; + fs_util::remove_allowed_local_media_file(&record.file_path)?; + if !store.delete(id).await? { + return Err("形象不存在".into()); } Ok(()) } @@ -115,16 +107,3 @@ pub async fn import_avatar_video( Ok(dest.to_string_lossy().to_string()) } -async fn delete_avatar_file_if_allowed(app: &AppHandle, path: &str) -> Result<(), String> { - let target = normalize_path(path); - let dir = avatars_dir(app)?; - if !is_under_avatars_dir(&dir, &target) { - return Err("不允许删除该路径下的文件".into()); - } - if target.is_file() { - tokio::fs::remove_file(&target) - .await - .map_err(|e| format!("删除文件失败: {e}"))?; - } - Ok(()) -} diff --git a/src/views/AvatarView.vue b/src/views/AvatarView.vue index dfd050b..b48729a 100644 --- a/src/views/AvatarView.vue +++ b/src/views/AvatarView.vue @@ -4,6 +4,7 @@ import { storeToRefs } from "pinia"; import { useAvatarStore } from "../stores/avatar.js"; import AvatarEditDialog from "../components/avatar/AvatarEditDialog.vue"; import { localVideoToPlayableUrl } from "../services/localVideo.js"; +import { revealLocalFileInFolder } from "../services/fileExport.js"; const avatarStore = useAvatarStore(); const { templates, loading } = storeToRefs(avatarStore); @@ -58,10 +59,26 @@ async function commitRename(item) { cancelRename(); } +async function onOpenInExplorer(item) { + if (!item?.filePath) { + showFeedback("error", "文件不存在"); + return; + } + try { + await revealLocalFileInFolder(item.filePath); + } catch (err) { + showFeedback("error", err instanceof Error ? err.message : String(err)); + } +} + async function onDelete(item) { if (!window.confirm(`确认删除形象「${item.name}」?`)) return; - await avatarStore.removeTemplate(item); - showFeedback("success", "已删除"); + try { + await avatarStore.removeTemplate(item); + showFeedback("success", "已删除"); + } catch (err) { + showFeedback("error", err instanceof Error ? err.message : String(err)); + } } function onSaved() { @@ -77,14 +94,14 @@ function onSaved() {

管理多个数字人形象

-
@@ -158,14 +175,24 @@ function onSaved() { /> -