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:
imeepos 2025-07-15 21:52:58 +08:00
parent 30a4cfc23f
commit 91eb22aaa9
3 changed files with 96 additions and 4 deletions

View File

@ -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,

View File

@ -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)
}
}
}

View File

@ -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">
<h4 className="text-sm font-medium text-gray-900 truncate"> <div className="flex items-center gap-2">
{segment.material_name || '未知素材'} <h4 className="text-sm font-medium text-gray-900 truncate">
</h4> {extractFileName(segment.segment.file_path)}
</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>