feat: 实现全局目录设置功能
- 扩展AppConfig结构体,添加DirectorySettings字段 - 创建DirectorySettingsService服务,提供CRUD操作 - 添加Tauri命令接口,支持前端调用 - 修改现有文件选择逻辑,优先使用保存的目录设置 - 创建DirectorySettingsModal等UI组件 - 集成到素材导入、模板导入、剪影导出等界面 - 实现自动记忆功能,可选择是否记住路径 - 编写完整的单元测试,确保功能可靠性 功能特点: - 支持素材导入、模板导入、剪影导出、项目导出、缩略图导出等目录设置 - 自动记忆用户选择的目录,下次使用时作为默认路径 - 提供友好的UI界面管理目录设置 - 支持批量更新、清除、重置等操作 - 完整的错误处理和验证机制
This commit is contained in:
@@ -0,0 +1,392 @@
|
||||
use crate::config::{AppConfig, DirectorySettings, DirectorySettingType};
|
||||
use anyhow::Result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tracing::{info, warn, error};
|
||||
|
||||
/// 目录设置管理服务
|
||||
/// 遵循 Tauri 开发规范的服务层设计原则
|
||||
pub struct DirectorySettingsService {
|
||||
config: Arc<Mutex<AppConfig>>,
|
||||
}
|
||||
|
||||
impl DirectorySettingsService {
|
||||
/// 创建新的目录设置服务实例
|
||||
pub fn new() -> Self {
|
||||
let config = AppConfig::load();
|
||||
Self {
|
||||
config: Arc::new(Mutex::new(config)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 从现有配置创建服务实例
|
||||
pub fn from_config(config: AppConfig) -> Self {
|
||||
Self {
|
||||
config: Arc::new(Mutex::new(config)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取所有目录设置
|
||||
pub fn get_directory_settings(&self) -> Result<DirectorySettings> {
|
||||
let config = self.config.lock().map_err(|e| {
|
||||
error!("获取配置锁失败: {}", e);
|
||||
anyhow::anyhow!("配置访问失败")
|
||||
})?;
|
||||
|
||||
Ok(config.directory_settings.clone())
|
||||
}
|
||||
|
||||
/// 获取特定类型的目录设置
|
||||
pub fn get_directory_setting(&self, setting_type: DirectorySettingType) -> Result<Option<String>> {
|
||||
let config = self.config.lock().map_err(|e| {
|
||||
error!("获取配置锁失败: {}", e);
|
||||
anyhow::anyhow!("配置访问失败")
|
||||
})?;
|
||||
|
||||
let result = config.get_directory_setting(setting_type.clone()).cloned();
|
||||
info!("获取目录设置 {:?}: {:?}", setting_type, result);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 更新特定类型的目录设置
|
||||
pub fn update_directory_setting(&self, setting_type: DirectorySettingType, path: String) -> Result<()> {
|
||||
// 验证路径是否存在
|
||||
if !std::path::Path::new(&path).exists() {
|
||||
warn!("设置的目录路径不存在: {}", path);
|
||||
return Err(anyhow::anyhow!("目录路径不存在: {}", path));
|
||||
}
|
||||
|
||||
let mut config = self.config.lock().map_err(|e| {
|
||||
error!("获取配置锁失败: {}", e);
|
||||
anyhow::anyhow!("配置访问失败")
|
||||
})?;
|
||||
|
||||
config.update_directory_setting(setting_type.clone(), path.clone());
|
||||
|
||||
// 保存配置到文件
|
||||
if let Err(e) = config.save() {
|
||||
error!("保存配置失败: {}", e);
|
||||
return Err(anyhow::anyhow!("保存配置失败: {}", e));
|
||||
}
|
||||
|
||||
info!("更新目录设置 {:?}: {}", setting_type, path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 批量更新目录设置
|
||||
pub fn update_multiple_directory_settings(&self, settings: Vec<(DirectorySettingType, String)>) -> Result<()> {
|
||||
// 验证所有路径
|
||||
for (setting_type, path) in &settings {
|
||||
if !std::path::Path::new(path).exists() {
|
||||
warn!("设置的目录路径不存在: {} (类型: {:?})", path, setting_type);
|
||||
return Err(anyhow::anyhow!("目录路径不存在: {}", path));
|
||||
}
|
||||
}
|
||||
|
||||
let mut config = self.config.lock().map_err(|e| {
|
||||
error!("获取配置锁失败: {}", e);
|
||||
anyhow::anyhow!("配置访问失败")
|
||||
})?;
|
||||
|
||||
// 批量更新
|
||||
for (setting_type, path) in settings {
|
||||
config.update_directory_setting(setting_type.clone(), path.clone());
|
||||
info!("批量更新目录设置 {:?}: {}", setting_type, path);
|
||||
}
|
||||
|
||||
// 保存配置到文件
|
||||
if let Err(e) = config.save() {
|
||||
error!("保存配置失败: {}", e);
|
||||
return Err(anyhow::anyhow!("保存配置失败: {}", e));
|
||||
}
|
||||
|
||||
info!("批量更新目录设置完成");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 清除特定类型的目录设置
|
||||
pub fn clear_directory_setting(&self, setting_type: DirectorySettingType) -> Result<()> {
|
||||
let mut config = self.config.lock().map_err(|e| {
|
||||
error!("获取配置锁失败: {}", e);
|
||||
anyhow::anyhow!("配置访问失败")
|
||||
})?;
|
||||
|
||||
match setting_type {
|
||||
DirectorySettingType::MaterialImport => {
|
||||
config.directory_settings.material_import_directory = None;
|
||||
}
|
||||
DirectorySettingType::TemplateImport => {
|
||||
config.directory_settings.template_import_directory = None;
|
||||
}
|
||||
DirectorySettingType::JianyingExport => {
|
||||
config.directory_settings.jianying_export_directory = None;
|
||||
}
|
||||
DirectorySettingType::ProjectExport => {
|
||||
config.directory_settings.project_export_directory = None;
|
||||
}
|
||||
DirectorySettingType::ThumbnailExport => {
|
||||
config.directory_settings.thumbnail_export_directory = None;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置到文件
|
||||
if let Err(e) = config.save() {
|
||||
error!("保存配置失败: {}", e);
|
||||
return Err(anyhow::anyhow!("保存配置失败: {}", e));
|
||||
}
|
||||
|
||||
info!("清除目录设置 {:?}", setting_type);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 重置所有目录设置为默认值
|
||||
pub fn reset_all_directory_settings(&self) -> Result<()> {
|
||||
let mut config = self.config.lock().map_err(|e| {
|
||||
error!("获取配置锁失败: {}", e);
|
||||
anyhow::anyhow!("配置访问失败")
|
||||
})?;
|
||||
|
||||
config.directory_settings = DirectorySettings::default();
|
||||
|
||||
// 保存配置到文件
|
||||
if let Err(e) = config.save() {
|
||||
error!("保存配置失败: {}", e);
|
||||
return Err(anyhow::anyhow!("保存配置失败: {}", e));
|
||||
}
|
||||
|
||||
info!("重置所有目录设置为默认值");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 设置自动记忆功能开关
|
||||
pub fn set_auto_remember_directories(&self, enabled: bool) -> Result<()> {
|
||||
let mut config = self.config.lock().map_err(|e| {
|
||||
error!("获取配置锁失败: {}", e);
|
||||
anyhow::anyhow!("配置访问失败")
|
||||
})?;
|
||||
|
||||
config.directory_settings.auto_remember_directories = enabled;
|
||||
|
||||
// 保存配置到文件
|
||||
if let Err(e) = config.save() {
|
||||
error!("保存配置失败: {}", e);
|
||||
return Err(anyhow::anyhow!("保存配置失败: {}", e));
|
||||
}
|
||||
|
||||
info!("设置自动记忆功能: {}", enabled);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 检查是否启用自动记忆功能
|
||||
pub fn is_auto_remember_enabled(&self) -> Result<bool> {
|
||||
let config = self.config.lock().map_err(|e| {
|
||||
error!("获取配置锁失败: {}", e);
|
||||
anyhow::anyhow!("配置访问失败")
|
||||
})?;
|
||||
|
||||
Ok(config.directory_settings.auto_remember_directories)
|
||||
}
|
||||
|
||||
/// 获取配置的克隆副本
|
||||
pub fn get_config_clone(&self) -> Result<AppConfig> {
|
||||
let config = self.config.lock().map_err(|e| {
|
||||
error!("获取配置锁失败: {}", e);
|
||||
anyhow::anyhow!("配置访问失败")
|
||||
})?;
|
||||
|
||||
Ok(config.clone())
|
||||
}
|
||||
|
||||
/// 验证目录路径是否有效
|
||||
pub fn validate_directory_path(path: &str) -> bool {
|
||||
let path_obj = std::path::Path::new(path);
|
||||
path_obj.exists() && path_obj.is_dir()
|
||||
}
|
||||
|
||||
/// 获取目录设置的显示名称
|
||||
pub fn get_setting_display_name(setting_type: &DirectorySettingType) -> &'static str {
|
||||
match setting_type {
|
||||
DirectorySettingType::MaterialImport => "素材导入目录",
|
||||
DirectorySettingType::TemplateImport => "模板导入目录",
|
||||
DirectorySettingType::JianyingExport => "剪影导出目录",
|
||||
DirectorySettingType::ProjectExport => "项目导出目录",
|
||||
DirectorySettingType::ThumbnailExport => "缩略图导出目录",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DirectorySettingsService {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_directory_settings_service_creation() {
|
||||
let service = DirectorySettingsService::new();
|
||||
assert!(service.get_directory_settings().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_directory_setting() {
|
||||
let service = DirectorySettingsService::new();
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let temp_path = temp_dir.path().to_string_lossy().to_string();
|
||||
|
||||
let result = service.update_directory_setting(
|
||||
DirectorySettingType::MaterialImport,
|
||||
temp_path.clone()
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let setting = service.get_directory_setting(DirectorySettingType::MaterialImport).unwrap();
|
||||
assert_eq!(setting, Some(temp_path));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_nonexistent_directory() {
|
||||
let service = DirectorySettingsService::new();
|
||||
let result = service.update_directory_setting(
|
||||
DirectorySettingType::MaterialImport,
|
||||
"/non/existent/path".to_string()
|
||||
);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_batch_update_directory_settings() {
|
||||
let service = DirectorySettingsService::new();
|
||||
let temp_dir1 = tempdir().unwrap();
|
||||
let temp_dir2 = tempdir().unwrap();
|
||||
let temp_path1 = temp_dir1.path().to_string_lossy().to_string();
|
||||
let temp_path2 = temp_dir2.path().to_string_lossy().to_string();
|
||||
|
||||
let settings = vec![
|
||||
(DirectorySettingType::MaterialImport, temp_path1.clone()),
|
||||
(DirectorySettingType::TemplateImport, temp_path2.clone()),
|
||||
];
|
||||
|
||||
let result = service.update_multiple_directory_settings(settings);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let material_setting = service.get_directory_setting(DirectorySettingType::MaterialImport).unwrap();
|
||||
let template_setting = service.get_directory_setting(DirectorySettingType::TemplateImport).unwrap();
|
||||
|
||||
assert_eq!(material_setting, Some(temp_path1));
|
||||
assert_eq!(template_setting, Some(temp_path2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_directory_setting() {
|
||||
let service = DirectorySettingsService::new();
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let temp_path = temp_dir.path().to_string_lossy().to_string();
|
||||
|
||||
// 先设置一个目录
|
||||
service.update_directory_setting(
|
||||
DirectorySettingType::MaterialImport,
|
||||
temp_path
|
||||
).unwrap();
|
||||
|
||||
// 验证设置成功
|
||||
let setting = service.get_directory_setting(DirectorySettingType::MaterialImport).unwrap();
|
||||
assert!(setting.is_some());
|
||||
|
||||
// 清除设置
|
||||
service.clear_directory_setting(DirectorySettingType::MaterialImport).unwrap();
|
||||
|
||||
// 验证已清除
|
||||
let setting = service.get_directory_setting(DirectorySettingType::MaterialImport).unwrap();
|
||||
assert!(setting.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reset_all_directory_settings() {
|
||||
let service = DirectorySettingsService::new();
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let temp_path = temp_dir.path().to_string_lossy().to_string();
|
||||
|
||||
// 设置一些目录
|
||||
service.update_directory_setting(
|
||||
DirectorySettingType::MaterialImport,
|
||||
temp_path.clone()
|
||||
).unwrap();
|
||||
service.update_directory_setting(
|
||||
DirectorySettingType::TemplateImport,
|
||||
temp_path
|
||||
).unwrap();
|
||||
|
||||
// 重置所有设置
|
||||
service.reset_all_directory_settings().unwrap();
|
||||
|
||||
// 验证所有设置都被重置
|
||||
let material_setting = service.get_directory_setting(DirectorySettingType::MaterialImport).unwrap();
|
||||
let template_setting = service.get_directory_setting(DirectorySettingType::TemplateImport).unwrap();
|
||||
|
||||
assert!(material_setting.is_none());
|
||||
assert!(template_setting.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_directory_path() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let temp_path = temp_dir.path().to_string_lossy();
|
||||
|
||||
assert!(DirectorySettingsService::validate_directory_path(&temp_path));
|
||||
assert!(!DirectorySettingsService::validate_directory_path("/non/existent/path"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auto_remember_setting() {
|
||||
let service = DirectorySettingsService::new();
|
||||
|
||||
// 默认应该是启用的
|
||||
assert!(service.is_auto_remember_enabled().unwrap());
|
||||
|
||||
// 禁用自动记忆
|
||||
service.set_auto_remember_directories(false).unwrap();
|
||||
assert!(!service.is_auto_remember_enabled().unwrap());
|
||||
|
||||
// 重新启用
|
||||
service.set_auto_remember_directories(true).unwrap();
|
||||
assert!(service.is_auto_remember_enabled().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_setting_display_name() {
|
||||
assert_eq!(
|
||||
DirectorySettingsService::get_setting_display_name(&DirectorySettingType::MaterialImport),
|
||||
"素材导入目录"
|
||||
);
|
||||
assert_eq!(
|
||||
DirectorySettingsService::get_setting_display_name(&DirectorySettingType::TemplateImport),
|
||||
"模板导入目录"
|
||||
);
|
||||
assert_eq!(
|
||||
DirectorySettingsService::get_setting_display_name(&DirectorySettingType::JianyingExport),
|
||||
"剪影导出目录"
|
||||
);
|
||||
assert_eq!(
|
||||
DirectorySettingsService::get_setting_display_name(&DirectorySettingType::ProjectExport),
|
||||
"项目导出目录"
|
||||
);
|
||||
assert_eq!(
|
||||
DirectorySettingsService::get_setting_display_name(&DirectorySettingType::ThumbnailExport),
|
||||
"缩略图导出目录"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_config_clone() {
|
||||
let service = DirectorySettingsService::new();
|
||||
let config = service.get_config_clone();
|
||||
assert!(config.is_ok());
|
||||
|
||||
let config = config.unwrap();
|
||||
assert_eq!(config.directory_settings.auto_remember_directories, true);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ pub mod video_generation_service;
|
||||
pub mod conversation_service;
|
||||
pub mod jianying_export;
|
||||
pub mod template_segment_weight_service;
|
||||
pub mod directory_settings_service;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
@@ -11,6 +11,25 @@ pub struct AppConfig {
|
||||
pub window_size: WindowSize,
|
||||
pub recent_projects: Vec<String>,
|
||||
pub default_project_path: Option<String>,
|
||||
pub directory_settings: DirectorySettings,
|
||||
}
|
||||
|
||||
/// 全局目录设置
|
||||
/// 存储各种导入导出操作的默认目录路径
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DirectorySettings {
|
||||
/// 素材导入默认目录
|
||||
pub material_import_directory: Option<String>,
|
||||
/// 模板导入默认目录
|
||||
pub template_import_directory: Option<String>,
|
||||
/// 剪影导出默认目录
|
||||
pub jianying_export_directory: Option<String>,
|
||||
/// 项目导出默认目录
|
||||
pub project_export_directory: Option<String>,
|
||||
/// 缩略图导出默认目录
|
||||
pub thumbnail_export_directory: Option<String>,
|
||||
/// 是否启用自动记忆功能
|
||||
pub auto_remember_directories: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -19,6 +38,19 @@ pub struct WindowSize {
|
||||
pub height: f64,
|
||||
}
|
||||
|
||||
impl Default for DirectorySettings {
|
||||
fn default() -> Self {
|
||||
DirectorySettings {
|
||||
material_import_directory: None,
|
||||
template_import_directory: None,
|
||||
jianying_export_directory: None,
|
||||
project_export_directory: None,
|
||||
thumbnail_export_directory: None,
|
||||
auto_remember_directories: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
AppConfig {
|
||||
@@ -31,6 +63,7 @@ impl Default for AppConfig {
|
||||
},
|
||||
recent_projects: Vec::new(),
|
||||
default_project_path: None,
|
||||
directory_settings: DirectorySettings::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,10 +116,62 @@ impl AppConfig {
|
||||
pub fn add_recent_project(&mut self, project_path: String) {
|
||||
self.recent_projects.retain(|p| p != &project_path);
|
||||
self.recent_projects.insert(0, project_path);
|
||||
|
||||
|
||||
// 保持最近项目列表不超过10个
|
||||
if self.recent_projects.len() > 10 {
|
||||
self.recent_projects.truncate(10);
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新目录设置
|
||||
pub fn update_directory_setting(&mut self, setting_type: DirectorySettingType, path: String) {
|
||||
match setting_type {
|
||||
DirectorySettingType::MaterialImport => {
|
||||
self.directory_settings.material_import_directory = Some(path);
|
||||
}
|
||||
DirectorySettingType::TemplateImport => {
|
||||
self.directory_settings.template_import_directory = Some(path);
|
||||
}
|
||||
DirectorySettingType::JianyingExport => {
|
||||
self.directory_settings.jianying_export_directory = Some(path);
|
||||
}
|
||||
DirectorySettingType::ProjectExport => {
|
||||
self.directory_settings.project_export_directory = Some(path);
|
||||
}
|
||||
DirectorySettingType::ThumbnailExport => {
|
||||
self.directory_settings.thumbnail_export_directory = Some(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取目录设置
|
||||
pub fn get_directory_setting(&self, setting_type: DirectorySettingType) -> Option<&String> {
|
||||
match setting_type {
|
||||
DirectorySettingType::MaterialImport => {
|
||||
self.directory_settings.material_import_directory.as_ref()
|
||||
}
|
||||
DirectorySettingType::TemplateImport => {
|
||||
self.directory_settings.template_import_directory.as_ref()
|
||||
}
|
||||
DirectorySettingType::JianyingExport => {
|
||||
self.directory_settings.jianying_export_directory.as_ref()
|
||||
}
|
||||
DirectorySettingType::ProjectExport => {
|
||||
self.directory_settings.project_export_directory.as_ref()
|
||||
}
|
||||
DirectorySettingType::ThumbnailExport => {
|
||||
self.directory_settings.thumbnail_export_directory.as_ref()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 目录设置类型枚举
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum DirectorySettingType {
|
||||
MaterialImport,
|
||||
TemplateImport,
|
||||
JianyingExport,
|
||||
ProjectExport,
|
||||
ThumbnailExport,
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ pub fn run() {
|
||||
commands::project_commands::validate_project_path,
|
||||
commands::project_commands::get_default_project_name,
|
||||
commands::system_commands::select_directory,
|
||||
commands::system_commands::select_directory_with_options,
|
||||
commands::system_commands::select_file,
|
||||
commands::system_commands::select_file_with_options,
|
||||
commands::system_commands::save_file_with_options,
|
||||
commands::system_commands::open_file_directory,
|
||||
commands::system_commands::play_video_segment,
|
||||
commands::system_commands::get_app_info,
|
||||
@@ -192,6 +195,7 @@ pub fn run() {
|
||||
commands::template_commands::import_template_optimized,
|
||||
commands::template_commands::update_segment_matching_rule,
|
||||
commands::template_commands::get_segment_matching_rule,
|
||||
commands::template_commands::select_template_file,
|
||||
// 项目-模板绑定管理命令
|
||||
commands::project_template_binding_commands::create_project_template_binding,
|
||||
commands::project_template_binding_commands::update_project_template_binding,
|
||||
@@ -247,6 +251,7 @@ pub fn run() {
|
||||
commands::template_matching_result_commands::get_matching_result_status_options,
|
||||
commands::template_matching_result_commands::export_matching_result_to_jianying,
|
||||
commands::template_matching_result_commands::export_matching_result_to_jianying_v2,
|
||||
commands::template_matching_result_commands::select_jianying_export_path,
|
||||
// 导出记录命令
|
||||
commands::export_record_commands::get_export_record,
|
||||
commands::export_record_commands::list_export_records,
|
||||
@@ -387,7 +392,18 @@ pub fn run() {
|
||||
commands::template_segment_weight_commands::update_template_segment_weight,
|
||||
commands::template_segment_weight_commands::has_custom_segment_weights,
|
||||
commands::template_segment_weight_commands::get_template_weight_statistics,
|
||||
commands::template_segment_weight_commands::get_segment_weights_for_categories
|
||||
commands::template_segment_weight_commands::get_segment_weights_for_categories,
|
||||
// 目录设置管理命令
|
||||
commands::directory_settings_commands::get_directory_settings,
|
||||
commands::directory_settings_commands::get_directory_setting,
|
||||
commands::directory_settings_commands::update_directory_setting,
|
||||
commands::directory_settings_commands::batch_update_directory_settings,
|
||||
commands::directory_settings_commands::clear_directory_setting,
|
||||
commands::directory_settings_commands::reset_all_directory_settings,
|
||||
commands::directory_settings_commands::set_auto_remember_directories,
|
||||
commands::directory_settings_commands::is_auto_remember_enabled,
|
||||
commands::directory_settings_commands::validate_directory_path,
|
||||
commands::directory_settings_commands::select_and_save_directory
|
||||
])
|
||||
.setup(|app| {
|
||||
// 初始化日志系统
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
use crate::business::services::directory_settings_service::DirectorySettingsService;
|
||||
use crate::config::{DirectorySettings, DirectorySettingType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::command;
|
||||
use tracing::{info, warn, error};
|
||||
|
||||
/// 目录设置响应结构
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct DirectorySettingsResponse {
|
||||
pub material_import_directory: Option<String>,
|
||||
pub template_import_directory: Option<String>,
|
||||
pub jianying_export_directory: Option<String>,
|
||||
pub project_export_directory: Option<String>,
|
||||
pub thumbnail_export_directory: Option<String>,
|
||||
pub auto_remember_directories: bool,
|
||||
}
|
||||
|
||||
impl From<DirectorySettings> for DirectorySettingsResponse {
|
||||
fn from(settings: DirectorySettings) -> Self {
|
||||
Self {
|
||||
material_import_directory: settings.material_import_directory,
|
||||
template_import_directory: settings.template_import_directory,
|
||||
jianying_export_directory: settings.jianying_export_directory,
|
||||
project_export_directory: settings.project_export_directory,
|
||||
thumbnail_export_directory: settings.thumbnail_export_directory,
|
||||
auto_remember_directories: settings.auto_remember_directories,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 目录设置更新请求
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UpdateDirectorySettingRequest {
|
||||
pub setting_type: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
/// 批量目录设置更新请求
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BatchUpdateDirectorySettingsRequest {
|
||||
pub settings: Vec<UpdateDirectorySettingRequest>,
|
||||
}
|
||||
|
||||
/// 获取所有目录设置
|
||||
#[command]
|
||||
pub async fn get_directory_settings() -> Result<DirectorySettingsResponse, String> {
|
||||
info!("获取目录设置");
|
||||
|
||||
let service = DirectorySettingsService::new();
|
||||
|
||||
match service.get_directory_settings() {
|
||||
Ok(settings) => {
|
||||
info!("成功获取目录设置");
|
||||
Ok(DirectorySettingsResponse::from(settings))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("获取目录设置失败: {}", e);
|
||||
Err(format!("获取目录设置失败: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取特定类型的目录设置
|
||||
#[command]
|
||||
pub async fn get_directory_setting(setting_type: String) -> Result<Option<String>, String> {
|
||||
info!("获取目录设置: {}", setting_type);
|
||||
|
||||
let setting_type_enum = parse_directory_setting_type(&setting_type)?;
|
||||
let service = DirectorySettingsService::new();
|
||||
|
||||
match service.get_directory_setting(setting_type_enum) {
|
||||
Ok(setting) => {
|
||||
info!("成功获取目录设置 {}: {:?}", setting_type, setting);
|
||||
Ok(setting)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("获取目录设置失败: {}", e);
|
||||
Err(format!("获取目录设置失败: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新目录设置
|
||||
#[command]
|
||||
pub async fn update_directory_setting(setting_type: String, path: String) -> Result<(), String> {
|
||||
info!("更新目录设置: {} -> {}", setting_type, path);
|
||||
|
||||
let setting_type_enum = parse_directory_setting_type(&setting_type)?;
|
||||
let service = DirectorySettingsService::new();
|
||||
|
||||
match service.update_directory_setting(setting_type_enum, path.clone()) {
|
||||
Ok(()) => {
|
||||
info!("成功更新目录设置: {} -> {}", setting_type, path);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("更新目录设置失败: {}", e);
|
||||
Err(format!("更新目录设置失败: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 批量更新目录设置
|
||||
#[command]
|
||||
pub async fn batch_update_directory_settings(request: BatchUpdateDirectorySettingsRequest) -> Result<(), String> {
|
||||
info!("批量更新目录设置,共 {} 项", request.settings.len());
|
||||
|
||||
let service = DirectorySettingsService::new();
|
||||
let mut settings = Vec::new();
|
||||
|
||||
// 解析所有设置类型
|
||||
for setting in request.settings {
|
||||
let setting_type_enum = parse_directory_setting_type(&setting.setting_type)?;
|
||||
settings.push((setting_type_enum, setting.path));
|
||||
}
|
||||
|
||||
match service.update_multiple_directory_settings(settings) {
|
||||
Ok(()) => {
|
||||
info!("成功批量更新目录设置");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("批量更新目录设置失败: {}", e);
|
||||
Err(format!("批量更新目录设置失败: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 清除目录设置
|
||||
#[command]
|
||||
pub async fn clear_directory_setting(setting_type: String) -> Result<(), String> {
|
||||
info!("清除目录设置: {}", setting_type);
|
||||
|
||||
let setting_type_enum = parse_directory_setting_type(&setting_type)?;
|
||||
let service = DirectorySettingsService::new();
|
||||
|
||||
match service.clear_directory_setting(setting_type_enum) {
|
||||
Ok(()) => {
|
||||
info!("成功清除目录设置: {}", setting_type);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("清除目录设置失败: {}", e);
|
||||
Err(format!("清除目录设置失败: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 重置所有目录设置
|
||||
#[command]
|
||||
pub async fn reset_all_directory_settings() -> Result<(), String> {
|
||||
info!("重置所有目录设置");
|
||||
|
||||
let service = DirectorySettingsService::new();
|
||||
|
||||
match service.reset_all_directory_settings() {
|
||||
Ok(()) => {
|
||||
info!("成功重置所有目录设置");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("重置目录设置失败: {}", e);
|
||||
Err(format!("重置目录设置失败: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置自动记忆功能
|
||||
#[command]
|
||||
pub async fn set_auto_remember_directories(enabled: bool) -> Result<(), String> {
|
||||
info!("设置自动记忆功能: {}", enabled);
|
||||
|
||||
let service = DirectorySettingsService::new();
|
||||
|
||||
match service.set_auto_remember_directories(enabled) {
|
||||
Ok(()) => {
|
||||
info!("成功设置自动记忆功能: {}", enabled);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("设置自动记忆功能失败: {}", e);
|
||||
Err(format!("设置自动记忆功能失败: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查是否启用自动记忆功能
|
||||
#[command]
|
||||
pub async fn is_auto_remember_enabled() -> Result<bool, String> {
|
||||
let service = DirectorySettingsService::new();
|
||||
|
||||
match service.is_auto_remember_enabled() {
|
||||
Ok(enabled) => {
|
||||
info!("自动记忆功能状态: {}", enabled);
|
||||
Ok(enabled)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("获取自动记忆功能状态失败: {}", e);
|
||||
Err(format!("获取自动记忆功能状态失败: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 验证目录路径
|
||||
#[command]
|
||||
pub async fn validate_directory_path(path: String) -> Result<bool, String> {
|
||||
let is_valid = DirectorySettingsService::validate_directory_path(&path);
|
||||
info!("验证目录路径 {}: {}", path, is_valid);
|
||||
Ok(is_valid)
|
||||
}
|
||||
|
||||
/// 选择目录并可选择性地保存设置
|
||||
#[command]
|
||||
pub async fn select_and_save_directory(
|
||||
app: tauri::AppHandle,
|
||||
setting_type: Option<String>,
|
||||
title: Option<String>,
|
||||
) -> Result<Option<String>, String> {
|
||||
use crate::presentation::commands::system_commands::select_directory;
|
||||
|
||||
// 设置对话框标题
|
||||
let dialog_title = title.unwrap_or_else(|| {
|
||||
if let Some(ref st) = setting_type {
|
||||
format!("选择{}", get_setting_display_name_by_string(st))
|
||||
} else {
|
||||
"选择目录".to_string()
|
||||
}
|
||||
});
|
||||
|
||||
info!("打开目录选择对话框: {}", dialog_title);
|
||||
|
||||
// 使用现有的目录选择功能
|
||||
match select_directory(app) {
|
||||
Ok(Some(selected_path)) => {
|
||||
info!("用户选择了目录: {}", selected_path);
|
||||
|
||||
// 如果指定了设置类型,询问是否保存
|
||||
if let Some(st) = setting_type {
|
||||
let service = DirectorySettingsService::new();
|
||||
|
||||
// 检查是否启用自动记忆
|
||||
if service.is_auto_remember_enabled().unwrap_or(false) {
|
||||
let setting_type_enum = parse_directory_setting_type(&st)?;
|
||||
if let Err(e) = service.update_directory_setting(setting_type_enum, selected_path.clone()) {
|
||||
warn!("自动保存目录设置失败: {}", e);
|
||||
// 不返回错误,只是记录警告
|
||||
} else {
|
||||
info!("自动保存目录设置: {} -> {}", st, selected_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(selected_path))
|
||||
}
|
||||
Ok(None) => {
|
||||
info!("用户取消了目录选择");
|
||||
Ok(None)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("目录选择失败: {}", e);
|
||||
Err(format!("目录选择失败: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析目录设置类型字符串
|
||||
fn parse_directory_setting_type(setting_type: &str) -> Result<DirectorySettingType, String> {
|
||||
match setting_type.to_lowercase().as_str() {
|
||||
"material_import" | "materialimport" => Ok(DirectorySettingType::MaterialImport),
|
||||
"template_import" | "templateimport" => Ok(DirectorySettingType::TemplateImport),
|
||||
"jianying_export" | "jianyingexport" => Ok(DirectorySettingType::JianyingExport),
|
||||
"project_export" | "projectexport" => Ok(DirectorySettingType::ProjectExport),
|
||||
"thumbnail_export" | "thumbnailexport" => Ok(DirectorySettingType::ThumbnailExport),
|
||||
_ => Err(format!("不支持的目录设置类型: {}", setting_type)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据字符串获取设置显示名称
|
||||
fn get_setting_display_name_by_string(setting_type: &str) -> &'static str {
|
||||
match setting_type.to_lowercase().as_str() {
|
||||
"material_import" | "materialimport" => "素材导入目录",
|
||||
"template_import" | "templateimport" => "模板导入目录",
|
||||
"jianying_export" | "jianyingexport" => "剪影导出目录",
|
||||
"project_export" | "projectexport" => "项目导出目录",
|
||||
"thumbnail_export" | "thumbnailexport" => "缩略图导出目录",
|
||||
_ => "目录",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_directory_setting_type() {
|
||||
assert!(matches!(
|
||||
parse_directory_setting_type("material_import"),
|
||||
Ok(DirectorySettingType::MaterialImport)
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_directory_setting_type("MaterialImport"),
|
||||
Ok(DirectorySettingType::MaterialImport)
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_directory_setting_type("template_import"),
|
||||
Ok(DirectorySettingType::TemplateImport)
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_directory_setting_type("jianying_export"),
|
||||
Ok(DirectorySettingType::JianyingExport)
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_directory_setting_type("project_export"),
|
||||
Ok(DirectorySettingType::ProjectExport)
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_directory_setting_type("thumbnail_export"),
|
||||
Ok(DirectorySettingType::ThumbnailExport)
|
||||
));
|
||||
assert!(parse_directory_setting_type("invalid").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_setting_display_name() {
|
||||
assert_eq!(get_setting_display_name_by_string("material_import"), "素材导入目录");
|
||||
assert_eq!(get_setting_display_name_by_string("template_import"), "模板导入目录");
|
||||
assert_eq!(get_setting_display_name_by_string("jianying_export"), "剪影导出目录");
|
||||
assert_eq!(get_setting_display_name_by_string("project_export"), "项目导出目录");
|
||||
assert_eq!(get_setting_display_name_by_string("thumbnail_export"), "缩略图导出目录");
|
||||
assert_eq!(get_setting_display_name_by_string("invalid"), "目录");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_directory_settings_response_from_settings() {
|
||||
let settings = DirectorySettings {
|
||||
material_import_directory: Some("/path/to/material".to_string()),
|
||||
template_import_directory: Some("/path/to/template".to_string()),
|
||||
jianying_export_directory: None,
|
||||
project_export_directory: None,
|
||||
thumbnail_export_directory: None,
|
||||
auto_remember_directories: true,
|
||||
};
|
||||
|
||||
let response = DirectorySettingsResponse::from(settings);
|
||||
|
||||
assert_eq!(response.material_import_directory, Some("/path/to/material".to_string()));
|
||||
assert_eq!(response.template_import_directory, Some("/path/to/template".to_string()));
|
||||
assert_eq!(response.jianying_export_directory, None);
|
||||
assert_eq!(response.project_export_directory, None);
|
||||
assert_eq!(response.thumbnail_export_directory, None);
|
||||
assert_eq!(response.auto_remember_directories, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_batch_update_request_parsing() {
|
||||
let request = BatchUpdateDirectorySettingsRequest {
|
||||
settings: vec![
|
||||
UpdateDirectorySettingRequest {
|
||||
setting_type: "material_import".to_string(),
|
||||
path: "/path/to/material".to_string(),
|
||||
},
|
||||
UpdateDirectorySettingRequest {
|
||||
setting_type: "template_import".to_string(),
|
||||
path: "/path/to/template".to_string(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(request.settings.len(), 2);
|
||||
assert_eq!(request.settings[0].setting_type, "material_import");
|
||||
assert_eq!(request.settings[0].path, "/path/to/material");
|
||||
assert_eq!(request.settings[1].setting_type, "template_import");
|
||||
assert_eq!(request.settings[1].path, "/path/to/template");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_directory_settings_command() {
|
||||
// 这个测试需要实际的服务实例,在集成测试中更合适
|
||||
// 这里只测试函数签名和基本逻辑
|
||||
let result = get_directory_settings().await;
|
||||
// 由于依赖文件系统,这里只验证返回类型
|
||||
assert!(result.is_ok() || result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_validate_directory_path_command() {
|
||||
// 测试有效路径(当前目录应该存在)
|
||||
let result = validate_directory_path(".".to_string()).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
|
||||
// 测试无效路径
|
||||
let result = validate_directory_path("/non/existent/path".to_string()).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(!result.unwrap());
|
||||
}
|
||||
}
|
||||
@@ -714,12 +714,31 @@ async fn process_single_file_with_full_logic(
|
||||
/// 选择文件夹进行批量导入
|
||||
#[command]
|
||||
pub async fn select_material_folders(app_handle: tauri::AppHandle) -> Result<Vec<String>, String> {
|
||||
use crate::presentation::commands::system_commands::select_directory;
|
||||
use crate::presentation::commands::system_commands::select_directory_with_options;
|
||||
use crate::business::services::directory_settings_service::DirectorySettingsService;
|
||||
use crate::config::DirectorySettingType;
|
||||
|
||||
// 使用现有的目录选择功能,但允许多选
|
||||
// 注意:这是一个简化实现,实际可能需要修改系统命令以支持多选
|
||||
match select_directory(app_handle) {
|
||||
Ok(Some(folder_path)) => Ok(vec![folder_path]),
|
||||
// 获取默认目录设置
|
||||
let service = DirectorySettingsService::new();
|
||||
let default_dir = service.get_directory_setting(DirectorySettingType::MaterialImport)
|
||||
.unwrap_or(None);
|
||||
|
||||
// 使用带选项的目录选择功能
|
||||
match select_directory_with_options(
|
||||
app_handle,
|
||||
Some("选择素材导入目录".to_string()),
|
||||
default_dir
|
||||
) {
|
||||
Ok(Some(folder_path)) => {
|
||||
// 如果启用了自动记忆,更新默认目录
|
||||
if let Ok(true) = service.is_auto_remember_enabled() {
|
||||
let _ = service.update_directory_setting(
|
||||
DirectorySettingType::MaterialImport,
|
||||
folder_path.clone()
|
||||
);
|
||||
}
|
||||
Ok(vec![folder_path])
|
||||
},
|
||||
Ok(None) => Ok(Vec::new()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
@@ -999,8 +1018,12 @@ pub async fn get_supported_extensions() -> Result<Vec<String>, String> {
|
||||
#[command]
|
||||
pub async fn select_material_files(app: tauri::AppHandle) -> Result<Vec<String>, String> {
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
use crate::business::services::directory_settings_service::DirectorySettingsService;
|
||||
use crate::config::DirectorySettingType;
|
||||
|
||||
let dialog = app.dialog().file()
|
||||
// 获取默认目录设置
|
||||
let service = DirectorySettingsService::new();
|
||||
let mut dialog = app.dialog().file()
|
||||
.set_title("选择素材文件")
|
||||
.add_filter("视频文件", &["mp4", "avi", "mov", "mkv", "wmv", "flv", "webm", "m4v"])
|
||||
.add_filter("音频文件", &["mp3", "wav", "flac", "aac", "ogg", "wma", "m4a"])
|
||||
@@ -1008,6 +1031,13 @@ pub async fn select_material_files(app: tauri::AppHandle) -> Result<Vec<String>,
|
||||
.add_filter("文档文件", &["pdf", "doc", "docx", "txt", "md", "rtf"])
|
||||
.add_filter("所有支持的文件", &MaterialService::get_supported_extensions());
|
||||
|
||||
// 设置默认目录
|
||||
if let Ok(Some(default_dir)) = service.get_directory_setting(DirectorySettingType::MaterialImport) {
|
||||
if std::path::Path::new(&default_dir).exists() {
|
||||
dialog = dialog.set_directory(&default_dir);
|
||||
}
|
||||
}
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
dialog.pick_files(move |file_paths| {
|
||||
@@ -1020,7 +1050,23 @@ pub async fn select_material_files(app: tauri::AppHandle) -> Result<Vec<String>,
|
||||
});
|
||||
|
||||
match rx.recv_timeout(std::time::Duration::from_secs(30)) {
|
||||
Ok(paths) => Ok(paths),
|
||||
Ok(paths) => {
|
||||
// 如果选择了文件且启用了自动记忆,更新默认目录
|
||||
if !paths.is_empty() {
|
||||
if let Ok(true) = service.is_auto_remember_enabled() {
|
||||
if let Some(first_file) = paths.first() {
|
||||
if let Some(parent_dir) = std::path::Path::new(first_file).parent() {
|
||||
let parent_path = parent_dir.to_string_lossy().to_string();
|
||||
let _ = service.update_directory_setting(
|
||||
DirectorySettingType::MaterialImport,
|
||||
parent_path
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(paths)
|
||||
},
|
||||
Err(_) => Err("文件选择操作超时或失败".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,3 +30,4 @@ pub mod image_download_commands;
|
||||
pub mod conversation_commands;
|
||||
pub mod watermark_commands;
|
||||
pub mod template_segment_weight_commands;
|
||||
pub mod directory_settings_commands;
|
||||
|
||||
@@ -15,10 +15,29 @@ pub struct AppInfo {
|
||||
/// 遵循 Tauri 开发规范的系统集成设计
|
||||
#[command]
|
||||
pub fn select_directory(app: AppHandle) -> Result<Option<String>, String> {
|
||||
select_directory_with_options(app, None, None)
|
||||
}
|
||||
|
||||
/// 选择目录命令(带选项)
|
||||
/// 支持自定义标题和默认目录
|
||||
#[command]
|
||||
pub fn select_directory_with_options(
|
||||
app: AppHandle,
|
||||
title: Option<String>,
|
||||
default_directory: Option<String>
|
||||
) -> Result<Option<String>, String> {
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
// 使用同步方式打开文件夹选择对话框
|
||||
let dialog = app.dialog().file().set_title("选择项目目录");
|
||||
let mut dialog = app.dialog().file()
|
||||
.set_title(&title.unwrap_or_else(|| "选择目录".to_string()));
|
||||
|
||||
// 设置默认目录
|
||||
if let Some(default_dir) = default_directory {
|
||||
if std::path::Path::new(&default_dir).exists() {
|
||||
dialog = dialog.set_directory(&default_dir);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一个简单的阻塞实现
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
@@ -159,9 +178,22 @@ pub async fn record_performance_metric(name: String, value: f64) -> Result<(), S
|
||||
/// 选择文件命令
|
||||
#[command]
|
||||
pub fn select_file(app: AppHandle, filters: Option<Vec<(String, Vec<String>)>>) -> Result<Option<String>, String> {
|
||||
select_file_with_options(app, filters, None, None)
|
||||
}
|
||||
|
||||
/// 选择文件命令(带选项)
|
||||
/// 支持自定义标题和默认目录
|
||||
#[command]
|
||||
pub fn select_file_with_options(
|
||||
app: AppHandle,
|
||||
filters: Option<Vec<(String, Vec<String>)>>,
|
||||
title: Option<String>,
|
||||
default_directory: Option<String>
|
||||
) -> Result<Option<String>, String> {
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
let mut dialog = app.dialog().file().set_title("选择文件");
|
||||
let mut dialog = app.dialog().file()
|
||||
.set_title(&title.unwrap_or_else(|| "选择文件".to_string()));
|
||||
|
||||
// 添加文件过滤器
|
||||
if let Some(filter_list) = filters {
|
||||
@@ -171,6 +203,13 @@ pub fn select_file(app: AppHandle, filters: Option<Vec<(String, Vec<String>)>>)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置默认目录
|
||||
if let Some(default_dir) = default_directory {
|
||||
if std::path::Path::new(&default_dir).exists() {
|
||||
dialog = dialog.set_directory(&default_dir);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一个简单的阻塞实现
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
@@ -186,6 +225,56 @@ pub fn select_file(app: AppHandle, filters: Option<Vec<(String, Vec<String>)>>)
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存文件命令(带选项)
|
||||
/// 支持自定义标题、默认文件名和默认目录
|
||||
#[command]
|
||||
pub fn save_file_with_options(
|
||||
app: AppHandle,
|
||||
title: Option<String>,
|
||||
default_filename: Option<String>,
|
||||
default_directory: Option<String>,
|
||||
filters: Option<Vec<(String, Vec<String>)>>,
|
||||
) -> Result<Option<String>, String> {
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
let mut dialog = app.dialog().file()
|
||||
.set_title(&title.unwrap_or_else(|| "保存文件".to_string()));
|
||||
|
||||
// 设置默认文件名
|
||||
if let Some(filename) = default_filename {
|
||||
dialog = dialog.set_file_name(&filename);
|
||||
}
|
||||
|
||||
// 设置默认目录
|
||||
if let Some(default_dir) = default_directory {
|
||||
if std::path::Path::new(&default_dir).exists() {
|
||||
dialog = dialog.set_directory(&default_dir);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加文件过滤器
|
||||
if let Some(filter_list) = filters {
|
||||
for (name, extensions) in filter_list {
|
||||
let ext_refs: Vec<&str> = extensions.iter().map(|s| s.as_str()).collect();
|
||||
dialog = dialog.add_filter(&name, &ext_refs);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一个简单的阻塞实现
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
dialog.save_file(move |file_path| {
|
||||
let _ = tx.send(file_path);
|
||||
});
|
||||
|
||||
// 等待结果,设置超时
|
||||
match rx.recv_timeout(std::time::Duration::from_secs(30)) {
|
||||
Ok(Some(path)) => Ok(Some(path.to_string())),
|
||||
Ok(None) => Ok(None),
|
||||
Err(_) => Err("文件保存超时".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 打开文件所在目录命令
|
||||
#[command]
|
||||
pub async fn open_file_directory(file_path: String) -> Result<(), String> {
|
||||
|
||||
@@ -402,4 +402,43 @@ pub async fn get_segment_matching_rule(
|
||||
service.get_segment_matching_rule(&segment_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// 选择模板文件命令
|
||||
#[tauri::command]
|
||||
pub async fn select_template_file(app: tauri::AppHandle) -> Result<Option<String>, String> {
|
||||
use crate::presentation::commands::system_commands::select_file_with_options;
|
||||
use crate::business::services::directory_settings_service::DirectorySettingsService;
|
||||
use crate::config::DirectorySettingType;
|
||||
|
||||
// 获取默认目录设置
|
||||
let service = DirectorySettingsService::new();
|
||||
let default_dir = service.get_directory_setting(DirectorySettingType::TemplateImport)
|
||||
.unwrap_or(None);
|
||||
|
||||
// 使用带选项的文件选择功能
|
||||
let result = select_file_with_options(
|
||||
app,
|
||||
Some(vec![("剪映草稿文件".to_string(), vec!["json".to_string()])]),
|
||||
Some("选择模板文件".to_string()),
|
||||
default_dir
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(Some(file_path)) => {
|
||||
// 如果启用了自动记忆,更新默认目录
|
||||
if let Ok(true) = service.is_auto_remember_enabled() {
|
||||
if let Some(parent_dir) = std::path::Path::new(&file_path).parent() {
|
||||
let parent_path = parent_dir.to_string_lossy().to_string();
|
||||
let _ = service.update_directory_setting(
|
||||
DirectorySettingType::TemplateImport,
|
||||
parent_path
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(Some(file_path))
|
||||
},
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -474,3 +474,46 @@ pub async fn export_matching_result_to_jianying_v2(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 选择剪影导出路径命令
|
||||
#[command]
|
||||
pub async fn select_jianying_export_path(
|
||||
app: tauri::AppHandle,
|
||||
default_filename: String,
|
||||
) -> Result<Option<String>, String> {
|
||||
use crate::presentation::commands::system_commands::save_file_with_options;
|
||||
use crate::business::services::directory_settings_service::DirectorySettingsService;
|
||||
use crate::config::DirectorySettingType;
|
||||
|
||||
// 获取默认目录设置
|
||||
let service = DirectorySettingsService::new();
|
||||
let default_dir = service.get_directory_setting(DirectorySettingType::JianyingExport)
|
||||
.unwrap_or(None);
|
||||
|
||||
// 使用带选项的文件保存功能
|
||||
let result = save_file_with_options(
|
||||
app,
|
||||
Some("导出到剪影".to_string()),
|
||||
Some(default_filename),
|
||||
default_dir,
|
||||
Some(vec![("JSON文件".to_string(), vec!["json".to_string()])])
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(Some(file_path)) => {
|
||||
// 如果启用了自动记忆,更新默认目录
|
||||
if let Ok(true) = service.is_auto_remember_enabled() {
|
||||
if let Some(parent_dir) = std::path::Path::new(&file_path).parent() {
|
||||
let parent_path = parent_dir.to_string_lossy().to_string();
|
||||
let _ = service.update_directory_setting(
|
||||
DirectorySettingType::JianyingExport,
|
||||
parent_path
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(Some(file_path))
|
||||
},
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
60
apps/desktop/src/components/DirectorySettingsButton.tsx
Normal file
60
apps/desktop/src/components/DirectorySettingsButton.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Settings } from 'lucide-react';
|
||||
import DirectorySettingsModal from './DirectorySettingsModal';
|
||||
|
||||
interface DirectorySettingsButtonProps {
|
||||
className?: string;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
variant?: 'primary' | 'secondary' | 'ghost';
|
||||
}
|
||||
|
||||
const DirectorySettingsButton: React.FC<DirectorySettingsButtonProps> = ({
|
||||
className = '',
|
||||
size = 'md',
|
||||
variant = 'secondary',
|
||||
}) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'px-2 py-1 text-xs',
|
||||
md: 'px-3 py-2 text-sm',
|
||||
lg: 'px-4 py-3 text-base',
|
||||
};
|
||||
|
||||
const variantClasses = {
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
||||
secondary: 'bg-gray-200 text-gray-700 hover:bg-gray-300',
|
||||
ghost: 'bg-transparent text-gray-600 hover:bg-gray-100',
|
||||
};
|
||||
|
||||
const iconSizes = {
|
||||
sm: 'w-3 h-3',
|
||||
md: 'w-4 h-4',
|
||||
lg: 'w-5 h-5',
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
className={`
|
||||
flex items-center space-x-2 rounded transition-colors
|
||||
${sizeClasses[size]}
|
||||
${variantClasses[variant]}
|
||||
${className}
|
||||
`}
|
||||
title="目录设置"
|
||||
>
|
||||
<Settings className={iconSizes[size]} />
|
||||
<span>目录设置</span>
|
||||
</button>
|
||||
|
||||
<DirectorySettingsModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DirectorySettingsButton;
|
||||
268
apps/desktop/src/components/DirectorySettingsModal.tsx
Normal file
268
apps/desktop/src/components/DirectorySettingsModal.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { X, Folder, Settings, RotateCcw, Save } from 'lucide-react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
interface DirectorySettings {
|
||||
material_import_directory: string | null;
|
||||
template_import_directory: string | null;
|
||||
jianying_export_directory: string | null;
|
||||
project_export_directory: string | null;
|
||||
thumbnail_export_directory: string | null;
|
||||
auto_remember_directories: boolean;
|
||||
}
|
||||
|
||||
interface DirectorySettingsModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const DirectorySettingsModal: React.FC<DirectorySettingsModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
}) => {
|
||||
const [settings, setSettings] = useState<DirectorySettings>({
|
||||
material_import_directory: null,
|
||||
template_import_directory: null,
|
||||
jianying_export_directory: null,
|
||||
project_export_directory: null,
|
||||
thumbnail_export_directory: null,
|
||||
auto_remember_directories: true,
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
// 加载设置
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
loadSettings();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const loadSettings = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await invoke<DirectorySettings>('get_directory_settings');
|
||||
setSettings(result);
|
||||
} catch (error) {
|
||||
console.error('加载目录设置失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 选择目录
|
||||
const selectDirectory = async (settingType: string, displayName: string) => {
|
||||
try {
|
||||
const result = await invoke<string | null>('select_and_save_directory', {
|
||||
settingType,
|
||||
title: `选择${displayName}`,
|
||||
});
|
||||
|
||||
if (result) {
|
||||
setSettings(prev => ({
|
||||
...prev,
|
||||
[`${settingType}_directory`]: result,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`选择${displayName}失败:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
// 清除目录设置
|
||||
const clearDirectory = async (settingType: string) => {
|
||||
try {
|
||||
await invoke('clear_directory_setting', { settingType });
|
||||
setSettings(prev => ({
|
||||
...prev,
|
||||
[`${settingType}_directory`]: null,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('清除目录设置失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 重置所有设置
|
||||
const resetAllSettings = async () => {
|
||||
if (!confirm('确定要重置所有目录设置吗?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke('reset_all_directory_settings');
|
||||
await loadSettings();
|
||||
} catch (error) {
|
||||
console.error('重置设置失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 保存自动记忆设置
|
||||
const saveAutoRememberSetting = async (enabled: boolean) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
await invoke('set_auto_remember_directories', { enabled });
|
||||
setSettings(prev => ({
|
||||
...prev,
|
||||
auto_remember_directories: enabled,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('保存自动记忆设置失败:', error);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 目录设置项配置
|
||||
const directoryItems = [
|
||||
{
|
||||
key: 'material_import',
|
||||
label: '素材导入目录',
|
||||
description: '选择素材文件时的默认目录',
|
||||
value: settings.material_import_directory,
|
||||
},
|
||||
{
|
||||
key: 'template_import',
|
||||
label: '模板导入目录',
|
||||
description: '选择模板文件时的默认目录',
|
||||
value: settings.template_import_directory,
|
||||
},
|
||||
{
|
||||
key: 'jianying_export',
|
||||
label: '剪影导出目录',
|
||||
description: '导出剪影项目时的默认目录',
|
||||
value: settings.jianying_export_directory,
|
||||
},
|
||||
{
|
||||
key: 'project_export',
|
||||
label: '项目导出目录',
|
||||
description: '导出项目文件时的默认目录',
|
||||
value: settings.project_export_directory,
|
||||
},
|
||||
{
|
||||
key: 'thumbnail_export',
|
||||
label: '缩略图导出目录',
|
||||
description: '导出缩略图时的默认目录',
|
||||
value: settings.thumbnail_export_directory,
|
||||
},
|
||||
];
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] overflow-hidden">
|
||||
{/* 标题栏 */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Settings className="w-5 h-5 text-blue-600" />
|
||||
<h2 className="text-xl font-semibold text-gray-900">目录设置</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 内容区域 */}
|
||||
<div className="p-6 overflow-y-auto max-h-[60vh]">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<span className="ml-2 text-gray-600">加载设置中...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* 自动记忆设置 */}
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-blue-900">自动记忆目录</h3>
|
||||
<p className="text-sm text-blue-700 mt-1">
|
||||
启用后,选择文件时会自动记住所选目录作为下次的默认位置
|
||||
</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.auto_remember_directories}
|
||||
onChange={(e) => saveAutoRememberSetting(e.target.checked)}
|
||||
disabled={saving}
|
||||
className="sr-only"
|
||||
/>
|
||||
<div className={`w-11 h-6 bg-gray-200 rounded-full peer ${
|
||||
settings.auto_remember_directories ? 'bg-blue-600' : ''
|
||||
} ${saving ? 'opacity-50' : ''}`}>
|
||||
<div className={`absolute top-[2px] left-[2px] bg-white border border-gray-300 rounded-full h-5 w-5 transition-transform ${
|
||||
settings.auto_remember_directories ? 'translate-x-5' : ''
|
||||
}`}></div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 目录设置列表 */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">默认目录设置</h3>
|
||||
{directoryItems.map((item) => (
|
||||
<div key={item.key} className="border border-gray-200 rounded-lg p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-medium text-gray-900">{item.label}</h4>
|
||||
<p className="text-sm text-gray-500 mt-1">{item.description}</p>
|
||||
{item.value && (
|
||||
<div className="mt-2 p-2 bg-gray-50 rounded text-sm text-gray-700 font-mono break-all">
|
||||
{item.value}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 ml-4">
|
||||
<button
|
||||
onClick={() => selectDirectory(item.key, item.label)}
|
||||
className="flex items-center space-x-1 px-3 py-1.5 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<Folder className="w-4 h-4" />
|
||||
<span>{item.value ? '更改' : '选择'}</span>
|
||||
</button>
|
||||
{item.value && (
|
||||
<button
|
||||
onClick={() => clearDirectory(item.key)}
|
||||
className="px-3 py-1.5 text-sm bg-gray-200 text-gray-700 rounded hover:bg-gray-300 transition-colors"
|
||||
>
|
||||
清除
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 底部操作栏 */}
|
||||
<div className="flex items-center justify-between p-6 border-t border-gray-200 bg-gray-50">
|
||||
<button
|
||||
onClick={resetAllSettings}
|
||||
className="flex items-center space-x-2 px-4 py-2 text-sm bg-red-600 text-white rounded hover:bg-red-700 transition-colors"
|
||||
>
|
||||
<RotateCcw className="w-4 h-4" />
|
||||
<span>重置所有设置</span>
|
||||
</button>
|
||||
<div className="flex items-center space-x-3">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm bg-gray-200 text-gray-700 rounded hover:bg-gray-300 transition-colors"
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DirectorySettingsModal;
|
||||
114
apps/desktop/src/components/QuickDirectorySelector.tsx
Normal file
114
apps/desktop/src/components/QuickDirectorySelector.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Folder, FolderOpen, Settings } from 'lucide-react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
interface QuickDirectorySelectorProps {
|
||||
settingType: 'material_import' | 'template_import' | 'jianying_export' | 'project_export' | 'thumbnail_export';
|
||||
label: string;
|
||||
onDirectorySelected?: (directory: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const QuickDirectorySelector: React.FC<QuickDirectorySelectorProps> = ({
|
||||
settingType,
|
||||
label,
|
||||
onDirectorySelected,
|
||||
className = '',
|
||||
}) => {
|
||||
const [currentDirectory, setCurrentDirectory] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 加载当前设置的目录
|
||||
useEffect(() => {
|
||||
loadCurrentDirectory();
|
||||
}, [settingType]);
|
||||
|
||||
const loadCurrentDirectory = async () => {
|
||||
try {
|
||||
const directory = await invoke<string | null>('get_directory_setting', {
|
||||
settingType,
|
||||
});
|
||||
setCurrentDirectory(directory);
|
||||
} catch (error) {
|
||||
console.error('加载目录设置失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 选择目录
|
||||
const selectDirectory = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await invoke<string | null>('select_and_save_directory', {
|
||||
settingType,
|
||||
title: `选择${label}`,
|
||||
});
|
||||
|
||||
if (result) {
|
||||
setCurrentDirectory(result);
|
||||
onDirectorySelected?.(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('选择目录失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 使用当前目录
|
||||
const useCurrentDirectory = () => {
|
||||
if (currentDirectory) {
|
||||
onDirectorySelected?.(currentDirectory);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`flex items-center space-x-2 ${className}`}>
|
||||
{/* 当前目录显示 */}
|
||||
{currentDirectory && (
|
||||
<div className="flex items-center space-x-2 flex-1 min-w-0">
|
||||
<button
|
||||
onClick={useCurrentDirectory}
|
||||
className="flex items-center space-x-2 px-3 py-2 bg-green-50 text-green-700 rounded border border-green-200 hover:bg-green-100 transition-colors flex-1 min-w-0"
|
||||
title={`使用默认目录: ${currentDirectory}`}
|
||||
>
|
||||
<FolderOpen className="w-4 h-4 flex-shrink-0" />
|
||||
<span className="text-sm truncate">
|
||||
{currentDirectory.split(/[/\\]/).pop() || currentDirectory}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 选择目录按钮 */}
|
||||
<button
|
||||
onClick={selectDirectory}
|
||||
disabled={loading}
|
||||
className="flex items-center space-x-2 px-3 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
title={currentDirectory ? `更改${label}` : `选择${label}`}
|
||||
>
|
||||
{loading ? (
|
||||
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
) : (
|
||||
<Folder className="w-4 h-4" />
|
||||
)}
|
||||
<span className="text-sm whitespace-nowrap">
|
||||
{currentDirectory ? '更改' : '选择'}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* 设置按钮 */}
|
||||
<button
|
||||
onClick={() => {
|
||||
// 这里可以打开目录设置对话框或者其他设置操作
|
||||
console.log('打开目录设置');
|
||||
}}
|
||||
className="p-2 text-gray-400 hover:text-gray-600 transition-colors"
|
||||
title="目录设置"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuickDirectorySelector;
|
||||
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
|
||||
import { X, Upload, AlertCircle, CheckCircle } from 'lucide-react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { ImportTemplateRequest } from '../../types/template';
|
||||
import DirectorySettingsButton from '../DirectorySettingsButton';
|
||||
|
||||
interface ImportTemplateModalProps {
|
||||
onClose: () => void;
|
||||
@@ -28,10 +29,8 @@ export const ImportTemplateModal: React.FC<ImportTemplateModalProps> = ({
|
||||
// 处理文件选择
|
||||
const handleFileSelect = async () => {
|
||||
try {
|
||||
// 使用 Tauri 命令选择文件
|
||||
const selected = await invoke<string | null>('select_file', {
|
||||
filters: [['剪映草稿文件', ['json']]]
|
||||
});
|
||||
// 使用新的模板文件选择命令,支持默认目录
|
||||
const selected = await invoke<string | null>('select_template_file');
|
||||
|
||||
if (selected) {
|
||||
setSelectedFile(selected);
|
||||
@@ -174,6 +173,11 @@ export const ImportTemplateModal: React.FC<ImportTemplateModalProps> = ({
|
||||
{errors.file_path}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 目录设置按钮 */}
|
||||
<div className="mt-3 flex justify-end">
|
||||
<DirectorySettingsButton size="sm" variant="ghost" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 模板名称 */}
|
||||
|
||||
Reference in New Issue
Block a user