Files
zn-ai/docs/TaskList-Implementation-Plan.md
2026-04-14 17:02:20 +08:00

11 KiB
Raw Blame History

任务列表TaskList实时显示脚本执行进度 — 实现思路与开发计划

基于 zn-ai/electron/scripts 中美团、抖音、飞猪等脚本,在渲染层任务列表组件中实时展示脚本执行过程,并支持任务状态流转与操作。


一、现状梳理

模块 当前状态 关键文件
脚本执行 通过 utilityProcess.fork 串行执行,阻塞式返回结果,无实时推送 electron/service/execute-script-service/index.ts
任务列表 UI 使用 @constant/task 静态假数据 src/components/TaskList/List.vueCard.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(对应美团、飞猪、抖音酒店、抖音温泉等脚本)。

// 子任务状态
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[];
  createdAt: string;
  updatedAt: string;
}

2.2 实时进度推送方案

utilityProcess.forkstdout 可以被主进程监听。采用 "特殊前缀 JSON" + IPC 主动推送" 的方案:

  1. 脚本侧:在执行关键步骤时输出进度日志
    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 包含状态 操作按钮
待处理 pendingrunning 查看(展开进度详情)
已处理 successfailedpartial_failed 成功 → 移除;失败 / 部分失败 → 重试失败项

排队机制:一个 Task 包含的 N 个 SubTask对应各渠道脚本在主进程中串行排队执行,避免并发启动多个浏览器实例导致资源冲突。

重试粒度:仅针对该 Task 下 status === 'failed' 的 SubTask 进行重试,成功的 SubTask 保持结果不变。


三、分阶段开发计划

Phase 1类型定义与 IPC 扩展

目标:打通主进程 -> 渲染进程的推送通道。

  1. src/lib/task-types.ts(新建)

    • 定义 TaskSubTaskTaskProgressPayload 等接口。
  2. src/lib/constants.ts(修改)

    • 新增 IPC 事件:
      TASK_PROGRESS = 'task:progress',
      TASK_STARTED = 'task:started',
      TASK_COMPLETED = 'task:completed',
      
  3. electron/preload/index.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主进程进度推送改造

目标:让脚本执行过程可被渲染层实时感知。

  1. electron/service/execute-script-service/index.ts(修改)

    • 继承 EventEmitter,新增事件:
      • progress:解析到 __ZN_PROGRESS__ 前缀时触发
      • stdout:有新的标准输出时触发
      • stderr:有新的错误输出时触发
    • executeScript 方法签名扩展,增加 taskId / subTaskId 参数用于上下文绑定。
  2. electron/process/runTaskOperationService.ts(修改)

    • EXECUTE_SCRIPT handler 改造:
      • 接收 options.taskId,生成 subTaskId 映射表
      • 执行每个脚本前,发送 TASK_STARTED IPC
      • 订阅 executeScriptServiceInstanceprogress/stdout/stderr 事件,组装 payload 后通过 webContents.send(IPC_EVENTS.TASK_PROGRESS, ...) 推送
      • 脚本退出后发送 TASK_COMPLETED IPC

Phase 3渲染层任务状态管理

目标:集中管理任务生命周期。

  1. src/store/task.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 { ... };
    });
    
  2. src/App.vuesrc/pages/home/index.vue(修改)

    • 应用挂载时注册 IPC 监听:
      onMounted(() => {
        window.api.onTaskProgress((_, payload) => taskStore.updateSubTaskProgress(...));
        window.api.onTaskStarted((_, payload) => ...);
        window.api.onTaskCompleted((_, payload) => taskStore.completeSubTask(...));
      });
      

Phase 4UI 组件改造

目标:将假数据替换为真实任务数据,支持 tab 切换与操作。

  1. src/components/TaskList/Card.vue(修改)

    • props 改为接收 TaskSubTask 对象
    • 根据 status 渲染不同状态标签(warning 运行中 / error 失败 / success 成功)
    • 按钮逻辑:
      • running → "查看"(可展开显示 stdoutTail
      • failed / partial_failed → "重试失败项"emit retry-failed
      • success → "移除"emit remove
    • 显示进度条(el-progress 或自定义 div
  2. src/components/TaskList/List.vue(修改)

    • useTaskStore() 读取任务列表
    • "待处理" tab 显示 pendingTasks"已处理" tab 显示 completedTasks
    • 动态计算 total 数量
    • 处理 retry-failed(调用 taskStore.retryFailedSubTasks(taskId)/ remove 事件

Phase 5调用点接入

目标:让用户在对话框确认后,任务立刻出现在列表中并开始推送进度。

  1. src/pages/home/components/TaskOperationDialog.vue(修改)

    • confirm 方法中,在调用 window.api.executeScript(options) 之前:
      const task = taskStore.createTask(options);
      options.taskId = task.id;
      window.api.executeScript(options);
      
  2. src/pages/scripts/index.vue(可选)

    • 脚本管理页的"测试运行"也可接入任务列表(通过 SCRIPT_RUN IPC保持体验一致性。

Phase 6脚本输出执行步骤可选但推荐

目标:让子任务进度条真正动起来。

  1. electron/scripts/mt_trace.jsfg_trace.jsdy_hotel_trace.jsdy_hot_spring_trace.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, ... })
        ↓
主进程只执行 status === pending 的 SubTask串行排队
    ↓
执行完成后更新 Task 状态,移回"已处理" Tab

五、文件变更清单汇总

新建文件 说明
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/components/TaskList/List.vue 接入真实数据与 tab 过滤
src/components/TaskList/Card.vue 动态状态、进度、操作按钮
src/pages/home/components/TaskOperationDialog.vue 执行前创建 Task
electron/scripts/*.js 插入 __ZN_PROGRESS__ 输出(可选)

六、调整说明2026-04-14

针对实现边界补充以下确认:

  1. 排队机制:一个 Task 内的多个 SubTask各渠道脚本在主进程中串行排队执行,不会并发启动多个浏览器实例。
  2. 重试粒度"重试"操作仅针对该 Task 下 status === 'failed' 的 SubTask成功的 SubTask 保持原结果不变。
  3. Store 方法src/store/task.ts 中增加 retryFailedSubTasks(taskId) 方法,用于批量重置并重新触发失败子任务。