diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 0253066..a97c410 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -175,6 +175,26 @@ export function App() { } }; + const deleteTasks = async (ids: string[]) => { + if (!ids.length) return; + if (ids.length >= flows.length) { + showToast("至少保留一个任务", "error"); + return; + } + if (!window.confirm(`确定删除选中的 ${ids.length} 个任务吗?删除后不可恢复。`)) return; + try { + for (const id of ids) await window.rpa.flows.delete(id); + await refreshFlows(); + if (activeFlowId && ids.includes(activeFlowId)) { + setActiveFlowId(null); + setPage("tasks"); + } + showToast(`已删除 ${ids.length} 个任务`); + } catch (deleteError) { + showToast(deleteError instanceof Error ? deleteError.message : String(deleteError), "error"); + } + }; + const runTask = async (id: string) => { try { const flow = await window.rpa.flows.get(id); @@ -260,6 +280,7 @@ export function App() { onOpenEditor={(id) => void openEditor(id)} onRun={(id) => void runTask(id)} onDelete={(id) => void deleteTask(id)} + onDeleteMany={(ids) => void deleteTasks(ids)} onRefresh={() => refreshFlows()} onToast={showToast} /> diff --git a/src/renderer/src/pages/TasksPage.tsx b/src/renderer/src/pages/TasksPage.tsx index 152c748..083f140 100644 --- a/src/renderer/src/pages/TasksPage.tsx +++ b/src/renderer/src/pages/TasksPage.tsx @@ -23,6 +23,7 @@ interface TasksPageProps { onOpenEditor: (id: string) => void; onRun: (id: string) => void; onDelete: (id: string) => void; + onDeleteMany: (ids: string[]) => void; onRefresh: () => void | Promise; onToast: (message: string, kind?: "ok" | "error") => void; } @@ -46,9 +47,10 @@ function formatDuration(value?: number): string { const RUN_LABEL = { succeeded: "成功", failed: "失败", cancelled: "已取消", never: "未运行" } as const; -export function TasksPage({ flows, onCreate, onCreateExamples, onOpenEditor, onRun, onDelete, onRefresh, onToast }: TasksPageProps) { +export function TasksPage({ flows, onCreate, onCreateExamples, onOpenEditor, onRun, onDelete, onDeleteMany, onRefresh, onToast }: TasksPageProps) { const [query, setQuery] = useState(""); const [selectedId, setSelectedId] = useState(flows[0]?.id || null); + const [checkedIds, setCheckedIds] = useState([]); const [refreshing, setRefreshing] = useState(false); const handleRefresh = async () => { @@ -70,12 +72,30 @@ export function TasksPage({ flows, onCreate, onCreateExamples, onOpenEditor, onR if (!selectedId || !flows.some((flow) => flow.id === selectedId)) setSelectedId(flows[0].id); }, [flows, selectedId]); + // 勾选集合随任务列表变化裁剪,避免残留已删除任务的 id + useEffect(() => { + setCheckedIds((previous) => { + const alive = new Set(flows.map((flow) => flow.id)); + const next = previous.filter((id) => alive.has(id)); + return next.length === previous.length ? previous : next; + }); + }, [flows]); + const filtered = useMemo(() => { const keyword = query.trim().toLowerCase(); if (!keyword) return flows; return flows.filter((flow) => flow.name.toLowerCase().includes(keyword)); }, [flows, query]); + const toggleChecked = (id: string) => { + setCheckedIds((previous) => (previous.includes(id) ? previous.filter((item) => item !== id) : [...previous, id])); + }; + + const allFilteredChecked = filtered.length > 0 && filtered.every((flow) => checkedIds.includes(flow.id)); + const toggleAllFiltered = () => { + setCheckedIds(allFilteredChecked ? [] : filtered.map((flow) => flow.id)); + }; + const selected = flows.find((flow) => flow.id === selectedId) || filtered[0] || null; const enabledCount = flows.filter((flow) => flow.enabled !== false).length; const failedCount = flows.filter((flow) => flow.lastRunStatus === "failed").length; @@ -112,6 +132,11 @@ export function TasksPage({ flows, onCreate, onCreateExamples, onOpenEditor, onR 任务列表 按最近修改时间排序 + {checkedIds.length > 0 && ( + + )}