Files

93 lines
8.5 KiB
Markdown
Raw Permalink 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: DONE
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。
- 不把打开目录失败写成任务失败,不修改任务状态、批次状态或图片路径字段。
## 执行记录
- 2026-07-10:已实现②「打开图片文件夹」按钮,位于 AI生成筛选行刷新按钮右侧;按钮按选中行、具体批次、全部批次三级规则定位本地图片目录,运行中不禁用,不修改任何任务/批次状态。
- 2026-07-10:新增 `app/gui/file_manager.py`,封装跨平台打开目录逻辑;Windows 使用 `os.startfile`,macOS/Linux 使用 `subprocess.Popen([...], shell=False)`,helper 不创建目录。
- 2026-07-10:选中行优先打开已有 `new_cover_path` / `old_cover_path` 的真实父目录;没有真实图片文件时回退到 `image_paths.task_image_path(..., "new")` 的账号目录;账号无法匹配时中文提示,不猜 slug。
- 2026-07-10:同步 `docs/routes.md` ②AI生成说明;补 `tests/test_gui.py` 覆盖选中行当前焦点优先、规范账号目录回退、具体批次目录、图片根目录、目录缺失不创建、账号未匹配提示、平台打开 helper 分支。
- 验证通过:
- `py -3.10 -m unittest tests.test_gui.GuiTests.test_generate_tab_opens_current_row_image_directory tests.test_gui.GuiTests.test_generate_tab_open_image_directory_falls_back_to_canonical_account_dir tests.test_gui.GuiTests.test_generate_tab_opens_batch_or_root_directory_without_selected_row tests.test_gui.GuiTests.test_generate_tab_open_image_directory_warns_without_creating_missing_dir tests.test_gui.GuiTests.test_generate_tab_open_image_directory_warns_when_selected_account_unmatched tests.test_gui.GuiTests.test_open_in_file_manager_uses_platform_file_manager`
- `python -m ruff check app tests main.py`
- `py -3.10 -m compileall app main.py`
- `git diff --check`(仅输出当前工作区既有 CRLF 提示,无空白错误)
- 提交后在干净 worktree `D:\chengma\cmshopee_verify_t581` 验证本次 T-581 提交:`python -m ruff check app tests main.py` 通过,`py -3.10 -m compileall app main.py` 通过,`git diff --check` 通过,`py -3.10 -m unittest tests.test_gui` 通过 144 项,`py -3.10 -m unittest discover -s tests` 通过 324 项。
- 当前工作区直接运行 `py -3.10 -m unittest tests.test_gui` 失败 2 项,原因是本任务开始前已有未提交改动把默认封面提示词从 `papa1` 改成「默认」,导致两个旧断言仍期望 `papa1`;该提示词改名不属于 T-581,本次未处理、未提交。