diff --git a/db/schedules.go b/db/schedules.go index 0183bcb..b5e908f 100644 --- a/db/schedules.go +++ b/db/schedules.go @@ -359,9 +359,12 @@ func (d *DB) SetSchedulePaused(scheduleID, taskID int64, paused bool) error { return err } -func (d *DB) SchedulePausedTasks(taskID int64) (bool, error) { +// TaskPausedBySchedule reads the authoritative task-level pause origin. The +// scheduler writes this flag when it pauses a task, so callers must use it to +// distinguish a scheduled pause from a manual pause. +func (d *DB) TaskPausedBySchedule(taskID int64) (bool, error) { var paused bool - err := d.QueryRow(`SELECT EXISTS(SELECT 1 FROM task_schedule_tasks WHERE task_id=$1 AND paused_by_schedule)`, taskID).Scan(&paused) + err := d.QueryRow(`SELECT schedule_paused FROM tasks WHERE id=$1 AND deleted_at IS NULL`, taskID).Scan(&paused) return paused, err } diff --git a/server/schedules.go b/server/schedules.go index 34ac5d2..aa6c972 100644 --- a/server/schedules.go +++ b/server/schedules.go @@ -234,7 +234,7 @@ func (s *Server) startScheduleTasks(item *db.TaskSchedule, forceManualPause bool started++ continue } - managed, err := s.m.pg.SchedulePausedTasks(taskID) + managed, err := s.m.pg.TaskPausedBySchedule(taskID) if err != nil { errors = append(errors, fmt.Sprintf("任务 #%d 状态读取失败", taskID)) _ = s.m.pg.RecordScheduleHistory(item.ID, taskID, "resume", false, "状态读取失败") diff --git a/web/src/app/(main)/function/schedules/page.tsx b/web/src/app/(main)/function/schedules/page.tsx index 49ff3c9..4e500e1 100644 --- a/web/src/app/(main)/function/schedules/page.tsx +++ b/web/src/app/(main)/function/schedules/page.tsx @@ -101,10 +101,11 @@ function startLabel(item: TaskSchedule) { } function endLabel(item: TaskSchedule) { + if (item.schedule_type === "once" && item.end_date) { + return `${formatDateValue(item.end_date)} ${item.end_time ? formatClock(item.end_time) : "24:00"}`; + } if (!item.end_time) return "持续运行"; const end = formatClock(item.end_time); - if (item.schedule_type === "once" && item.end_date) - return `${formatDateValue(item.end_date)} ${end === "未设置" ? "24:00" : end}`; const start = formatClock(item.start_time); return `${end}${end <= start ? "(次日)" : ""}`; } @@ -506,13 +507,7 @@ export default function SchedulesPage() { return current.filter((id) => !visibleIds.includes(id)); }); }; - const toggleTimeline = async (id: number) => { - if (expandedId === id) { - setExpandedId(null); - return; - } - setExpandedId(id); - if (timelineById[id]) return; + const refreshTimeline = async (id: number) => { setTimelineLoadingId(id); try { const detail = await api.schedule(id); @@ -523,6 +518,24 @@ export default function SchedulesPage() { setTimelineLoadingId(null); } }; + const invalidateTimeline = async (id: number) => { + setTimelineById((current) => { + if (!current[id]) return current; + const next = { ...current }; + delete next[id]; + return next; + }); + if (expandedId === id) await refreshTimeline(id); + }; + const toggleTimeline = async (id: number) => { + if (expandedId === id) { + setExpandedId(null); + return; + } + setExpandedId(id); + if (timelineById[id]) return; + await refreshTimeline(id); + }; const save = async (value: ScheduleInput) => { if ( !value.name.trim() || @@ -549,6 +562,12 @@ export default function SchedulesPage() { if (!window.confirm("确定删除这个计划吗?")) return; try { await api.deleteSchedule(id); + setTimelineById((current) => { + const next = { ...current }; + delete next[id]; + return next; + }); + if (expandedId === id) setExpandedId(null); await load(); } catch (e) { toast.error((e as Error).message); @@ -568,6 +587,7 @@ export default function SchedulesPage() { toast.success(`已立即运行 ${result.started} 个任务`); } } + await invalidateTimeline(id); await load(); } catch (e) { toast.error((e as Error).message); @@ -593,6 +613,7 @@ export default function SchedulesPage() { toast.success(`已立即运行 ${started} 个任务`); } setSelectedIds([]); + await Promise.all(ids.map((id) => invalidateTimeline(id))); await load(); }; const bulkRemove = async () => { @@ -602,6 +623,14 @@ export default function SchedulesPage() { const failed = results.filter((result) => result.status === "rejected").length; if (failed) toast.warning(`已删除 ${ids.length - failed} 个计划,${failed} 个计划删除失败`); else toast.success(`已删除 ${ids.length} 个计划`); + setTimelineById((current) => { + const next = { ...current }; + ids.forEach((id) => { + delete next[id]; + }); + return next; + }); + if (expandedId && ids.includes(expandedId)) setExpandedId(null); setSelectedIds([]); await load(); };