Files
cmshoppe/docs/tasks/T-581.md
T

83 lines
6.3 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.
---
id: T-581
title: ②AI生成增加「打开图片文件夹」按钮(级联:选中行账号图 / 批次 / 全部批次根)
phase: 7
deps: [T-566]
status: TODO
created: 2026-07-10
---
## 问题 / 背景
运营需要把某商品/某账号/整批的 AI 图片(旧封面、新封面、归档候选)用文件管理器打开,便于打包发给他人做文件级操作。当前②只能在画廊弹窗里看单商品候选(T-567),无法跳到文件夹做批量文件操作。
已核事实:
- 图片落盘层级 `<image_root>/<batch_id>/<slug>/{task_id}_{item_id}_{old|new|new_<ts>}.jpg`(`app/image_paths.py`)。slug 文件夹里直接是图片文件(含旧封面、新封面、归档候选);批次文件夹下一层是各账号 slug 子文件夹。
- `image_root = appconfig.image_dir(config)`(`app/appconfig.py:622`)。
- ②批次来源 = `batch_filter.currentData()`(`generate.py:1866`,None/"all" 即全部批次);行选中 = 表格 selectionModel。
- **仓库无任何"打开文件夹"实现**——净新增,需跨平台且 Linux 测试环境不崩(单测在 WSL 跑)。
## 方案(改哪个文件、改成什么)
### 级联打开规则(越具体开越深)
| 状态 | 打开目录 |
| --- | --- |
| 选中了某行 | 优先打开该行已有 `new_cover_path` / `old_cover_path` 的真实父目录;若没有已有图片路径,再回退到 `<image_root>/<batch_id>/<slug>/`(该行任务对应账号的图片,直达图片) |
| 未选行、批次筛选=具体批次 | `<image_root>/<batch_id>/`(整批,含各账号子文件夹,供整批打包) |
| 未选行、批次筛选=全部批次 | `<image_root>/`(所有批次根) |
- 选中多行时以表格当前焦点行(`currentIndex/currentRow`)为准;若当前行无效,再取 selectionModel 的第一条选中行。只打开一个目录,不同时打开多个目录。
- 选中行有历史图片路径时,优先打开真实存在图片文件的父目录,避免老数据或路径结构调整后“有图但打开空目录”。如果已有路径不存在或为空,再用规范路径回退。
- slug 目录路径复用 `image_paths.task_image_path(image_root, task, account, "new")` 取 `os.path.dirname(...)`,保证与新图片实际落盘位置一致;账号解析复用②既有 account 解析(`_account_for_task`/`account_by_alias` 同款)。
- 若选中行缺少账号 alias、或 alias 找不到账号,不能猜测 slug;直接中文提示「选中任务没有匹配账号,无法定位图片文件夹」,不打开、不创建目录。
- 未选行时只按批次筛选级联:具体批次打开 `<image_root>/<batch_id>/`,全部批次打开 `<image_root>/`,不需要账号解析。
### `app/gui/tabs/generate.py` — 按钮与布局
- 新增 `self.open_image_dir_button = QPushButton("打开图片文件夹")`,放在**筛选行**(`filter_layout`)紧挨 `refresh_button` 右侧(`generate.py:895` 后)。理由:级联依据(批次筛选、选中行)都在这一行,且它是只读导航动作,不与「开始生成/停止/重置生成结果」这类消耗点数/改数据的执行按钮混排。
- tooltip:「选中某行→打开该账号图片文件夹;未选中→打开当前批次文件夹;全部批次→打开图片根目录」。
- 点击 handler:按上表算出目标目录 → 若目录**不存在**:弹提示(如「该批次/账号还没有图片」或「图片根目录不存在,请先采集或生成图片」),**不自动创建目录**、不打开 → 存在则调用跨平台打开 helper。
### 跨平台打开 helper(可 mock)
- 新增小工具,优先放在独立模块 `app/gui/file_manager.py`,提供 `open_in_file_manager(path)`,便于单测 mock 且不把系统调用塞进通用控件模块。
- `open_in_file_manager(path)` 先把路径转为绝对路径并确认 `os.path.isdir(path)`;目录不存在时抛出可中文化处理的错误,不负责创建目录。
- Windows 用 `os.startfile(path)`;macOS 用 `subprocess.Popen(["open", path], stdout=DEVNULL, stderr=DEVNULL, shell=False)`;Linux/其它平台用 `subprocess.Popen(["xdg-open", path], stdout=DEVNULL, stderr=DEVNULL, shell=False)`。不拼 shell 字符串、不用 `shell=True`,避免路径空格/特殊字符问题;测试环境 mock 掉真实调用。
- handler 调用该 helper,单测通过 `mock.patch` 断言「用正确目标路径调用了 helper」,**不真正打开**(Linux CI/WSL 不炸)。
### 文档同步
- `docs/routes.md` / ②相关 UI 文档:登记「打开图片文件夹」按钮及级联规则。
## 验收要点
- 选中某行 → 若已有图片路径存在,优先用该图片父目录调用打开 helper;否则用 `<image_root>/<batch_id>/<slug>/`(= 该行 new 图 dirname)调用。
- 多选时以当前焦点行打开,不因 selectionModel 返回顺序导致打开到非当前行。
- 选中行账号无法匹配 → 中文提示,不调用打开 helper、不猜路径。
- 未选行 + 批次筛选具体批次 → 用 `<image_root>/<batch_id>/` 调用。
- 未选行 + 全部批次 → 用 `<image_root>/` 调用。
- 目标目录不存在 → 弹提示、**不调用打开 helper、不创建目录**。
- 打开 helper 被 mock:单测断言目标路径正确、平台分支正确;不真实执行 `os.startfile` / `open` / `xdg-open`。
- 按钮位于筛选行、不在生成/重置按钮排;tooltip 说明级联。
- 生成运行中按钮仍可用(只读动作,不必禁用)。
- 验证命令(unittest,不引入 pytest):
- `py -3.10 -m unittest tests.test_gui`
- `python -m ruff check app tests main.py`
- `py -3.10 -m compileall app main.py`
- `py -3.10 -m unittest discover -s tests`
- `git diff --check`
## 边界(不改什么)
- 不改图片落盘路径/层级、`image_paths` 生成逻辑、`image_dir` 配置。
- 不新建「打包/压缩」功能(只打开文件夹,压缩由用户在文件管理器做)。
- 不改画廊弹窗(T-567)、生成/重置链路、批次筛选逻辑本身。
- 不自动创建缺失目录、不删除任何文件。
- 不改 ③/①/CDP/DB/cmhub。
- 不把打开目录失败写成任务失败,不修改任务状态、批次状态或图片路径字段。
## 执行记录
(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策。)