diff --git a/docs/tasks/T-628.md b/docs/tasks/T-628.md new file mode 100644 index 0000000..9c8274d --- /dev/null +++ b/docs/tasks/T-628.md @@ -0,0 +1,88 @@ +--- +id: T-628 +title: 采集当前记录阶段与单条耗时指示器 +phase: 2 +deps: [T-620] +status: TODO +created: 2026-07-14 +--- + +## 问题 / 背景 + +①「导入采集」处理商品详情页时,打开页面、等待加载和下载封面都可能持续较长时间。当前页面只有静态的「批次进度」汇总、底部状态栏和运行日志,用户在一条记录长时间未结束时难以判断采集是否仍在执行。 + +已核实的现状: + +- `collectBatchProgressOverview` 是根据 SQLite 任务快照生成的汇总 label,`refresh_tasks()` 时会整体重写文本,本身没有运行计时语义。 +- `CollectWorker.progress` 主要在一条记录结束后发出,不能反映当前记录正在执行哪个步骤。 +- `row_updated(status=running)` 在单条记录登录状态检查之后才发出;若以它作为计时起点,会漏掉前置检查和本条登录检查耗时。 +- `editor.collect()` 已有 `open_product / wait_ready / read_title / read_cover / download_cover` 等步骤回调,可作为结构化阶段来源。 + +顾客希望在「批次进度」同行右侧增加动态计时,并在每条记录开始时重置,让用户能看到当前采集任务仍处于运行状态。 + +计时数字持续变化只能证明 GUI 计时器仍在运行,不能证明 CDP 一定在继续推进。因此本任务把需求优化为「**当前阶段 + 当前记录耗时**」指示器:阶段显示最后一次已知进展,秒数显示该记录已经等待多久,不向用户承诺任务一定健康。 + +## 方案 + +### 1. 批次进度同行增加独立运行指示器 + +- 在现有「批次进度」右侧增加独立 label,不把计时字符串拼入 `batch_progress_label`,避免 `refresh_tasks()` 重写批次汇总时覆盖计时内容。 +- 两个 label 放入同一水平布局:批次汇总占可伸缩区域,运行指示器靠右并按最长合理文案预留稳定宽度,秒数变化不得造成布局抖动。 +- 运行中推荐文案:`正在采集 3/20 · 等待商品页加载 · 本条 00:18`。 +- 窗口空间不足时,允许批次汇总自然换行;运行状态、序号和时间必须保持可见,完整商品 ID / 账号可放在中文 tooltip 中,不在紧凑 label 内强行堆叠。 +- 不使用纯装饰性转圈或闪烁动画;每秒变化的计时和明确的「正在采集」文字即为运行反馈。 + +### 2. 使用结构化 Worker 活动信号 + +- 为 `CollectWorker` 增加专用结构化活动信号,例如 `activity: Signal(dict)`;不得通过解析用户日志文本推断当前任务或步骤。 +- 活动 payload 至少支持: + - `preflight_started`:开始检查账号和 Chrome; + - `task_started`:一条 eligible 记录开始处理; + - `task_step`:当前记录进入新步骤; + - `task_finished`:当前记录成功、失败或略过。 +- payload 使用稳定字段,如 `state / index / total / task_id / item_id / alias / step / result`,不得把账号密码、Cookie、token 或未脱敏异常放入信号。 +- `task_started` 应在进入每条记录处理循环后尽早发出,覆盖本条账号匹配、登录检查和后续采集耗时;不得等到 `db.mark_running()` 后才开始计时。 +- `editor.collect()` 的步骤回调继续用于日志,同时把步骤作为结构化 `task_step` 发给 GUI。阶段映射集中维护并全部显示中文,至少覆盖:检查账号、打开商品页、等待商品页加载、读取标题、读取封面、下载封面、保存采集结果。 +- 结构化活动信号只增加可观察性,不改变原有 `progress / row_updated / log / failed / finished / cancelled` 的业务语义。 +- 本轮正常完成、停止和前置阻断继续以现有 `finished / cancelled` 信号及 summary 中的 `blocked` 字段为唯一终态来源,不在 `activity` 中复制一套终态协议。 + +### 3. 计时口径与生命周期 + +- GUI 使用父对象为 `CollectTab` 的 `QTimer`,每 1000ms 刷新一次;真实耗时通过 `time.monotonic()` 计算,不通过简单 `seconds += 1` 累加。 +- 点击「采集旧标题/旧封面」并成功创建 Worker 后,记录批次开始时间,显示 `正在检查账号 · 00:00`。 +- 收到 `task_started` 时重置当前记录起点并开始显示 `本条 00:00`;收到 `task_step` 只更新阶段文字,不重置本条计时。 +- 下一条记录开始时重新从 `00:00` 计时;成功、失败或略过可短暂冻结本条耗时,随后由下一条 `task_started` 覆盖。 +- 用户点击「停止」后,指示器立即改为 `正在停止 · 本条 00:18`,在当前不可中断步骤退出前继续显示等待时间;收到 `cancelled` 后再冻结为终态,不能提前伪装成已经停止。 +- 正常完成、前置检查阻断、用户停止、Worker 异常及线程释放路径都必须停止 QTimer、清理当前任务引用并冻结最终文字,不能在采集结束后继续走秒。 +- 本轮终态文案应区分: + - 正常完成:`采集完成 · 总用时 08:32`; + - 用户停止:`采集已停止 · 总用时 03:10`; + - 前置阻断:`采集未开始 · 检查未通过`; + - 异常结束:`采集已结束 · 请查看运行日志`。 +- 计时只用于本次 GUI 显示,不写入 SQLite 或 Excel;现有逐条诊断日志中的 `elapsed_ms` 保持不变。 + +### 4. 状态语义与视觉边界 + +- 运行中使用现有信息色/进行中色,并保留「正在采集」文字,不能只靠颜色表达状态。 +- 完成、停止、阻断和异常沿用项目既有状态语义色;不增加“卡死”自动判定。页面加载和图片下载可能合法地持续较久,仅凭固定秒数变色会产生误报。 +- 用户看到持续增加的本条耗时,应理解为“该记录尚未结束”;阶段长时间不变化时可据此决定查看日志或停止,但 UI 不显示“运行正常”等未经验证的结论。 + +## 验收要点 + +- 有批次任务但未运行时,批次汇总显示正常,右侧运行指示器不误报正在采集。 +- 点击采集后,在首条记录开始前显示中文前置检查状态和持续计时;GUI 保持可响应。 +- 第一条记录开始时显示 `1/N` 并从 `00:00` 计时;步骤变化时阶段文字变化、本条计时不重置。 +- 第二条记录开始时显示 `2/N`,本条时间重新从 `00:00` 开始。 +- 成功、失败和略过记录都能进入正确的下一条计时;不会因为某条失败而停止整轮指示器。 +- 正常完成、前置阻断、用户停止、Worker 异常和线程收尾后计时全部停止,等待数秒后界面秒数不再变化。 +- 批次进度汇总刷新不会覆盖运行指示器;运行指示器更新也不会改变批次阶段统计。 +- 运行状态 label 宽度稳定,在最小支持窗口及 Windows 100% / 125% / 150% 显示缩放下无重叠和明显布局跳动。 +- 单测使用可控 `time.monotonic()` 或注入时间验证,不通过真实 `sleep()` 拉长测试。 +- 自动验证:`py -3.10 -m unittest discover -s tests`、`python -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`git diff --check` 全绿。 + +## 边界(不改什么) + +- 不修改 CDP 选择器、商品页打开/关闭策略、Chrome 前后台切换、登录判定或采集重试策略。 +- 不修改任务 `stage/status`、SQLite schema、Excel schema、回写流程或批次统计口径。 +- 不把计时器放进 Worker 线程,不让 GUI 每秒查询 SQLite,也不把每秒 tick 写入运行日志。 +- 不改变②AI生成、③更新蝦皮及⑥商品套图的计时显示。