Files
cmbot/tasks.md
T
adminandClaude Opus 4.8 dc2cd123fc docs: align update design with non-blocking two-phase model
Launcher only applies a staged update + launches (no download at startup);
downloading happens in the app's settings dialog. docs/10 §3/§6/§8/§12/§16/§18
rewritten for the two-phase flow (installer.download_and_stage / apply_staged);
docs/07 §4.3/§4.4 cover the gear dot + "检查并更新" button; tasks 17.21.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 12:03:23 +08:00

1008 lines
37 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.
# 开发任务清单
## 任务文档说明
本文档用于跟踪自动合成印花服饰效果图工具的整体开发任务。后续 AI 或开发者领取任务前,必须先阅读对应任务的前置文档,并严格遵守 `docs/04-development-rules.md`。
任务状态说明:
- `[x]` 已完成。
- `[ ]` 未开始。
- `[~]` 进行中或部分完成。
## 任务执行顺序
任务编号即建议开发顺序,后续任务依赖前序任务的产出:
```
1(基础服务)→ 2(数据模型)→ 3/4(文件/模板服务)
→ 5(图片合成核心)→ 6(批量任务核心)→ 6.5(低对比组合筛选核心)→ 7(主界面布局)→ 8-13(各 UI 面板)→ 14(微调状态)→ 15(测试)→ 16(打包)
```
任务 1-6 完成前不应开始任务 7 及以后的 UI 任务,否则 UI 层将缺少可依赖的数据模型和服务层。
执行每个「完善 xxx.py」类任务前,必须先读取该文件的当前内容,了解骨架代码现状,再决定新增或修改哪些内容,避免覆盖已有实现。
## 0. 已完成基础工作
- [x] 编写项目愿景文档:`docs/01-product-vision.md`
- [x] 编写 PRD:`docs/02-prd.md`
- [x] 编写技术栈文档:`docs/03-technical-stack.md`
- [x] 编写开发规则文档:`docs/04-development-rules.md`
- [x] 编写项目架构文档:`docs/05-project-architecture.md`
- [x] 编写 UI 效果图提示词:`docs/06-ui-mockup-prompt.md`
- [x] 整理 Claude UI 提示词版本:`docs/06-ui-mockup-prompt-claude.md`
- [x] 保存 UI 效果图设计稿:`docs/ui-v1.html`
- [x] 保存 UI 效果图截图:`docs/ui-v1.png`
- [x] 编写 UI 设计文档:`docs/07-ui-design.md`
- [x] 编写图像编辑器设计文档:`docs/08-image-editor-design.md`
- [x] 编写本地打包发布文档:`docs/09-packaging-release.md`
- [x] 创建 `requirements.txt`
- [x] 创建 `src/version.py`,统一维护 `APP_NAME` 和 `APP_VERSION`
- [x] 创建 `src/main.py` 最小 PySide6 入口
- [x] 创建 `src/app/main_window.py` 最小主窗口
- [x] 创建 `src/app/widgets/` 占位模块
- [x] 创建 `src/core/` 占位模块
- [x] 创建 `src/services/` 占位模块
- [x] 创建 `src/resources/` 资源目录
- [x] 创建 `tests/` 测试目录
- [x] 验证本机 Python 版本为 `3.7.9`
- [x] 验证 PySide6 版本为 `6.5.3`
- [x] 通过 `python -m py_compile` 语法检查
- [x] 验证最小 PySide6 窗口可以创建并关闭
- [x] 已提交文档规划代码
- [x] 已提交 PySide6 工程骨架代码
## 1. 工程基础完善
### 1.1 日志服务
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
- [x] 先读取 `src/services/log_service.py` 现有内容
- [x] 完善 `src/services/log_service.py`
- [x] 启动时创建 `logs/` 目录
- [x] 日志文件按启动时间或日期命名
- [x] 日志格式包含时间、模块、级别、消息
- [x] 在 `src/main.py` 中初始化日志
验收:
- [x] 启动程序后生成日志文件
- [x] 程序启动信息写入日志
- [x] 不使用 `print` 作为正式日志
### 1.2 路径与资源服务
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/09-packaging-release.md`
任务:
- [x] 在 `src/services/file_service.py` 中新增路径辅助函数(不单独建模块)
- [x] 实现 `get_app_dir()`:返回程序根目录,兼容开发环境和 PyInstaller 打包环境
- [x] 实现 `get_resource_path(relative_path)`:返回资源文件绝对路径
- [x] 实现 `get_config_path(relative_path)`:返回配置文件绝对路径
- [x] 实现 `get_log_dir()`:返回日志目录路径
- [x] 实现 `get_output_dir()`:返回默认输出目录路径
- [x] 保证 Windows 中文路径可用
验收:
- [x] 路径函数不使用开发机绝对路径
- [x] 程序目录下缺少 `logs/` 或 `output/` 时可自动创建
### 1.3 配置服务
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
- [x] 先读取 `src/services/config_service.py` 现有内容
- [x] 完善 `src/services/config_service.py`
- [x] 定义默认配置
- [x] 支持读取 JSON 配置
- [x] 支持保存 JSON 配置
- [x] 配置损坏时使用安全默认值并记录日志
验收:
- [x] 缺少配置文件时程序可启动
- [x] 配置文件损坏时程序可启动并记录日志
- [x] 不会无提示清空用户配置
## 2. 核心数据模型
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 先读取 `src/core/models.py` 现有内容
- [x] 完善 `src/core/models.py`
- [x] 定义 `ImageAsset`
- [x] 定义 `TransformState`
- [x] 定义 `Template`
- [x] 定义 `ExportOptions`
- [x] 定义 `BatchOptions`
- [x] 定义 `ComposeResult`
- [x] 定义 `BatchResult`
验收:
- [x] `models.py` 不依赖 PySide6 UI 控件
- [x] 变换状态包含 `x/y/width/height/rotation/keep_aspect_ratio`
- [x] 模型可以被 core、services 和 UI 层复用
## 3. 文件扫描与素材导入
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
- [x] 先读取 `src/services/file_service.py` 现有内容
- [x] 完善 `src/services/file_service.py`
- [x] 支持递归扫描衣服图片文件夹
- [x] 支持递归扫描印花图片文件夹
- [x] 支持格式:PNG、JPG、JPEG、WEBP
- [x] 忽略不支持格式并记录日志
- [x] 保留来源子文件夹信息
- [x] 生成安全输出文件名
验收:
- [x] 可以扫描包含子文件夹的中文路径
- [x] 不修改原始素材文件
- [x] 不支持文件格式不会导致扫描失败
## 4. 模板服务
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
- [x] 先读取 `src/services/template_service.py` 现有内容
- [x] 完善 `src/services/template_service.py`
- [x] 定义内置模板
- [x] 支持读取自定义模板 JSON
- [x] 支持保存自定义模板 JSON
- [x] 支持新增、重命名、删除自定义模板
- [x] 校验模板字段
验收:
- [x] 内置模板和自定义模板可区分
- [x] 单个模板损坏不影响全部模板加载
- [x] UI 不直接操作模板 JSON 文件
## 5. 图片合成核心
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 先读取 `src/core/composer.py` 现有内容
- [x] 完善 `src/core/composer.py`
- [x] 使用 Pillow 读取衣服底图和印花图
- [x] 支持透明 PNG alpha 合成
- [x] 支持按 `TransformState` 缩放印花
- [x] 支持按 `TransformState` 旋转印花
- [x] 支持旋转后按中心点对齐粘贴
- [x] 支持 PNG 导出
- [x] 支持 JPG 导出
- [x] 输出文件名避免默认覆盖
验收:
- [x] 合成逻辑不依赖 PySide6 UI 控件
- [x] 输出尺寸默认与衣服底图一致
- [x] 透明 PNG 不出现黑底或白底
- [x] 印花部分超出画布时不会报错
## 6. 批量任务核心
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
- [x] 先读取 `src/core/batch.py` 现有内容
- [x] 完善 `src/core/batch.py`
- [x] 支持多衣服 × 单印花
- [x] 支持单衣服 × 多印花
- [x] 支持一一匹配
- [x] 支持全组合
- [x] 复用 `core/composer.py`
- [x] 单个任务失败时继续处理剩余任务
- [x] 汇总成功数量、失败数量和失败原因
验收:
- [x] 不复制第二套合成算法
- [x] 单张失败不会中断整个批量任务
- [x] 失败原因可供 UI 和日志使用
## 6.5 低对比组合筛选核心
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 定义可见度状态:`正常`、`偏低`、`不明显`、`无法判断`
- [x] 新增低对比分析核心函数,位置应符合架构分层,不能写进 UI 事件
- [x] 基于当前模板或 `TransformState` 获取衣服目标区域
- [x] 只分析衣服目标区域颜色,不使用整张衣服图判断
- [x] 只统计印花 alpha 有效像素,例如 `alpha > 20`
- [x] 计算 RGB 颜色距离
- [x] 计算亮度差
- [x] 根据阈值输出可见度状态和评分
- [x] 单个组合分析失败时返回 `无法判断`,不得中断整个队列
- [x] 为后续队列项保存可见度结果预留字段或结果结构
验收:
- [x] 白色衣服配浅色印花时可标记为 `偏低` 或 `不明显`
- [x] 深色衣服配深色印花时可标记为 `偏低` 或 `不明显`
- [x] 透明 PNG 的透明区域不参与印花颜色判断
- [x] 低对比分析不修改原始衣服图片和原始印花图片
- [x] 分析逻辑不依赖 PySide6 UI 控件
- [x] 失败项不会中断批量队列生成或导出流程
## 7. 主界面布局
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
- [x] 先读取 `src/app/main_window.py` 现有内容
- [x] 将 `src/app/main_window.py` 从最小窗口扩展为主布局
- [x] ~~实现顶部标题和版本号~~(后移除内容区自绘标题栏:与系统窗口标题重复,名称/版本改由系统标题栏显示,见 17.7)
- [x] 实现流程页签区域
- [x] 实现左侧素材栏容器
- [x] 实现中间预览区容器
- [x] 实现右侧参数栏容器
- [x] 实现底部合成队列容器
- [x] 实现底部状态栏
验收:
- [x] 窗口标题显示 `APP_NAME` 和 `APP_VERSION`
- [x] UI 布局接近 `docs/ui-v1.png`
- [x] 不把所有业务逻辑塞进 `main_window.py`
## 8. 素材列表 UI
前置阅读:
- `docs/02-prd.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
- [x] 先读取 `src/app/widgets/image_list_panel.py` 现有内容
- [x] 实现衣服图片列表
- [x] 实现印花图片列表
- [x] 实现打开文件夹按钮
- [x] 实现全选和取消全选
- [x] 实现单张勾选和取消勾选
- [x] 显示已选数量和总数量(例如:`含子文件夹 · 共 12`)
- [x] 显示来源子文件夹标签和文件名
- [x] 实现排序选择器(默认按文件夹+名称排序)
- [x] 点击素材后更新当前预览选择
验收:
- [x] 支持递归导入素材
- [x] 当前预览选中态和批量勾选态可区分
- [x] 不在 UI 层重复实现文件扫描规则
## 9. 图像预览编辑器
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 先读取 `src/app/widgets/image_canvas.py` 现有内容
- [x] 使用 `QGraphicsView / QGraphicsScene`
- [x] 显示衣服底图(scene 坐标与原图像素坐标保持一致)
- [x] 显示印花图层
- [x] 实现印花拖动
- [x] 实现缩放控制点(至少四角)
- [x] 实现旋转控制点(顶部中心外侧)
- [x] 实现预览缩放(通过 `QGraphicsView` view transform,不修改 scene 内容)
- [x] 实现预览顶部栏:显示当前底图文件名、印花文件名、操作模式按钮(移动/缩放/旋转)、缩放比例控制
- [x] 实现画布操作提示 Overlay(拖动移动 / 四角缩放 / 顶部旋转 / Shift 锁比例)
- [x] 将交互结果同步为 `TransformState`
验收:
- [x] 预览缩放不改变真实合成参数
- [x] 拖动后坐标为衣服原图像素坐标
- [x] 缩放后宽高同步
- [x] 旋转后角度同步
- [x] 不使用截图作为导出结果
## 10. 参数面板 UI
前置阅读:
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 先读取 `src/app/widgets/transform_panel.py` 现有内容
- [x] 完善 `src/app/widgets/transform_panel.py`
- [x] 实现 X/Y 坐标输入
- [x] 实现宽度/高度输入
- [x] 实现锁定宽高比例
- [x] 实现角度输入
- [x] 实现左旋 90 度和右旋 90 度按钮
- [x] 与 `ImageCanvas` 双向同步 `TransformState`
验收:
- [x] 参数输入后预览同步更新
- [x] 预览交互后参数输入框同步更新
- [x] 程序化更新输入框不会造成信号循环
## 11. 模板面板 UI
前置阅读:
- `docs/02-prd.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
- [x] 先读取 `src/app/widgets/template_panel.py` 现有内容
- [x] 完善 `src/app/widgets/template_panel.py`
- [x] 显示模板下拉框
- [x] 支持选择模板
- [x] 支持保存模板
- [x] 支持另存为模板
- [x] 支持重置为模板
- [x] 通过 `template_service` 操作模板
验收:
- [x] 选择模板后更新预览和参数面板
- [x] UI 不直接修改模板 JSON
- [x] 自定义模板重启后仍可用
## 12. 导出面板 UI
前置阅读:
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
- `docs/09-packaging-release.md`
任务:
- [x] 先读取 `src/app/widgets/export_panel.py` 现有内容
- [x] 完善 `src/app/widgets/export_panel.py`
- [x] 实现输出目录选择
- [x] 实现输出格式选择
- [x] 实现质量选择
- [x] 实现导出当前单张
- [x] ~~实现应用为模板入口~~(后移除:与模板区域「另存为」重复,见 17.3)
- [x] ~~实现重置为模板入口~~(后移除:与模板区域「重置为模板」重复,见 17.3)
- [x] 显示当前操作影响范围提示
验收:
- [x] 缺少衣服或印花时导出按钮禁用
- [x] 输出目录不可写时提示用户并记录日志
- [x] 单张导出调用 `core/composer.py`
## 13. 合成队列 UI
前置阅读:
- `docs/02-prd.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
- [x] 实现底部合成队列表格(建议使用 `QTableView` + 模型视图结构)
- [x] 实现队列头部批量模式切换:多衣服×单印花 / 单衣服×多印花 / 一一匹配 / 全组合(矩阵)
- [x] 实现队列统计显示(共 N 项 · 完成 · 进行 · 失败 · 待导出)
- [x] 实现「重置全部」按钮
- [x] 实现「导出选中」按钮
- [x] 实现「开始批量导出」按钮(导出中切换为停止/暂停)
- [x] 实现队列折叠/展开
- [x] 显示任务状态(已完成 / 待导出 / 导出中 / 失败)
- [x] 导出中任务显示进度条
- [x] 显示失败原因
- [x] 支持选中队列项并更新预览区和参数面板
验收:
- [x] 队列统计显示总数、完成、进行、失败、待导出
- [x] 导出中任务显示进度条
- [x] 失败行显示可理解原因
- [x] 选中队列项可以加载对应状态
## 14. 微调状态
前置阅读:
- `docs/07-ui-design.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 定义队列项微调状态
- [x] 手动调整队列项后标记为 `已微调`
- [x] 微调项保存独立 `TransformState`
- [x] 批量导出时微调项使用自己的参数
- [x] 未微调项使用模板参数
验收:
- [x] 已微调项不会被无提示覆盖
- [x] 队列表格能显示 `模板` 和 `已微调`
## 15. 测试
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 编写 `tests/test_composer.py`
- [x] 编写 `tests/test_templates.py`
- [x] 测试透明 PNG 合成
- [x] 测试缩放参数
- [x] 测试旋转中心
- [x] 测试文件扫描
- [x] 测试模板读写
- [x] 测试批量任务生成
验收:
- [x] 核心逻辑测试不依赖 GUI
- [x] 测试可在 Python 3.7 环境运行
## 16. 打包
前置阅读:
- `docs/03-technical-stack.md`
- `docs/04-development-rules.md`
- `docs/09-packaging-release.md`
任务:
- [x] 创建打包脚本
- [x] 验证 PyInstaller `onedir` 打包
- [x] 打包资源文件
- [x] 打包默认配置和模板
- [x] 验证打包后程序启动
- [x] 验证标题栏版本号
- [x] 验证单张导出
验收:
- [ ] 打包产物可在无 Python 环境的 Windows 电脑启动
- [x] 发布包不包含源代码、缓存、测试输出和私人配置
## 17. 体验优化(持久化与独立重置)
第二阶段根据实际使用反馈新增。
### 17.1 用户偏好持久化
前置阅读:
- `docs/02-prd.md`(6.6 模板选择、6.9 批量合成导出、9 数据与文件结构)
- `docs/05-project-architecture.md`(4.12 config_service)
任务:
- [x] 先读取 `src/services/config_service.py` 与 `src/app/main_window.py` 现有内容
- [x] 在 `main_window` 启动时调用 `load_config()` 并把初值分发给各面板(集中管理,不让控件各自读写)
- [x] 在 `config_service` 默认配置中新增 `last_template`、`last_batch_mode`
- [x] 记住并在启动时恢复上次选择的模板;自定义模板已删除时回退到第一个内置模板
- [x] 记住并在启动时恢复上次选择的批量模式;持久化值非法时回退默认(全组合)
- [x] 面板选择变化时「改一次存一次」回写配置
- [x] `template_panel` 增加模板选择变化信号(携带模板名)
- [x] `queue_panel` 增加 `set_batch_mode()` 与批量模式变化信号
验收:
- [x] 重启后自动恢复上次选择的模板和批量模式
- [x] 自定义模板被删除或批量模式值非法时安全回退,不报错
- [x] UI 控件不直接读写配置文件
### 17.2 位置 / 尺寸 / 旋转独立重置
前置阅读:
- `docs/07-ui-design.md`(7 右侧参数栏、10 视觉规范)
- `docs/08-image-editor-design.md`
任务:
- [x] 先读取 `src/app/widgets/transform_panel.py` 现有内容
- [x] 在 `位置` / `尺寸` / `旋转` 三个分区标题右侧各加一个独立 `重置` 按钮
- [x] 各重置只把对应参数还原为当前选中模板的值,互不影响(位置→仅 X/Y;尺寸→仅宽/高,位置不变;旋转→仅角度)
- [x] 移除合并的 `重置位置 / 尺寸 / 角度` 按钮与角度旁的 `归零` 按钮
- [x] 重置按钮采用幽灵样式(静止灰、悬停蓝、按下深蓝、禁用更灰),不使用红色
- [x] 未选模板或未加载衣服/印花时三个按钮置灰
- [x] 由 `main_window` 取模板换算值并按项合并进当前 `TransformState`
验收:
- [x] 位置、尺寸、旋转可分别独立重置,互不影响
- [x] 尺寸重置只改变宽 / 高,不改变位置 X / Y
- [x] 按钮视觉层级符合 `docs/07-ui-design.md` 第 10 节
### 17.3 按钮去重(导出面板模板入口)
前置阅读:
- `docs/07-ui-design.md`(7.1 模板区域、7.7 操作按钮)
任务:
- [x] 移除导出面板的 `应用为模板` 与 `重置为模板` 按钮(与模板区域的 `另存为` / `重置为模板` 是同一动作的重复入口)
- [x] 模板相关操作统一留在模板区域,导出面板只负责输出与导出
验收:
- [x] 同一个动作在界面上只出现一次,命名一致
- [x] 导出面板仅保留输出设置与 `导出当前单张`
### 17.4 「重置为模板」归位并更名为「位置尺寸旋转重置为当前模板」
前置阅读:
- `docs/07-ui-design.md`(7 右侧参数栏、10 视觉规范)
任务:
- [x] 把模板区域的 `重置为模板` 按钮移出,移到参数栏底部(旋转分区下方)
- [x] 更名为 `位置尺寸旋转重置为当前模板`,与三个分项 `↺ 重置` 构成「全部 + 分项」两级
- [x] 还原逻辑改为把位置/尺寸/旋转整体设为模板值,并与分项重置同走 `_apply_preview_transform`
- [x] 与三个分项重置一起受「未加载图片时置灰」控制
- [x] 采用安静、全宽、悬停出强调色的样式(不使用红色)
验收:
- [x] 模板区域只剩 `保存` / `另存为`,不含整体重置
- [x] `位置尺寸旋转重置为当前模板` 在旋转分区下方,未加载图片时置灰
- [x] 点击后位置、尺寸、旋转一并还原为当前选中模板
### 17.5 模板删除按钮
前置阅读:
- `docs/07-ui-design.md`(7.1 模板区域)
任务:
- [x] 在模板区域增加 `删除` 按钮(复用 `template_service.delete_template`)
- [x] `删除` 仅对自定义模板可用,选中内置模板时隐藏(与 `保存` 一致)
- [x] 删除前弹二次确认对话框;确认后才删除
- [x] 删除后刷新下拉框并回选第一个内置模板,同步持久化选择
- [x] `删除` 用红色文字提示危险但保持安静样式
验收:
- [x] 可在界面上删除自定义模板,无需手改 `templates.json`
- [x] 内置模板不显示 `删除`
- [x] 删除有二次确认,取消则不删
### 17.6 参数/导出面板紧凑化与命名
前置阅读:
- `docs/07-ui-design.md`(7.2 位置区域、7.3 尺寸区域、7.5 输出设置区域)
任务:
- [x] 「位置」的 X、Y 输入框放在同一行(标签简化为 `X` / `Y`)
- [x] 「尺寸」的宽、高输入框放在同一行(标签简化为 `宽` / `高`),`锁定宽高比例` 在其下方
- [x] 导出面板「格式」「质量」放在同一行
- [x] 底部整体重置按钮更名为 `位置尺寸旋转重置为当前模板`
- [x] 仅改布局/文案,不动同步与导出逻辑
验收:
- [x] X/Y 一行、宽/高一行、格式/质量一行,界面更紧凑
- [x] 拖动/缩放预览后输入框仍正常同步
- [x] 质量仍仅在 JPG 时可用
### 17.7 移除重复的自绘标题栏与无效「设置」按钮
前置阅读:
- `docs/07-ui-design.md`(3 整体布局、4.1 标题栏)
任务:
- [x] 移除标题栏里点了无反应的占位「设置」按钮
- [x] 移除内容区自绘标题栏(与系统窗口标题重复),名称/版本由系统标题栏(`setWindowTitle`)显示
- [x] 同步清理相关样式与未用常量
验收:
- [x] 界面不再出现重复的软件名称/版本号
- [x] 系统窗口标题仍显示 `APP_NAME` 与 `APP_VERSION`
- [x] 工作区因移除标题栏而获得更多纵向空间
### 17.8 加载文件夹后默认预览第一张
前置阅读:
- `docs/07-ui-design.md`(5.1 衣服图片面板)
任务:
- [x] 衣服、印花面板加载文件夹后,自动选中并预览第一张(按当前排序)
- [x] 空文件夹不选;把选中逻辑抽成按行选择以复用
- [x] 与现有「模板粘性」衔接:自动选印花后按当前模板落位
验收:
- [x] 加载文件夹后画布不再为空、无需手动点选
- [x] 两个文件夹加载顺序无关,最终都能得到合成预览
### 17.9 输出按「时间戳 → 印花」分组到子文件夹
前置阅读:
- `docs/02-prd.md`(6.8 单张合成导出、9 数据与文件结构)
任务:
- [x] `make_safe_output_path` 输出到 `<输出目录>/<印花文件名>/<衣服文件名>.<扩展名>`
- [x] 新增 `timestamped_run_dir(base)`,每次导出运行在输出目录下新建时间戳文件夹
- [x] 一次导出运行(批量/单张)共用同一个时间戳;批量在开始时计算一次
- [x] 单张导出、队列导出、`core.batch.run_batch` 均接入时间戳层
- [x] 子文件夹由 `composer.compose` 保存时自动创建;重名仍追加 `_1`/`_2`
验收:
- [x] 路径形如 `输出目录/<时间戳>/<印花文件名>/<衣服文件名>.<扩展名>`
- [x] 重复运行落在不同时间戳文件夹,互不覆盖
- [x] 一次批量运行内所有图片共用同一个时间戳文件夹
### 17.10 记住上次的衣服/印花文件夹
前置阅读:
- `docs/02-prd.md`(6.1 图片加载、9 数据与文件结构)
- `docs/05-project-architecture.md`(4.12 config_service)
任务:
- [x] `_AssetPanel` 打开对话框时定位到上次目录(`set_start_dir`),并在选中后发 `folder_opened` 信号
- [x] `ImageListPanel` 暴露 `set_garment_start_dir` / `set_print_start_dir` 与 `garment_folder_opened` / `print_folder_opened`
- [x] `main_window` 启动恢复 `last_garment_dir` / `last_print_dir`,并在打开文件夹时「改一次存一次」回写
- [x] UI 不直接读写配置,统一经主窗口
验收:
- [x] 重启后打开「打开文件夹」对话框定位到上次的衣服/印花目录
- [x] 目录已不存在时安全回退,不报错
### 17.11 修复:批量导出忽略所选输出格式/质量/目录
前置阅读:
- `docs/07-ui-design.md`(7.5 输出设置区域、8 底部合成队列)
问题:
- `queue_panel.set_export_options()` 从未被调用,导出面板也无「选项变化」信号,导致队列始终使用默认 `ExportOptions`(PNG、默认目录),批量导出无视 UI 所选格式/质量/目录。
任务:
- [x] 导出面板新增 `export_options_changed` 信号与公开 `current_options()`
- [x] 格式/质量/输出目录任一变化即发出当前 `ExportOptions`
- [x] `main_window` 接到 `queue_panel.set_export_options`,并在启动时同步一次
- [x] 单张导出 `_do_export` 改用同一 `current_options()` 来源
验收:
- [x] 选 JPG 后批量导出的文件为 `.jpg` 且按 JPEG 保存
- [x] 批量导出跟随所选输出目录与质量
### 17.12 合并图文件名加上印花名
前置阅读:
- `docs/02-prd.md`(6.8 单张合成导出、9 数据与文件结构)
任务:
- [x] `make_safe_output_path` 的文件名由 `<衣服文件名>` 改为 `<衣服文件名>_<印花文件名>`
- [x] 目录分组结构不变(仍 `输出目录/<时间戳>/<印花文件名>/`)
- [x] 重名仍追加 `_1`/`_2` 避免覆盖
验收:
- [x] 输出文件形如 `输出目录/<时间戳>/TY030/1_TY030.png`
- [x] 单张导出与批量导出一致
### 17.13 隐藏「导出当前单张」按钮
前置阅读:
- `docs/07-ui-design.md`(7.7 操作按钮、10 视觉规范)
任务:
- [x] 隐藏导出面板的 `导出当前单张` 按钮(`setVisible(False)`)
- [x] 保留按钮与 `_do_export` 逻辑,便于后续需要时再显示
- [x] 导出统一经底部合成队列完成
验收:
- [x] 导出面板不再显示 `导出当前单张`,只剩输出设置
- [x] 队列的批量/选中导出不受影响
### 17.14 修复:选中未微调队列项时预览未按模板落位
前置阅读:
- `docs/02-prd.md`(6.6 模板选择)
- `docs/07-ui-design.md`(12.4 选中队列项)
问题:
- `_on_queue_item_activated` 仅在 `item.transform` 存在(已微调)时覆盖画布变换;未微调项保留 `load_print` 的写死默认(印花≈衣服宽 40%、居中),预览尺寸与选中模板不一致——「模板粘性」在队列项激活路径的遗漏分支。
任务:
- [x] 选中队列项且 `item.transform` 为空时,调用 `template_panel.apply_current()` 按当前模板落位
- [x] 仍在 `_loading_queue_item` 保护下,避免被误标为「已微调」
- [x] 与预览流程(`_on_print_preview`)行为一致
验收:
- [x] 点击未微调队列项,预览印花为选中模板要求的大小/位置
- [x] 已微调项仍显示其自身参数,不被覆盖
### 17.15 修复:批量导出对未微调项复用单个像素变换
前置阅读:
- `docs/02-prd.md`(6.6 模板选择)
- `docs/08-image-editor-design.md`(变换状态)
问题:
- `QueuePanel` 用单个像素级 `TransformState`(`_template_transform`,来自 `template_applied`,按「当时画布那张衣服/印花尺寸」算出)作为所有未微调项的回退变换。
- `TransformState` 是「衣服原图像素坐标」,对尺寸不同的衣服/印花原样复用会错位、错尺寸(如 1000×1000 算出的 x=400/w=250 套到 2000×3000 上,印花跑到左上、尺寸只剩一半)。
- 影响批量导出与「导出选中」中所有未微调项。
任务:
- [x] 队列改存模板对象(比例坐标 `Template`)而非单个像素变换:`set_template()` 取代 `set_transform()`
- [x] 导出时按每项自身衣服/印花尺寸现算:抽纯函数 `core.composer.resolve_transform()`(已微调项用自身变换,否则 `template.to_transform_state(gw,gh,pw,ph)`)
- [x] widget 仅负责读图尺寸(`core.composer.image_size()`,只读 header)+ 单次运行内缓存
- [x] `main_window` 在 `template_changed`/`template_applied` 时把模板推给队列,并在启动时初始同步
- [x] 新增 4 个纯函数单测覆盖:已微调优先、不同尺寸各自换算、空模板/空尺寸返回 None
验收:
- [x] 一个批次内含不同尺寸的衣服/印花时,未微调项各自按模板正确落位
- [ ] GUI 实测:混合尺寸批量导出,结果图印花位置/大小均正确
### 17.16 局域网更新 · 阶段②:启动时检测并通知
前置阅读:
- `docs/10-lan-update.md`(§7 更新源、§8 流程、§16 阶段②)
- `docs/02-prd.md`(app_config 的 `update_source`)
说明:
- 阶段①(数据目录分离)已在 commit `76f2c6d` 完成。本任务实现阶段②「只读通知」,不自动安装。
任务:
- [x] 配置项 `update_source`(空 = 不检查)写入 `DEFAULT_CONFIG`
- [x] `services/update_service.py`:`parse_version` / `is_newer` / `check_for_update`,读取 `<source>/manifest.json`,任何不可达/损坏/非更新一律返回 None(不抛错、不阻塞)
- [x] 主窗口顶部通知横幅(默认隐藏):「发现新版本 vX.Y.Z」+「打开更新目录」+ 关闭
- [x] 检查在后台守护线程进行,经 Qt 队列信号回主线程显示横幅(更新源不可达不卡启动)
- [x] 「打开更新目录」用 `QDesktopServices` 打开 `manifest.source`
- [x] 15 个纯函数单测覆盖版本比较与 `check_for_update` 各分支
验收:
- [x] 单测通过;更新源为空/不可达时静默跳过
- [x] GUI 实测:配置可达更新源 + 高版本 manifest → 启动后显示横幅,点击打开目录
- [x] GUI 实测:更新源不可达 → 正常启动、无横幅、无卡顿
### 17.17 局域网更新 · 阶段③:自动安装启动器(脚本)
前置阅读:
- `docs/10-lan-update.md`(§3 架构、§8 流程、§16 阶段③)
说明:
- 实现 PowerShell 启动器 `scripts/update.ps1`,跑通「检查 → staging → 校验 → 原子切换 → 启动 → 降级」整条链路。先以脚本验证流程,后续再决定是否编译为 `Launcher.exe`。
任务:
- [x] `scripts/update.ps1`:读 `current.txt` 与 `data\config\app_config.json` 的 `update_source`
- [x] 读远端 `manifest.json`、语义化版本比较,仅当远端更高才更新
- [x] robocopy 拷到 `staging\<ver>.tmp` → 校验 `marker` → 重命名进 `versions\<ver>` → 原子写 `current.txt`(ascii 无 BOM)
- [x] 启动 `versions\<current>\CMBot.exe` 并设 `CMBOT_DATA_DIR`
- [x] 降级:源不可达 / robocopy 失败 / marker 缺失 / 坏 manifest 一律启动本地现版本
- [x] 假版本目录验证 6 个用例(更新、幂等、源不可达、未配置源、下载损坏、坏 manifest)全过
- [x] 接入真实安装结构:新增 `scripts/install_local.ps1` 产出 `%LOCALAPPDATA%\CMBot\app` / `data` / `staging` 启动器布局
- [ ] 真实双机实测
### 17.18 更新传输统一改为 HTTP
前置阅读:
- `docs/10-lan-update.md`(已整体改为 HTTP:§7 源、§8 流程、§14 安全、§16 阶段)
- `docs/02-prd.md`(`update_source` / `update_user` / `update_pass`)
背景:
- 更新源改为 HTTP(S) 文件服务(已部署 gohttpserver + nginx,HTTP Basic Auth,示例 `http://cm.xiapi.com/`)。§17.16/§17.17 的本地文件版与 UNC/robocopy 版作为历史保留,本任务把传输统一到 HTTP。
任务:
- [x] 文档:`docs/10` 由 UNC/robocopy 全面改为 HTTP(源/清单/流程/安全/阶段);`docs/02`、`docs/05` 同步配置项
- [x] 配置项 `update_user` / `update_pass` 写入 `DEFAULT_CONFIG`
- [x] `update_service.check_for_update` 支持 `http(s)://` 源 + Basic Auth(当前仅 `open()` 本地文件,填 URL 静默返回 None),补单测
- [x] 启动器 `update.ps1` 改 HTTP:带凭据下载 zip → 校验 SHA-256 → 解压到 `staging\app.new` → `app/app.old` 切换回滚
- [x] manifest 字段由 `source`/`files`/`marker` 改为 `url`/`sha256`/`size`
- [x] 发布流程脚本:build → 打 zip → 算 SHA-256 → 写 manifest → 上传
- [x] BOM 兼容:`check_for_update` 用 `utf-8-sig` 解码,`build.ps1` 写 manifest 不带 BOM(否则 PS5.1 的 UTF8 BOM 会让 app 内 `json.loads` 静默失效);补 2 个 BOM 单测
- [x] `update.ps1` HTTP 版以本地 HTTP server 假发布包端到端验证(拉清单→下载→SHA-256→`app/app.old` 切换)
- [ ] 生产前将更新源切到 HTTPS、客户端改用只读账号
- [ ] 真实环境(cm.xiapi.com)端到端实测
### 17.19 启动器改 Launcher.exe + 数据移到 ~/.cmbot(便携模型)
前置阅读:
- `docs/10-lan-update.md`(§3 架构、§4 布局、§5 数据根三级回退、§9 权限、§11、§16 阶段④)
背景:
- 改用编译的 `Launcher.exe`(Python + PyInstaller onefile)取代 `update.ps1`,对终端用户更友好。
- 程序采用便携布局:解压任意可写目录即用,安装根 = `Launcher.exe` 所在目录,不限定 `%LOCALAPPDATA%`。
- 用户数据移到 `~/.cmbot`(`%USERPROFILE%\.cmbot`):始终可写、按用户隔离、不随程序更新丢失。
任务:
- [x] 文档:`docs/10` 改为便携 + `Launcher.exe` + `~/.cmbot` 模型(§3/§4/§5/§8/§9/§11/§16)
- [x] `get_data_dir()` 三级回退:`CMBOT_DATA_DIR` → 打包态 `~/.cmbot` → 开发态项目根;`tests/test_file_service.py` 5 个单测
- [x] `src/launcher.py`:复用 `update_service`,下载 zip→SHA-256→解压→`app/app.old` 切换→启动;安装根可写性检测;首次把 `app\config\` 默认模板播种到 `~/.cmbot`;`update_service` 扩展(`UpdateInfo.sha256/size/min_supported`、绝对 url 解析、`make_auth_header`/`download`);`tests/test_launcher.py` 11 个单测 + 本地 HTTP server 真实端到端验证
- [x] `build.ps1` 增产 `Launcher.exe`(PyInstaller onefile,console);发布目录改为便携布局 `Launcher.exe` + `app\`;产出两个 zip——自更新载荷 `CMBot-<ver>.zip`(app\ 内容,manifest.url 指向它)与便携安装包 `CMBot-<ver>-portable.zip`;`launcher.py` 日志初始化健壮化(--windowed/只读根不崩)。语法校验通过;实际 PyInstaller 构建需在 Windows 跑
- [x] 退休 `scripts/update.ps1` 与 `scripts/install_local.ps1`(已删除,git 历史可查)
- [ ] 端到端实测(解压到 D 盘运行、自更新、回滚)
### 17.20 设置对话框(更新配置入口)
前置阅读:
- `docs/07-ui-design.md`(4.3 设置入口、4.4 设置对话框)
- `docs/02-prd.md`(`update_source` / `update_user` / `update_pass`)
- `docs/05-project-architecture.md`(4.12 config 集中管理)
背景:
- 更新地址/账号/密码目前只能手改 `~/.cmbot/config/app_config.json`,加一个应用内设置入口,管理员配一次,launcher 与 app 内通知都跟着用。
任务:
- [x] 文档:`docs/07` §3 布局、§4.1 去掉「暂不提供设置入口」、新增 §4.3 入口 + §4.4 对话框
- [x] 页签栏右端加低调 `⚙ 配置` 按钮(`addStretch` 与编号页签隔开,非编号页签)
- [x] `src/app/widgets/settings_dialog.py`(`QDialog`,纯 UI):更新地址 / 账号 / 密码(掩码+显示) / 测试连接 / 当前版本 + 安全提示
- [x] `测试连接`:后台线程调用 `update_service.load_manifest`(区分「连不上」与「已是最新」),反馈最新/发现新版/连接失败
- [x] `main_window`:打开时注入当前 config,保存时集中 `save_config`,UI 不直接写配置
- [x] 保存后重新触发一次在线更新检查(横幅刷新;`_update_found` 改为只连一次避免重复)
- [x] 取消不改动配置
验收:
- [x] 单测:`load_manifest` 返回 dict / 缺失抛错(2 个);对话框 Qt 符号导入校验通过
- [ ] GUI 实测:改更新地址/账号/密码并持久化到 `~/.cmbot/config/app_config.json`
- [ ] GUI 实测:测试连接正确反馈三种结果
- [ ] GUI 实测:改完无需重启 launcher,app 内横幅按新配置刷新
### 17.21 更新改为非阻塞:app 内下载 + 启动器只应用
前置阅读:
- `docs/10-lan-update.md`(§3 两阶段架构、§6 时机、§8 流程、§12 体验)
- `docs/07-ui-design.md`(4.3 角标、4.4 设置对话框「检查并更新」)
背景:
- 原启动器在启动时下载更新、阻塞进主界面(57MB 包看着像卡死)。改为「下载在 app 内、切换在启动器」,启动永不阻塞。
任务:
- [x] `services/installer.py`:`download_and_stage()`(app 运行时下载→SHA-256→解压→`staging\app.new`)、`apply_staged()`(启动器秒切 `app/app.old`,含「不比当前新则丢弃」「移动失败回滚」)、`staged_version()`、`is_writable`
- [x] `launcher.py` 瘦身:seed + `apply_staged` + 启动,不联网;`tests/test_launcher.py` 重写
- [x] `main_window`:发现新版点亮 `⚙ 配置` 红点(删除旧阻塞横幅与「打开更新目录」)
- [x] `settings_dialog`:加「检查并更新」→ 下载暂存 → 「下次启动生效」
- [x] `tests/test_installer.py` 11 个单测 + 本地 HTTP server 真实端到端(下载暂存→app 不动→应用切换)
- [x] 文档:`docs/10` §3/§6/§8/§12/§16/§18、`docs/07` §4.3/§4.4 对齐
- [ ] GUI 实测:角标点亮 → 设置里更新 → 重启生效(真机)
## 18. 后续暂缓任务
以下任务第一阶段暂不做,后续需要时再新增设计文档:
- [ ] 按电脑名控制版本
- [ ] 强制更新与版本保留策略(`mandatory` / `min_supported` 生效、`app.old` 保留策略;docs/10 §16 阶段④)
- [ ] AI 穿搭
- [ ] 导出上架流程
- [ ] 自动抠图
- [ ] 自动识别衣服区域
- [ ] 移动端应用
已交付(原属暂缓,本季完成,见 §17.16–§17.20、`docs/10-lan-update.md`):
- [x] 在线更新(HTTP 检测 + 通知 + `Launcher.exe` 自动安装 + 回滚)
- [x] 局域网/在线分发(便携包 + HTTP 文件服务 + manifest)
- [x] ~~多版本启动器~~(改为 `app`/`app.old` 单版本切换,不再做多版本并排)