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

113 lines
7.8 KiB
Markdown
Raw Normal View History

2026-07-16 16:26:27 +08:00
---
id: T-638
title: 商品套图卖点输入自动保存与自适应高度
phase: 7
deps: [T-637]
2026-07-16 16:39:40 +08:00
status: DONE
2026-07-16 16:26:27 +08:00
created: 2026-07-16
---
## 问题 / 背景
⑥「商品套图」的「商品卖点与要求」输入框当前存在两个体验问题:
1. `textChanged` 只把文本更新到当前 `SuiteTaskState.prompt`,不会立即写入 SQLite。只有修改生成设置、开始生成等后续动作碰巧调用 `_persist_state()` 时才会保存;用户仅修改文本后直接关闭任务、关闭软件,内容可能丢失。
2. 输入框当前固定在约 `96~112px` 高度,内容较多时依赖内部竖向滚动条。左侧配置区已经由外层 `suiteConfigScroll` 统一滚动,继续保留输入框内部滚动会形成嵌套滚动,运营用户不容易判断是否还有未看到的内容。
本任务把卖点文本改为可靠的防抖自动保存,并让输入框根据完整文本高度自然向下展开。
## 方案
### 1. 卖点文本使用 500ms 防抖自动保存
修改 `app/gui/tabs/product_suite.py`:
- `prompt_edit.textChanged` 后继续立即更新当前 `SuiteTaskState.prompt`,同时启动约 `500ms` 的单次防抖保存。
- 连续输入只在最后一次变更稳定后写入 SQLite,避免每个字符都触发数据库更新。
- 防抖任务必须绑定触发变更的 `state.key` 或等价稳定标识;定时器触发时不得直接读取届时的 `_displayed_state`,避免用户快速切换商品任务后把文本写入错误项目。
- 仅当该状态已有 `project_id` 时调用既有 `image_studio.update_project_prompt()` 或等价统一持久化入口。
- 尚未创建项目时只保存在当前任务内存,不因输入一个字符自动创建临时项目,避免产生大量空草稿;后续添加图片、绑定商品或开始生成并创建项目时,必须把当前文本一并写入。
- 程序化加载项目文本期间继续受 `_loading` 或信号阻断保护,不得把回填动作识别成用户修改。
- 保存成功保持安静,不连续弹窗或刷状态栏;保存失败通过现有中文 danger 状态提示,并保留内存文本供用户继续操作。
- 可记录每个任务最近一次成功保存的文本,内容未变化时不重复写库。
### 2. 离开关键路径前同步补保存
防抖保存之外,下列操作前同步刷新控件状态并尝试保存已有项目,作为关闭窗口和快速切换时的兜底:
- 切换套图任务;
- 关闭套图任务;
- 关闭程序;
- 开始生成;
- AI 帮写结果写回后;
- 创建或绑定项目完成后。
若有尚未触发的防抖计时,先停止该计时,再保存对应任务,避免之后重复写入或保存错对象。历史生成 job 的 `prompt` 快照语义不变。
### 3. 输入框关闭内部滚动并自然增高
- 关闭「商品卖点与要求」输入框的横向和纵向滚动条。
- 保留约 `96px` 的空内容最低高度,删除当前 `112px` 最大高度限制。
- 根据文本换行后的文档实际高度动态调整控件高度,保证全部内容可见;不能只隐藏滚动条而保留固定高度。
- 文本增加时向下撑开,删除文本时允许缩回最低高度。
- 输入框宽度、窗口尺寸、字体或 Windows DPI 变化导致换行变化时重新计算高度。
- 动态高度计算应包含 frame、contents margin 和少量安全余量,避免最后一行被裁切;高度未变化时不要反复设置几何尺寸。
- 页面过长时继续由已有外层 `suiteConfigScroll` 承担滚动,不增加新的嵌套滚动区域。
实现可在 `app/gui/tabs/product_suite.py` 内增加仅供本页使用的自适应 `QPlainTextEdit` 子类,或放入现有 GUI widgets 模块复用;不为了单一控件引入复杂抽象。
### 4. 文档同步
- 更新 `docs/routes.md` 中⑥「商品套图」说明:商品卖点会自动保存,输入框随内容自然增高并由外层配置区统一滚动。
- 如果持久化调用边界有调整,在本任务执行记录中写明,不修改 SQLite schema。
## 验收要点
- [ ] 已打开正式项目或临时草稿时,修改卖点内容并停止输入约 `500ms` 后,SQLite `draft_prompt` 自动更新。
- [ ] 连续快速输入只保存最终稳定内容,不按每个字符重复写库。
- [ ] 修改后立即切换到另一套图任务,原任务内容保存到原项目,不会串写到新任务。
- [ ] 修改后立即关闭任务或关闭程序,再次打开项目仍能看到修改后的内容。
- [ ] 尚未创建项目时输入内容不会自动新增临时项目;后续添加图片或开始生成创建项目后,当前内容正确保存。
- [ ] AI 帮写结果写回输入框后同样自动保存,不需要额外点击按钮。
- [ ] 空内容时输入框高度不低于约 `96px`;输入多行内容后完整向下展开,不显示内部横向或纵向滚动条。
- [ ] 删除多行内容后输入框能缩回;窗口变窄导致换行增加时高度重新计算,最后一行不被裁切。
- [ ] 长内容由 `suiteConfigScroll` 统一滚动,下方套图结构配置仍可访问,页面不存在嵌套滚动。
- [ ] 自动保存失败时显示中文错误提示,内存中的编辑内容不被清空。
- [ ] 不影响 T-637 提示词设置弹窗、模板渲染、AI 帮写、生成任务 prompt 快照和历史重试。
## 测试
- `tests/test_product_suite_gui.py` 覆盖:
- 已有项目防抖自动保存及最终文本;
- 快速切换任务不会串写;
- 关闭前同步保存;
- 无项目输入不自动创建草稿,项目建立后保存;
- AI 帮写写回后的保存路径;
- 输入框内部滚动条关闭;
- 空文本、多行文本、删除文本和宽度变化时的动态高度。
- 运行:
```bash
py -3.10 -m unittest tests.test_product_suite_gui
py -3.10 -m unittest discover -s tests
py -3.10 -m ruff check app tests main.py
py -3.10 -m compileall app main.py
git diff --check
```
## 边界(不改什么)
- 不修改 SQLite schema、项目表字段或历史数据迁移。
- 不在用户刚输入文本时自动创建临时项目。
- 不增加显式「保存卖点」按钮,不为每次成功保存弹窗。
- 不修改 T-637 的全局套图提示词模板文件、占位符、校验器或预览弹窗。
- 不修改 cmhub 提交、轮询、下载、计费、并发或超时。
- 不修改 CDP、蝦皮主图拉取、①导入采集、②AI生成、③更新蝦皮、④账号管理或⑤设置。
## 执行记录
2026-07-16 16:39:40 +08:00
- 2026-07-16:⑥商品套图为每个 `SuiteTaskState` 增加独立的 500ms 单次保存定时器和最近成功保存快照;连续输入只写最终内容,定时回调按 `state.key` 找回原任务,不依赖届时显示中的任务。切换任务、关闭任务、关闭程序、切换项目、创建项目和开始本地工作前会停止待执行定时并同步补保存。
- 2026-07-16:未创建项目时卖点只保存在当前任务内存,不因输入文字创建临时草稿;创建正式项目或临时草稿时把当前卖点写入 `draft_prompt`。AI 帮写结果继续通过统一项目持久化入口立即保存,失败时保留内存文本并显示中文 danger 状态。
- 2026-07-16:新增 `AutoHeightPlainTextEdit`,关闭内部横向和纵向滚动条,按每个实际换行的文本行高度动态增高;空内容保持 96px,删除内容可缩回,宽度变化时重新计算,由现有 `suiteConfigScroll` 统一承载页面滚动。同步更新 `docs/routes.md`。
- 2026-07-16:`tests/test_product_suite_gui.py` 新增 6 项场景测试,覆盖防抖去重、任务切换/关闭不串写、无项目不建草稿、保存失败、AI 帮写保存和自适应高度。定向 25 项通过;隔离工作树全量 524 项 unittest、Ruff、compileall、`git diff --check` 全部通过。当前主工作区全量测试仅有 3 项原有封面默认模板 `papa1` 改名导致的失败,该未提交用户改动未纳入本任务。