Files
zn-ai/docs/TaskList-Implementation-Plan.md
DEV_DSW 210e8eb363 feat: add task management and progress reporting
- Implemented task and subtask structures with progress tracking.
- Added reporting functionality to log progress at various stages in hotel room status management scripts.
- Created a task store to manage tasks and their states, including persistence to local storage.
- Updated UI components to display task lists and handle task actions (retry, remove).
- Removed deprecated TaskCard and TaskList components, replacing them with a new structure for better maintainability.
- Enhanced script execution service to emit progress events for UI updates.
2026-04-16 16:59:49 +08:00

346 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 任务列表TaskList实时显示脚本执行进度 — 实现思路与开发计划
> 基于 `zn-ai/electron/scripts` 中美团、抖音、飞猪等脚本,在渲染层任务列表组件中实时展示脚本执行过程,并支持任务状态流转与操作。
---
## 一、现状梳理
| 模块 | 当前状态 | 关键文件 |
|---|---|---|
| 脚本执行 | 通过 `utilityProcess.fork` 串行执行,**阻塞式返回结果**,无实时推送 | `electron/service/execute-script-service/index.ts` |
| 任务列表 UI | 使用 `@constant/task` **静态假数据** | `src/pages/home/components/TaskList.vue``TaskCard.vue` |
| 脚本触发入口 | `TaskOperationDialog.vue` 中调用 `window.api.executeScript(options)` | `src/pages/home/components/TaskOperationDialog.vue` |
| 状态管理 | 已有 `store/script.ts` 管理脚本元数据,**缺少任务Task生命周期管理** | `src/store/script.ts` |
| IPC 通信 | 只有 Request/Response 模式invoke/handle**无主进程主动推送** | `electron/preload/index.ts` |
---
## 二、核心设计思路
### 2.1 数据模型Task任务+ SubTask子任务
一次"操作房型"的执行产生 **1 个 Task**,该 Task 包含 **N 个 SubTask**(对应美团、飞猪、抖音酒店、抖音温泉等脚本)。
```ts
// 子任务状态
type SubTaskStatus = 'pending' | 'running' | 'success' | 'failed';
interface SubTask {
id: string; // 子任务唯一 ID
taskId: string; // 所属主任务 ID
scriptId: string; // 脚本标识,如 mt_trace.js
name: string; // 渠道名称,如"美团房态追踪"
status: SubTaskStatus;
progress: number; // 0-100
message: string; // 当前执行步骤描述
stdoutTail: string; // 最新输出
stderrTail: string; // 最新错误
error?: string; // 失败原因
startedAt: string;
completedAt?: string;
}
// 主任务状态
type TaskStatus = 'pending' | 'running' | 'success' | 'partial_failed' | 'failed';
interface Task {
id: string;
title: string; // 如"关闭渠道房型 - 大床房"
operation: 'open' | 'close';
roomType: string;
dateRange: [string, string];
status: TaskStatus;
subTasks: SubTask[];
roomList: any[]; // 保留原始房型列表,用于重试时重新传参
createdAt: string;
updatedAt: string;
}
```
### 2.2 实时进度推送方案
`utilityProcess.fork``stdout` 可以被主进程监听。采用 **"特殊前缀 JSON" + IPC 主动推送"** 的方案:
1. **脚本侧**:在执行关键步骤时输出进度日志
```js
console.log('__ZN_PROGRESS__' + JSON.stringify({ step: '正在登录美团后台', percent: 30 }));
```
2. **主进程侧**`execute-script-service` 解析 stdout匹配 `__ZN_PROGRESS__` 前缀emit 内部事件
3. **IPC 推送**`runTaskOperationService.ts` 订阅内部事件,通过 `BrowserWindow.webContents.send('task:progress', payload)` 推送到渲染进程
> 如果暂时不修改脚本文件,也可以先实现"开始/完成"两个节点的推送,中间进度用 stdout 文本更新。
### 2.3 状态流转与操作权限
| Tab | 包含状态 | 操作按钮 |
|---|---|---|
| **待处理** | `pending`、`running` | 查看(展开进度详情) |
| **已处理** | `success`、`failed`、`partial_failed` | 成功 → **移除**;失败 / 部分失败 → **重试失败项** |
**排队机制**:一个 Task 包含的 N 个 SubTask对应各渠道脚本在主进程中**串行排队执行**,避免并发启动多个浏览器实例导致资源冲突。
**重试粒度**:仅针对该 Task 下 `status === 'failed'` 的 SubTask 进行重试,成功的 SubTask 保持结果不变。
---
## 三、分阶段开发计划
### Phase 1类型定义与 IPC 扩展
**目标**:打通主进程 -> 渲染进程的推送通道。
1. **`src/lib/task-types.ts`**(新建)
- 定义 `Task`、`SubTask`、`TaskProgressPayload` 等接口。
2. **`src/lib/constants.ts`**(修改)
- 新增 IPC 事件:
```ts
TASK_PROGRESS = 'task:progress',
TASK_STARTED = 'task:started',
TASK_COMPLETED = 'task:completed',
```
3. **`electron/preload/index.ts`**(修改)
- 暴露监听接口:
```ts
onTaskProgress: (cb) => ipcRenderer.on(IPC_EVENTS.TASK_PROGRESS, cb),
onTaskStarted: (cb) => ipcRenderer.on(IPC_EVENTS.TASK_STARTED, cb),
onTaskCompleted: (cb) => ipcRenderer.on(IPC_EVENTS.TASK_COMPLETED, cb),
```
4. **`global.d.ts`**(修改)
- 更新 `WindowApi` 类型声明。
---
### Phase 2主进程进度推送改造
**目标**:让脚本执行过程可被渲染层实时感知。
5. **`electron/service/execute-script-service/index.ts`**(修改)
- 继承 `EventEmitter`,新增事件:
- `progress`:解析到 `__ZN_PROGRESS__` 前缀时触发
- `stdout`:有新的标准输出时触发
- `stderr`:有新的错误输出时触发
- `executeScript` 方法签名扩展,增加 `taskId` / `subTaskId` 参数用于上下文绑定。
6. **`electron/process/runTaskOperationService.ts`**(修改)
- `EXECUTE_SCRIPT` handler 改造:
- 接收 `options.taskId`,生成 `subTaskId` 映射表
- 执行每个脚本前,发送 `TASK_STARTED` IPC
- 订阅 `executeScriptServiceInstance` 的 `progress`/`stdout`/`stderr` 事件,组装 payload 后通过 `webContents.send(IPC_EVENTS.TASK_PROGRESS, ...)` 推送
- 脚本退出后发送 `TASK_COMPLETED` IPC
---
### Phase 3渲染层任务状态管理
**目标**:集中管理任务生命周期。
7. **`src/store/task.ts`**(新建)
```ts
export const useTaskStore = defineStore('task', () => {
const tasks = ref<Task[]>([]);
// 创建任务(在 TaskOperationDialog 确认时调用)
const createTask = (options: ExecuteScriptOptions): Task => { ... };
// 更新子任务进度(监听 TASK_PROGRESS 时调用)
const updateSubTaskProgress = (taskId, subTaskId, payload) => { ... };
// 完成子任务
const completeSubTask = (taskId, subTaskId, result) => { ... };
// 重试该任务下所有失败的子任务
const retryFailedSubTasks = async (taskId) => { ... };
// 移除已处理任务
const removeTask = (taskId) => { ... };
// Computed
const pendingTasks = computed(() => ...); // pending + running
const completedTasks = computed(() => ...); // success + failed
return { ... };
});
```
8. **`src/App.vue` 或 `src/pages/home/index.vue`**(修改)
- 应用挂载时注册 IPC 监听:
```ts
onMounted(() => {
window.api.onTaskProgress((_, payload) => taskStore.updateSubTaskProgress(...));
window.api.onTaskStarted((_, payload) => ...);
window.api.onTaskCompleted((_, payload) => taskStore.completeSubTask(...));
});
```
---
### Phase 4UI 组件改造
**目标**:将假数据替换为真实任务数据,支持 tab 切换与操作。
9. **`src/pages/home/components/TaskCard.vue`**(修改)
- `props` 改为接收 `Task` 或 `SubTask` 对象
- 根据 `status` 渲染不同状态标签(`warning` 运行中 / `error` 失败 / `success` 成功)
- 按钮逻辑:
- `running` → "查看"(可展开显示 stdoutTail
- `failed` / `partial_failed` → "重试失败项"emit `retry-failed`
- `success` → "移除"emit `remove`
- 显示进度条(`el-progress` 或自定义 div
10. **`src/pages/home/components/TaskList.vue`**(修改)
- 从 `useTaskStore()` 读取任务列表
- "待处理" tab 显示 `pendingTasks`"已处理" tab 显示 `completedTasks`
- 动态计算 `total` 数量
- 顶部日期时间实时动态化("今天"/"昨天"/具体日期 + `HH:mm:ss`
- 处理 `retry-failed`(调用 `taskStore.retryFailedSubTasks(taskId)`/ `remove` 事件
---
### Phase 5调用点接入
**目标**:让用户在对话框确认后,任务立刻出现在列表中并开始推送进度。
11. **`src/pages/home/components/TaskOperationDialog.vue`**(修改)
- `confirm` 方法中,在调用 `window.api.executeScript(options)` 之前:
```ts
const task = taskStore.createTask(options);
options.taskId = task.id;
window.api.executeScript(options);
```
12. **`src/pages/scripts/index.vue`**(可选)
- 脚本管理页的"测试运行"也可接入任务列表(通过 `SCRIPT_RUN` IPC保持体验一致性。
---
### Phase 6脚本输出执行步骤可选但推荐
**目标**:让子任务进度条真正动起来。
13. **`electron/scripts/mt_trace.js`、`fg_trace.js`、`dy_hotel_trace.js`、`dy_hot_spring_trace.js`**(修改)
- 在关键步骤插入进度输出:
```js
function reportProgress(step, percent) {
console.log('__ZN_PROGRESS__' + JSON.stringify({ step, percent }));
}
// 示例
reportProgress('连接本地浏览器', 10);
reportProgress('定位目标页面', 30);
reportProgress('操作房态数据', 60);
reportProgress('保存并校验', 90);
```
---
## 四、关键数据流
### 首次执行
```
用户点击确认
TaskOperationDialog 调用 taskStore.createTask() → 生成 taskId
window.api.executeScript({ taskId, roomType, startTime, endTime, operation })
主进程 EXECUTE_SCRIPT handler
├─ 发送 IPC task:started
├─ for 循环串行执行每个脚本utilityProcess.fork
│ ├─ 脚本 stdout → 解析 __ZN_PROGRESS__ → 发送 IPC task:progress
│ └─ 脚本 exit → 发送 IPC task:completed
渲染层 Store 接收 IPC → 更新 tasks 数组
List.vue / Card.vue 响应式更新 UI
```
### 重试失败项
```
用户点击"重试失败项"
List.vue 调用 taskStore.retryFailedSubTasks(taskId)
├─ 将该 Task 下所有 failed 的 SubTask 重置为 pending
├─ Task 状态回退为 pending / running
└─ 重新调用 window.api.executeScript({ taskId, roomType, startTime, endTime, operation, roomList })
主进程只执行 status === pending 的 SubTask串行排队
执行完成后更新 Task 状态,移回"已处理" Tab
```
> **注意**:重试时需要 `roomList` 参数。`createTask` 会将 `options.roomList` 存入 `Task.roomList` 并随任务列表一起持久化到 `electron-store`,确保刷新后重试仍能拿到完整参数。
---
## 五、文件变更清单汇总
| 新建文件 | 说明 |
|---|---|
| `src/lib/task-types.ts` | Task / SubTask 类型定义 |
| `src/store/task.ts` | 任务状态管理 Pinia Store |
| 修改文件 | 说明 |
|---|---|
| `src/lib/constants.ts` | 新增 `TASK_PROGRESS` 等 IPC 常量 |
| `electron/preload/index.ts` | 暴露 `onTaskProgress` 等监听 API |
| `global.d.ts` | 更新 `WindowApi` 类型 |
| `electron/service/execute-script-service/index.ts` | 解析进度并 emit 事件 |
| `electron/process/runTaskOperationService.ts` | 绑定 taskId 并推送 IPC |
| `src/pages/home/components/TaskList.vue` | 接入真实数据、tab 过滤、日期时间动态化 |
| `src/pages/home/components/TaskCard.vue` | 动态状态、进度、操作按钮 |
| `src/pages/home/components/TaskOperationDialog.vue` | 执行前创建 Task |
| `electron/scripts/*.js` | 插入 `__ZN_PROGRESS__` 输出(可选) |
---
## 六、调整说明2026-04-16
针对实现边界补充以下确认:
1. **排队机制**:一个 Task 内的多个 SubTask各渠道脚本在主进程中**串行排队执行**,不会并发启动多个浏览器实例。
2. **重试粒度**"重试"操作仅针对该 Task 下 `status === 'failed'` 的 SubTask成功的 SubTask 保持原结果不变。
3. **Store 方法**`src/store/task.ts` 中增加 `retryFailedSubTasks(taskId)` 方法,用于批量重置并重新触发失败子任务。
4. **视觉 UI 延用当前**`TaskList.vue` 与 `TaskCard.vue` 保持现有样式和布局,仅将数据来源从 `@constant/task` 静态假数据替换为 `useTaskStore`,并在现有样式框架内绑定状态、进度与操作按钮。
5. **日期时间动态化**`TaskList.vue` 顶部原有的静态日期("今天")和时间("02:32:05")改为响应式实时显示:
- 左侧日期标签根据当前日期自动判断显示 **"今天"**、**"昨天"** 或具体日期(如 `04/16`)。
- 右侧时间通过 `setInterval` 每秒更新,格式为 `HH:mm:ss`。
- 组件卸载时自动清理定时器。
6. **数据持久化使用 `electron-store`**
- 渲染层 Store `useTaskStore` 通过 IPC (`GET_CONFIG` / `SET_CONFIG`) 读写任务列表,避免主进程直接暴露 Store 实例到渲染层。
- 主进程在 `config-service` 的 `DEFAULT_CONFIG` 中新增 `CONFIG_KEYS.TASK_LIST`,默认值为空数组 `[]`。
- `useTaskStore` 初始化时异步加载已持久化的任务列表;每次 `createTask`、`completeSubTask`、`removeTask` 等变更操作后,通过 `window.api.invoke(IPC_EVENTS.SET_CONFIG, CONFIG_KEYS.TASK_LIST, tasks.value)` 同步到 `electron-store`。
- 注意:持久化数据为任务列表(含 SubTask 状态),实时进度更新(`task:progress`)频繁变化时可**仅更新内存状态**,待 `task:completed` 后再统一持久化,以减少写盘次数。
---
## 七、Sub-agent 开发分工
共启动 **4 个 sub-agent** 按流水线并行开发。
| Sub-agent | 负责阶段 | 关键文件 | 依赖 |
|---|---|---|---|
| **SA-1 主进程** | Phase 1 + Phase 2 | `src/lib/task-types.ts`、`src/lib/constants.ts`、`electron/preload/index.ts`、`global.d.ts`、`electron/service/execute-script-service/index.ts`、`electron/process/runTaskOperationService.ts` | 无 |
| **SA-2 状态管理** | Phase 3 | `src/store/task.ts`(新建)、`src/App.vue`(挂载监听) | 需 SA-1 的类型与 IPC 契约 |
| **SA-3 前端 UI** | Phase 4 + Phase 5 | `src/pages/home/components/TaskList.vue`、`TaskCard.vue`、`TaskOperationDialog.vue` | 需 SA-2 的 Store API可按本计划接口契约先行开发 |
| **SA-4 脚本进度** | Phase 6 | `electron/scripts/mt_trace.js`、`fg_trace.js`、`dy_hotel_trace.js`、`dy_hot_spring_trace.js` | 无 |
### 各 Sub-agent 验收标准
**SA-1**
- `executeScriptService` 继承 `EventEmitter`,能解析 `__ZN_PROGRESS__` 前缀并触发 `progress`/`stdout`/`stderr` 事件。
- `runTaskOperationService` 的 `EXECUTE_SCRIPT` handler 接收 `options.taskId`,为每个脚本生成 `subTaskId`,串行执行并推送 `task:started` / `task:progress` / `task:completed` IPC 事件。
**SA-2**
- `useTaskStore` 通过 `electron-store` 持久化任务列表IPC 读写)。
- 提供 `createTask`、`updateSubTaskProgress`、`completeSubTask`、`retryFailedSubTasks`、`removeTask`。
- `pendingTasks` / `completedTasks` computed 正确过滤。
**SA-3**
- `TaskList.vue` / `TaskCard.vue` 接入 `useTaskStore`Tab 数量和任务数量动态计算。
- `running` 状态显示进度条和最新日志展开;`failed` / `partial_failed` 显示"重试失败项"`success` 显示"移除"。
- `TaskOperationDialog.vue` 确认时先 `taskStore.createTask(options)`,再 `window.api.executeScript(options)`。
**SA-4**
- 每个脚本在关键步骤输出 `__ZN_PROGRESS__{"step":"...","percent":N}`。
- 不影响原有脚本逻辑。