From 5795c39a6fbd6252d5af3f01665dce0c762d5f51 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Mon, 2 Nov 2026 15:18:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8B=BE=E9=80=89=E6=89=B9=E9=87=8F=E5=88=A0?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 每行任务左侧新增复选框; 表头「任务」列新增全选(仅作用于当前筛选结果) - 勾选后列表头部出现「删除所选(N)」按钮, 二次确认后批量删除 - 批量删除保留「至少保留一个任务」保护; 勾选集合随列表变化自动裁剪已删除 id - 单行删除行为不变 --- src/renderer/src/App.tsx | 21 +++++++++++++ src/renderer/src/pages/TasksPage.tsx | 46 ++++++++++++++++++++++++++-- src/renderer/src/styles/app.css | 22 +++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) 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 && ( + + )}