SPEC-006 — task delete 二选一 confirm UI¶
1. 背景¶
来源:[[2026-05-26-task-delete-cascade-cleanup-tbd|2026-05-26-task-delete级联清理待确认]] — v0.10.47 第三轮独立 agent 跨模块发现。
现状:task-manager.ts:319-326 controlTask('delete'):
- ✅ 调 removeTask(id) — 清 task store
- ✅ 调 clearTaskProgress(id) — 清进度
- ❌ 不删 MapTaskData 表里 taskId == 被删 task 的商家行
后果: - 删了任务后,该 taskId 的商家数据变孤儿 - TaskFilterPicker 只列存在的 task → 用户无法再按这个 taskId 过滤 - DataView 默认显示全部 → 孤儿数据仍可见但无法溯源
是 bug 还是设计:可能是设计意图(保留爬取结果),也可能是忘记级联删。没文档说明。
2. 决策:二选一 confirm UI¶
3 方案权衡:
| 方案 | 优点 | 缺点 |
|---|---|---|
| A. 保留 + 文档化 | 最省事 | 孤儿数据 + TaskFilterPicker 问题没解 |
| B. 级联删(默认行为) | 数据干净 | 已删任务的商家数据会丢,无挽回 |
| C. 二选一 confirm UI ✅ | UX 最佳,用户决定 | 多一个 dialog 步骤 |
选 C: - 删任务前弹 Dialog - 显示「该任务共采集到 N 条 商家数据,是否一并删除?」 - 按钮:「仅删任务(保留数据)」「全部删除」「取消」
3. 目标 / 非目标¶
目标¶
- 删任务时让用户明确决定商家数据去留
- 不删数据时仍保留可溯源能力(见 §6)
- 全部删除时一致清掉:task / progress / MapTaskData 行
非目标¶
- 不批量删(多任务一次性删)— 本期仅单任务
- 不做"软删除"(标记删除但保留)— 用户选了删就真删
4. UX 设计¶
4.1 触发点¶
task-view.tsx任务卡上点删除按钮- TaskTableActions 删除按钮
4.2 Dialog 设计¶
┌────────────────────────────────────────┐
│ 删除任务「{任务名}」? │
│ │
│ 该任务共采集到 N 条商家数据。 │
│ 请选择数据处理方式: │
│ │
│ ⚪ 仅删任务,保留商家数据(推荐) │
│ 数据可在「商家列表」中继续查看 │
│ ⚪ 同时删除该任务的全部 N 条商家数据 │
│ 数据无法恢复 │
│ │
│ [取消] [确认删除] │
└────────────────────────────────────────┘
默认选项:保留数据(安全默认,符合最小破坏原则)。
4.3 边缘情况¶
- N = 0:不弹 dialog,直接走原 delete 流程
- N > 10000:dialog 显示「N 条数据,删除可能耗时」+ loading 状态
5. 数据流改动¶
5.1 API 层¶
controlTask('delete') 签名扩展:
export async function controlTask(
id: string,
action: 'pause' | 'resume' | 'stop' | 'delete' | 'rename',
payload?: any // { deleteData?: boolean } for action=delete
)
5.2 实现¶
if (action === 'delete') {
if (isBatchActive(id)) await stopBatch(id);
const cur = await storage.getItem<string>(CURRENT_TASK_KEY);
if (cur === id) await storage.setItem(CURRENT_TASK_KEY, '');
await removeTask(id);
await clearTaskProgress(id);
if (payload?.deleteData === true) {
await removeByQuery('MapTaskData', { taskId: id }); // 新增
}
await pumpTasks();
}
5.3 UI 层¶
新组件 src/components/task/task-delete-confirm-dialog.tsx:
- props: { taskId, taskName, open, onClose, onConfirm }
- mount 时调 countByQuery('MapTaskData', { taskId }) 拿 N
- onConfirm 调 controlTask(taskId, 'delete', { deleteData: chosen })
6. 保留数据时的溯源(关键)¶
如果用户选「保留数据」,孤儿数据要能溯源:
| 方案 | 优 | 缺 |
|---|---|---|
| a. TaskFilterPicker 加"已删任务"分组 | 用户能筛 | 要查所有 taskId 跟 task store 对比,慢 |
b. 删任务时给 MapTaskData 行加 taskDeleted: true 字段 |
快 | 加字段,要写迁移 |
| c. 不管 — 用户筛"全部" + 按时间排序找 | 零成本 | UX 差 |
推荐 a:TaskFilterPicker 异步算"孤儿 taskId 集合" → 加 "已删任务" 分组(可折叠)。可接受性能开销(只在 dropdown 打开时算一次)。
7. 批量删任务(2026-05-28 决策追加)¶
用户选「做」。多任务一次性删时:
- Dialog 显示「选中 M 个任务,共 N 条商家数据,请选择数据处理方式」
- N = sum(countByQuery({ taskId }) for each task)
- 实现:controlTask 单次调用 → 新建 controlTasksBatch(ids, action, payload) 包装
UI:TaskTableToolbar 加批量删按钮(仅 selection.length > 1 时显示)。
// 批量删 API(新增)
export async function controlTasksBatch(
ids: string[],
action: 'delete' | 'stop' | 'pause' | 'resume',
payload?: any
) {
// 串行,避免 jsstore 并发冲突
for (const id of ids) {
await controlTask(id, action, payload);
}
}
性能注意:N 条 MapTaskData 删除可能慢(10w+ 时几百 ms),UI 期间显示 loading。
8. 实施计划¶
| Step | 工作 | 估时 |
|---|---|---|
| 1 | 新 task-delete-confirm-dialog.tsx 组件(含批量模式 props) |
1.5h |
| 2 | task-manager.ts controlTask 加 deleteData payload |
0.5h |
| 3 | 新 controlTasksBatch API + background message handler |
0.5h |
| 4 | task-view / TaskTableActions 单删 trigger 点替换 | 0.5h |
| 5 | TaskTableToolbar 批量删按钮(多选时显示) | 0.5h |
| 6 | TaskFilterPicker 加"已删任务"分组(方案 a) | 1h |
| 7 | 测试 + sanity check(单删 / 批量删 / 0 数据 / 10w 数据) | 1h |
| 合计 | ~5.5h |
9. 决策记录(2026-05-28 用户拍板)¶
| # | 决策点 | 用户选择 |
|---|---|---|
| 1 | 默认选项 | 保留数据(安全默认) |
| 2 | TaskFilterPicker 孤儿分组 | 做(方案 a) |
| 3 | 批量删任务 | 做(本期一并实现) |
相关¶
- [[2026-05-26-task-delete-cascade-cleanup-tbd|2026-05-26-task-delete级联清理待确认]] — 原始发现
- 共享队列架构 — task store / progress / MapTaskData 三层关系