feat: 优化MaterialSegmentView文件显示和添加打开目录功能
文件显示优化: - 提取文件名显示:从完整路径中提取文件名,避免显示过长的路径 - 处理Windows长路径格式:正确处理\\\\?\\前缀的长路径 - 简洁的文件名展示:只显示文件名而不是完整路径 打开目录功能: - 添加FolderOpen图标按钮:每个片段卡片都有打开目录按钮 - 跨平台支持:Windows使用explorer /select,macOS使用open -R,Linux使用xdg-open - 智能路径处理:自动检测文件/目录并使用合适的打开方式 - 错误处理:完善的错误处理和日志记录 后端命令实现: - 新增open_file_directory命令:支持打开文件所在目录 - 注册到invoke_handler:在lib.rs中正确注册新命令 - 系统集成:使用系统默认的文件管理器打开目录 UI/UX改进: - 文件名+按钮布局:文件名和打开按钮在同一行显示 - 悬停效果:按钮有hover状态,提供良好的交互反馈 - 工具提示:按钮有'打开文件所在目录'的提示文字 - 图标设计:使用FolderOpen图标,直观表达功能 功能特点: - 一键打开:点击按钮直接在文件管理器中打开文件所在目录 - 文件定位:Windows下会自动选中对应文件 - 路径兼容:支持各种路径格式,包括长路径 - 安全检查:文件不存在时会给出错误提示 现在用户可以: 1. 看到简洁的文件名而不是冗长的完整路径 2. 点击文件夹图标快速打开文件所在目录 3. 在文件管理器中直接定位到对应文件 4. 享受跨平台一致的用户体验
This commit is contained in:
parent
30a4cfc23f
commit
91eb22aaa9
|
|
@ -38,6 +38,7 @@ pub fn run() {
|
||||||
commands::project_commands::get_default_project_name,
|
commands::project_commands::get_default_project_name,
|
||||||
commands::system_commands::select_directory,
|
commands::system_commands::select_directory,
|
||||||
commands::system_commands::select_file,
|
commands::system_commands::select_file,
|
||||||
|
commands::system_commands::open_file_directory,
|
||||||
commands::system_commands::get_app_info,
|
commands::system_commands::get_app_info,
|
||||||
commands::system_commands::validate_directory,
|
commands::system_commands::validate_directory,
|
||||||
commands::system_commands::get_directory_name,
|
commands::system_commands::get_directory_name,
|
||||||
|
|
|
||||||
|
|
@ -185,3 +185,63 @@ pub fn select_file(app: AppHandle, filters: Option<Vec<(String, Vec<String>)>>)
|
||||||
Err(_) => Err("文件选择超时".to_string()),
|
Err(_) => Err("文件选择超时".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 打开文件所在目录命令
|
||||||
|
#[command]
|
||||||
|
pub async fn open_file_directory(file_path: String) -> Result<(), String> {
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let path = Path::new(&file_path);
|
||||||
|
|
||||||
|
// 获取文件所在目录
|
||||||
|
let directory = if path.is_file() {
|
||||||
|
path.parent().ok_or("无法获取文件所在目录")?
|
||||||
|
} else if path.is_dir() {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
return Err("文件路径不存在".to_string());
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据操作系统打开目录
|
||||||
|
let result = if cfg!(target_os = "windows") {
|
||||||
|
// Windows: 使用 explorer 并选中文件
|
||||||
|
if path.is_file() {
|
||||||
|
Command::new("explorer")
|
||||||
|
.args(["/select,", &file_path])
|
||||||
|
.spawn()
|
||||||
|
} else {
|
||||||
|
Command::new("explorer")
|
||||||
|
.arg(directory)
|
||||||
|
.spawn()
|
||||||
|
}
|
||||||
|
} else if cfg!(target_os = "macos") {
|
||||||
|
// macOS: 使用 open 命令
|
||||||
|
if path.is_file() {
|
||||||
|
Command::new("open")
|
||||||
|
.args(["-R", &file_path])
|
||||||
|
.spawn()
|
||||||
|
} else {
|
||||||
|
Command::new("open")
|
||||||
|
.arg(directory)
|
||||||
|
.spawn()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Linux: 使用 xdg-open
|
||||||
|
Command::new("xdg-open")
|
||||||
|
.arg(directory)
|
||||||
|
.spawn()
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("成功打开目录: {:?}", directory);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let error_msg = format!("打开目录失败: {}", e);
|
||||||
|
tracing::error!("{}", error_msg);
|
||||||
|
Err(error_msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import {
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Eye,
|
Eye,
|
||||||
Edit,
|
Edit,
|
||||||
Trash2
|
Trash2,
|
||||||
|
FolderOpen
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { SearchInput } from './InteractiveInput';
|
import { SearchInput } from './InteractiveInput';
|
||||||
|
|
@ -73,6 +74,25 @@ interface MaterialSegmentView {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 提取文件名的工具函数
|
||||||
|
const extractFileName = (filePath: string): string => {
|
||||||
|
if (!filePath) return '未知文件';
|
||||||
|
|
||||||
|
// 处理Windows路径格式,包括长路径前缀
|
||||||
|
const cleanPath = filePath.replace(/^\\\\\?\\/, '');
|
||||||
|
const parts = cleanPath.split(/[\\\/]/);
|
||||||
|
return parts[parts.length - 1] || '未知文件';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开文件所在目录
|
||||||
|
const openFileDirectory = async (filePath: string) => {
|
||||||
|
try {
|
||||||
|
await invoke('open_file_directory', { filePath });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('打开目录失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 素材片段管理组件 - 多条件检索标签页风格
|
* 素材片段管理组件 - 多条件检索标签页风格
|
||||||
*/
|
*/
|
||||||
|
|
@ -215,9 +235,20 @@ export const MaterialSegmentView: React.FC<MaterialSegmentViewProps> = ({ projec
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<h4 className="text-sm font-medium text-gray-900 truncate">
|
<h4 className="text-sm font-medium text-gray-900 truncate">
|
||||||
{segment.material_name || '未知素材'}
|
{extractFileName(segment.segment.file_path)}
|
||||||
</h4>
|
</h4>
|
||||||
|
{segment.segment.file_path && (
|
||||||
|
<button
|
||||||
|
onClick={() => openFileDirectory(segment.segment.file_path)}
|
||||||
|
className="p-1 text-gray-400 hover:text-blue-600 transition-colors"
|
||||||
|
title="打开文件所在目录"
|
||||||
|
>
|
||||||
|
<FolderOpen size={14} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
{formatDuration(segment.segment.start_time)} - {formatDuration(segment.segment.end_time)}
|
{formatDuration(segment.segment.start_time)} - {formatDuration(segment.segment.end_time)}
|
||||||
<span className="ml-2">时长: {formatDuration(segment.segment.duration)}</span>
|
<span className="ml-2">时长: {formatDuration(segment.segment.duration)}</span>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue