From 15c3518940b378ee2d4e293ec48437e77ecc4739 Mon Sep 17 00:00:00 2001 From: hicocos Date: Tue, 25 Aug 2026 14:00:15 +0800 Subject: [PATCH] fix: reset repeated task notice timers Give every task notice a sequence so identical consecutive messages restart the four-second dismissal timer. --- frontend/src/components/pages/TasksPage.tsx | 12 +++++++----- .../pages/frontendInteractionPolish.test.ts | 3 +++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/pages/TasksPage.tsx b/frontend/src/components/pages/TasksPage.tsx index 53a5f33..27a22b2 100644 --- a/frontend/src/components/pages/TasksPage.tsx +++ b/frontend/src/components/pages/TasksPage.tsx @@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next'; import { useRuntimeUiLocalization } from './useRuntimeUiLocalization'; interface TasksPageProps { onUnauthorized?: () => void; onOpenUploads?: () => void; initialAccountId?: string | null; } +interface TaskNotice { message: string; sequence: number; } const SOURCE_OPTIONS: Array<{ value: '' | UnifiedTaskSource; label: string }> = [ { value: '', label: '全部来源' }, { value: 'web_upload', label: 'Web 上传' }, @@ -73,7 +74,8 @@ export const TasksPage = ({ onUnauthorized, onOpenUploads, initialAccountId = nu const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); - const [notice, setNotice] = useState(null); + const [notice, setNotice] = useState(null); + const showNotice = (message: string) => setNotice(previous => ({ message, sequence: (previous?.sequence ?? 0) + 1 })); const [pendingAction, setPendingAction] = useState<{ task: UnifiedTask; action: 'cancel' | 'retry' } | null>(null); const [dismissalPreview, setDismissalPreview] = useState(null); const [selectionMode, setSelectionMode] = useState(false); @@ -112,7 +114,7 @@ export const TasksPage = ({ onUnauthorized, onOpenUploads, initialAccountId = nu if (!notice) return; const timer = window.setTimeout(() => setNotice(null), 4_000); return () => window.clearTimeout(timer); - }, [notice]); + }, [notice?.sequence]); const summary = useMemo(() => ({ active: tasks.filter(t => ['pending', 'running', 'paused', 'waiting'].includes(t.status)).length, @@ -141,7 +143,7 @@ export const TasksPage = ({ onUnauthorized, onOpenUploads, initialAccountId = nu : pendingAction.task.sourceType === 'telegram_target' ? 'Telegram 会话目标已清除' : '任务已取消'; - setNotice(pendingAction.action === 'cancel' ? cancelledNotice : '任务已重新提交'); + showNotice(pendingAction.action === 'cancel' ? cancelledNotice : '任务已重新提交'); setPendingAction(null); await loadTasks(true); } catch (actionError: any) { if (actionError?.message === 'UNAUTHORIZED') { authService.clearToken(); onUnauthorized?.(); } @@ -166,7 +168,7 @@ export const TasksPage = ({ onUnauthorized, onOpenUploads, initialAccountId = nu setActing(true); try { const result = await fileApi.confirmTaskDismissal(dismissalPreview); - setNotice(result.failed.length ? `已删除 ${result.dismissed.length} 条,${result.failed.length} 条因状态变化未删除` : `已从任务中心删除 ${result.dismissed.length} 条记录`); + showNotice(result.failed.length ? `已删除 ${result.dismissed.length} 条,${result.failed.length} 条因状态变化未删除` : `已从任务中心删除 ${result.dismissed.length} 条记录`); setDismissalPreview(null); setSelected([]); setSelectionMode(false); await loadTasks(true); } catch (dismissError: any) { if (dismissError?.message === 'UNAUTHORIZED') { authService.clearToken(); onUnauthorized?.(); } @@ -209,7 +211,7 @@ export const TasksPage = ({ onUnauthorized, onOpenUploads, initialAccountId = nu {selectionMode &&
已选择 {selected.length} 条
} - {notice &&
{notice}
} + {notice &&
{notice.message}
} {error &&
{error}
} {loading ?
: tasks.length === 0 ?

没有符合条件的任务

调整来源或状态筛选后再查看。

: ( diff --git a/frontend/src/components/pages/frontendInteractionPolish.test.ts b/frontend/src/components/pages/frontendInteractionPolish.test.ts index 176c238..d31a4a3 100644 --- a/frontend/src/components/pages/frontendInteractionPolish.test.ts +++ b/frontend/src/components/pages/frontendInteractionPolish.test.ts @@ -7,7 +7,10 @@ const settings = fs.readFileSync(new URL('./SettingsPage.tsx', import.meta.url), const layout = fs.readFileSync(new URL('../layout/AppLayout.tsx', import.meta.url), 'utf8'); test('task success feedback auto-dismisses while retaining a manual close control', () => { + assert.match(tasks, /interface TaskNotice \{ message: string; sequence: number; \}/); + assert.match(tasks, /const showNotice = \(message: string\) => setNotice\(previous => \(\{ message, sequence: \(previous\?\.sequence \?\? 0\) \+ 1 \}\)\);/); assert.match(tasks, /useEffect\(\(\) => \{\s*if \(!notice\) return;[\s\S]*setTimeout\(\(\) => setNotice\(null\), 4_000\)/); + assert.match(tasks, /\}, \[notice\?\.sequence\]\);/); assert.match(tasks, /aria-live="polite"/); assert.match(tasks, /aria-label="关闭提示"/); });