feat: 实现全局目录设置功能

- 添加目录设置模态框,支持管理所有导入导出的默认目录
- 实现自动记忆目录功能,可开启/关闭
- 优化目录设置开关的视觉反馈,增加明显的状态区别
- 使用统一的Modal组件替代自定义弹框,确保正确渲染到modal-root
- 在各个导入导出界面添加目录设置按钮:
  * 素材导入对话框
  * 模板匹配结果管理(剪影导出)
  * 导出记录管理
  * 批量模板导入
  * 项目详情页面
  * 模板管理页面
- 修改相关导出功能使用新的目录选择命令
- 创建QuickDirectoryButton组件用于快速目录选择
- 改进DirectorySettingsButton的视觉效果和交互体验
This commit is contained in:
imeepos
2025-07-25 19:03:00 +08:00
parent 7c43f7a7ed
commit fab29519a7
17 changed files with 424 additions and 152 deletions

View File

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

View File

@@ -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!("没有设置默认目录");
}
// 创建一个简单的阻塞实现

View File

@@ -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';

View File

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

View 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;

View File

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

View File

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

View File

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

View 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;

View File

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

View File

@@ -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>
{/* 自动上传选项 */}

View File

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

View File

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

View File

@@ -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">
{/* 页面头部 */}

View File

@@ -1,6 +1,5 @@
import React, { useState } from 'react';
import {
ArrowLeft,
Play,
CheckCircle,
XCircle,

View File

@@ -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>('');

View File

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