feat: 实现全局目录设置功能
- 添加目录设置模态框,支持管理所有导入导出的默认目录 - 实现自动记忆目录功能,可开启/关闭 - 优化目录设置开关的视觉反馈,增加明显的状态区别 - 使用统一的Modal组件替代自定义弹框,确保正确渲染到modal-root - 在各个导入导出界面添加目录设置按钮: * 素材导入对话框 * 模板匹配结果管理(剪影导出) * 导出记录管理 * 批量模板导入 * 项目详情页面 * 模板管理页面 - 修改相关导出功能使用新的目录选择命令 - 创建QuickDirectoryButton组件用于快速目录选择 - 改进DirectorySettingsButton的视觉效果和交互体验
This commit is contained in:
@@ -723,6 +723,9 @@ pub async fn select_material_folders(app_handle: tauri::AppHandle) -> Result<Vec
|
||||
let default_dir = service.get_directory_setting(DirectorySettingType::MaterialImport)
|
||||
.unwrap_or(None);
|
||||
|
||||
// 添加调试日志
|
||||
tracing::info!("素材导入默认目录: {:?}", default_dir);
|
||||
|
||||
// 使用带选项的目录选择功能
|
||||
match select_directory_with_options(
|
||||
app_handle,
|
||||
|
||||
@@ -34,9 +34,15 @@ pub fn select_directory_with_options(
|
||||
|
||||
// 设置默认目录
|
||||
if let Some(default_dir) = default_directory {
|
||||
tracing::info!("尝试设置默认目录: {}", default_dir);
|
||||
if std::path::Path::new(&default_dir).exists() {
|
||||
tracing::info!("默认目录存在,设置成功: {}", default_dir);
|
||||
dialog = dialog.set_directory(&default_dir);
|
||||
} else {
|
||||
tracing::warn!("默认目录不存在: {}", default_dir);
|
||||
}
|
||||
} else {
|
||||
tracing::info!("没有设置默认目录");
|
||||
}
|
||||
|
||||
// 创建一个简单的阻塞实现
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
@@ -22,9 +22,9 @@ const DirectorySettingsButton: React.FC<DirectorySettingsButtonProps> = ({
|
||||
};
|
||||
|
||||
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',
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700 shadow-sm hover:shadow-md',
|
||||
secondary: 'bg-gray-200 text-gray-700 hover:bg-gray-300 border border-gray-300 hover:border-gray-400',
|
||||
ghost: 'bg-transparent text-gray-600 hover:bg-gray-100 hover:text-gray-800',
|
||||
};
|
||||
|
||||
const iconSizes = {
|
||||
@@ -38,14 +38,15 @@ const DirectorySettingsButton: React.FC<DirectorySettingsButtonProps> = ({
|
||||
<button
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
className={`
|
||||
flex items-center space-x-2 rounded transition-colors
|
||||
flex items-center space-x-2 rounded transition-all duration-200
|
||||
hover:scale-105 active:scale-95 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
|
||||
${sizeClasses[size]}
|
||||
${variantClasses[variant]}
|
||||
${className}
|
||||
`}
|
||||
title="目录设置"
|
||||
title="目录设置 - 管理导入导出的默认目录"
|
||||
>
|
||||
<Settings className={iconSizes[size]} />
|
||||
<Settings className={`${iconSizes[size]} transition-transform duration-200 group-hover:rotate-90`} />
|
||||
<span>目录设置</span>
|
||||
</button>
|
||||
|
||||
|
||||
144
apps/desktop/src/components/DirectorySettingsDemo.tsx
Normal file
144
apps/desktop/src/components/DirectorySettingsDemo.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import React, { useState } from 'react';
|
||||
import DirectorySettingsButton from './DirectorySettingsButton';
|
||||
import QuickDirectoryButton from './QuickDirectoryButton';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
const DirectorySettingsDemo: React.FC = () => {
|
||||
const [selectedDirectory, setSelectedDirectory] = useState<string>('');
|
||||
const [autoRememberStatus, setAutoRememberStatus] = useState<boolean | null>(null);
|
||||
|
||||
const checkAutoRememberStatus = async () => {
|
||||
try {
|
||||
const status = await invoke<boolean>('is_auto_remember_enabled');
|
||||
setAutoRememberStatus(status);
|
||||
} catch (error) {
|
||||
console.error('检查自动记忆状态失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDirectorySelected = (directory: string) => {
|
||||
setSelectedDirectory(directory);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
checkAutoRememberStatus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-4xl mx-auto">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">目录设置功能演示</h1>
|
||||
|
||||
{/* 状态显示 */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6">
|
||||
<h2 className="text-lg font-semibold text-gray-800 mb-3">当前状态</h2>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-600">自动记忆功能:</span>
|
||||
<span className={`text-sm font-medium px-2 py-1 rounded ${
|
||||
autoRememberStatus === true
|
||||
? 'bg-green-100 text-green-800'
|
||||
: autoRememberStatus === false
|
||||
? 'bg-red-100 text-red-800'
|
||||
: 'bg-gray-100 text-gray-600'
|
||||
}`}>
|
||||
{autoRememberStatus === true ? '已启用' : autoRememberStatus === false ? '已关闭' : '检查中...'}
|
||||
</span>
|
||||
<button
|
||||
onClick={checkAutoRememberStatus}
|
||||
className="text-xs text-blue-600 hover:text-blue-800 underline"
|
||||
>
|
||||
刷新状态
|
||||
</button>
|
||||
</div>
|
||||
{selectedDirectory && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-600">最后选择的目录:</span>
|
||||
<span className="text-sm font-mono bg-gray-100 px-2 py-1 rounded">
|
||||
{selectedDirectory}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 按钮演示 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* 目录设置按钮 */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-3">目录设置按钮</h3>
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
点击打开目录设置对话框,可以管理所有导入导出的默认目录
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-sm text-gray-500 w-16">小号:</span>
|
||||
<DirectorySettingsButton size="sm" variant="primary" />
|
||||
<DirectorySettingsButton size="sm" variant="secondary" />
|
||||
<DirectorySettingsButton size="sm" variant="ghost" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-sm text-gray-500 w-16">中号:</span>
|
||||
<DirectorySettingsButton size="md" variant="primary" />
|
||||
<DirectorySettingsButton size="md" variant="secondary" />
|
||||
<DirectorySettingsButton size="md" variant="ghost" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-sm text-gray-500 w-16">大号:</span>
|
||||
<DirectorySettingsButton size="lg" variant="primary" />
|
||||
<DirectorySettingsButton size="lg" variant="secondary" />
|
||||
<DirectorySettingsButton size="lg" variant="ghost" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 快速目录选择按钮 */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-3">快速目录选择</h3>
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
直接选择特定类型的目录,会自动保存到对应的设置中
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<QuickDirectoryButton
|
||||
settingType="material_import"
|
||||
onDirectorySelected={handleDirectorySelected}
|
||||
size="md"
|
||||
variant="primary"
|
||||
/>
|
||||
<QuickDirectoryButton
|
||||
settingType="template_import"
|
||||
onDirectorySelected={handleDirectorySelected}
|
||||
size="md"
|
||||
variant="secondary"
|
||||
/>
|
||||
<QuickDirectoryButton
|
||||
settingType="jianying_export"
|
||||
onDirectorySelected={handleDirectorySelected}
|
||||
size="md"
|
||||
variant="ghost"
|
||||
/>
|
||||
<QuickDirectoryButton
|
||||
settingType="project_export"
|
||||
onDirectorySelected={handleDirectorySelected}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
showLabel={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 使用说明 */}
|
||||
<div className="bg-blue-50 rounded-lg border border-blue-200 p-4 mt-6">
|
||||
<h3 className="text-lg font-semibold text-blue-900 mb-2">使用说明</h3>
|
||||
<ul className="text-sm text-blue-800 space-y-1">
|
||||
<li>• <strong>目录设置按钮</strong>:打开完整的目录设置对话框,可以管理所有类型的默认目录</li>
|
||||
<li>• <strong>快速目录选择</strong>:直接选择特定类型的目录,立即保存到设置中</li>
|
||||
<li>• <strong>自动记忆功能</strong>:启用后,每次选择文件时会自动更新对应的默认目录</li>
|
||||
<li>• <strong>视觉反馈</strong>:开关状态有明显的颜色和图标区别,便于识别当前状态</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DirectorySettingsDemo;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { X, Folder, Settings, RotateCcw, Save } from 'lucide-react';
|
||||
import { Folder, Settings, RotateCcw } from 'lucide-react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { Modal } from './Modal';
|
||||
|
||||
interface DirectorySettings {
|
||||
material_import_directory: string | null;
|
||||
@@ -146,104 +147,131 @@ const DirectorySettingsModal: React.FC<DirectorySettingsModalProps> = ({
|
||||
},
|
||||
];
|
||||
|
||||
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>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title="目录设置"
|
||||
subtitle="管理导入导出的默认目录"
|
||||
icon={<Settings className="w-6 h-6 text-white" />}
|
||||
size="lg"
|
||||
variant="default"
|
||||
closeOnBackdropClick={true}
|
||||
closeOnEscape={true}
|
||||
>
|
||||
<div className="p-6">
|
||||
{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>
|
||||
<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 className="space-y-6">
|
||||
{/* 自动记忆设置 */}
|
||||
<div className={`p-4 rounded-lg border-2 transition-all duration-200 ${settings.auto_remember_directories
|
||||
? 'bg-blue-50 border-blue-200'
|
||||
: 'bg-gray-50 border-gray-200'
|
||||
}`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className={`text-sm font-medium ${settings.auto_remember_directories ? 'text-blue-900' : 'text-gray-700'
|
||||
}`}>
|
||||
自动记忆目录
|
||||
</h3>
|
||||
<p className={`text-sm mt-1 ${settings.auto_remember_directories ? 'text-blue-700' : 'text-gray-600'
|
||||
}`}>
|
||||
启用后,选择文件时会自动记住所选目录作为下次的默认位置
|
||||
</p>
|
||||
</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>
|
||||
<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={`relative w-11 h-6 rounded-full transition-colors duration-200 ease-in-out ${settings.auto_remember_directories
|
||||
? 'bg-blue-600'
|
||||
: 'bg-gray-300'
|
||||
} ${saving ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}>
|
||||
<div className={`absolute top-0.5 left-0.5 bg-white rounded-full h-5 w-5 transition-transform duration-200 ease-in-out shadow-sm ${settings.auto_remember_directories ? 'translate-x-5' : 'translate-x-0'
|
||||
}`}>
|
||||
{/* 添加图标指示器 */}
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
{settings.auto_remember_directories ? (
|
||||
<svg className="w-3 h-3 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-3 h-3 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* 状态文字 */}
|
||||
<span className={`ml-3 text-sm font-medium ${settings.auto_remember_directories ? 'text-blue-600' : 'text-gray-500'
|
||||
}`}>
|
||||
{settings.auto_remember_directories ? '已启用' : '已关闭'}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* 额外的状态说明 */}
|
||||
<div className={`mt-3 p-2 rounded text-xs ${settings.auto_remember_directories
|
||||
? 'bg-blue-100 text-blue-800'
|
||||
: 'bg-gray-100 text-gray-600'
|
||||
}`}>
|
||||
{settings.auto_remember_directories
|
||||
? '✓ 系统将自动记住您选择的目录路径'
|
||||
: '○ 每次都需要手动选择目录路径'
|
||||
}
|
||||
</div>
|
||||
</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 className="flex items-center justify-between p-6 border-t border-gray-200 bg-gray-50">
|
||||
<div className="flex items-center justify-between pt-6 border-t border-gray-200">
|
||||
<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"
|
||||
@@ -261,7 +289,7 @@ const DirectorySettingsModal: React.FC<DirectorySettingsModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
FileJson,
|
||||
Film
|
||||
} from 'lucide-react';
|
||||
import DirectorySettingsButton from './DirectorySettingsButton';
|
||||
|
||||
interface ExportRecordManagerProps {
|
||||
projectId?: string;
|
||||
@@ -143,22 +144,9 @@ const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
||||
// 重新导出
|
||||
const handleReExport = async (recordId: string) => {
|
||||
try {
|
||||
// 打开文件选择对话框
|
||||
const { open } = await import('@tauri-apps/plugin-dialog');
|
||||
const selected = await open({
|
||||
title: '选择导出路径',
|
||||
directory: false,
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: '剪映项目',
|
||||
extensions: ['json']
|
||||
},
|
||||
{
|
||||
name: '所有文件',
|
||||
extensions: ['*']
|
||||
}
|
||||
]
|
||||
// 使用新的剪影导出路径选择命令
|
||||
const selected = await invoke<string | null>('select_jianying_export_path', {
|
||||
defaultFilename: 'draft_content_reexport.json'
|
||||
});
|
||||
|
||||
if (selected) {
|
||||
@@ -545,6 +533,7 @@ const ExportRecordManager: React.FC<ExportRecordManagerProps> = ({
|
||||
>
|
||||
清理过期
|
||||
</InteractiveButton>
|
||||
<DirectorySettingsButton size="sm" variant="secondary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CreateMaterialRequest, MaterialImportResult } from '../types/material';
|
||||
import { Model } from '../types/model';
|
||||
import { MaterialModelBindingService } from '../services/materialModelBindingService';
|
||||
import { CustomSelect } from './CustomSelect';
|
||||
import DirectorySettingsButton from './DirectorySettingsButton';
|
||||
|
||||
interface MaterialImportDialogProps {
|
||||
isOpen: boolean;
|
||||
@@ -314,6 +315,11 @@ export const MaterialImportDialog: React.FC<MaterialImportDialogProps> = ({
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 目录设置按钮 */}
|
||||
<div className="flex justify-center pt-4 border-t border-gray-200">
|
||||
<DirectorySettingsButton size="sm" variant="ghost" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
110
apps/desktop/src/components/QuickDirectoryButton.tsx
Normal file
110
apps/desktop/src/components/QuickDirectoryButton.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import { Folder, Settings } from 'lucide-react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
interface QuickDirectoryButtonProps {
|
||||
settingType: 'material_import' | 'template_import' | 'jianying_export' | 'project_export' | 'thumbnail_export';
|
||||
label?: string;
|
||||
onDirectorySelected?: (directory: string) => void;
|
||||
className?: string;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
variant?: 'primary' | 'secondary' | 'ghost';
|
||||
showLabel?: boolean;
|
||||
}
|
||||
|
||||
const QuickDirectoryButton: React.FC<QuickDirectoryButtonProps> = ({
|
||||
settingType,
|
||||
label,
|
||||
onDirectorySelected,
|
||||
className = '',
|
||||
size = 'md',
|
||||
variant = 'secondary',
|
||||
showLabel = true,
|
||||
}) => {
|
||||
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',
|
||||
};
|
||||
|
||||
const getDisplayName = () => {
|
||||
if (label) return label;
|
||||
|
||||
switch (settingType) {
|
||||
case 'material_import':
|
||||
return '素材目录';
|
||||
case 'template_import':
|
||||
return '模板目录';
|
||||
case 'jianying_export':
|
||||
return '导出目录';
|
||||
case 'project_export':
|
||||
return '项目目录';
|
||||
case 'thumbnail_export':
|
||||
return '缩略图目录';
|
||||
default:
|
||||
return '选择目录';
|
||||
}
|
||||
};
|
||||
|
||||
const getTitle = () => {
|
||||
switch (settingType) {
|
||||
case 'material_import':
|
||||
return '选择素材导入目录';
|
||||
case 'template_import':
|
||||
return '选择模板导入目录';
|
||||
case 'jianying_export':
|
||||
return '选择剪影导出目录';
|
||||
case 'project_export':
|
||||
return '选择项目导出目录';
|
||||
case 'thumbnail_export':
|
||||
return '选择缩略图导出目录';
|
||||
default:
|
||||
return '选择目录';
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = async () => {
|
||||
try {
|
||||
const result = await invoke<string | null>('select_and_save_directory', {
|
||||
settingType,
|
||||
title: getTitle(),
|
||||
});
|
||||
|
||||
if (result && onDirectorySelected) {
|
||||
onDirectorySelected(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('选择目录失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className={`
|
||||
flex items-center space-x-2 rounded transition-colors
|
||||
${sizeClasses[size]}
|
||||
${variantClasses[variant]}
|
||||
${className}
|
||||
`}
|
||||
title={getTitle()}
|
||||
>
|
||||
<Folder className={iconSizes[size]} />
|
||||
{showLabel && <span>{getDisplayName()}</span>}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuickDirectoryButton;
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { save } from '@tauri-apps/plugin-dialog';
|
||||
import {
|
||||
TemplateMatchingResult,
|
||||
TemplateMatchingResultQueryOptions,
|
||||
@@ -14,6 +13,7 @@ import { DeleteConfirmDialog } from './DeleteConfirmDialog';
|
||||
import { CustomSelect } from './CustomSelect';
|
||||
import { TemplateMatchingResultCard } from './TemplateMatchingResultCard';
|
||||
import { TemplateMatchingResultDetailModal } from './TemplateMatchingResultDetailModal';
|
||||
import DirectorySettingsButton from './DirectorySettingsButton';
|
||||
import { TemplateMatchingResultStatsPanel } from './TemplateMatchingResultStatsPanel';
|
||||
import { useNotifications } from './NotificationSystem';
|
||||
|
||||
@@ -200,16 +200,9 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
|
||||
// 导出到剪映 (V1版本)
|
||||
const handleExportToJianying = async (result: TemplateMatchingResult) => {
|
||||
try {
|
||||
// 选择保存路径
|
||||
const filePath = await save({
|
||||
title: '导出到剪映 (V1)',
|
||||
defaultPath: `draft_content_v1_${result.result_name}.json`,
|
||||
filters: [
|
||||
{
|
||||
name: 'JSON文件',
|
||||
extensions: ['json']
|
||||
}
|
||||
]
|
||||
// 使用新的剪影导出路径选择命令
|
||||
const filePath = await invoke<string | null>('select_jianying_export_path', {
|
||||
defaultFilename: `draft_content_v1_${result.result_name}.json`
|
||||
});
|
||||
|
||||
if (!filePath) {
|
||||
@@ -235,16 +228,9 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
|
||||
// 导出到剪映 (V2版本)
|
||||
const handleExportToJianyingV2 = async (result: TemplateMatchingResult) => {
|
||||
try {
|
||||
// 选择保存路径
|
||||
const filePath = await save({
|
||||
title: '导出到剪映',
|
||||
defaultPath: 'draft_content.json',
|
||||
filters: [
|
||||
{
|
||||
name: 'JSON文件',
|
||||
extensions: ['json']
|
||||
}
|
||||
]
|
||||
// 使用新的剪影导出路径选择命令
|
||||
const filePath = await invoke<string | null>('select_jianying_export_path', {
|
||||
defaultFilename: `draft_content_${result.result_name}.json`
|
||||
});
|
||||
|
||||
if (!filePath) {
|
||||
@@ -429,6 +415,7 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
|
||||
>
|
||||
刷新
|
||||
</button>
|
||||
<DirectorySettingsButton size="sm" variant="secondary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { X, FolderOpen, AlertCircle, CheckCircle, Settings } from 'lucide-react'
|
||||
import { BatchImportRequest } from '../../types/template';
|
||||
import { CustomSelect } from '../CustomSelect';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import DirectorySettingsButton from '../DirectorySettingsButton';
|
||||
|
||||
interface BatchImportModalProps {
|
||||
onClose: () => void;
|
||||
@@ -28,8 +29,11 @@ export const BatchImportModal: React.FC<BatchImportModalProps> = ({
|
||||
// 处理文件夹选择
|
||||
const handleFolderSelect = async () => {
|
||||
try {
|
||||
// 使用 Tauri 的文件夹选择对话框
|
||||
const selected = await invoke<string | null>('select_directory');
|
||||
// 使用带默认目录的文件夹选择对话框
|
||||
const selected = await invoke<string | null>('select_and_save_directory', {
|
||||
settingType: 'template_import',
|
||||
title: '选择包含剪映草稿的文件夹'
|
||||
});
|
||||
if (selected) {
|
||||
setSelectedFolder(selected);
|
||||
setFormData(prev => ({
|
||||
@@ -152,6 +156,11 @@ export const BatchImportModal: React.FC<BatchImportModalProps> = ({
|
||||
{errors.folder_path}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 目录设置按钮 */}
|
||||
<div className="flex justify-end mt-3">
|
||||
<DirectorySettingsButton size="sm" variant="ghost" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 自动上传选项 */}
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
UpdateProjectTemplateBindingRequest
|
||||
} from '../types/projectTemplateBinding';
|
||||
import { MaterialMatchingService } from '../services/materialMatchingService';
|
||||
import DirectorySettingsButton from '../components/DirectorySettingsButton';
|
||||
import { MaterialMatchingResult, MaterialMatchingRequest } from '../types/materialMatching';
|
||||
import { MaterialSegmentView } from '../components/MaterialSegmentView';
|
||||
import { BatchMatchingService } from '../services/batchMatchingService';
|
||||
@@ -959,6 +960,8 @@ export const ProjectDetails: React.FC = () => {
|
||||
导入素材
|
||||
</button>
|
||||
|
||||
<DirectorySettingsButton size="md" variant="secondary" />
|
||||
|
||||
<button
|
||||
onClick={handleBatchClassification}
|
||||
disabled={classificationLoading}
|
||||
@@ -1412,6 +1415,7 @@ export const ProjectDetails: React.FC = () => {
|
||||
<Upload className="w-4 h-4 mr-2" />
|
||||
导入素材
|
||||
</button>
|
||||
<DirectorySettingsButton size="sm" variant="secondary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { TemplateDetailModal } from '../components/template/TemplateDetailModal'
|
||||
import { ImportProgressModal } from '../components/template/ImportProgressModal';
|
||||
import { BatchImportProgressModal } from '../components/template/BatchImportProgressModal';
|
||||
import { CustomSelect } from '../components/CustomSelect';
|
||||
import DirectorySettingsButton from '../components/DirectorySettingsButton';
|
||||
import { DeleteConfirmDialog } from '../components/DeleteConfirmDialog';
|
||||
import { TemplateCardSkeleton } from '../components/SkeletonLoader';
|
||||
import { useTemplateStore } from '../stores/templateStore';
|
||||
@@ -219,6 +220,8 @@ const TemplateManagement: React.FC = () => {
|
||||
<Upload className="w-4 h-4" />
|
||||
批量导入
|
||||
</button>
|
||||
|
||||
<DirectorySettingsButton size="md" variant="secondary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import React from 'react';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AdvancedFilterDemo } from '../../components/outfit/AdvancedFilterDemo';
|
||||
|
||||
/**
|
||||
@@ -8,12 +6,6 @@ import { AdvancedFilterDemo } from '../../components/outfit/AdvancedFilterDemo';
|
||||
* 遵循 Tauri 开发规范和 UI/UX 设计标准
|
||||
*/
|
||||
const AdvancedFilterTool: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleGoBack = () => {
|
||||
navigate('/tools');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50">
|
||||
{/* 页面头部 */}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Play,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
Sparkles,
|
||||
ArrowLeft,
|
||||
Wand2,
|
||||
Shirt,
|
||||
Download,
|
||||
@@ -10,7 +9,6 @@ import {
|
||||
Settings,
|
||||
Info
|
||||
} from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import OutfitRecommendationService from '../../services/outfitRecommendationService';
|
||||
import { OutfitRecommendation, STYLE_OPTIONS, OCCASION_OPTIONS, SEASON_OPTIONS } from '../../types/outfitRecommendation';
|
||||
import OutfitRecommendationList from '../../components/outfit/OutfitRecommendationList';
|
||||
@@ -22,8 +20,6 @@ import { MaterialSearchPanel, MaterialDetailModal } from '../../components/mater
|
||||
* 遵循 Tauri 开发规范和 UI/UX 设计标准
|
||||
*/
|
||||
const OutfitRecommendationTool: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 状态管理
|
||||
const [query, setQuery] = useState('');
|
||||
const [targetStyle, setTargetStyle] = useState<string>('');
|
||||
|
||||
@@ -4,13 +4,11 @@ import {
|
||||
RotateCcw,
|
||||
TrendingUp,
|
||||
Zap,
|
||||
ArrowLeft,
|
||||
Loader2,
|
||||
X,
|
||||
ExternalLink,
|
||||
Copy
|
||||
} from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
useSimilaritySearchStore,
|
||||
useSimilaritySearchSelectors,
|
||||
@@ -30,8 +28,6 @@ import { SearchConfig, DEFAULT_SEARCH_CONFIG } from '../../types/outfitSearch';
|
||||
* 遵循 Tauri 开发规范和 UI/UX 设计标准
|
||||
*/
|
||||
const SimilaritySearchTool: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 本地状态
|
||||
const [selectedResult, setSelectedResult] = useState<any>(null);
|
||||
const [showDetailModal, setShowDetailModal] = useState(false);
|
||||
|
||||
Reference in New Issue
Block a user