fix: 统一按钮样式

This commit is contained in:
imeepos
2025-07-14 22:12:39 +08:00
parent 2d88274c3a
commit 49c5b1a033
6 changed files with 142 additions and 136 deletions

View File

@@ -7,6 +7,7 @@ import { ProjectCard } from './ProjectCard';
import { EmptyState } from './EmptyState';
import { LoadingSpinner } from './LoadingSpinner';
import { ErrorMessage } from './ErrorMessage';
import { DeleteConfirmDialog } from './DeleteConfirmDialog';
import { Plus, Trash2 } from 'lucide-react';
/**
@@ -31,6 +32,9 @@ export const ProjectList: React.FC = () => {
} = useUIStore();
const [isCleaningUp, setIsCleaningUp] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [projectToDelete, setProjectToDelete] = useState<string | null>(null);
const [deleting, setDeleting] = useState(false);
// 清理无效项目记录
const handleCleanupInvalidProjects = async () => {
@@ -66,16 +70,33 @@ export const ProjectList: React.FC = () => {
};
// 处理项目删除
const handleProjectDelete = async (id: string) => {
if (window.confirm('确定要删除这个项目吗?此操作不可撤销。')) {
try {
await deleteProject(id);
} catch (error) {
console.error('删除项目失败:', error);
}
const handleProjectDelete = (id: string) => {
setProjectToDelete(id);
setShowDeleteConfirm(true);
};
// 确认删除项目
const confirmDeleteProject = async () => {
if (!projectToDelete) return;
try {
setDeleting(true);
await deleteProject(projectToDelete);
setShowDeleteConfirm(false);
setProjectToDelete(null);
} catch (error) {
console.error('删除项目失败:', error);
} finally {
setDeleting(false);
}
};
// 取消删除
const cancelDeleteProject = () => {
setShowDeleteConfirm(false);
setProjectToDelete(null);
};
// 错误状态
if (error) {
return (
@@ -101,7 +122,7 @@ export const ProjectList: React.FC = () => {
{isCleaningUp ? '清理中...' : '清理'}
</button>
<button
className="btn btn-primary"
className="flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-primary-500 to-primary-600 text-white rounded-lg hover:from-primary-600 hover:to-primary-700 transition-all duration-200 shadow-sm hover:shadow-md text-sm font-medium"
onClick={openCreateProjectModal}
>
<Plus size={20} />
@@ -146,7 +167,7 @@ export const ProjectList: React.FC = () => {
{isCleaningUp ? '清理中...' : '清理'}
</button>
<button
className="btn btn-primary"
className="flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-primary-500 to-primary-600 text-white rounded-lg hover:from-primary-600 hover:to-primary-700 transition-all duration-200 shadow-sm hover:shadow-md text-sm font-medium"
onClick={openCreateProjectModal}
disabled
>
@@ -189,7 +210,7 @@ export const ProjectList: React.FC = () => {
{isCleaningUp ? '清理中...' : '清理'}
</button>
<button
className="btn btn-primary shadow-glow"
className="flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-primary-500 to-primary-600 text-white rounded-lg hover:from-primary-600 hover:to-primary-700 transition-all duration-200 shadow-sm hover:shadow-md text-sm font-medium"
onClick={openCreateProjectModal}
disabled={isLoading}
>
@@ -264,6 +285,15 @@ export const ProjectList: React.FC = () => {
</div>
</div>
)}
<DeleteConfirmDialog
isOpen={showDeleteConfirm}
title="删除项目"
message="确定要删除这个项目吗?此操作不可撤销。"
deleting={deleting}
onConfirm={confirmDeleteProject}
onCancel={cancelDeleteProject}
/>
</div>
);
};

View File

@@ -2,7 +2,6 @@ import React, { useState } from 'react';
import { X, FolderOpen, AlertCircle, CheckCircle, Settings } from 'lucide-react';
import { BatchImportRequest } from '../../types/template';
import { CustomSelect } from '../CustomSelect';
import { useProjectStore } from '../../store/projectStore';
import { invoke } from '@tauri-apps/api/core';
interface BatchImportModalProps {
@@ -26,8 +25,6 @@ export const BatchImportModal: React.FC<BatchImportModalProps> = ({
const [errors, setErrors] = useState<Record<string, string>>({});
const [showAdvanced, setShowAdvanced] = useState(false);
const { projects } = useProjectStore();
// 处理文件夹选择
const handleFolderSelect = async () => {
try {
@@ -77,13 +74,6 @@ export const BatchImportModal: React.FC<BatchImportModalProps> = ({
}
};
const projectOptions = [
{ value: '', label: '不关联项目' },
...projects.map(project => ({
value: project.id,
label: project.name,
})),
];
const concurrentOptions = [
{ value: `1`, label: '1 个(慢速)' },
@@ -164,20 +154,6 @@ export const BatchImportModal: React.FC<BatchImportModalProps> = ({
)}
</div>
{/* 关联项目 */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<CustomSelect
value={formData.project_id || ''}
onChange={(value) => setFormData(prev => ({ ...prev, project_id: value }))}
options={projectOptions}
placeholder="选择项目(可选)"
className="w-full"
/>
</div>
{/* 自动上传选项 */}
<div className="mb-6">
<label className="flex items-center">

View File

@@ -16,6 +16,7 @@ export const ImportProgressModal: React.FC<ImportProgressModalProps> = ({
}) => {
const [progress, setProgress] = useState<ImportProgress | null>(null);
const [lastValidProgress, setLastValidProgress] = useState<ImportProgress | null>(null);
const [hasCompleted, setHasCompleted] = useState(false);
const { getImportProgress } = useTemplateStore();
useEffect(() => {
@@ -35,10 +36,17 @@ export const ImportProgressModal: React.FC<ImportProgressModalProps> = ({
setLastValidProgress(progressData);
}
// 如果进度数据为空(已被清除),说明导入完成,停止轮询
if (!progressData) {
// 使用最后一次有效的进度数据,但更新状态为完成
if (lastValidProgress) {
// 检查是否导入完成
const isCompleted = !progressData ||
progressData.status === ImportStatus.Completed ||
progressData.status === ImportStatus.Failed;
if (isCompleted && !hasCompleted) {
// 标记为已完成,避免重复处理
setHasCompleted(true);
// 如果进度数据为空但有最后有效进度,使用它
if (!progressData && lastValidProgress) {
const completedProgress: ImportProgress = {
...lastValidProgress,
status: ImportStatus.Completed,
@@ -54,36 +62,20 @@ export const ImportProgressModal: React.FC<ImportProgressModalProps> = ({
interval = null;
}
// 触发完成回调
if (onComplete) {
// 立即触发完成回调,然后延迟关闭弹窗
if (onComplete && (progressData?.status === ImportStatus.Completed || !progressData)) {
// 立即刷新列表
onComplete();
// 延迟关闭弹窗,让用户看到完成状态
setTimeout(() => {
if (isMounted) {
onComplete();
onClose();
}
}, 2000); // 2秒后自动关闭
}, 2000);
}
return;
}
// 如果进度状态为完成或失败,也停止轮询
if (progressData.status === ImportStatus.Completed ||
progressData.status === ImportStatus.Failed) {
// 清除轮询
if (interval) {
clearInterval(interval);
interval = null;
}
if (progressData.status === ImportStatus.Completed && onComplete) {
setTimeout(() => {
if (isMounted) {
onComplete();
}
}, 2000); // 2秒后自动关闭
}
return; // 重要:完成后直接返回,不再继续轮询
}
} catch (error) {
console.error('获取导入进度失败:', error);
// 如果出错,也停止轮询

View File

@@ -1,9 +1,7 @@
import React, { useState } from 'react';
import { X, Upload, File, AlertCircle, CheckCircle } from 'lucide-react';
import { X, Upload, AlertCircle, CheckCircle } from 'lucide-react';
import { invoke } from '@tauri-apps/api/core';
import { ImportTemplateRequest } from '../../types/template';
import { CustomSelect } from '../CustomSelect';
import { useProjectStore } from '../../store/projectStore';
interface ImportTemplateModalProps {
onClose: () => void;
@@ -26,7 +24,6 @@ export const ImportTemplateModal: React.FC<ImportTemplateModalProps> = ({
const [selectedFile, setSelectedFile] = useState<string>('');
const [errors, setErrors] = useState<Record<string, string>>({});
const { projects } = useProjectStore();
// 处理文件选择
const handleFileSelect = async () => {
@@ -114,14 +111,6 @@ export const ImportTemplateModal: React.FC<ImportTemplateModalProps> = ({
}
};
const projectOptions = [
{ value: '', label: '不关联项目' },
...projects.map(project => ({
value: project.id,
label: project.name,
})),
];
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-md mx-4">
@@ -155,6 +144,7 @@ export const ImportTemplateModal: React.FC<ImportTemplateModalProps> = ({
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={handleFileSelect}
>
{selectedFile ? (
<div className="flex items-center justify-center">
@@ -179,15 +169,6 @@ export const ImportTemplateModal: React.FC<ImportTemplateModalProps> = ({
</div>
</>
)}
<button
type="button"
onClick={handleFileSelect}
className="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-colors"
>
<File className="w-4 h-4 mr-2" />
</button>
</div>
{errors.file_path && (
@@ -220,20 +201,6 @@ export const ImportTemplateModal: React.FC<ImportTemplateModalProps> = ({
)}
</div>
{/* 关联项目 */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<CustomSelect
value={formData.project_id || ''}
onChange={(value) => setFormData(prev => ({ ...prev, project_id: value }))}
options={projectOptions}
placeholder="选择项目(可选)"
className="w-full"
/>
</div>
{/* 自动上传选项 */}
<div className="mb-6">
<label className="flex items-center">

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import { Activity, Database, Clock, TrendingUp, RefreshCw, Trash2 } from 'lucide-react';
import { invoke } from '@tauri-apps/api/core';
import { DeleteConfirmDialog } from '../DeleteConfirmDialog';
interface PerformanceStats {
template_import?: {
@@ -42,6 +43,8 @@ export const PerformanceMonitor: React.FC<PerformanceMonitorProps> = ({ onClose
const [performanceStats, setPerformanceStats] = useState<PerformanceStats>({});
const [cacheStats, setCacheStats] = useState<CacheStats | null>(null);
const [loading, setLoading] = useState(true);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [deleting, setDeleting] = useState(false);
const [refreshing, setRefreshing] = useState(false);
// 加载性能数据
@@ -66,17 +69,29 @@ export const PerformanceMonitor: React.FC<PerformanceMonitorProps> = ({ onClose
};
// 清理性能数据
const handleCleanupData = async () => {
if (window.confirm('确定要清理7天前的性能数据吗')) {
try {
await invoke('cleanup_template_performance_data', { olderThanHours: 24 * 7 });
await loadPerformanceData();
} catch (error) {
console.error('清理性能数据失败:', error);
}
const handleCleanupData = () => {
setShowDeleteConfirm(true);
};
// 确认清理性能数据
const confirmCleanupData = async () => {
try {
setDeleting(true);
await invoke('cleanup_template_performance_data', { olderThanHours: 24 * 7 });
await loadPerformanceData();
setShowDeleteConfirm(false);
} catch (error) {
console.error('清理性能数据失败:', error);
} finally {
setDeleting(false);
}
};
// 取消清理
const cancelCleanupData = () => {
setShowDeleteConfirm(false);
};
// 预热缓存
const handleWarmCache = async () => {
try {
@@ -346,6 +361,15 @@ export const PerformanceMonitor: React.FC<PerformanceMonitorProps> = ({ onClose
)}
</div>
</div>
<DeleteConfirmDialog
isOpen={showDeleteConfirm}
title="清理性能数据"
message="确定要清理7天前的性能数据吗此操作不可撤销。"
deleting={deleting}
onConfirm={confirmCleanupData}
onCancel={cancelCleanupData}
/>
</div>
);
};

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Plus, Upload, Search, Filter, FileText, Clock, CheckCircle, XCircle, AlertCircle, Activity } from 'lucide-react';
import { Plus, Upload, Search, Filter, FileText, Clock, CheckCircle, XCircle, AlertCircle } from 'lucide-react';
import { Template, ImportStatus } from '../types/template';
import { TemplateCard } from '../components/template/TemplateCard';
import { ImportTemplateModal } from '../components/template/ImportTemplateModal';
@@ -7,8 +7,8 @@ import { BatchImportModal } from '../components/template/BatchImportModal';
import { TemplateDetailModal } from '../components/template/TemplateDetailModal';
import { ImportProgressModal } from '../components/template/ImportProgressModal';
import { BatchImportProgressModal } from '../components/template/BatchImportProgressModal';
import { PerformanceMonitor } from '../components/template/PerformanceMonitor';
import { CustomSelect } from '../components/CustomSelect';
import { DeleteConfirmDialog } from '../components/DeleteConfirmDialog';
import { useTemplateStore } from '../stores/templateStore';
const TemplateManagement: React.FC = () => {
@@ -22,9 +22,11 @@ const TemplateManagement: React.FC = () => {
const [showImportProgress, setShowImportProgress] = useState(false);
const [showBatchProgress, setShowBatchProgress] = useState(false);
const [currentImportId, setCurrentImportId] = useState<string | null>(null);
const [showPerformanceMonitor, setShowPerformanceMonitor] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [templateToDelete, setTemplateToDelete] = useState<string | null>(null);
const [deleting, setDeleting] = useState(false);
const pageSize = 12;
const {
@@ -97,9 +99,9 @@ const TemplateManagement: React.FC = () => {
// 处理导入完成
const handleImportComplete = () => {
setShowImportProgress(false);
setCurrentImportId(null);
// 立即刷新模板列表,显示新导入的模板
loadTemplates();
// 注意:不在这里关闭弹窗,弹窗会自动关闭
};
// 处理批量导入完成
@@ -109,17 +111,34 @@ const TemplateManagement: React.FC = () => {
};
// 处理删除模板
const handleDeleteTemplate = async (templateId: string) => {
if (window.confirm('确定要删除这个模板吗?')) {
try {
await deleteTemplate(templateId);
loadTemplates();
} catch (error) {
console.error('删除模板失败:', error);
}
const handleDeleteTemplate = (templateId: string) => {
setTemplateToDelete(templateId);
setShowDeleteConfirm(true);
};
// 确认删除模板
const confirmDeleteTemplate = async () => {
if (!templateToDelete) return;
try {
setDeleting(true);
await deleteTemplate(templateToDelete);
loadTemplates();
setShowDeleteConfirm(false);
setTemplateToDelete(null);
} catch (error) {
console.error('删除模板失败:', error);
} finally {
setDeleting(false);
}
};
// 取消删除
const cancelDeleteTemplate = () => {
setShowDeleteConfirm(false);
setTemplateToDelete(null);
};
// 获取状态图标
const getStatusIcon = (status: ImportStatus) => {
switch (status) {
@@ -178,7 +197,7 @@ const TemplateManagement: React.FC = () => {
<div className="flex items-center space-x-3">
<button
onClick={() => setShowImportModal(true)}
className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
className="flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-primary-500 to-primary-600 text-white rounded-lg hover:from-primary-600 hover:to-primary-700 transition-all duration-200 shadow-sm hover:shadow-md text-sm font-medium"
>
<Plus className="w-4 h-4 mr-2" />
@@ -186,19 +205,11 @@ const TemplateManagement: React.FC = () => {
<button
onClick={() => setShowBatchImportModal(true)}
className="flex items-center px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
className="inline-flex items-center px-3 sm:px-4 py-2 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-lg hover:from-purple-600 hover:to-pink-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-sm hover:shadow-md text-sm"
>
<Upload className="w-4 h-4 mr-2" />
</button>
<button
onClick={() => setShowPerformanceMonitor(true)}
className="flex items-center px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
>
<Activity className="w-4 h-4 mr-2" />
</button>
</div>
</div>
@@ -211,7 +222,7 @@ const TemplateManagement: React.FC = () => {
placeholder="搜索模板名称..."
value={searchKeyword}
onChange={(e) => handleSearch(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
className="w-full pl-10 pr-3 py-2 border border-gray-200 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200 placeholder-gray-400 text-sm text-gray-900 hover:border-gray-300"
/>
</div>
@@ -241,7 +252,7 @@ const TemplateManagement: React.FC = () => {
<p className="text-gray-500 mb-6"></p>
<button
onClick={() => setShowImportModal(true)}
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
className="inline-flex items-center items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-primary-500 to-primary-600 text-white rounded-lg hover:from-primary-600 hover:to-primary-700 transition-all duration-200 shadow-sm hover:shadow-md text-sm font-medium"
>
<Plus className="w-4 h-4 mr-2" />
@@ -317,7 +328,10 @@ const TemplateManagement: React.FC = () => {
{showImportProgress && currentImportId && (
<ImportProgressModal
templateId={currentImportId}
onClose={() => setShowImportProgress(false)}
onClose={() => {
setShowImportProgress(false);
setCurrentImportId(null);
}}
onComplete={handleImportComplete}
/>
)}
@@ -329,11 +343,14 @@ const TemplateManagement: React.FC = () => {
/>
)}
{showPerformanceMonitor && (
<PerformanceMonitor
onClose={() => setShowPerformanceMonitor(false)}
/>
)}
<DeleteConfirmDialog
isOpen={showDeleteConfirm}
title="删除模板"
message="确定要删除这个模板吗?此操作不可撤销。"
deleting={deleting}
onConfirm={confirmDeleteTemplate}
onCancel={cancelDeleteTemplate}
/>
</div>
);
};