--- id: T-638 title: 商品套图卖点输入自动保存与自适应高度 phase: 7 deps: [T-637] status: DONE 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:⑥商品套图为每个 `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` 改名导致的失败,该未提交用户改动未纳入本任务。