11
This commit is contained in:
@@ -169,16 +169,11 @@ impl AvatarDb {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 删除记录并返回文件路径(供调用方删除磁盘文件)
|
pub fn delete(&self, id: i64) -> Result<bool, AvatarDbError> {
|
||||||
pub fn delete(&self, id: i64) -> Result<Option<String>, AvatarDbError> {
|
|
||||||
let row = self.get(id)?;
|
|
||||||
let Some(record) = row else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
let conn = self.conn.lock().map_err(|_| {
|
let conn = self.conn.lock().map_err(|_| {
|
||||||
AvatarDbError::Message("形象数据库锁异常".into())
|
AvatarDbError::Message("形象数据库锁异常".into())
|
||||||
})?;
|
})?;
|
||||||
conn.execute("DELETE FROM avatars WHERE id = ?1", params![id])?;
|
let n = conn.execute("DELETE FROM avatars WHERE id = ?1", params![id])?;
|
||||||
Ok(Some(record.file_path))
|
Ok(n > 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,12 @@ impl AvatarStore {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, id: i64) -> Result<Option<String>, String> {
|
pub async fn get(&self, id: i64) -> Result<Option<AvatarRecord>, String> {
|
||||||
|
self.with_db(move |db| db.get(id).map_err(|e| e.to_string()))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&self, id: i64) -> Result<bool, String> {
|
||||||
self.with_db(move |db| db.delete(id).map_err(|e| e.to_string()))
|
self.with_db(move |db| db.delete(id).map_err(|e| e.to_string()))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::avatar_db::AvatarRecord;
|
use crate::avatar_db::AvatarRecord;
|
||||||
use crate::avatar_store::AvatarStore;
|
use crate::avatar_store::AvatarStore;
|
||||||
|
use crate::commands::fs_util;
|
||||||
|
|
||||||
fn normalize_path(path: &str) -> PathBuf {
|
fn normalize_path(path: &str) -> PathBuf {
|
||||||
Path::new(path)
|
Path::new(path)
|
||||||
@@ -24,15 +25,6 @@ fn avatars_dir(app: &AppHandle) -> Result<PathBuf, String> {
|
|||||||
Ok(dir)
|
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]
|
#[tauri::command]
|
||||||
pub async fn list_avatars(store: State<'_, AvatarStore>) -> Result<Vec<AvatarRecord>, String> {
|
pub async fn list_avatars(store: State<'_, AvatarStore>) -> Result<Vec<AvatarRecord>, String> {
|
||||||
store.list().await
|
store.list().await
|
||||||
@@ -65,16 +57,16 @@ pub async fn avatar_name_exists(
|
|||||||
store.name_exists(&name, exclude_id).await
|
store.name_exists(&name, exclude_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 删除数据库记录并删除应用目录内的视频文件
|
/// 删除应用目录内的视频文件及数据库记录
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn delete_avatar(
|
pub async fn delete_avatar(store: State<'_, AvatarStore>, id: i64) -> Result<(), String> {
|
||||||
app: AppHandle,
|
let record = store
|
||||||
store: State<'_, AvatarStore>,
|
.get(id)
|
||||||
id: i64,
|
.await?
|
||||||
) -> Result<(), String> {
|
.ok_or_else(|| "形象不存在".to_string())?;
|
||||||
let file_path = store.delete(id).await?;
|
fs_util::remove_allowed_local_media_file(&record.file_path)?;
|
||||||
if let Some(path) = file_path {
|
if !store.delete(id).await? {
|
||||||
delete_avatar_file_if_allowed(&app, &path).await?;
|
return Err("形象不存在".into());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -115,16 +107,3 @@ pub async fn import_avatar_video(
|
|||||||
Ok(dest.to_string_lossy().to_string())
|
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(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { storeToRefs } from "pinia";
|
|||||||
import { useAvatarStore } from "../stores/avatar.js";
|
import { useAvatarStore } from "../stores/avatar.js";
|
||||||
import AvatarEditDialog from "../components/avatar/AvatarEditDialog.vue";
|
import AvatarEditDialog from "../components/avatar/AvatarEditDialog.vue";
|
||||||
import { localVideoToPlayableUrl } from "../services/localVideo.js";
|
import { localVideoToPlayableUrl } from "../services/localVideo.js";
|
||||||
|
import { revealLocalFileInFolder } from "../services/fileExport.js";
|
||||||
|
|
||||||
const avatarStore = useAvatarStore();
|
const avatarStore = useAvatarStore();
|
||||||
const { templates, loading } = storeToRefs(avatarStore);
|
const { templates, loading } = storeToRefs(avatarStore);
|
||||||
@@ -58,10 +59,26 @@ async function commitRename(item) {
|
|||||||
cancelRename();
|
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) {
|
async function onDelete(item) {
|
||||||
if (!window.confirm(`确认删除形象「${item.name}」?`)) return;
|
if (!window.confirm(`确认删除形象「${item.name}」?`)) return;
|
||||||
await avatarStore.removeTemplate(item);
|
try {
|
||||||
showFeedback("success", "已删除");
|
await avatarStore.removeTemplate(item);
|
||||||
|
showFeedback("success", "已删除");
|
||||||
|
} catch (err) {
|
||||||
|
showFeedback("error", err instanceof Error ? err.message : String(err));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSaved() {
|
function onSaved() {
|
||||||
@@ -77,14 +94,14 @@ function onSaved() {
|
|||||||
<p class="mt-1 text-sm text-slate-500">管理多个数字人形象</p>
|
<p class="mt-1 text-sm text-slate-500">管理多个数字人形象</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<Button
|
<!-- <Button
|
||||||
label="云端形象"
|
label="云端形象"
|
||||||
size="small"
|
size="small"
|
||||||
severity="secondary"
|
severity="secondary"
|
||||||
outlined
|
outlined
|
||||||
icon="pi pi-cloud"
|
icon="pi pi-cloud"
|
||||||
@click="onCloud"
|
@click="onCloud"
|
||||||
/>
|
/> -->
|
||||||
<Button label="添加" size="small" icon="pi pi-plus" @click="onAdd" />
|
<Button label="添加" size="small" icon="pi pi-plus" @click="onAdd" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,14 +175,24 @@ function onSaved() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<div class="flex shrink-0 gap-1">
|
||||||
icon="pi pi-trash"
|
<Button
|
||||||
size="small"
|
label="打开"
|
||||||
severity="danger"
|
icon="pi pi-folder-open"
|
||||||
text
|
size="small"
|
||||||
aria-label="删除"
|
severity="secondary"
|
||||||
@click="onDelete(item)"
|
outlined
|
||||||
/>
|
@click="onOpenInExplorer(item)"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-trash"
|
||||||
|
size="small"
|
||||||
|
severity="danger"
|
||||||
|
text
|
||||||
|
aria-label="删除"
|
||||||
|
@click="onDelete(item)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden rounded-lg bg-black p-1">
|
<div class="overflow-hidden rounded-lg bg-black p-1">
|
||||||
<video
|
<video
|
||||||
|
|||||||
Reference in New Issue
Block a user