From 18dffa826a67131121ed04db86bfbba77898424e Mon Sep 17 00:00:00 2001 From: attacker Date: Mon, 15 Jun 2026 06:04:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E7=AE=A1=E7=90=86(CronJob)=E5=85=A8=E6=A0=88=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=88=A0=E9=99=A4=E7=A1=AE=E8=AE=A4=E6=A1=86?= =?UTF-8?q?+=E4=B8=BB=E9=A2=98=E8=89=B2+=E7=A9=BA=E7=8A=B6=E6=80=81+?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E6=8C=89=E9=92=AE+toast=E5=8F=8D=E9=A6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增删除确认框(useConfirm variant:destructive),显示任务名称 - 全量主题色替换:bg-slate-800→var(--dg-bg-secondary)、border-slate-700→var(--dg-border)等 - 新增空状态(PackageOpen图标+提示文案),无任务时优雅降级 - 新增刷新按钮(双loading state:首次spinner+手动刷新spin icon) - toggle操作新增toast成功反馈 - 执行历史空状态优化(History图标+提示) - 修复Fragment key警告(<>→) - 描述为空时显示—占位符 --- .../components/automation/CronJobManager.tsx | 214 +++++++++++------- 1 file changed, 127 insertions(+), 87 deletions(-) diff --git a/frontend/src/components/automation/CronJobManager.tsx b/frontend/src/components/automation/CronJobManager.tsx index e316f4e..5ee5cc4 100644 --- a/frontend/src/components/automation/CronJobManager.tsx +++ b/frontend/src/components/automation/CronJobManager.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback, Fragment } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -41,21 +41,25 @@ import { XCircle, Loader2, Terminal, + RefreshCw, + PackageOpen, } from 'lucide-react'; import { toast } from 'sonner'; import { cronJobApi, taskExecutionApi } from '@/services/api'; +import { useConfirm } from '@/components/ui/confirm-dialog'; export function CronJobManager() { const [searchQuery, setSearchQuery] = useState(''); const [jobs, setJobs] = useState([]); - const [loading, setLoading] = useState(true); + const [initialLoading, setInitialLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); const [isFormDialogOpen, setIsFormDialogOpen] = useState(false); const [isLogDialogOpen, setIsLogDialogOpen] = useState(false); const [executions, setExecutions] = useState([]); const [executionsLoading, setExecutionsLoading] = useState(false); const [expandedExec, setExpandedExec] = useState(null); const [executingId, setExecutingId] = useState(null); - // editingId === null 表示新建模式;非 null 表示编辑已有任务 + // editingId === null 表示新建模式;非 null 表示编辑已有任务 // Logan.Li const [editingId, setEditingId] = useState(null); const [formJob, setFormJob] = useState({ name: '', @@ -65,6 +69,8 @@ export function CronJobManager() { content: '', }); + const _confirm = useConfirm(); + const openCreate = () => { setEditingId(null); setFormJob({ name: '', description: '', cron_expr: '', type: 'command', content: '' }); @@ -83,21 +89,24 @@ export function CronJobManager() { setIsFormDialogOpen(true); }; - const loadData = async () => { + // 加载任务列表(双 loading state:首次全屏 spinner,手动刷新只 spin icon) // Logan.Li + const refreshData = useCallback(async () => { + setRefreshing(true); try { - setLoading(true); const data = await cronJobApi.getJobs(); setJobs(data.list || []); } catch (error) { console.error('Failed to load cron jobs:', error); + toast.error('加载失败'); } finally { - setLoading(false); + setRefreshing(false); + setInitialLoading(false); } - }; + }, []); useEffect(() => { - loadData(); - }, []); + refreshData(); + }, [refreshData]); const loadExecutions = async () => { try { @@ -147,7 +156,7 @@ export function CronJobManager() { ); default: return ( - + 从未执行 ); @@ -186,7 +195,7 @@ export function CronJobManager() { toast.success('任务创建成功'); } setIsFormDialogOpen(false); - loadData(); + refreshData(); } catch (error) { console.error('Failed to save cron job:', error); toast.error(editingId !== null ? '更新失败' : '创建失败'); @@ -196,7 +205,8 @@ export function CronJobManager() { const handleToggleJob = async (id: any) => { try { await cronJobApi.toggleJob(id); - loadData(); + toast.success('状态已切换'); + refreshData(); } catch (error) { console.error('Failed to toggle job:', error); toast.error('操作失败'); @@ -213,7 +223,7 @@ export function CronJobManager() { } else { toast.error('执行失败', { description: (result?.output || '').slice(0, 100) }); } - loadData(); + refreshData(); } catch (error) { console.error('Failed to execute job:', error); toast.error('执行失败'); @@ -222,11 +232,18 @@ export function CronJobManager() { } }; - const handleDeleteJob = async (id: any) => { + // 删除任务(带确认框) // Logan.Li + const handleDeleteJob = async (job: any) => { + const ok = await _confirm({ + title: '删除定时任务', + description: `确定删除「${job.name}」吗?此操作不可恢复。`, + variant: 'destructive', + }); + if (!ok) return; try { - await cronJobApi.deleteJob(id); - toast.success('已删除'); - loadData(); + await cronJobApi.deleteJob(job.id); + toast.success('任务已删除'); + refreshData(); } catch (error) { console.error('Failed to delete job:', error); toast.error('删除失败'); @@ -238,14 +255,27 @@ export function CronJobManager() { {/* Header */}
-

定时任务

-

Cron 任务管理与执行

+

定时任务

+

Cron 任务管理与执行

- + @@ -401,66 +438,66 @@ export function CronJobManager() { {/* Create / Edit Job Dialog (共用) */} - + {editingId !== null ? '编辑定时任务' : '新建定时任务'} - 配置 Cron 定时任务 + 配置 Cron 定时任务
- + setFormJob({ ...formJob, name: e.target.value })} placeholder="例如: 数据库备份" - className="bg-slate-800 border-slate-700 text-slate-100 mt-1" + className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] mt-1" />
- +