diff --git a/.gitignore b/.gitignore index ebc829e..8988c7c 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ prompts/ title_prompt.txt build/ dist/ +release/ diff --git a/app/gui/__init__.py b/app/gui/__init__.py index 1721602..600560c 100644 --- a/app/gui/__init__.py +++ b/app/gui/__init__.py @@ -6,6 +6,7 @@ import os import sys from .. import appconfig +from ..version import APP_NAME, display_name from . import widgets as _widgets from .widgets import * @@ -33,7 +34,7 @@ if QT_IMPORT_ERROR is None: else: class MainWindow(QMainWindow): def __init__(self): - raise RuntimeError("蝦皮圈優化助手 GUI 无法启动:当前 Python 环境未安装 PySide6。") + raise RuntimeError(f"{APP_NAME} GUI 无法启动:当前 Python 环境未安装 PySide6。") def _ensure_offscreen_for_headless_tests(): @@ -43,7 +44,7 @@ def _ensure_offscreen_for_headless_tests(): def main() -> int: if QT_IMPORT_ERROR is not None: - print("蝦皮圈優化助手 GUI 无法启动:当前 Python 环境未安装 PySide6。") + print(f"{APP_NAME} GUI 无法启动:当前 Python 环境未安装 PySide6。") return 1 _ensure_offscreen_for_headless_tests() app = QApplication.instance() or QApplication(sys.argv) diff --git a/app/gui/main_window.py b/app/gui/main_window.py index 397195b..78499d7 100644 --- a/app/gui/main_window.py +++ b/app/gui/main_window.py @@ -2,6 +2,7 @@ from __future__ import annotations +from ..version import display_name from .tabs.accounts import AccountsTab from .tabs.apply import ApplyTab from .tabs.collect import CollectTab @@ -9,6 +10,7 @@ from .tabs.generate import GenerateTab from .tabs.settings import SettingsTab from .widgets import * + PREFERRED_WINDOW_SIZE = (1180, 760) MIN_WINDOW_SIZE = (960, 640) WINDOW_SCREEN_MARGIN = 40 @@ -68,6 +70,7 @@ def _fit_and_center_window( max_y = available.y() + max(0, available.height() - height) window.move(min(max(x, available.x()), max_x), min(max(y, available.y()), max_y)) + class MainWindow(QMainWindow): """Main application window with the fixed five-tab workflow.""" @@ -86,7 +89,7 @@ class MainWindow(QMainWindow): or self.config.get("ai_models_path") or appconfig.ai_models_config_path(self.config) ) - self.setWindowTitle("蝦皮圈優化助手") + self.setWindowTitle(display_name()) _fit_and_center_window(self) self.setStyleSheet(BUTTON_BASE_STYLE) self._settings_tab_index = TAB_TITLES.index("⑤ 设置") diff --git a/app/version.py b/app/version.py new file mode 100644 index 0000000..e6ce626 --- /dev/null +++ b/app/version.py @@ -0,0 +1,9 @@ +"""Application identity and release version.""" + +APP_NAME = "蝦皮圈優化助手" +APP_CODE_NAME = "cmshopee" +APP_VERSION = "0.1.0" + + +def display_name() -> str: + return f"{APP_NAME} v{APP_VERSION}" diff --git a/docs/03-tech-stack.md b/docs/03-tech-stack.md index dd8b5bd..007d76a 100644 --- a/docs/03-tech-stack.md +++ b/docs/03-tech-stack.md @@ -7,7 +7,7 @@ | 维度 | 选型 | 状态 | 理由 / 说明 | | --- | --- | --- | --- | -| 语言 | Python 3.10+ | 已定 | 已有脚本均为 Python;标准库够用 | +| 语言 | Python 3.10(正式打包固定);CI 使用 3.11 | 已定 | 发布构建固定使用 `py -3.10`;源码曾验证可在 3.7.9 运行,但不作为发布打包基线 | | 运行平台 | Windows(生产);WSL 可用于开发 | 已定 | Chrome 与各 user-data-dir 在 Windows;GUI 与 Chrome 同机,CDP 走 `localhost` | | 浏览器自动化 | 自研 CDP 客户端 `app/cdp.py` | 已定 | 基于 websocket-client + requests 手写;不引入 playwright/selenium,规避代理与 Origin 坑 | | WebSocket | `websocket-client`(import `websocket`) | 已定 | 讲 CDP 协议;用 `suppress_origin=True` 绕过 403 | @@ -41,19 +41,19 @@ - **T-504 更新执行增强**:③ 支持 dry-run 预览、运行日志和按账号并行;默认 dry-run 关闭、并行关闭,不引入新依赖。 - **不引入数据库(指外部 DB)**:用 stdlib SQLite 足够;不引入 Postgres/MySQL 等。 - **生产在 Windows 直跑**:开发期我们用过 WSL→Windows 的 `netsh portproxy`(9333→9222)连 CDP;但 GUI 与 Chrome 都在 Windows 时,直接连 `127.0.0.1:9222`,无需 portproxy。 -- **PyInstaller 使用 onedir 免安装包**:T-524 先做 `dist/cmshopee/` onedir,不做自动更新器;当前打包依赖锁定 PyInstaller 5.13.2,onedir 产物是扁平结构,`cmshopee.exe` 和 DLL/PYD/依赖目录同级,`_internal/` 不作为当前验收标准。T-538 后打包版不再 `chdir` 到 exe 目录,用户本地配置、DB、图片、日志、登录态和提示词统一保留在 exe 同级 `data/` 子目录,更新时覆盖程序文件并保留 `data/`。 +- **PyInstaller 使用 onedir 免安装包**:T-524 先做 `dist/cmshopee/` onedir,不做自动更新器;T-540 后当前打包依赖锁定 PyInstaller 6.11.1,产物必须是 `cmshopee.exe` + `_internal/` 集中依赖布局。T-538 后打包版不再 `chdir` 到 exe 目录,用户本地配置、DB、图片、日志、登录态和提示词统一保留在 exe 同级 `data/` 子目录,更新时覆盖程序文件并保留 `data/`。`app/version.py` 是唯一版本源,打包脚本固定使用 `py -3.10`,并把 `dist/cmshopee/` 组装为 `release/cmshopee-/` 与 `release/cmshopee--portable.zip`。 ## 三、构建与运行命令 | 用途 | 命令 | | --- | --- | -| 安装依赖 | `python -m pip install -r requirements.txt` | +| 安装依赖 | `py -3.10 -m pip install -r requirements.txt` | | 检查 PySide6 | `python -c "import PySide6; print(PySide6.__version__)"` | -| 语法检查 | `python -m compileall app main.py` | -| 启动 GUI | `python main.py` / `python -m app` | -| 单元测试(T-006 后) | `python -m unittest discover -s tests` | +| 语法检查 | `py -3.10 -m compileall app main.py` | +| 启动 GUI | `py -3.10 main.py` / `py -3.10 -m app` | +| 单元测试(T-006 后) | `py -3.10 -m unittest discover -s tests` | | CI 自动验证 | GitHub Actions `.github/workflows/tests.yml`(Windows + Python 3.11 + `QT_QPA_PLATFORM=offscreen`) | -| 安装打包依赖 | `python -m pip install -r requirements-build.txt` | +| 安装打包依赖 | `py -3.10 -m pip install -r requirements-build.txt` | | 打包 exe | `powershell -ExecutionPolicy Bypass -File scripts\\build_exe.ps1` | | 跑单账号演示 | `python prototypes/demo.py`(分步)/ `set AUTO=1 && python prototypes/demo.py`(自动) | | 提交更新(真改线上) | `set UPDATE=1 && python prototypes/demo.py` | diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 3da8004..ff5f274 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -23,7 +23,7 @@ | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | -| T-000 | 正式代码包结构:创建 `app/`、迁入 `cdp.py` 为 `app/cdp.py`、新增 `app/__init__.py`、`app/__main__.py`、根入口 `main.py`、最小 `app/gui.py` 占位入口,并修正 prototypes 导入 | - | `python -m compileall app main.py` 通过;`python -m app`/`python main.py` 可进入入口(本机 `python` 不符合版本时用 `py -3 -m app`);GUI 未完成时给明确提示并退出;`prototypes/demo.py` 可从项目根导入 `app.cdp` | DONE | +| T-000 | 正式代码包结构:创建 `app/`、迁入 `cdp.py` 为 `app/cdp.py`、新增 `app/__init__.py`、`app/__main__.py`、根入口 `main.py`、最小 `app/gui.py` 占位入口,并修正 prototypes 导入 | - | `python -m compileall app main.py` 通过;`python -m app`/`python main.py` 可进入入口(本机 `python` 若为 isolated 且 `-m app` 不搜索当前目录,可用 `python main.py` 或 `py -3 -m app`);GUI 未完成时给明确提示并退出;`prototypes/demo.py` 可从项目根导入 `app.cdp` | DONE | | T-001 | `app/editor.py`:改标题/换封面/点更新/登录检测/**采集(读旧标题+旧封面下载)**/apply_task,复用 `app/cdp.py` | T-000 | 函数可调用,在测试商品跑通;与 `prototypes/demo.py` 行为一致 | DONE | | T-002 | `app/appconfig.py` + `config.json`(含 image_dir、ai 选择/参数段、端口等默认值;不含 AI Key) | T-000 | 读写正常;不存在则写默认;AI Key 留给 `config/ai_models.json`/T-501 | DONE | | T-003 | `app/db.py` + SQLite 建表(batches/accounts/tasks,含 Excel 行定位、状态、时间戳、重试字段) | T-000 | `init_db` 幂等;`connect` 设置 WAL/busy_timeout/foreign_keys;账号/批次/任务/各 set_* 可用;schema 同架构 5.2 | DONE | @@ -141,11 +141,12 @@ | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | -| T-541 | 打包版主窗口初始位置与小屏适配 | T-523, T-537, T-540 | Bug:在 Windows 10 虚拟机/小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` 或 release 包时,主窗口可能贴到屏幕左上角且左边/标题栏显示不全,看不到「蝦皮圈優化助手 v」标题,用户无法拖动窗口到中间。根因:`MainWindow.__init__()` 只调用 `self.resize(1180, 760)`,没有根据 `QApplication.primaryScreen().availableGeometry()` 限制初始尺寸、没有居中、没有保证窗口 frame/title bar 在可用屏幕范围内;PyInstaller 本身不是根因,小屏、高 DPI、任务栏占用、多显示器或 VM 分辨率更容易触发。方案:① 抽一个主窗口初始布局 helper(如 `_fit_and_center_window(window, preferred_size=(1180,760), margin=40)`),读取当前屏幕 `availableGeometry()`;② 初始宽高不超过可用区域减安全边距,保留合理最小值(如 960x640,若屏幕更小则继续适配);③ `resize()` 后按可用区域中心 `move()`,确保窗口左上角不小于可用区域左上角,标题栏完整可见;④ offscreen/headless 测试环境不依赖真实屏幕时可跳过或用 mock geometry;⑤ 不保存/恢复历史窗口位置,避免把坏坐标持久化。同步 `docs/packaging.md` 打包验收:在目标 VM/小分辨率环境启动 release exe,窗口标题栏必须完整可见且可拖动。GUI 单测覆盖小屏 mock 下尺寸被限制、窗口被居中/夹进可用区域;不改业务流程、DB、AI、Excel、CDP/Shopee、打包产物结构 | DONE | +| T-541 | 打包版主窗口初始位置与小屏适配 | T-523, T-537, T-540 | Bug:在 Windows 10 虚拟机/小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` 或 release 包时,主窗口可能贴到屏幕左上角且左边/标题栏显示不全,看不到「蝦皮圈優化助手 v」标题,用户无法拖动窗口到中间。根因:`MainWindow.__init__()` 只调用 `self.resize(1180, 760)`,没有根据 `QApplication.primaryScreen().availableGeometry()` 限制初始尺寸、没有居中、没有保证窗口 frame/title bar 在可用屏幕范围内;PyInstaller 本身不是根因,小屏、高 DPI、任务栏占用、多显示器或 VM 分辨率更容易触发。方案:① 抽一个主窗口初始布局 helper(如 `_fit_and_center_window(window, preferred_size=(1180,760), margin=40)`),读取当前屏幕 `availableGeometry()`;② 初始宽高不超过可用区域减安全边距,保留合理最小值(如 960x640,若屏幕更小则继续适配);③ `resize()` 后按可用区域中心 `move()`,确保 `frameGeometry()` 左上角不小于可用区域左上角,标题栏完整可见;④ offscreen/headless 测试环境不依赖真实屏幕时可跳过或用 mock geometry;⑤ 不保存/恢复历史窗口位置,避免把坏坐标持久化。同步 `docs/packaging.md` 打包验收:在目标 VM/小分辨率环境启动 release exe,窗口标题栏必须完整可见且可拖动。GUI 单测覆盖小屏 mock 下尺寸被限制、窗口被居中/夹进可用区域;不改业务流程、DB、AI、Excel、CDP/Shopee、打包产物结构 | DONE | | T-525 | 引入 ruff(lint + format)+ 可选 pre-commit | T-006 | 依据 `docs/engineering-review.md` P1。加 `ruff` 配置(lint + format),先以现状为基线不做大规模风格重排,只开启安全规则(未用 import/变量、明显错误);可选 `.pre-commit-config.yaml`;不改业务逻辑;CI(T-522)可串入 ruff 检查。数据模型渐进上 mypy 作为后续可选。当前因业务优先级调整顺延到 T-526~T-528 之后 | TODO | | T-537 | 品牌名「蝦皮圈優化助手」(显示层中文品牌,cmshopee 保留为代号/标识符) | T-523 | 需求:项目/软件正式定名「蝦皮圈優化助手」。**原则**:产品**显示/品牌名** = 「蝦皮圈優化助手」;`cmshopee` 保留为**代码库/仓库/技术代号**,一切**标识符不改**——遵项目「文档与 UI 文案用中文、标识符用英文」规则,且避免非 ASCII 文件名破坏现有用户数据。**改(显示/品牌层)**:① `app/gui/main_window.py` 窗口标题 `setWindowTitle("cmshopee")` → `"蝦皮圈優化助手"`(任务栏/标题栏显示中文品牌);② 文档产品名散文 `docs/00-ai-start-here.md`、`docs/README.md`、`AGENTS.md`(及 vision/brief 若有)把「cmshopee 是一个…」改为「蝦皮圈優化助手(代号 cmshopee)是一个…」,明确品牌名并保留代号;③ 空的 `README.md` 补品牌标题行;④ 可选:`app/gui/__init__.py` PySide6 缺失启动失败提示带上品牌名。**不改(技术标识符,保留 cmshopee)**:`app` 包名、`cmshopee.exe` 打包产物名、`cmshopee.db`(+`-wal/-shm`)/`db_path` 默认值、`cmshopee.spec`、`cmshopee.log`、`config.json`/`config/*.json` 文件名、slug/`chrome_user_data_dir`、仓库目录;`app/editor.py` 注入页面的 JS 全局 `window.__cmshopee_*`(JS 标识符须 ASCII)、`app/ai.py` multipart boundary `----cmshopee-*`、`app/workers.py` `thread._cmshopee_worker`、`app/chrome.py` 快捷方式内部前缀 `cmshopee-`。**决策点**:`app/excel.py` 回写文件名后缀 `_cmshopee回写` 为用户可见产物名——本任务**默认保留**(改动会影响已生成副本命名习惯),如需改「_優化助手回写」另行确认。**影响**:不改 db/exe/spec/包名 → 打包、更新流程、现有用户数据零影响。验收:窗口标题栏/任务栏/README/相关文档显示「蝦皮圈優化助手」;`cmshopee.db`/exe/包名不变;GUI 单测若断言窗口标题须同步更新;`python -m compileall app main.py` + `python -m unittest discover -s tests` 全绿。边界:纯显示/文档层,不改任何文件名、schema、流程、CDP/Shopee 逻辑 | DONE | -| T-538 | 打包产物用户数据收进 `data/` 子文件夹(便携方案 1) | T-524 | 问题:当前 onedir 产物把用户数据(`config.json`、`config/ai_models.json`、`config/cmhub.json`、`cmshopee.db(+-wal/-shm)`、`chrome_user_data_dir/`、`images/`、`logs/`、`prompts/`、`title_prompt.txt`)生成在 exe 顶层,和程序文件/依赖文件混放:更新易误删数据、放进 Program Files 只读目录会写失败/被 VirtualStore 重定向、程序与数据关注点混杂。方案(保持便携):① **统一数据根**——所有用户数据落到 exe 同级的单个 `data/` 子文件夹;`app/appconfig.py` 用显式 `data_dir`(默认 `/data`,源码运行时可为项目根/`data`)解析并返回绝对路径,**替代现在 `main.py` 的 chdir**(chdir 影响全进程相对路径/文件对话框默认目录,脆弱);文件名保持不变(仍是 `cmshopee.db` 等),只是移入 `data/`。② **旧布局迁移**——新版首次运行若在 exe 顶层发现旧的 `config.json`/`cmshopee.db` 等,自动迁移进 `data/`(或明确提示手动迁移),不得对老用户生成空 `data/` 丢失原数据。③ **可写性检测**——启动时若 `data_dir` 不可写(如放在 Program Files),弹清晰错误「请把程序放到可写目录,勿放 Program Files」并优雅退出,不静默失败。④ **发布只含程序**——`cmshopee.spec`/`scripts/build_exe.ps1` 构建检查确保不打包 `data/` 或任何用户数据;当前锁定 PyInstaller 5.13.2,产物是扁平 onedir,`cmshopee.exe` 和 DLL/PYD/依赖目录同级,`_internal/` 不作为当前验收标准;发布 zip 不含 `data/`,从而「整包解压覆盖」也安全(覆盖程序文件、不碰 `data/`)。⑤ **更新流程前提**——原地更新=换程序留 `data/`,依赖:DB 迁移幂等且只向前(现 `init_db`+`ALTER TABLE ADD COLUMN`)、`config` 向前兼容(`load_config` 补默认值)。同步更新 `docs/packaging.md`(目录结构、首次运行数据位置、§5 更新步骤简化为「覆盖新版程序文件和依赖文件,`data/` 不动」)。测试:`test_appconfig` 断言各路径解析进 `data_dir`、可写性检测行为、旧布局迁移;`test_packaging` 断言产物不含 `data/`/用户数据。边界:只改路径解析层(`appconfig` 路径 + `main.py` 数据根 + 迁移/可写检测)、spec/构建脚本、打包文档;不改文件名、DB schema、业务逻辑、AI/Excel、CDP/Shopee 流程 | DONE | +| T-538 | 打包产物用户数据收进 `data/` 子文件夹(便携方案 1) | T-524 | 问题:当前 onedir 产物把用户数据(`config.json`、`config/ai_models.json`、`config/cmhub.json`、`cmshopee.db(+-wal/-shm)`、`chrome_user_data_dir/`、`images/`、`logs/`、`prompts/`、`title_prompt.txt`)生成在 exe 顶层,和程序文件/依赖文件混放:更新易误删数据、放进 Program Files 只读目录会写失败/被 VirtualStore 重定向、程序与数据关注点混杂。方案(保持便携):① **统一数据根**——所有用户数据落到 exe 同级的单个 `data/` 子文件夹;`app/appconfig.py` 用显式 `data_dir`(默认 `/data`,源码运行时可为项目根/`data`)解析并返回绝对路径,**替代现在 `main.py` 的 chdir**(chdir 影响全进程相对路径/文件对话框默认目录,脆弱);文件名保持不变(仍是 `cmshopee.db` 等),只是移入 `data/`。② **旧布局迁移**——新版首次运行若在 exe 顶层发现旧的 `config.json`/`cmshopee.db` 等,自动迁移进 `data/`(或明确提示手动迁移),不得对老用户生成空 `data/` 丢失原数据。③ **可写性检测**——启动时若 `data_dir` 不可写(如放在 Program Files),弹清晰错误「请把程序放到可写目录,勿放 Program Files」并优雅退出,不静默失败。④ **发布只含程序**——`cmshopee.spec`/`scripts/build_exe.ps1` 构建检查确保不打包 `data/` 或任何用户数据;发布 zip 不含 `data/`,从而「整包解压覆盖」也安全(覆盖程序文件、不碰 `data/`);T-540 后当前打包脚本固定 PyInstaller 6.11.1,产物为 `cmshopee.exe` + `_internal/` onedir,具体版本和 release 命名由 T-540 维护。⑤ **更新流程前提**——原地更新=换程序留 `data/`,依赖:DB 迁移幂等且只向前(现 `init_db`+`ALTER TABLE ADD COLUMN`)、`config` 向前兼容(`load_config` 补默认值)。同步更新 `docs/packaging.md`(目录结构、首次运行数据位置、§5 更新步骤简化为「覆盖新版程序文件和依赖文件,`data/` 不动」)。测试:`test_appconfig` 断言各路径解析进 `data_dir`、可写性检测行为、旧布局迁移;`test_packaging` 断言产物不含 `data/`/用户数据。边界:只改路径解析层(`appconfig` 路径 + `main.py` 数据根 + 迁移/可写检测)、spec/构建脚本、打包文档;不改文件名、DB schema、业务逻辑、AI/Excel、CDP/Shopee 流程 | DONE | | T-539 | ⑤ 隐藏数据路径设置(设置简化,非防逆向) | T-538, T-517 | 背景:T-538 后用户数据固定收进 exe 同级 `data/`,⑤「路径与端口」里的 DB 路径(`dbPathEdit`)、图片目录(`imageDirEdit`)、账号数据根目录(`userDataRootEdit`)已不该由用户改动——改了会指向 `data/` 之外导致数据分裂/丢失。**定性说明**:本任务是**设置简化**,与 T-517/T-529 隐藏 `test_item_id`/`dry_run` 同一思路;**不是防逆向手段**——`cmshopee.db` 在磁盘上任何 SQLite 浏览器可直接打开,隐藏 UI 字段对逆向无意义,防逆向由另行任务(发布包加固/价值上移 cmhub)承担。方案:① ⑤普通设置页**隐藏** `db_path`/`image_dir`/账号数据根三个输入框(不再占表单位置),`config.json` 对应字段保留为内部兼容——手工编辑配置仍生效(内部回滚路径),加载/保存不丢字段;② **保留可见**:Chrome 路径(每台机器安装位置不同,真实配置需求)与默认端口/端口区间/CDP 就绪超时(端口冲突需可调);③ 首次运行/迁移后各路径默认解析到 `data/` 下(沿用 T-538 的 `data_dir` 规则),无需用户感知。同步 `docs/routes.md` ⑤ 说明与 GUI 设置单测(隐藏字段不再可见、config 字段仍读写、手工配置值仍被尊重)。边界:只改⑤设置页展示层与文档;不改 `config.json` schema、路径解析逻辑(T-538 范围)、DB、业务流程、CDP/Shopee | TODO | +| T-540 | 打包版本号与 release 产物命名统一 | T-537, T-538, T-524 | 参考 `D:\chengma\cmbot` 的轻量版本控制方式,但本阶段不引入 Launcher/manifest/自动更新。方案:① 新增 `app/version.py`,统一维护 `APP_NAME="蝦皮圈優化助手"`、`APP_CODE_NAME="cmshopee"`、`APP_VERSION`,作为 GUI 标题、打包脚本、release 目录、zip 文件名和 `version.txt` 的唯一版本源;② 主窗口标题改为 `蝦皮圈優化助手 v`,PySide6 缺失提示继续读中文品牌名;③ `scripts/build_exe.ps1` 从 `app/version.py` 读取版本,并固定通过 Windows Python Launcher 调用 `py -3.10`,返回版本必须是 `3.10.x`;④ PyInstaller 仍输出 `dist\cmshopee\` 作为中间产物,再组装 `release\cmshopee-\` 和 `release\cmshopee--portable.zip`,发布目录内写 `version.txt`(无 BOM)和中文 `README.txt`;⑤ 发布目录/zip 仍不得包含 `data/`、DB、配置、图片、日志、Chrome 登录态或运营 Excel;当前 PyInstaller 6.11.1 onedir 结构必须包含 `cmshopee.exe` + `_internal\`,脚本会校验 `_internal\` 存在;⑥ 测试覆盖版本文件解析、窗口标题版本、构建脚本 release 命名/排除数据/固定 `py -3.10` 打包。边界:只做手动发版版本号一致性和 release 组装;不做在线更新、启动器、manifest、sha256 下载校验、强制更新或自动替换程序目录 | DONE | ## 里程碑 diff --git a/docs/current-state.md b/docs/current-state.md index 478273d..49d358b 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -6,11 +6,11 @@ ## 当前快照 - 日期:2026-07-07 -- 阶段:V0 单账号 CDP 流程已验证;V1 已完成 T-000 正式代码包结构、T-001 `app/editor.py` 模块化、T-002 `app/appconfig.py` 应用配置、T-003 SQLite 持久化地基、T-004 本地数据忽略规则、T-005 AI 模型清单后端、T-006 单元测试基座、T-101 账号 user-data-dir 工具、T-102 Chrome 启动器、T-103 登录保活与检测、T-103b 登录检测补充识别 Shopee accounts 登录页、T-104 PySide6 主窗口骨架、T-104b PySide6 worker 基座、T-105 Tab④ 账号管理、T-105b ④启动登录复用已打开 Chrome、T-106 账号快捷方式、T-201 Excel 导入入库、T-202 Tab① 任务列表与导入按钮、T-202b Tab① 导入汇总栏、T-203 采集旧标题旧封面、T-204 回写旧字段到原 Excel、T-204b 采集完成自动回写、T-205 首次未配账号/Chrome 未启动/未登录引导保护、T-205b 采集后关闭自动新建商品页 tab、T-206 Tab① 删除指定批次软删除、T-301 AI 生成接口、T-302 Tab② AI 生成布局与任务列表、T-302p 提示词管理、T-303 Tab② 开始生成/停止/进度、T-303b ②/③ 商品ID筛选、T-401 Tab③ 更新列表筛选与开始更新确认、T-402 Tab③ 确认后串行更新、T-403 Tab③ 结果回写与结束汇总、T-501 Tab⑤ AI 模型管理 UI、T-501b Tab⑤ 角色与生成参数、T-501c Tab⑤ Shopee 更新安全开关、T-502 换封面删第一张再上传、T-503 敏感信息本地明文保存提示与日志脱敏、T-504 多账号并行/dry-run/运行日志、T-207 ①采集诊断日志、② AI生成图片失败诊断日志补丁、T-404a ②/③ 选中记录重置、T-404 真实 Shopee 单条更新冒烟验收、T-506 正式使用批量更新体验、T-507 正式批量更新移除普通流程测试商品 ID 限制、T-508 ③ 更新shopee生产化操作区、T-509 ② 新标题人工微调、T-510 ③ 检查本轮更新文案统一、T-505 全流程诊断日志扩展、T-511 语义色板与任务状态列上色、T-512 ③高风险按钮上色与①导入校验数字标红、T-513 登录点/③Tab危险标识/破坏性按钮上色、T-514 ①②③ 首次空状态引导卡片、T-515 批次阶段进度总览、T-516 ①筛选对齐②③、T-517 ⑤设置分区与兼容字段清理、T-518 ②左栏提示词区组件密度优化、T-519 ②AI生成长任务进度条与用户可读滚动日志、T-520 ②AI生成封面可选生成开关、T-521 依赖清单(锁版本 requirements)、T-522 CI 自动跑语法 + 单元 / GUI 测试、T-523 拆分 `app/gui.py` 为 `app/gui/` 包、T-523a ②/③ 新一轮运行前清空界面日志显示、T-404b 商品详情页加载失败 toast 自动捕获、T-524 PyInstaller 打包为免安装 exe、T-526 `app/ai.py` + `appconfig` 接入 cmhub backend、T-527 ⑤设置 cmhub 网关面板、T-528 ②计费错误提示与余额展示、T-529 默认 cmhub 网关并隐藏 AI 后端选择、T-530 cmhub Base URL 规整与 404 明确提示、T-531 ⑤设置未保存状态追踪与离开确认、T-532 ⑤cmhub连接成功提示显示账号名、T-533 ②增量生成:按缺失组件补生成、T-534 ②重置增强:多选/筛选范围 + 按组件重置、T-535 ②cmhub生成标题读取等待固定600秒、T-536 GUI按钮圆角全局统一、T-537 品牌名显示为蝦皮圈優化助手、T-538 打包产物用户数据收进 `data/`。 -- 技术栈:Python 3.10+,根目录 `requirements.txt` 锁定运行依赖,GitHub Actions 使用 Windows + Python 3.11 自动跑语法和单元/GUI 测试,PyInstaller onedir 打包为 Windows 免安装 exe;自研 CDP(websocket-client + requests),SQLite(sqlite3)+ `config.json` + openpyxl + AI(默认 cmhub: `config.json` 网关配置 + `config/cmhub.json` Key;direct: `config/ai_models.json` 通用 HTTP 内部兼容),GUI PySide6 5 Tab(已定)。 +- 阶段:V0 单账号 CDP 流程已验证;V1 已完成 T-000 正式代码包结构、T-001 `app/editor.py` 模块化、T-002 `app/appconfig.py` 应用配置、T-003 SQLite 持久化地基、T-004 本地数据忽略规则、T-005 AI 模型清单后端、T-006 单元测试基座、T-101 账号 user-data-dir 工具、T-102 Chrome 启动器、T-103 登录保活与检测、T-103b 登录检测补充识别 Shopee accounts 登录页、T-104 PySide6 主窗口骨架、T-104b PySide6 worker 基座、T-105 Tab④ 账号管理、T-105b ④启动登录复用已打开 Chrome、T-106 账号快捷方式、T-201 Excel 导入入库、T-202 Tab① 任务列表与导入按钮、T-202b Tab① 导入汇总栏、T-203 采集旧标题旧封面、T-204 回写旧字段到原 Excel、T-204b 采集完成自动回写、T-205 首次未配账号/Chrome 未启动/未登录引导保护、T-205b 采集后关闭自动新建商品页 tab、T-206 Tab① 删除指定批次软删除、T-301 AI 生成接口、T-302 Tab② AI 生成布局与任务列表、T-302p 提示词管理、T-303 Tab② 开始生成/停止/进度、T-303b ②/③ 商品ID筛选、T-401 Tab③ 更新列表筛选与开始更新确认、T-402 Tab③ 确认后串行更新、T-403 Tab③ 结果回写与结束汇总、T-501 Tab⑤ AI 模型管理 UI、T-501b Tab⑤ 角色与生成参数、T-501c Tab⑤ Shopee 更新安全开关、T-502 换封面删第一张再上传、T-503 敏感信息本地明文保存提示与日志脱敏、T-504 多账号并行/dry-run/运行日志、T-207 ①采集诊断日志、② AI生成图片失败诊断日志补丁、T-404a ②/③ 选中记录重置、T-404 真实 Shopee 单条更新冒烟验收、T-506 正式使用批量更新体验、T-507 正式批量更新移除普通流程测试商品 ID 限制、T-508 ③ 更新shopee生产化操作区、T-509 ② 新标题人工微调、T-510 ③ 检查本轮更新文案统一、T-505 全流程诊断日志扩展、T-511 语义色板与任务状态列上色、T-512 ③高风险按钮上色与①导入校验数字标红、T-513 登录点/③Tab危险标识/破坏性按钮上色、T-514 ①②③ 首次空状态引导卡片、T-515 批次阶段进度总览、T-516 ①筛选对齐②③、T-517 ⑤设置分区与兼容字段清理、T-518 ②左栏提示词区组件密度优化、T-519 ②AI生成长任务进度条与用户可读滚动日志、T-520 ②AI生成封面可选生成开关、T-521 依赖清单(锁版本 requirements)、T-522 CI 自动跑语法 + 单元 / GUI 测试、T-523 拆分 `app/gui.py` 为 `app/gui/` 包、T-523a ②/③ 新一轮运行前清空界面日志显示、T-404b 商品详情页加载失败 toast 自动捕获、T-524 PyInstaller 打包为免安装 exe、T-526 `app/ai.py` + `appconfig` 接入 cmhub backend、T-527 ⑤设置 cmhub 网关面板、T-528 ②计费错误提示与余额展示、T-529 默认 cmhub 网关并隐藏 AI 后端选择、T-530 cmhub Base URL 规整与 404 明确提示、T-531 ⑤设置未保存状态追踪与离开确认、T-532 ⑤cmhub连接成功提示显示账号名、T-533 ②增量生成:按缺失组件补生成、T-534 ②重置增强:多选/筛选范围 + 按组件重置、T-535 ②cmhub生成标题读取等待固定600秒、T-536 GUI按钮圆角全局统一、T-537 品牌名显示为蝦皮圈優化助手、T-538 打包产物用户数据收进 `data/`、T-540 打包版本号与 release 产物命名统一、T-541 打包版主窗口初始位置与小屏适配。 +- 技术栈:正式打包固定 Python 3.10(脚本调用 `py -3.10`),CI 使用 Windows + Python 3.11 自动跑语法和单元/GUI 测试,根目录 `requirements.txt` 锁定运行依赖,PyInstaller onedir 打包为 Windows 免安装 exe;自研 CDP(websocket-client + requests),SQLite(sqlite3)+ `data/config.json` + openpyxl + AI(默认 cmhub: `data/config.json` 网关配置 + `data/config/cmhub.json` Key;direct: `data/config/ai_models.json` 通用 HTTP 内部兼容),GUI PySide6 5 Tab(已定)。 - 生产代码:已建立 `app/` 包 + 根入口 `main.py`;`app/cdp.py` 为已验证 CDP 底座,已区分 `CDP.close()` 断开 WebSocket 与 `close_tab()` 关闭浏览器 target;`app/editor.py` 已封装登录状态检测、标题/封面/采集/更新按钮能力,打开商品详情页时会安装 toast 捕获并在详情页加载失败时上浮商品失效/无权限等错误原因,`open_product()` 内部失败会关闭本轮自动新建的失败 tab,复用用户已有 tab 不关闭,并在采集结束后只关闭本轮自动新建的商品编辑页 tab、保留用户已有 tab,更新提交成功且设置开启时可关闭本轮自动新建商品页,确认后跳回商品列表页时关闭前等待 2 秒;`click_update()` 已补 Shopee 站点侧确认框处理,页面主「更新」后若出现 `確定您要更新商品嗎?` modal(`.eds-modal__content` / `.eds-modal__box`),只点击弹窗主按钮「更新」并避开「立即優化」,且记录确认后是否跳回商品列表页;`replace_cover()` 已实现更新封面统一先校验本地旧封面备份,再点第一张删除、可见确认框、等待图片管理器稳定和上传 input 恢复,随后先点击上传块模拟人工入口、短暂等待、重新获取 input、注入文件上传新图并等待 Shopee CDN 后拖到第一位的代码路径;已修正 2026-06-30 稳定等待回归:删除第一张前不要求上传 input 可用,只等图片列表稳定;删除后再等上传入口恢复;`重複/重复/duplicate` 上传 toast 会立即判为新封面重复错误;`app/image_paths.py` 已统一新采集/新生成图片路径为 `images///__old/new.jpg`,历史 DB 路径继续按原路径读取;`app/appconfig.py` 已实现 `config.json` 默认值/读写/更新、AI 参数、端口读取、Shopee 更新安全与执行模式默认值,`ai.backend` 默认 `cmhub`,`config/ai_models.json` 模型清单 CRUD/过滤/打码/测试连接与 OpenAI-compatible base URL 自动补 endpoint,`config/cmhub.json` cmhub Key 读写/打码 helper、cmhub Base URL 规整与请求 URL 拼接 helper,以及 `mask_secret()`、`sanitize_for_log()`、`redact_secrets()` 敏感信息脱敏工具;`app/ai.py` 已实现 `gen_title()`/`gen_cover()`/`generate_batch()`,支持 direct 通用 HTTP 和 cmhub 网关 backend,direct 按默认文本/图片模型调用,cmhub 按 Base URL + 生文/生图别名调用 title/image/models/balance 接口,生图 `image_url` 安全下载后转本地 JPEG,计费 metadata 通过事件回调传出,HTTP 404 映射为 `not_found` 并给出中文排障提示,且保留重试、错误脱敏、图片 URL/base64 解析、resolution resize、jpg_quality 保存、按 `ai.generate_cover` 选择只生成标题或先并发标题再并发封面、逐条 `set_generated`、失败 `mark_failed`、步骤级事件/错误回调与停止取消未开始项;`app/prompts.py` 已实现标题提示词读写、封面模板 CRUD 与变量替换;`app/db.py` 已实现 SQLite schema、连接 PRAGMA、批次/账号/任务与阶段写库函数、T-206 批次软删除标记与默认业务查询过滤、T-404a/T-534 本地生成结果/更新状态重置函数(②生成结果可按标题/封面组件重置并保持 generated)、T-509 `update_generated_title()` 本地新标题微调函数,以及 `run_logs/run_log_events` 运行日志函数;`app/diagnostics.py` 已实现 gitignore 本地诊断日志、滚动写入、结构化 payload 和自由文本脱敏;T-505 已把 Excel 导入/回写、③更新shopee、④Chrome 启动/登录检测、⑤AI模型测试连接接入 `run_logs/run_log_events` 与本地脱敏诊断日志;`app/excel.py` 已实现多 Excel 输入列解析、整文件列校验、脏行统计跳过、导入批次与任务入库、别名匹配统计、旧标题/旧封面路径回写原 Excel、更新结果回写原 Excel 与另存副本;`app/config.py` 已实现账号 slug 与 user-data-dir 创建;`app/accounts.py` 已实现账号 CRUD 服务、端口默认分配、启动登录幂等复用已打开 Chrome、检测登录、生成快捷方式;`app/chrome.py` 已实现 Chrome 参数拼装、启动、CDP 端口探测、PowerShell `.lnk` 快捷方式生成;`app/gui/` 已由 T-523 拆分为 PySide6 GUI 包,包入口 `__init__.py` 兼容旧导入,`main_window.py` 放 `MainWindow`,`models.py` 放 3 个 TableModel,`widgets.py` 放色板/空状态/批次总览/helper,`workers.py` 放具体 GUI worker,`tabs/` 放 ①~⑤ Tab;整体仍实现 PySide6 `MainWindow`(窗口标题显示「蝦皮圈優化助手」)、五 Tab、顶部 Tab 栏防误点样式、统一语义色板、①②③任务状态列前景色、③「开始更新」warning 描边/文字色和①导入校验数字标红、④登录状态点上色、③更新shopee Tab warning 小圆点和删除类按钮 danger 样式、①②③首次空状态引导卡片、①②③批次阶段进度总览、① 导入采集的 Excel 导入按钮/导入汇总栏/批次筛选与删除批次软删除入口/QTableView 任务列表/未匹配筛选与略过标记/采集旧标题旧封面 worker/采集前账号就绪预检与④引导/采集完成自动回写/旧数据回写重试按钮与 worker/采集运行日志视图、② AI生成左右布局/标题与封面提示词管理/批次/店铺/商品ID/状态筛选/任务列表/新标题列本地微调/变量预览/生成封面图片成本开关/开始生成/停止/标题与图片双进度条/双击新旧封面预览/用户可读自动滚动AI生成运行日志/cmhub余额显示/点数不足弹窗/计费日志/重置生成结果与 `GenerateWorker`,且②重置支持多选/当前筛选结果并可只重置标题、只重置封面或重置全部;③ 更新shopee批次/店铺/商品ID/状态筛选栏/任务列表/开始更新主按钮/重置更新状态右键菜单/更新安全开关拦截与「前往设置」跳转/「检查本轮更新」按钮/开始更新确认弹窗/确认后 `ApplyWorker` 按每批最大更新条数分批执行当前筛选全部可更新记录/账号就绪和端口冲突预检/按账号并行可选/逐条 `set_applied`/运行日志/自动回写结果到 Excel/结束汇总弹窗/手动回写重试按钮、④ 账号管理表格/弹窗/按钮/快捷方式与状态栏、密码明文保存提示、⑤ 设置页 T-529 后默认展示 cmhub 网关 Base URL、API Key、动态生文/生图别名下拉、Base URL 网关根提示、刷新别名与测试连接/查余额,不再显示「AI 后端」label/dropdown、direct 模型选择、模型详情或标题/图片模型角色下拉;保存设置固定写 `ai.backend=cmhub`,保存/刷新前会把 Base URL 规整为网关根,允许先保存不完整 cmhub 配置,生成时再提示补齐;direct 模型配置、`AIModelTestWorker` 和 `config/ai_models.json` 仅作内部兼容/手工回滚;通用生成参数、路径/端口、Shopee 更新安全与多账号并行设置继续持久化 `config.json`,cmhub Key 单独写入 `config/cmhub.json`,保存成功后弹轻量提示框;⑤ 设置页已将「Shopee 更新安全 / 执行模式」前置、将「基础设施(路径与端口)」后置,`test_item_id` 与 `dry_run` 不再有用户可操作控件,保存时保留 `test_item_id` 兼容值并固定 `dry_run=false`;③ 普通正式更新不再用 `test_item_id` 阻断非测试商品,确认弹窗不再显示测试商品 ID;`app/workers.py` 已实现 `BaseWorker`、通用 signals、取消标记和 `QThread` 启动包装。 - 测试:`tests/` 已建立;T-522 已新增 GitHub Actions 在 push / pull_request 自动运行语法检查与全量 unittest;T-006 后纯逻辑改动必须运行 `python -m unittest discover -s tests`,当前覆盖 appconfig/db/config/accounts(含 T-105b 启动登录复用已运行 Chrome)/chrome 启动与快捷方式/editor 登录检测(含 Shopee accounts 登录页)与商品 tab 生命周期、商品详情页失效/隐藏 toast 捕获、更新成功后关闭本轮新开 tab、更新封面备份缺失阻断/8张与9张先删第一张/删除确认/上传入口初始不可用/重复图片 toast/物流错误不阻断上传/删图后稳定等待/上传状态诊断/拖首位 mock 路径/Shopee 更新确认框、成功跳回商品列表和残留错误 toast 不覆盖成功 mock 路径/excel 导入/旧字段与更新结果回写/ai 标题与封面 HTTP 解析/`generate_batch` 正常、失败与停止/prompts 读写与渲染/gui ① 导入采集、删除批次软删除与采集诊断日志/gui ② AI生成布局与批次/店铺/商品ID/状态筛选、提示词管理、生成 worker、图片失败诊断日志、双击预览、重置生成结果与新标题本地编辑/gui ③ 更新shopee批次/店铺/商品ID/状态筛选列表、确认弹窗、开始更新主按钮、重置更新状态右键菜单、Shopee 更新安全拦截与前往设置、`ApplyWorker` 串行/检查/分批/按账号并行/端口冲突预检、运行日志、结果回写与汇总/gui ④ 账号管理、密码打码与明文保存提示/gui ⑤ AI 模型管理、API Key 打码与明文保存提示、角色/生成参数设置、Shopee 更新安全设置、检查按钮/每批最大更新条数/多账号并行设置、测试商品 ID 限制移除/worker signal 与线程包装、T-505 全流程诊断日志(import/write_back/apply/chrome_launch/login_check/ai_model_test)与本地日志脱敏、gui T-511 语义色板和任务状态列前景色、T-512 高风险按钮与导入校验数字样式、T-513 登录状态/Tab 标识/删除按钮样式、T-514 首次空状态引导卡片、T-515 批次阶段进度总览、T-516 ①筛选对齐②③、T-517 ⑤设置分区与兼容字段清理、T-518 ②左栏提示词区组件密度优化、T-519 ②AI生成长任务进度条与用户可读滚动日志、T-520 ②AI生成封面可选生成开关、T-524 PyInstaller 打包入口/构建脚本/发布目录校验、T-526 cmhub backend mock、T-527 设置页 cmhub backend 切换/别名下拉/worker mock、T-528 ②cmhub余额显示/计费日志/点数不足中止提示、T-529 默认 cmhub/隐藏 AI 后端选择/保存固定 cmhub/direct 兼容、T-530 cmhub Base URL 规整/404 not_found 明确提示、T-531 设置页未保存状态/离开确认/放弃还原、T-532 cmhub连接成功账号名提示、T-533 增量生成组件口径、T-534 ②按标题/封面组件重置与多选/筛选范围重置、T-535 cmhub标题请求读取等待固定600秒、T-536 GUI按钮全局基础样式与语义色按钮叠加样式、T-537 主窗口标题显示中文品牌;2026-07-01 已完成 5 个真实商品的 T-404 更新验收,后续 CDP/Shopee 改动仍需测试商品手动验证。 -- 数据:`config.json`、`config/ai_models.json`、`config/cmhub.json`、`cmshopee.db`、`cmshopee.db-*`、`chrome_user_data_dir/`、`images/`、`logs/`、`prompts/`、`title_prompt.txt`、`build/`、`dist/` 已由 `.gitignore` 排除;密码与 API Key 本地明文保存但保存/变更时提示,UI 打码,日志/导出必须脱敏;运营填写后的 Excel 业务文件默认忽略,标准空模板 `shopee待处理任务模板.xlsx` 可提交;`app/appconfig.py` 首次读取缺失的 `config.json` 时会在本地写默认配置,`app/db.py` 调用 `init_db()` 时会在本地创建 SQLite DB。 +- 数据:T-538 后默认用户数据根为 `data/`(整体 gitignore);旧布局的 `config.json`、`config/ai_models.json`、`config/cmhub.json`、`cmshopee.db`、`cmshopee.db-*`、`chrome_user_data_dir/`、`images/`、`logs/`、`prompts/`、`title_prompt.txt`、`build/`、`dist/` 仍由 `.gitignore` 排除以支持迁移前安全;密码与 API Key 本地明文保存但保存/变更时提示,UI 打码,日志/导出必须脱敏;运营填写后的 Excel 业务文件默认忽略,标准空模板 `shopee待处理任务模板.xlsx` 可提交;`app/appconfig.py` 首次读取缺失的 `data/config.json` 时会在本地写默认配置,`app/db.py` 调用 `init_db()` 时会在本地创建 SQLite DB。 ## 既定设计要点(文档已定) @@ -18,11 +18,11 @@ - GUI:**PySide6 5 Tab 流水线**,顺序 ① 导入采集 → ② AI生成 → ③ 更新shopee → ④ 账号管理 → ⑤ 设置;后台任务用 worker/QThread/signal。 - 全局用户可见文案规则:弹窗标题/正文/按钮、窗口标题、按钮、菜单、label、placeholder、tooltip、状态栏、空状态、确认框、运行日志、错误提示和成功提示等都必须使用中文。URL、配置键、API 字段、模型别名、第三方/Shopee 原始错误可保留原文,但必须配中文解释,不得裸露英文技术报错。 - 流水线阶段:imported → collected(采集旧标题/旧封面+下载+回写)→ generated(AI 提示词生成新标题/新封面)→ applied(③ 弹窗批量确认后改 Shopee 并提交)。**无 confirmed、无常驻提交开关。** -- 存储:`config.json`(应用设置和 AI backend 内部字段,普通默认 cmhub)+ `config/ai_models.json`(direct AI 模型清单与本地明文 Key)+ `config/cmhub.json`(cmhub 本地明文 Key)+ SQLite `cmshopee.db`(账号/任务/各阶段结果,密码本地明文仅参考)+ openpyxl(Excel)+ 本地 `images/`(旧/新封面);密码/API Key 保存或变更时提示,展示和日志/导出必须脱敏。 +- 存储:`data/config.json`(应用设置和 AI backend 内部字段,普通默认 cmhub)+ `data/config/ai_models.json`(direct AI 模型清单与本地明文 Key)+ `data/config/cmhub.json`(cmhub 本地明文 Key)+ SQLite `data/cmshopee.db`(账号/任务/各阶段结果,密码本地明文仅参考)+ openpyxl(Excel)+ 本地 `data/images/`(旧/新封面);密码/API Key 保存或变更时提示,展示和日志/导出必须脱敏。 - 多账号隔离:每账号独立 user-data-dir(非 profile)。 - 账号↔任务绑定:以 Excel“别名”列为权威;未匹配略过,结束弹窗汇总。 - 执行:默认多账号串行、单条失败继续;③ 提供「检查本轮更新」按钮,检查只写运行日志和汇总,不打开 Shopee、不提交线上、不改任务状态;⑤ 可开启多账号并行(不同账号并行、同账号内串行)并设置每批最大更新条数。③ 点击「开始更新」后先按真实更新安全设置弹窗确认当前筛选范围、任务数量、每批大小和预计批次;确认后做本轮账号 Chrome/CDP/登录态预检,未启动或未登录则弹窗列出账号并中止、不自动打开 Chrome;账号就绪后按批逐条点「更新」提交线上,点击停止后不再开始下一条或下一批。 -- AI:普通产品默认 `backend=cmhub`,由 `config.json` 的 Base URL/别名和 `config/cmhub.json` Key 配置,Base URL 会规整为网关根;`app/ai.py` 仍支持 `backend=direct` 作为内部兼容/手工回滚路径,direct 由 `config/ai_models.json` 配置服务商/模型/Key。生成返回值保持不变,计费 metadata 通过事件传出。生成内容直接用于更新,本地留档+回写 Excel 供追溯。 +- AI:普通产品默认 `backend=cmhub`,由 `data/config.json` 的 Base URL/别名和 `data/config/cmhub.json` Key 配置,Base URL 会规整为网关根;`app/ai.py` 仍支持 `backend=direct` 作为内部兼容/手工回滚路径,direct 由 `data/config/ai_models.json` 配置服务商/模型/Key。生成返回值保持不变,计费 metadata 通过事件传出。生成内容直接用于更新,本地留档+回写 Excel 供追溯。 - 登录:人工登录 + 程序检测,不自动登录;④「启动登录」会打开或复用对应账号浏览器供人工登录,④「检测登录」和①/③预检只验证登录态;无 Shopee tab 时检测入口为 `https:///`(默认 `https://seller.shopee.tw/`);页面跳到 `accounts.shopee.tw/seller/login` 时已明确判为未登录;首次未配账号、对应账号 Chrome 未启动或未登录时,① ③ 应禁用或执行前预检提示,并引导去④,③ 不静默启动缺失账号 Chrome。 ## 当前目录要点 @@ -30,32 +30,34 @@ | 路径 | 状态 | 说明 | | --- | --- | --- | | `docs/` | 已有 | 本 harness coding 文档集合 | -| `docs/packaging.md` | 已有 | T-524 产出:PyInstaller 打包命令、发布目录排除本地数据规则、首次运行数据位置和用户手动覆盖更新步骤 | +| `docs/packaging.md` | 已有 | T-524/T-538 产出:PyInstaller 打包命令、发布目录排除本地数据规则、首次运行 `data/` 数据位置、旧顶层布局迁移和用户手动覆盖程序文件并保留 `data/` 的更新步骤 | | `app/cdp.py` | 已有 | CDP 底座:连接/找 tab/开 tab/关闭 target/执行 JS/拖拽;`CDP.close()` 只断开 WebSocket,`close_tab()` 才关闭浏览器页面 | | `prototypes/` | 已有 | 已验证原型/探查脚本(demo/set_title/set_cover/get_title/cookies/inspect_images/grab/1.py),保留作人工回归与探查参考;见 `prototypes/README.md` | | `chrome-remote-debug-lan.md` | 已有 | WSL→Windows CDP 转发排查记录 | -| `docs/troubleshooting.md` | 已有 | 常见问题排查;已记录 `config/ai_models.json` 缺失 `category` 导致 GUI 启动报 “AI 模型 category 必须是 text 或 image” 的原因、修复和验证步骤 | +| `docs/troubleshooting.md` | 已有 | 常见问题排查;已记录 `data/config/ai_models.json` 缺失 `category`、cmhub 404、②无可生成任务、④启动登录重复开 Chrome,以及打包版窗口左上角显示不全/标题栏不可拖动的原因、临时处理和修复方案 | | `app/__init__.py` / `app/__main__.py` / `main.py` | 已有 | 正式包与启动入口;`python main.py` / `python -m app` 可运行占位入口 | | `requirements.txt` | 已有 | T-521 产出:锁定运行时直接依赖 PySide6 6.5.3、openpyxl 3.1.3、requests 2.31.0、websocket-client 1.6.1、Pillow 9.5.0;换机/CI 使用 `python -m pip install -r requirements.txt` | -| `requirements-build.txt` | 已有 | T-524 产出:打包环境依赖,先安装运行依赖再安装 PyInstaller 5.13.2;不作为运行时依赖文件 | +| `requirements-build.txt` | 已有 | T-524/T-540 产出:打包环境依赖,先安装运行依赖再安装 PyInstaller 6.11.1;不作为运行时依赖文件 | | `.github/workflows/tests.yml` | 已有 | T-522 产出:push / pull_request 自动在 `windows-latest` + Python 3.11 安装 `requirements.txt`,设置 `QT_QPA_PLATFORM=offscreen`,运行 `python -m compileall app main.py` 与 `python -m unittest discover -s tests` | -| `cmshopee.spec` / `scripts/build_exe.ps1` | 已有 | T-524 产出:PyInstaller onedir 构建配置与发布目录校验脚本;`cmshopee.spec` 不声明本地数据 `datas`,脚本检查 `dist\\cmshopee` 不含配置、DB、图片、日志、Chrome 登录态或提示词 | -| `app/gui/` | 已有 | T-523 产出:由旧 `app/gui.py` 拆分的 PySide6 GUI 包,包入口 `__init__.py` 继续兼容 `from app import gui` / `from app.gui import MainWindow`;`main_window.py` 放 MainWindow,`models.py` 放 3 个 TableModel,`widgets.py` 放色板/空状态/批次总览/helper,`workers.py` 放具体 GUI workers,`tabs/` 放 ①~⑤ Tab; T-104/T-105/T-106/T-202/T-202b/T-203/T-204/T-204b/T-205/T-206/T-207/T-302/T-302p/T-303/T-303b/T-401/T-402/T-403/T-404a/T-501/T-501b/T-501c/T-503/T-504/T-506/T-507/T-508/T-509/T-510/T-505/T-511/T-512/T-513/T-514/T-515/T-516/T-517/T-518/T-519/T-520/T-527/T-528/T-529/T-530/T-531/T-532/T-536/T-537 产出:PySide6 `QMainWindow` + 五 Tab;窗口标题显示「蝦皮圈優化助手」;顶部 Tab 栏防误点样式;全局 `QPushButton` 基础样式统一 4px 圆角、hover/pressed/disabled/focus 状态;统一语义色板、①②③状态列前景色、③开始更新 warning 语义色、①导入校验数字标红、④登录状态点、③Tab warning 小圆点、删除/未匹配按钮 danger 语义色样式、①②③首次空状态引导卡片和批次阶段进度总览;① 导入采集导入按钮、导入汇总栏、批次/店铺/商品ID/状态筛选、删除批次软删除按钮、`QTableView` 任务列表、未匹配筛选与略过标记、采集旧标题旧封面 worker(旧封面路径按批次/店铺/任务细分)、采集前账号就绪预检与④引导、采集完成自动回写与手动重试、采集运行日志视图;② AI生成左右布局、提示词管理、批次/店铺/商品ID/状态筛选栏、任务列表、新标题列本地编辑、开始生成/停止/标题与图片双进度条、双击新旧封面预览、用户可读自动滚动AI生成运行日志、重置生成结果与 `GenerateWorker`;③ 更新shopee批次/店铺/商品ID/状态筛选栏、任务列表、开始更新主按钮、重置更新状态右键菜单、Shopee 更新安全拦截与「前往设置」跳转、「检查本轮更新」按钮、开始更新确认弹窗、`ApplyWorker` 检查/分批串行/按账号并行、账号与端口预检、运行日志、逐条 `set_applied`、自动回写结果到 Excel、结束汇总弹窗与手动回写重试;④ 账号管理表格、账号弹窗、密码本地明文保存提示、启动登录、检测登录、快捷方式;⑤ 设置普通默认展示 cmhub 网关 Base URL、API Key、生文/生图动态别名、Base URL 网关根提示、刷新别名、测试连接/查余额并保存 `config/cmhub.json` Key,不再显示 AI 后端选择、direct 模型选择/详情/角色下拉;direct 配置仅内部兼容/手工回滚;通用生成参数、路径端口配置、三列 Shopee 更新安全与执行模式设置仍保留,高频安全/执行模式分区前置、基础设施分区后置,`test_item_id`/`dry_run` 兼容字段无用户入口且普通更新不再阻断正式更新;②封面模板「另存为/重命名/删除」低频操作已收敛进「模板操作」菜单;②AI生成底部已增加标题/图片双进度条,运行日志已改为用户可读、自动滚动、脱敏的长任务日志,且②本轮「生成封面图片(成本较高)」开关默认关闭并持久化到 `ai.generate_cover`,关闭时只生成标题并进入可更新状态;②cmhub模式会显示生成后的剩余点数,记录 points_cost/call_id 计费日志,并在点数不足时弹提示且中止未开始任务;⑤设置页已显示“● 未保存更改”,切 Tab/关闭窗口时拦截保存/放弃/取消,放弃会从 `config.json` 与 `config/cmhub.json` 重新回填,刷新别名/测试连接不自动保存并提醒点保存;cmhub 测试连接/查余额成功时会优先显示 cmhub 账号名或脱敏邮箱,缺账号信息时保留普通成功提示 | +| `app/version.py` | 已有 | T-540 产出:统一维护 `APP_NAME`、`APP_CODE_NAME`、`APP_VERSION` 和窗口显示名;GUI 标题、打包脚本、release 目录、portable zip 和 `version.txt` 共用该版本源 | +| `cmshopee.spec` / `scripts/build_exe.ps1` | 已有 | T-524/T-540 产出:PyInstaller onedir 构建配置与发布目录校验脚本;`cmshopee.spec` 不声明本地数据 `datas`,脚本固定使用 `py -3.10`,先检查 `dist\\cmshopee` 不含配置、DB、图片、日志、Chrome 登录态或提示词,再组装 `release\\cmshopee-\\` 与 `release\\cmshopee--portable.zip` | +| `app/gui/` | 已有 | T-523 产出:由旧 `app/gui.py` 拆分的 PySide6 GUI 包,包入口 `__init__.py` 继续兼容 `from app import gui` / `from app.gui import MainWindow`;`main_window.py` 放 MainWindow,`models.py` 放 3 个 TableModel,`widgets.py` 放色板/空状态/批次总览/helper,`workers.py` 放具体 GUI workers,`tabs/` 放 ①~⑤ Tab; T-104/T-105/T-106/T-202/T-202b/T-203/T-204/T-204b/T-205/T-206/T-207/T-302/T-302p/T-303/T-303b/T-401/T-402/T-403/T-404a/T-501/T-501b/T-501c/T-503/T-504/T-506/T-507/T-508/T-509/T-510/T-505/T-511/T-512/T-513/T-514/T-515/T-516/T-517/T-518/T-519/T-520/T-527/T-528/T-529/T-530/T-531/T-532/T-536/T-537/T-540/T-541 产出:PySide6 `QMainWindow` + 五 Tab;窗口标题显示「蝦皮圈優化助手 v」,启动时按屏幕可用区域限制尺寸、居中并保证标题栏可见;顶部 Tab 栏防误点样式;全局 `QPushButton` 基础样式统一 4px 圆角、hover/pressed/disabled/focus 状态;统一语义色板、①②③状态列前景色、③开始更新 warning 语义色、①导入校验数字标红、④登录状态点、③Tab warning 小圆点、删除/未匹配按钮 danger 语义色样式、①②③首次空状态引导卡片和批次阶段进度总览;① 导入采集导入按钮、导入汇总栏、批次/店铺/商品ID/状态筛选、删除批次软删除按钮、`QTableView` 任务列表、未匹配筛选与略过标记、采集旧标题旧封面 worker(旧封面路径按批次/店铺/任务细分)、采集前账号就绪预检与④引导、采集完成自动回写与手动重试、采集运行日志视图;② AI生成左右布局、提示词管理、批次/店铺/商品ID/状态筛选栏、任务列表、新标题列本地编辑、开始生成/停止/标题与图片双进度条、双击新旧封面预览、用户可读自动滚动AI生成运行日志、重置生成结果与 `GenerateWorker`;③ 更新shopee批次/店铺/商品ID/状态筛选栏、任务列表、开始更新主按钮、重置更新状态右键菜单、Shopee 更新安全拦截与「前往设置」跳转、「检查本轮更新」按钮、开始更新确认弹窗、`ApplyWorker` 检查/分批串行/按账号并行、账号与端口预检、运行日志、逐条 `set_applied`、自动回写结果到 Excel、结束汇总弹窗与手动回写重试;④ 账号管理表格、账号弹窗、密码本地明文保存提示、启动登录、检测登录、快捷方式;⑤ 设置普通默认展示 cmhub 网关 Base URL、API Key、生文/生图动态别名、Base URL 网关根提示、刷新别名、测试连接/查余额并保存 `config/cmhub.json` Key,不再显示 AI 后端选择、direct 模型选择/详情/角色下拉;direct 配置仅内部兼容/手工回滚;通用生成参数、路径端口配置、三列 Shopee 更新安全与执行模式设置仍保留,高频安全/执行模式分区前置、基础设施分区后置,`test_item_id`/`dry_run` 兼容字段无用户入口且普通更新不再阻断正式更新;②封面模板「另存为/重命名/删除」低频操作已收敛进「模板操作」菜单;②AI生成底部已增加标题/图片双进度条,运行日志已改为用户可读、自动滚动、脱敏的长任务日志,且②本轮「生成封面图片(成本较高)」开关默认关闭并持久化到 `ai.generate_cover`,关闭时只生成标题并进入可更新状态;②cmhub模式会显示生成后的剩余点数,记录 points_cost/call_id 计费日志,并在点数不足时弹提示且中止未开始任务;⑤设置页已显示“● 未保存更改”,切 Tab/关闭窗口时拦截保存/放弃/取消,放弃会从 `config.json` 与 `config/cmhub.json` 重新回填,刷新别名/测试连接不自动保存并提醒点保存;cmhub 测试连接/查余额成功时会优先显示 cmhub 账号名或脱敏邮箱,缺账号信息时保留普通成功提示 | | `app/workers.py` | 已有 | T-104b 产出:`BaseWorker` + 通用 signals + 取消标记 + `run_worker()` QThread 包装 | | `app/accounts.py` | 已有 | T-105/T-106 产出:账号 CRUD 服务、目录创建、端口分配、启动登录、检测登录、快捷方式 | | `app/editor.py` | 已有 | T-001/T-103/T-205b/T-207/T-501c/T-502/T-404b + T-404 补丁产出:登录状态检测、打开商品页、详情页加载失败 toast 捕获与商品失效归因、读/写标题、读/下载封面、采集步骤回调、更新步骤回调、采集后关闭自动新建商品页 tab、上传前等待图片管理器稳定、点击上传块后注入文件并检测 CDN 后拖封面、更新封面统一先校验旧封面备份再删线上第一张、重复图片 toast 明确失败、页面主更新按钮、Shopee 站点侧确认框主按钮、apply_task;更新成功后可按设置关闭本轮自动新建商品页 | -| `app/appconfig.py` | 已有 | T-002/T-501c/T-503/T-504/T-520/T-526/T-527 产出:`config.json` 默认值、读写、更新、路径/端口/AI 参数读取、AI `backend=direct/cmhub` 和 `ai.cmhub` 默认值、`config/cmhub.json` Key helper、Shopee 更新安全与 dry-run/多账号并行默认值,②生成封面默认关闭的 `ai.generate_cover`;拒绝敏感字段写入;提供敏感值打码、结构化日志脱敏与自由文本替换工具 | -| `app/diagnostics.py` | 已有 | T-207 + AI生成诊断补丁 + T-505 产出:本地 `logs/cmshopee.log` 诊断日志、大小滚动、异常类型/traceback/step/耗时记录,结构化 payload 和自由文本脱敏后写入 | +| `app/appconfig.py` | 已有 | T-002/T-501c/T-503/T-504/T-520/T-526/T-527/T-538 产出:`data/config.json` 默认值、读写、更新、路径/端口/AI 参数读取、`data_dir` 路径解析、旧顶层用户数据迁移、`data/` 可写性检测、AI `backend=direct/cmhub` 和 `ai.cmhub` 默认值、`data/config/cmhub.json` Key helper、Shopee 更新安全与 dry-run/多账号并行默认值,②生成封面默认关闭的 `ai.generate_cover`;拒绝敏感字段写入;提供敏感值打码、结构化日志脱敏与自由文本替换工具 | +| `app/diagnostics.py` | 已有 | T-207 + AI生成诊断补丁 + T-505 产出:本地 `data/logs/cmshopee.log` 诊断日志、大小滚动、异常类型/traceback/step/耗时记录,结构化 payload 和自由文本脱敏后写入 | | `app/ai.py` | 已有 | T-301/T-303 + AI生成诊断补丁 + T-519/T-520/T-526/T-528/T-533/T-535 产出:`gen_title()`/`gen_cover()`/`generate_batch()`;支持 direct 默认模型直连和 cmhub 网关 backend;cmhub 使用 `CMHubError` 结构化错误、tuple timeout、非幂等生图读超时不重发、`image_url` 安全下载、`fetch_cmhub_models()` 别名发现、`fetch_cmhub_balance()` 余额查询和 metadata 事件;cmhub 标题请求连接超时取 `ai.cmhub.connect_timeout`、读取等待固定 600 秒;direct 保留通用 HTTP 调用、失败重试、错误脱敏;封面按 resolution/jpg_quality 保存并按批次/店铺/任务路径落盘,封面请求与图片下载仍按分辨率读取等待;批量生成可按 `ai.generate_cover` 跳过封面阶段,开启封面时按缺失组件增量补齐,已有标题不重生、不覆盖手动标题,已有封面不重生;进度回调按标题/封面组件统计,逐条落库、失败标记、事件/错误回调、停止取消未开始项 | | `app/prompts.py` | 已有 | T-302p 产出:标题提示词读写、封面模板列表/读取/保存/重命名/删除、变量替换 | | `app/db.py` | 已有 | T-003/T-206/T-404a/T-504/T-509/T-534 产出:batches/accounts/tasks schema;batches 软删除字段与默认业务查询过滤;run_logs/run_log_events;WAL/busy_timeout/foreign_keys;账号/批次/任务与 set_* 阶段写库;生成结果/更新状态本地重置,②生成结果支持按标题/封面组件清空并保持 generated;`update_generated_title()` 本地新标题微调;运行日志写入与查询 | | `app/config.py` | 已有 | T-101 产出:别名→稳定 slug;创建并返回绝对 user-data-dir | -| `app/image_paths.py` | 已有 | 本地图片路径 helper:新采集旧封面和新生成封面统一写入 `images///__old/new.jpg`;历史 DB 已存路径继续按原路径读取 | +| `app/image_paths.py` | 已有 | 本地图片路径 helper:T-538 后新采集旧封面和新生成封面默认写入 `data/images///__old/new.jpg`;历史 DB 已存路径继续按原路径读取 | | `app/chrome.py` | 已有 | T-102/T-106 产出:Chrome 启动参数、`subprocess.Popen` 启动、`/json/version` 端口探测、PowerShell `.lnk` 快捷方式 | -| `tests/` | 已有 | T-006/T-201/T-202/T-202b/T-203/T-204/T-204b/T-205/T-206/T-207/T-301/T-302/T-302p/T-303/T-303b/T-401/T-402/T-403/T-501/T-501b/T-501c/T-502/T-503/T-504/T-506/T-507/T-508/T-509/T-510/T-505/T-511/T-512/T-513/T-514/T-515/T-516/T-517/T-518/T-519/T-520/T-523/T-523a/T-404b/T-524/T-526/T-527/T-528 产出:stdlib unittest 基座;覆盖 appconfig/db/config/accounts/chrome/editor/excel/ai/image_paths/prompts/gui/workers/packaging | +| `tests/` | 已有 | T-006/T-201/T-202/T-202b/T-203/T-204/T-204b/T-205/T-206/T-207/T-301/T-302/T-302p/T-303/T-303b/T-401/T-402/T-403/T-501/T-501b/T-501c/T-502/T-503/T-504/T-506/T-507/T-508/T-509/T-510/T-505/T-511/T-512/T-513/T-514/T-515/T-516/T-517/T-518/T-519/T-520/T-523/T-523a/T-404b/T-524/T-526/T-527/T-528/T-538/T-540/T-541 产出:stdlib unittest 基座;覆盖 appconfig/db/config/accounts/chrome/editor/excel/ai/image_paths/prompts/gui/workers/packaging,含 `data_dir` 路径解析、旧布局迁移、可写性检测、版本源、GUI 标题版本、打包 release 组装脚本校验和主窗口小屏适配 | | `app/excel.py` | 已有 | T-201/T-204/T-403 产出:多文件 Excel 输入列解析、必需列整文件拒绝、脏行逐行跳过、批次/任务入库、匹配统计;按源文件/工作表/行号回写旧标题与旧封面路径;按源文件/工作表/行号回写新标题、新封面路径、更新状态;支持原文件被占用时另存副本 | | `shopee待处理任务模板.xlsx` | 已有,已提交 | 标准空 Excel 模板;单工作表 `待处理任务`,表头 `账号名 | 别名 | 商品id | 旧标题 | 旧封面图片路径 | 新标题 | 新封面图片路径 | 更新状态`;运营复制后填写,填写副本不提交 | -| `config.json` / `config/ai_models.json` / `config/cmhub.json` / `cmshopee.db` / `cmshopee.db-*` / `chrome_user_data_dir/` / `images/` / `logs/` / `prompts/` / `title_prompt.txt` | 本地存在或按需生成,已忽略 | 含配置、密钥、业务、登录态、图片、本地诊断日志和用户提示词,不提交版本库;密码/API Key 保存或变更时提示,展示/日志/导出脱敏 | +| `data/` | 本地存在或按需生成,已忽略 | T-538 后统一用户数据根,包含 `config.json`、`config/ai_models.json`、`config/cmhub.json`、`cmshopee.db`、`chrome_user_data_dir/`、`images/`、`logs/`、`prompts/`、`title_prompt.txt` 等配置、密钥、业务、登录态、图片、本地诊断日志和用户提示词;打包更新时保留,不提交版本库 | +| 旧布局本地数据 | 兼容迁移,已忽略 | 顶层 `config.json` / `config/ai_models.json` / `config/cmhub.json` / `cmshopee.db` / `cmshopee.db-*` / `chrome_user_data_dir/` / `images/` / `logs/` / `prompts/` / `title_prompt.txt` 仍保持 gitignore;新版启动时若无冲突会迁移到 `data/` | ## 已验证能力(单账号) @@ -71,16 +73,16 @@ 任务状态以 [`06-tasks.md`](06-tasks.md) 为准,历史记录见 [`../progress.md`](../progress.md)。 - 已完成:T-000(正式代码包结构)、T-001(`app/editor.py` 模块化)、T-002(`app/appconfig.py` + `config.json`)、T-003(SQLite 建表)、T-004(本地数据 gitignore)、T-005(AI 模型清单后端)、T-006(单元测试基座)、T-101(账号 slug/user-data-dir)、T-102(Chrome 启动器)、T-103(登录保活与检测)、T-103b(登录检测补充识别 Shopee accounts 登录页)、T-104(PySide6 五 Tab 主窗口骨架)、T-104b(PySide6 worker 基座)、T-105(Tab④ 账号管理)、T-105b(④启动登录复用已打开 Chrome)、T-106(账号快捷方式)、T-201(Excel 导入:解析多文件输入列入库)、T-202(Tab① 任务列表 + 导入按钮 + 别名匹配标记)、T-202b(Tab① 导入汇总栏)、T-203(采集旧标题+旧封面)、T-204(回写旧字段到原 Excel)、T-204b(采集完成自动回写旧字段)、T-205(首次未配账号 / Chrome 未启动 / 未登录引导保护)、T-205b(采集后关闭自动新建商品页 tab)、T-206(Tab① 删除指定批次软删除)、T-301(AI 生成接口)、T-302(Tab② 左右布局与任务列表)、T-302p(提示词管理)、T-303(Tab② 开始生成 + 停止 + 进度)、T-303b(②/③ 商品ID筛选)、T-401(Tab③ 更新列表筛选 + 开始更新确认弹窗)、T-402(Tab③ 确认后串行更新)、T-403(Tab③ 结果回写与结束汇总)、T-501(Tab⑤ AI 模型管理 UI)、T-501b(Tab⑤ 角色与生成参数)、T-501c(Tab⑤ Shopee 更新安全开关)、T-502(换封面删第一张再上传)、T-503(敏感信息本地明文保存提示与日志脱敏)、T-504(多账号并行 / dry-run / 运行日志)、T-207(①采集诊断日志)、② AI生成图片失败诊断日志补丁、T-404a(②/③ 选中记录重置)、T-404(真实 Shopee 单条更新冒烟验收)、T-506(正式使用批量更新体验)、T-507(正式批量更新:移除普通流程测试商品 ID 限制)、T-508(③ 更新shopee生产化操作区)、T-509(② 新标题人工微调)、T-510(③ 检查本轮更新文案统一)、T-505(全流程诊断日志扩展)、T-511(语义色板 + ①②③任务状态列上色)、T-512(③高风险按钮上色 + ①导入校验数字标红)、T-513(登录点 / ③Tab危险标识 / 破坏性按钮上色)、T-514(①②③ 首次空状态引导卡片)、T-515(批次阶段进度总览)、T-516(①筛选对齐②③)、T-517(⑤设置分区 + 清理兼容字段)、T-518(②左栏提示词区组件密度优化)、T-519(②AI生成长任务进度条 + 用户可读滚动日志)、T-520(②AI生成封面可选生成开关)、T-521(依赖清单:锁版本 requirements)、T-522(CI:自动跑语法 + 单元 / GUI 测试)、T-523(拆分 `app/gui.py` 为 `app/gui/` 包)、T-523a(②/③ 新一轮运行前清空界面日志显示)、T-404b(商品详情页加载失败 toast 自动捕获)、T-524(PyInstaller 打包为免安装 exe)、T-526(`app/ai.py` + `appconfig` 接入 cmhub backend)、T-527(⑤设置 cmhub 网关面板)、T-528(②计费错误提示 + 余额展示)、T-529(默认 cmhub 网关并隐藏 AI 后端选择)、T-530(cmhub Base URL 规整 + 404 明确提示)、T-531(⑤设置未保存状态追踪 + 离开确认)、T-532(⑤cmhub连接成功提示显示账号名)。 -- 最近完成补充:T-533(②增量生成:按缺失组件补生成)、T-534(②重置增强:多选/筛选范围 + 按组件重置)、T-535(②cmhub生成标题读取等待固定600秒)、T-536(GUI按钮圆角全局统一)、T-537(品牌名显示为蝦皮圈優化助手)。 +- 最近完成补充:T-533(②增量生成:按缺失组件补生成)、T-534(②重置增强:多选/筛选范围 + 按组件重置)、T-535(②cmhub生成标题读取等待固定600秒)、T-536(GUI按钮圆角全局统一)、T-537(品牌名显示为蝦皮圈優化助手)、T-538(打包产物用户数据收进 `data/`)、T-540(打包版本号与 release 产物命名统一)、T-541(打包版主窗口初始位置与小屏适配)。 - 下一个可领取任务:T-525(引入 ruff lint/format,先以安全规则为主)。 ## 当前已知限制 -- T-505 已完成:①采集、②AI生成、Excel 导入/回写、③更新shopee、④Chrome 启动/登录检测、⑤AI模型测试连接均已接入业务可读 `run_logs/run_log_events`;异常路径写本地 `logs/cmshopee.log` 脱敏 traceback,业务日志不记录 Cookie、密码、API Key、token。 +- T-505 已完成:①采集、②AI生成、Excel 导入/回写、③更新shopee、④Chrome 启动/登录检测、⑤AI模型测试连接均已接入业务可读 `run_logs/run_log_events`;异常路径写本地 `data/logs/cmshopee.log` 脱敏 traceback,业务日志不记录 Cookie、密码、API Key、token。 - T-105b 已完成:④账号管理「启动登录」改为幂等入口。同一账号再次点击时会先检查 `debug_port` 是否已有 CDP 响应;已运行则复用现有账号 Chrome 并打开/激活卖家中心登录 tab,状态栏提示“该账号 Chrome 已打开,已复用现有窗口”,不会再次 `subprocess.Popen()`;未运行才新启动 Chrome 并等待 CDP 就绪。不自动登录、不填密码、不改变①/③预检不自动启动 Chrome 的规则。 - ① 采集和③真实更新都依赖对应账号 Chrome 已用专属 user-data-dir 和 CDP 端口启动并登录;T-205/T-402 已在执行前拦截未配置账号、Chrome 未启动、CDP 端口不可访问、未登录,并引导去④账号管理。③ 点击「开始更新」后若本轮需要账号未就绪,必须中止本轮更新,不自动打开账号 Chrome、不提交任何商品。 -- T-501/T-501b/T-501c 已完成 `config/ai_models.json` 模型清单 UI,以及 `config.json` 里的标题/图片默认模型角色、并发、重试、分辨率、jpg 质量、路径/端口设置和 Shopee 更新安全开关。 +- T-501/T-501b/T-501c 已完成 `data/config/ai_models.json` 模型清单 UI,以及 `data/config.json` 里的标题/图片默认模型角色、并发、重试、分辨率、jpg 质量、路径/端口设置和 Shopee 更新安全开关。 - T-506 已完成:③ 已将用户可见 `dry-run` 改为「检查本轮更新」按钮;真实更新对当前筛选结果按每批最大更新条数自动分批,确认弹窗显示任务数和预计批次,停止为当前商品安全结束后不再开始新任务;⑤ 设置页已改为居中内容区,左右留白已缩短为 T-506 初始实现约 40%,模型详情/角色与生成参数/路径与端口/Shopee 更新安全使用三列布局,长字段跨列;「多账号并行更新」与「最大并行账号数」已合并为同一个横向组件,最大并行账号数紧跟其后且不换行。 - T-507 已完成:普通正式更新移除 `test_item_id` 商品 ID 限制;③ 确认弹窗不再显示测试商品 ID;⑤ 普通设置页隐藏测试商品 ID,只作为历史/调试兼容字段保留;仍保留允许真实提交、允许更新封面、每批最大条数、二次确认、账号就绪预检、多账号并行上限、运行日志和 Excel 回写。 - T-508/T-509/T-510 已完成:③「开始更新」作为主操作视觉强化,「重置更新状态」移到任务表右键菜单,更新安全拦截弹窗写明具体设置并可跳到⑤;②「新标题」列允许已生成、未提交线上、非运行中任务本地微调,写回 `tasks.new_title`,清空 `last_error` 并回到可更新状态,不触碰 Shopee/CDP/Excel/封面;③ 用户可见「预览本轮更新」已统一改名为「检查本轮更新」,内部 `dry_run` 字段保留。 @@ -96,29 +98,31 @@ - T-519 已完成:②AI生成底部增加标题/图片两条独立进度条,现有「AI生成运行日志」改为用户可读、自动滚动、脱敏的长任务日志;日志显示本轮开始、标题/图片开始与成功、商品ID/店铺、调用失败重试、失败、停止请求和完成汇总,不显示 API Key、密码、Cookie、token、完整请求体、base64 图片或超长 prompt,不改变 AI/DB/Excel/Shopee/CDP 核心流程。 - T-520 已完成:②AI生成页增加「生成封面图片(成本较高)」开关,默认关闭以避免无意产生图片模型成本;关闭时标题生成成功即可写库进入 generated,`new_cover_path=NULL`,③只更新标题且不受「允许更新封面」阻断;开启时保持标题后图片两段生成流程。2026-07-06 运行修正:②AI生成页会把失败按阶段显示为“采集失败 / 生成失败 / 更新失败”;「开始生成」只处理已采集待生成或生成失败可重试任务,采集失败会提示先回到①完成旧数据采集,更新失败不会被②误重试。未改 DB schema、AI HTTP 协议、Excel、Shopee/CDP 更新流程。 - T-528 已完成:②AI生成页在 cmhub backend 下显示生成后的剩余点数;GenerateWorker 读取计费 metadata,把 points_cost、points_balance、call_id 写入脱敏运行日志;遇到 CMHubError.code=insufficient_points 时弹出「点数不足,请先充值」并中止本轮未开始任务,不靠中文错误字符串匹配。未改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程。 -- T-529 已完成:普通默认 `ai.backend=cmhub`;⑤设置页不再显示「AI 后端」label/dropdown、direct 模型选择、模型详情或标题/图片模型角色下拉,直接展示 cmhub 网关配置;保存设置固定写 `ai.backend=cmhub` 且允许先保存不完整 cmhub 配置,②生成时仍由 `app/ai.py` 提示补齐 Base URL/API Key/别名。direct 代码和 `config/ai_models.json` 保留为内部兼容/手工回滚,不改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程。 +- T-529 已完成:普通默认 `ai.backend=cmhub`;⑤设置页不再显示「AI 后端」label/dropdown、direct 模型选择、模型详情或标题/图片模型角色下拉,直接展示 cmhub 网关配置;保存设置固定写 `ai.backend=cmhub` 且允许先保存不完整 cmhub 配置,②生成时仍由 `app/ai.py` 提示补齐 Base URL/API Key/别名。direct 代码和 `data/config/ai_models.json` 保留为内部兼容/手工回滚,不改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程。 - T-530 已完成:`appconfig.normalize_cmhub_base_url()` / `cmhub_request_url()` 会把 cmhub Base URL 规整到 scheme+host(+port),去掉 `/api`、`/api/v1`、其它路径、查询串和片段;⑤设置页输入框提示只填网关根,保存/刷新前同步规整;cmhub HTTP 404 统一映射为 `CMHubError(code="not_found")`,显示“cmhub 接口不存在,请检查 Base URL 或该实例是否已部署 /api/v1/models”。未改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程。 -- T-531 已完成:⑤设置页增加未保存状态追踪与离开确认。用户修改设置后显示“● 未保存更改”;`save_app_settings()` 成功写入 `config.json` + `config/cmhub.json` 后清 dirty,失败保留 dirty;切换 Tab/关闭窗口时提供保存、放弃、取消三选一,且使用自定义中文按钮避免系统默认英文按钮;放弃会重新从 `config.json` 与 `config/cmhub.json` 回填控件;程序化回填和刷新别名写入下拉不置脏;`currentChanged` 回退使用 guard 防递归;刷新别名/测试连接成功只提醒保存,不自动落盘。 +- T-531 已完成:⑤设置页增加未保存状态追踪与离开确认。用户修改设置后显示“● 未保存更改”;`save_app_settings()` 成功写入 `data/config.json` + `data/config/cmhub.json` 后清 dirty,失败保留 dirty;切换 Tab/关闭窗口时提供保存、放弃、取消三选一,且使用自定义中文按钮避免系统默认英文按钮;放弃会重新从 `data/config.json` 与 `data/config/cmhub.json` 回填控件;程序化回填和刷新别名写入下拉不置脏;`currentChanged` 回退使用 guard 防递归;刷新别名/测试连接成功只提醒保存,不自动落盘。 - T-532 已完成:⑤设置页 cmhub 测试连接/查余额成功后,会从 `/balance` 返回的 `account.display_name/account.username/user` 以及 name/account_name/email/id 等字段提取 cmhub 账号身份,并显示 `cmhub 账号「<账号名>」连接成功:...`;当前接口结构 `{ "user": "cmhub_user", "points_balance": 88, "account": { "username": "cmhub_user", "display_name": "主账号" } }` 会优先显示 `主账号`;邮箱只显示脱敏形式(如 `o***r@example.com`),无账号信息时保留 `cmhub 连接成功:...` 兜底。`sanitize_for_log()` 同步脱敏 email 字段,避免 run log summary 保存完整邮箱;未改 cmhub HTTP 协议、配置 schema、保存逻辑、AI 生成或 Shopee/CDP 流程。 - T-533 已完成:②AI生成已从整条任务生成改为按缺失组件增量补齐。标题已存在但封面缺失、且本轮开启“生成封面图片(成本较高)”时,只补封面,不再调用生文、不覆盖手动标题;组件全齐时不纳入本轮生成;标题/图片进度分别按 `title_total` / `cover_total` 统计。未改 cmhub HTTP 协议、DB schema、Excel、Shopee/CDP 流程。 - T-535 已完成:cmhub 生文 `title_request` 的读取等待固定为 600 秒,不再跟随当前分辨率的 `resolution_timeouts`;连接超时仍取 `ai.cmhub.connect_timeout`,重试次数仍取 `ai.retry`。封面生成和图片下载继续按分辨率读取等待;direct 兼容路径不变。 - T-536 已完成:`MainWindow` 全局应用 `BUTTON_BASE_STYLE`,所有 `QPushButton` 统一 4px 圆角、基础边框、hover/pressed/disabled/focus 状态;③「开始更新」、①「删除批次」、④「删除账号」和①「未匹配」按钮只叠加 warning/danger 语义颜色,不再各自硬写圆角或内边距。未改任何按钮行为、启用/禁用逻辑、DB、Excel、Shopee/CDP。 - T-537 已完成:产品显示名定为「蝦皮圈優化助手」;主窗口标题、PySide6 缺失启动提示、根 README、入口/导航/愿景文档和 UI 线框图显示中文品牌;`cmshopee` 继续作为仓库、包名、exe、数据库、日志、配置和兼容文件名等技术代号保留。 +- T-538 已完成:默认用户数据根统一为 `<程序目录>/data`(源码为项目根 `data/`,打包版为 exe 同级 `data/`);`main.py` 不再 `chdir` 到 exe 目录;启动时迁移 T-524 旧顶层布局数据并检测 `data/` 可写;旧布局和 `data/` 同名冲突时弹“数据迁移冲突”,不可写时才弹“数据目录不可写”;`scripts/build_exe.ps1` 发布目录校验新增禁止打包 `data/`;用户更新方式改为覆盖当前发布包里的程序文件和依赖文件并保留 `data/`。T-540 后当前锁定 PyInstaller 6.11.1,`dist/cmshopee` 必须包含 `cmshopee.exe` 和 `_internal/`。 +- T-540 已完成:新增 `app/version.py` 作为唯一版本源,GUI 标题显示 `蝦皮圈優化助手 v`;`scripts/build_exe.ps1` 读取 `APP_VERSION`,固定使用 `py -3.10` 产出 `dist/cmshopee`,再组装 `release/cmshopee-`、写入 `version.txt`/中文 `README.txt`,并生成 `release/cmshopee--portable.zip`。本阶段不引入 Launcher、manifest、sha256 下载校验或自动更新。 +- T-541 已完成:`MainWindow` 启动时通过 `_fit_and_center_window()` 读取 `QApplication.primaryScreen().availableGeometry()`,按可用屏幕限制初始尺寸、居中并夹进屏幕;首次 `showEvent` 后再应用一次,保证打包版在 Windows 10 VM/小分辨率环境标题栏完整可见、可拖动,不保存历史坏坐标。 +- 主窗口标题当前由 `app.version.display_name()` 统一显示为「蝦皮圈優化助手 v」;T-541 不改品牌/版本来源,只改启动尺寸与位置。 - T-521 已完成:根目录新增 `requirements.txt` 并锁定当前运行依赖版本;`docs/03-tech-stack.md` 和 `docs/README.md` 已改为 `python -m pip install -r requirements.txt` 安装;未新增运行时依赖、未改业务代码。 - T-522 已完成:新增 `.github/workflows/tests.yml`,在 push / pull_request 上用 Windows + Python 3.11 安装 `requirements.txt`,设置 `QT_QPA_PLATFORM=offscreen`,自动运行语法检查和全量单元/GUI 测试;CI 不连接真实 Shopee 或真实 AI。 - T-523 已完成:旧 `app/gui.py` 已拆为 `app/gui/` 包,公开导入路径保持兼容;本轮为纯结构重构,未改 Shopee/CDP、AI、DB、Excel 行为。 - T-523a 已完成:②点击「开始生成」、③点击「检查本轮更新」或「开始更新」时,先清空对应界面的旧日志显示并写入本轮开始摘要;运行中只追加本轮日志。历史 `run_logs/run_log_events` 和本地 `logs/` 不删除、不自动混入当前运行界面。 - T-404b 已完成:`open_product()` 在导航商品详情页前后安装 toast 捕获,等待详情页关键元素超时时会读取最近 `.eds-toasts`/toast/message 节点及页面缓存,把明确商品失效/商品不存在/无权限类 toast 上浮为 `商品失效:<原始toast>`;如果失败发生在 `open_product()` 内部,本轮自动新建的失败 tab 会关闭,复用用户已有 tab 不关闭;①列表“阶段”列只在这类明确失效错误时显示“商品失效”,底层仍保持 `stage=imported/status=failed`,其他打开失败仍显示“失败”。 -- T-524 已完成:新增 PyInstaller onedir 打包配置、构建脚本和发布目录校验;T-538 后打包版不再 `chdir`,首次运行在 exe 同级 `data/` 中生成/使用本地配置、DB、图片、日志、登录态和提示词;发布包不内置这些本地数据,用户后续更新采用关闭程序后覆盖程序文件、保留 `data/` 的方式。 -- T-541 已完成:`MainWindow` 启动时通过 `_fit_and_center_window()` 读取 `QApplication.primaryScreen().availableGeometry()`,按可用屏幕限制初始尺寸、居中并夹进屏幕;首次 `showEvent` 后再应用一次,保证打包版在 Windows 10 VM/小分辨率环境标题栏完整可见、可拖动,不保存历史坏坐标。 -- T-538 已完成:`app/appconfig.py` 新增 `data_dir`、旧布局迁移、迁移冲突保护和可写性检测;`config.json`、`config/ai_models.json`、`config/cmhub.json`、DB、图片、日志、提示词和 Chrome 登录态默认统一落到 `data/`;旧 DB 已存绝对/历史路径继续按原路径读取。 -- Phase 7 cmhub 网关对接已完成 T-526~T-535;②生成阶段已支持按标题/封面缺失组件增量生成,②重置生成结果已支持多选/筛选范围和按标题/封面组件重置,cmhub 标题请求读取等待固定 600 秒。下一步按看板回到 T-525 ruff。 +- T-524 已完成:新增 PyInstaller onedir 打包配置、构建脚本和发布目录校验;T-538 后打包版不再切换全进程工作目录,首次运行在 exe 同级 `data/` 生成/使用本地配置、DB、图片、日志、登录态和提示词;发布包不内置 `data/`,用户后续更新采用关闭程序后覆盖程序文件、保留 `data/` 的方式。 +- Phase 7 cmhub 网关对接已完成 T-526~T-535;②生成阶段已支持按标题/封面缺失组件增量生成,②重置生成结果已支持多选/筛选范围和按标题/封面组件重置,cmhub 标题请求读取等待固定 600 秒。T-541 已完成,下一步按看板回到 T-525 ruff / T-539 设置简化。 -- T-301/T-303 已完成通用 HTTP AI 接口、批量生成编排和 GUI 接入 mock 单测;真实 AI 生成还需要在 `config/ai_models.json` 填入可用 url/model/api_key 后做一次成本可控的小样本实测;URL 可填完整 endpoint 或 OpenAI-compatible base URL。 -- 本地 `config/ai_models.json` 若由旧版本或手工维护,可能缺少 `category`;启动报 “AI 模型 category 必须是 text 或 image” 时,按 [`troubleshooting.md`](troubleshooting.md) 只补 `category` / `enabled` 等非密钥字段,保留 API Key,且不要提交该文件。 +- T-301/T-303 已完成通用 HTTP AI 接口、批量生成编排和 GUI 接入 mock 单测;真实 AI 生成还需要在 `data/config/ai_models.json` 填入可用 url/model/api_key 后做一次成本可控的小样本实测;URL 可填完整 endpoint 或 OpenAI-compatible base URL。 +- 本地 `data/config/ai_models.json` 若由旧版本或手工维护,可能缺少 `category`;启动报 “AI 模型 category 必须是 text 或 image” 时,按 [`troubleshooting.md`](troubleshooting.md) 只补 `category` / `enabled` 等非密钥字段,保留 API Key,且不要提交该文件。 - T-403/T-501c 已完成③更新结果回写、结束汇总与真实更新安全开关;2026-06-29 已用测试商品跑到真实编辑页并完成标题/封面替换、页面主「更新」点击,但被 Shopee 站点侧确认框拦住。当前代码已补确认框处理、确认后列表页跳转观测、上传状态诊断、删除后稳定等待和 mock 单测;2026-06-30 用户复跑发现稳定等待顺序回归:满 9 张时删除前要求上传 input 可用,导致不删除第一张图;当前已修正为删除前只等图片列表稳定、删除后再等上传入口恢复,并补 mock 回归测试;2026-06-30 又发现物流/备货页面级校验 toast 会被误判成封面上传失败,当前已修复为上传阶段只识别图片/文件/上传相关错误,物流错误留到提交阶段处理。2026-06-30 用户复跑确认浏览器已成功更新标题/图片并跳转商品列表,但 GUI 仍报 `POST_UPDATE_ERROR`;已修正提交后观测优先级:列表页跳转优先判成功,残留 error toast 不再覆盖成功跳转,错误 toast 仅在未跳转且持续存在时判失败。当前又发现商品 26887160467 手动上传 220KB 新图成功,但代码直接注入文件后上传组件长期转圈;T-404 当前补丁已让 `replace_cover()` 上传前先点击上传块模拟人工入口、短暂等待、重新获取 input 后再 `DOM.setFileInputFiles`,并补 mock 回归测试。25120403046 只有 8 张商品图时未删除第一张导致 Shopee 提示 `有1張重複的圖片`;当前已落地为更新封面统一先删当前第一张再上传,并把 `重複/重复/duplicate` toast 识别为封面上传失败。新采集/新生成图片路径已改为按批次细分,避免同一账号多批次图片混放;历史 DB 已存路径继续可用。确认后若返回我的商品列表页且设置了成功后关闭本次新开编辑页,关闭前等待 2 秒。2026-07-01 用户已用包含 5 个商品 ID 的 Excel 完成导入、AI 生成标题/图片、Tab③ 更新到 Shopee 的真实链路验收,T-404 判定完成。 - T-404a/T-534 已完成:②「重置生成结果」已从单条整清升级为多选/当前筛选结果批量重置,确认框提供「重置标题 / 重置封面 / 重置全部」三种组件操作;只重置封面时保留 `new_title` 和 T-509 手动标题,只重置标题时保留 `new_cover_path`,重置后任务保持 `stage=generated/status=success` 供 T-533 按缺口补生成。默认不删除本地新封面文件,不触碰 Shopee、不自动回写 Excel;已提交线上记录会提示本地重置不回滚 Shopee、重生成后再更新会再次提交线上;运行中禁用并写 `run_type=reset` 运行日志。③「重置更新状态」仍保留新标题/新封面并退回 generated/pending,入口按 T-508 保持在任务表右键菜单。 -- T-502 换封面删除流程已完成代码路径、mock 单测和真实 9 图商品不提交流程实测;更新封面统一先删当前第一张,删除前必须已有本地旧封面备份(`old_cover_path` 存在且文件存在),缺失备份时拒绝删除线上图片;mock 已覆盖 8 张图也先删第一张、重复图片 toast 立即失败。本轮实测只操作编辑页并关闭测试 tab,未点击「更新」保存线上。`images/` 新路径按 `batch_id/slug/task_id_item_id` 细分,旧 DB 路径兼容不迁移。 +- T-502 换封面删除流程已完成代码路径、mock 单测和真实 9 图商品不提交流程实测;更新封面统一先删当前第一张,删除前必须已有本地旧封面备份(`old_cover_path` 存在且文件存在),缺失备份时拒绝删除线上图片;mock 已覆盖 8 张图也先删第一张、重复图片 toast 立即失败。本轮实测只操作编辑页并关闭测试 tab,未点击「更新」保存线上。T-538 后 `data/images/` 新路径按 `batch_id/slug/task_id_item_id` 细分,旧 DB 路径兼容不迁移。 ## 当前可运行内容 @@ -127,20 +131,20 @@ python -m pip install -r requirements.txt # 安装打包依赖并构建免安装 exe(T-524 后) -python -m pip install -r requirements-build.txt +py -3.10 -m pip install -r requirements-build.txt powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1 # 语法检查(T-000 后;本机优先用 py -3) py -3 -m compileall app main.py # appconfig 默认配置读写(写入临时目录) -py -3 -c "import os,tempfile; from app import appconfig; d=tempfile.TemporaryDirectory(dir='.'); print(appconfig.load_config(os.path.join(d.name,'config.json'))['image_dir'])" +py -3 -c "import os,tempfile; from app import appconfig; d=tempfile.TemporaryDirectory(dir='.'); cfg=appconfig.load_config(os.path.join(d.name,'config.json')); print(appconfig.image_dir(cfg))" # db 临时库初始化与 PRAGMA 检查 py -3 -c "import os,tempfile; from app import db; d=tempfile.TemporaryDirectory(dir='.'); p=os.path.join(d.name,'cmshopee.db'); db.init_db(p); c=db.connect(p); print(c.execute('PRAGMA foreign_keys').fetchone()[0], c.execute('PRAGMA journal_mode').fetchone()[0], c.execute('PRAGMA busy_timeout').fetchone()[0]); c.close()" # gitignore 核心本地数据检查 -git check-ignore -v -- config.json config/ai_models.json config/cmhub.json cmshopee.db chrome_user_data_dir/ images/ +git check-ignore -v -- data/ config.json config/ai_models.json config/cmhub.json cmshopee.db chrome_user_data_dir/ images/ # ai_models 临时清单读写与打码检查 py -3 -c "import os,tempfile; from app import appconfig; d=tempfile.TemporaryDirectory(dir='.'); p=os.path.join(d.name,'ai_models.json'); print([m['category'] for m in appconfig.list_ai_models(path=p)])" @@ -181,7 +185,7 @@ set UPDATE=1 && python prototypes/demo.py - 已提供根目录 `requirements.txt` 锁定运行依赖;换机、CI 或新环境使用 `python -m pip install -r requirements.txt`。 - PySide6 当前环境已可导入(验证版本 6.5.3);GUI 实现固定使用 PySide6。 - 默认连 `127.0.0.1:9222`(开发期可用 `CDP_HOST` 指向 WSL 转发的 `192.168.0.224:9333`)。 -- 本机 `python` 当前指向 `C:\Python37\python.exe`(3.7.9,isolated,不符合 Python 3.10+ 要求且 `python -m app` 不搜索当前目录);开发/验证优先用 `py -3`(当前 3.14.4)或确认 `python` 已指向 3.10+。 +- 本机 `python` 当前仍可能指向 `C:\Python37\python.exe`(3.7.9,isolated);正式打包不再依赖 PATH 上的 `python`,统一使用 `py -3.10`。若源码调试时 `python -m app` 不搜索当前目录,可改用 `py -3.10 main.py` 或 `py -3.10 -m app`。 ## 开始编码前检查 diff --git a/docs/packaging.md b/docs/packaging.md index 54e6728..a6e5eff 100644 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -1,19 +1,19 @@ # 打包与分发 -> T-524 目标:先把当前 Windows 桌面工具打成免安装 onedir `.exe`,方便第一版交付使用。T-538 后用户数据统一收进程序同级 `data/`,暂不做自动更新器、安装器、增量补丁或在线升级。 +> T-524 目标:先把当前 Windows 桌面工具打成免安装 onedir `.exe`,方便第一版交付使用。暂不做自动更新器、安装器、增量补丁或在线升级。 ## 一、打包前提 - 在 Windows 环境执行。 - Python 环境已能运行源码版 `python main.py`。 -- 打包脚本使用当前 PATH 中的 `python`;正式出包前先确认 `python --version` 符合项目目标 Python 3.10+。 +- 打包脚本固定使用 Windows Python Launcher 的 `py -3.10`,不使用当前 PATH 中的 `python`;正式出包前先确认 `py -3.10 --version` 可用。 - 安装运行依赖和打包依赖: ```powershell -python -m pip install -r requirements-build.txt +py -3.10 -m pip install -r requirements-build.txt ``` -`requirements-build.txt` 会先安装 `requirements.txt` 的运行依赖,再安装 PyInstaller。 +`requirements-build.txt` 会先安装 `requirements.txt` 的运行依赖,再安装 PyInstaller。依赖必须安装到 Python 3.10 环境里,避免 PATH 上的 Python 3.7/3.12/3.14 打出不可比对的包。 ## 二、打包命令 @@ -24,24 +24,105 @@ powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1 脚本会执行: ```powershell -python -m PyInstaller --noconfirm --clean cmshopee.spec +py -3.10 -m PyInstaller --noconfirm --clean cmshopee.spec ``` 成功后输出: ```text -dist\cmshopee\cmshopee.exe +dist\cmshopee\ + cmshopee.exe + _internal\ + ``` -发布给用户时,以整个 `dist\cmshopee\` 文件夹为单位压缩分发,用户双击 `cmshopee.exe` 启动。当前 PyInstaller 5.13.2 的 onedir 是扁平结构,`cmshopee.exe` 与 DLL/PYD/依赖目录同级,`_internal\` 不作为当前验收标准。 +正式分发入口是 `release\cmshopee--portable.zip`。`dist\cmshopee\` 只是 PyInstaller 构建中间产物,不直接作为正式发包名称。 -## 三、绝不打包的本地数据 +当前 `requirements-build.txt` 锁定 PyInstaller 6.11.1,onedir 产物必须是 `cmshopee.exe` + `_internal\` 集中依赖布局。`scripts\build_exe.ps1` 会校验 `_internal\` 存在;缺失时直接失败,避免把不一致的打包环境误发给用户。 + +发布包只包含程序文件和依赖文件: + +```text +cmshopee\ + cmshopee.exe + _internal\ + +``` + +首次运行后才会在同级生成 `data\`,用于保存用户本地数据。 + +## 三、版本号与发布目录 + +参考 `D:\chengma\cmbot` 的打包方式,版本控制采用“单一版本源 + 发布目录带版本号”的轻量方案,暂不引入启动器、自动更新或 manifest。 + +### 3.1 单一版本源 + +新增 `app/version.py`,作为软件名称和版本号的唯一来源: + +```python +APP_NAME = "蝦皮圈優化助手" +APP_CODE_NAME = "cmshopee" +APP_VERSION = "0.1.0" +``` + +规则: + +- 修改版本号只改 `app/version.py` 的 `APP_VERSION`。 +- GUI 窗口标题读取同一版本源,显示为 `蝦皮圈優化助手 v0.1.0`。 +- 打包脚本读取同一版本源生成发布目录、压缩包和 `version.txt`。 +- 禁止在 GUI、构建脚本或文档示例之外重复硬编码不同版本号。 + +### 3.2 发布目录结构 + +`scripts\build_exe.ps1` 仍先产出 PyInstaller 原始目录 `dist\cmshopee\`,再组装面向用户分发的 `release\` 目录: + +```text +release\ + cmshopee-0.1.0\ + cmshopee.exe + _internal\ + + version.txt + README.txt + cmshopee-0.1.0-portable.zip +``` + +说明: + +- `dist\cmshopee\` 是构建中间产物,不直接作为正式发包名称。 +- `release\cmshopee-\` 是人工验收和分发目录。 +- `release\cmshopee--portable.zip` 是交付给用户的便携压缩包。 +- `version.txt` 使用 ASCII/UTF-8 无 BOM 写入 `APP_VERSION`,用于人工排查和未来更新机制读取。 +- `README.txt` 用中文写明启动方式、不要放入 `Program Files`、保留 `data\`、升级时覆盖程序文件但不覆盖 `data\`。 + +当前 PyInstaller 6.11.1 的 onedir 为集中依赖结构,所以 `release\cmshopee-\` 必须包含 `cmshopee.exe` 和 `_internal\`,不得只复制单个 exe。 + +### 3.3 正式出包流程 + +1. 修改 `app/version.py` 中的 `APP_VERSION`。 +2. 运行语法检查和单元测试。 +3. 运行 `powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1`。 +4. 检查 `release\cmshopee-\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。 +5. 在无 Python 环境的 Windows 10/11 机器上解压 `cmshopee--portable.zip` 并启动验证。 + +正式发布构建固定使用 Python 3.10。源码在 Python 3.7.9 上曾验证可运行,但发布包不再以 3.7.9 作为打包基线;脚本会直接调用 `py -3.10` 并校验返回版本必须是 `3.10.x`,避免同一个 `APP_VERSION` 在不同 Python 版本下打出不可比对的包。CI 仍可继续使用 Python 3.11 做自动测试。 + +### 3.4 暂不引入自动更新 + +本阶段不迁移 `cmbot` 的 `Launcher.exe`、`manifest.json`、SHA-256 下载校验、强制更新或在线更新机制。原因: + +- 当前用户已验证“拷贝发布目录到无 Python 环境运行”的第一版交付路径。 +- `cmshopee` 刚完成 `data\` 数据隔离,先把手动发版和版本号一致性做稳。 +- 自动更新需要安装根可写、下载暂存、目录切换、回滚和安全校验,属于独立后续任务,不能混入当前打包版本号控制。 + +后续如果要做在线更新,再另起任务,参考 `cmbot` 的 `Launcher.exe + app\ + manifest.json + sha256 + version.txt` 设计。 + +## 四、绝不打包的本地数据 发布包里不能包含以下本地数据、密钥、业务数据或登录态: - `config.json` - `config/ai_models.json` -- `config/cmhub.json` - `cmshopee.db`、`cmshopee.db-wal`、`cmshopee.db-shm` - `db.sqlite` - `chrome_user_data_dir/` @@ -54,7 +135,7 @@ dist\cmshopee\cmshopee.exe `cmshopee.spec` 不声明任何 `datas`;`scripts/build_exe.ps1` 会在打包后检查 `dist\cmshopee\`,如果发现上述路径会直接失败。 -## 四、首次运行与本地数据位置 +## 五、首次运行与本地数据位置 T-538 后,打包版不再把全进程工作目录切到 `cmshopee.exe` 所在目录;路径由 `app/appconfig.py` 按显式数据根解析。 @@ -63,7 +144,8 @@ T-538 后,打包版不再把全进程工作目录切到 `cmshopee.exe` 所在 ```text cmshopee\ cmshopee.exe - + _internal\ + data\ config.json config\ @@ -89,33 +171,34 @@ cmshopee\ 启动时会检测 `data\` 是否可写;如果程序放在 `Program Files` 等只读目录导致写入失败,才会以“数据目录不可写”提示“请把程序放到可写目录,勿放 Program Files”并退出。 -## 五、用户后续更新方式 +## 六、用户后续更新方式 第一版不做自动更新。给用户发新版本时: 1. 让用户先关闭 cmshopee。 2. 建议用户备份当前整个程序文件夹。 -3. 解压新版 `dist\cmshopee\`。 -4. 覆盖新版程序文件和依赖文件。当前 PyInstaller 5.13.2 是扁平 onedir,不能只覆盖 `cmshopee.exe`,也没有 `_internal/` 可单独替换。 +3. 解压新版 `release\cmshopee--portable.zip`。 +4. 覆盖新版程序文件和依赖文件。当前 PyInstaller 6.11.1 是 `cmshopee.exe` + `_internal\` onedir,不能只覆盖 `cmshopee.exe`;应把新版 `cmshopee.exe`、`_internal\`、`version.txt`、`README.txt` 等程序文件整体覆盖到旧程序目录。 5. 保留旧目录里的 `data\`,不要删除、覆盖或合并它。 如果用户把整个旧目录删除再放新版,`data\` 里的账号、任务记录、图片和登录态也会一起丢失,只能从备份恢复。 -## 六、验证清单 +## 七、验证清单 打包前后至少执行: ```powershell -python -m compileall app main.py -python -m unittest discover -s tests +py -3.10 -m compileall app main.py +py -3.10 -m unittest discover -s tests powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1 ``` 打包成功后确认: - `dist\cmshopee\cmshopee.exe` 存在。 -- 当前 PyInstaller 5.13.2 下 `dist\cmshopee\` 是扁平 onedir,`_internal\` 不存在也属于正常。 -- `dist\cmshopee\` 中没有第三节列出的本地数据,尤其不能含 `data\`。 +- 当前 PyInstaller 6.11.1 下 `dist\cmshopee\_internal\` 必须存在。 +- `dist\cmshopee\` 中没有第四节列出的本地数据,尤其不能含 `data\`。 +- `release\cmshopee-\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。 - 在干净目录首次启动时能生成 `data\config.json` 并进入 GUI。 - 在目标 Windows 10/11 机器或虚拟机上启动 release exe 后,主窗口标题栏完整可见,左边缘不出屏,用户能用标题栏拖动窗口;小分辨率环境不得出现窗口卡在左上角且标题栏不可拖动的问题(见 T-541)。 diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 17649b5..cddb839 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1,6 +1,6 @@ # 常见问题排查 -> 本文只记录可复用的本地排障步骤。T-538 后用户数据默认位于 `data/`,涉及 `data/config.json`、`data/data/config/ai_models.json`、`data/config/cmhub.json`、`data/cmshopee.db`、`data/chrome_user_data_dir/`、`data/images/` 时,默认它们是本机敏感或业务数据,必须保持 gitignore,不把真实密码、API Key、Cookie、token 写入文档、日志或提交。 +> 本文只记录可复用的本地排障步骤。T-538 后用户数据默认位于 `data/`,涉及 `data/config.json`、`data/config/ai_models.json`、`data/config/cmhub.json`、`data/cmshopee.db`、`data/chrome_user_data_dir/`、`data/images/` 时,默认它们是本机敏感或业务数据,必须保持 gitignore,不把真实密码、API Key、Cookie、token 写入文档、日志或提交。 ## 启动时报 “AI 模型 category 必须是 text 或 image” @@ -14,17 +14,17 @@ AI 模型 category 必须是 text 或 image ### 原因 -`data/data/config/ai_models.json` 是本地 AI 模型清单,里面每个模型都必须有合法的 `category`: +`data/config/ai_models.json` 是本地 AI 模型清单,里面每个模型都必须有合法的 `category`: - `text`:标题生成模型,会出现在“标题大模型”下拉。 - `image`:封面生成模型,会出现在“图片大模型”下拉。 -早期或手工维护过的 `data/data/config/ai_models.json` 可能缺少 `category`,或者填了中文、空值、旧字段,导致 `appconfig.load_ai_models_config()` 严格校验失败,GUI 启动被阻断。 +早期或手工维护过的 `data/config/ai_models.json` 可能缺少 `category`,或者填了中文、空值、旧字段,导致 `appconfig.load_ai_models_config()` 严格校验失败,GUI 启动被阻断。 ### 不要这样做 -- 不要删除 `data/data/config/ai_models.json` 来“重置”,否则会丢失本地明文 API Key 和模型配置。 -- 不要把 `data/data/config/ai_models.json` 提交到 git。 +- 不要删除 `data/config/ai_models.json` 来“重置”,否则会丢失本地明文 API Key 和模型配置。 +- 不要把 `data/config/ai_models.json` 提交到 git。 - 不要把完整文件内容贴到聊天、文档或日志里;该文件含本地明文 API Key。 ### 推荐修复 @@ -35,7 +35,7 @@ AI 模型 category 必须是 text 或 image python -c "import json; p='data/config/ai_models.json'; data=json.load(open(p,encoding='utf-8')); [print({'index': i+1, 'name': m.get('name'), 'category': m.get('category'), 'api_type': m.get('api_type'), 'enabled': m.get('enabled'), 'has_api_key': bool(m.get('api_key'))}) for i,m in enumerate(data.get('models', []))]" ``` -然后手工编辑 `data/data/config/ai_models.json`,只补这些字段: +然后手工编辑 `data/config/ai_models.json`,只补这些字段: ```json { @@ -75,7 +75,7 @@ print('updated' if changed else 'unchanged')" python -c "import os,sys; sys.path.insert(0, os.getcwd()); from app import appconfig; print([(m['name'], m['category'], m['enabled'], m['api_key_set']) for m in appconfig.list_ai_models()])" ``` -确认 `data/data/config/ai_models.json` 仍被忽略: +确认 `data/config/ai_models.json` 仍被忽略: ```powershell git status --short --ignored data/config/ai_models.json @@ -125,7 +125,7 @@ https://api.vectorengine.ai/v1 https://api.vectorengine.ai/v1/chat/completions ``` -如果仍然 404,优先检查服务商文档要求的 endpoint 路径和 `api_type`,不要把 `data/data/config/ai_models.json` 或 API Key 贴到日志、文档或聊天里。 +如果仍然 404,优先检查服务商文档要求的 endpoint 路径和 `api_type`,不要把 `data/config/ai_models.json` 或 API Key 贴到日志、文档或聊天里。 ### 非敏感检查 @@ -207,7 +207,7 @@ T-530 已实现:保存和请求前都会把 Base URL 规整为网关根,去 2. 如果②状态列显示“生成失败”:留在②,确认 cmhub Base URL、API Key、别名和点数正常后,直接点击「开始生成」重试当前筛选结果。 3. 如果②状态列显示“更新失败”:到③更新shopee处理;需要重复更新时使用③的重置更新状态入口,不要在②重新生成。 -排查时不要把 `data/cmshopee.db`、`data/config/cmhub.json`、`data/data/config/ai_models.json` 或任何 API Key、密码、Cookie 发到聊天、文档或提交里。 +排查时不要把 `data/cmshopee.db`、`data/config/cmhub.json`、`data/config/ai_models.json` 或任何 API Key、密码、Cookie 发到聊天、文档或提交里。 ## AI生成:标题成功但图片生成失败,且看不到原因 @@ -216,7 +216,7 @@ T-530 已实现:保存和请求前都会把 Base URL 规整为网关根,去 - 页面右下「AI生成运行日志」显示最近一次 `run_type=generate` 的逐条事件,例如 `phase=cover step=cover_request result=failed detail=...`。 - 本地 `data/logs/cmshopee.log` 保存脱敏后的 traceback、任务 id、alias、item_id、phase 和 step,用于判断卡在模型配置、封面请求、图片解析、保存文件还是写库。 -排查顺序:先看 ② 页面运行日志里的 `phase` / `step` / `detail`;如果只看到简短错误,再查看本地 `data/logs/cmshopee.log`。不要把 `data/data/config/ai_models.json` 或 API Key 发到聊天、文档或提交里。 +排查顺序:先看 ② 页面运行日志里的 `phase` / `step` / `detail`;如果只看到简短错误,再查看本地 `data/logs/cmshopee.log`。不要把 `data/config/ai_models.json` 或 API Key 发到聊天、文档或提交里。 ## ④启动登录重复打开 Chrome @@ -232,17 +232,23 @@ T-530 已实现:保存和请求前都会把 Base URL 规整为网关根,去 ### 现象 -在 Windows 10 虚拟机或小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` / release 包后,主窗口出现在屏幕左上角,左边和标题栏显示不全,看不到「蝦皮圈優化助手」标题,导致无法用鼠标拖动窗口到中间。 +在 Windows 10 虚拟机或小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` / `release/cmshopee-<版本>-portable.zip` 后,主窗口出现在屏幕左上角,左边和标题栏显示不全,看不到「蝦皮圈優化助手 v<版本>」标题,导致无法用鼠标拖动窗口到中间。 ### 原因 -旧逻辑只设置固定窗口大小: +修复前旧逻辑只设置了固定窗口大小: ```python self.resize(1180, 760) ``` -没有按当前屏幕可用区域限制初始尺寸、居中显示,也没有保证窗口标题栏完整落在屏幕内。虚拟机、小分辨率、高 DPI、任务栏占用较多或多显示器切换环境下更容易暴露该问题;PyInstaller 打包不是根因。 +没有按当前屏幕可用区域做以下处理: + +- 限制初始尺寸不超过屏幕可用区域。 +- 居中显示。 +- 保证窗口 frame/title bar 完整落在屏幕内。 + +因此在虚拟机、小分辨率、高 DPI、任务栏占用较多或多显示器切换环境下,Qt/Windows 默认放置窗口时可能让标题栏或左边缘落到屏幕外。PyInstaller 打包不是根因,只是打包版更常在目标虚拟机里暴露该问题。 ### 临时处理 @@ -261,7 +267,7 @@ T-541 已在主窗口启动时读取 `QApplication.primaryScreen().availableGeom - 大屏保持接近 `1180x760`。 - 小屏自动缩小到可用区域内。 - `resize()` 后按可用区域居中 `move()`。 -- 确保窗口左上角不小于可用区域左上角,标题栏完整可见、可拖动。 +- 确保窗口 frame 左上角不小于可用区域左上角,标题栏完整可见、可拖动。 - 不保存坏的历史窗口坐标,避免下次继续打开到屏幕外。 修复后需要在普通桌面和 Windows 10 虚拟机/小分辨率环境各启动一次打包版,确认标题栏完整可见。 diff --git a/progress.md b/progress.md index 5a1e637..002d141 100644 --- a/progress.md +++ b/progress.md @@ -1272,22 +1272,6 @@ - 边界:只改显示层和文档层;`cmshopee` 继续作为仓库、包名、exe、数据库、日志、配置、回写后缀和兼容标识符保留;未改任何文件名、schema、AI、Excel、DB、CDP 或 Shopee 更新流程。 - 验证:`python -m py_compile app\gui\main_window.py app\gui\__init__.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p test_gui.py` 通过(95 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过;`git diff --check` 通过。 -## 【2026-07-07】T-541 文档 · 打包版窗口左上角显示不全 - -- 现象:运行打包后的 `dist/cmshopee/cmshopee.exe` / release 包时,Windows 10 虚拟机或小分辨率环境可能出现主窗口贴到屏幕左上角、左边和标题栏显示不全,用户看不到品牌标题且无法拖动窗口到中间。 -- 原因:`MainWindow.__init__()` 当前只 `resize(1180, 760)`,没有读取 `QApplication.primaryScreen().availableGeometry()` 限制初始尺寸、居中显示,也没有保证窗口标题栏完整落在可用屏幕内。 -- 文档:`docs/06-tasks.md` 新增 T-541;`docs/troubleshooting.md` 增加临时处理(`Alt+Space` → `M` → 方向键移动)和正式修复方案;`docs/packaging.md` 打包验收清单新增标题栏完整可见/可拖动检查;`docs/current-state.md` 记录 T-541 修复口径。 -- 方案:后续代码在主窗口启动时根据屏幕可用区域限制尺寸并居中,保证标题栏可见;小屏继续适配,不保存坏的历史窗口坐标。 -- 边界:本轮只更新文档,未改 GUI 代码、打包脚本、业务逻辑、DB、AI、Excel、CDP 或 Shopee 流程。 - -## 【2026-07-07】T-541 完成 · 打包版主窗口初始位置与小屏适配 - -- 状态:DONE -- 代码:`app/gui/main_window.py` 新增 `_fit_and_center_window()`,启动时读取 `QApplication.primaryScreen().availableGeometry()`,将主窗口尺寸限制在可用屏幕范围内并居中;首次 `showEvent` 后再应用一次,避免打包版在 Windows 10 VM/小分辨率环境标题栏出屏。 -- 文档:`docs/06-tasks.md` 将 T-541 标记为 DONE;`docs/troubleshooting.md`、`docs/packaging.md`、`docs/current-state.md` 更新为当前修复与打包验收口径。 -- 测试:`tests/test_gui.py` 新增小屏和大屏 mock geometry 单测,覆盖尺寸限制与居中位置;`scripts/build_exe.ps1` 已重新构建 release,确认 T-541 合入后仍可打包。 -- 边界:不保存/恢复窗口历史坐标;未改业务流程、DB、AI、Excel、CDP/Shopee、打包产物结构或用户数据路径。 - ## 【2026-07-07】T-538 完成 · 打包产物用户数据收进 data/ - 状态:DONE @@ -1296,7 +1280,7 @@ - 打包:`.gitignore` 新增 `data/`;`scripts/build_exe.ps1` 发布目录校验禁止带出 `data/`;更新方式改为覆盖发布包里的程序文件和依赖文件并保留 `data/`。当前 PyInstaller 5.13.2 为扁平 onedir,不要求存在 `_internal/`。 - 文档:`docs/06-tasks.md` 将 T-538 标记为 DONE;同步 `docs/packaging.md`、架构/技术栈/API/排障/路由/current-state 和 UI 线框图里的数据路径说明。 - 边界:未改 DB schema、AI HTTP 协议、Excel、账号、CDP 或 Shopee 页面操作流程;旧 DB 已存图片路径继续按原路径读取。 -- 验证:`python -m py_compile app\appconfig.py app\diagnostics.py app\prompts.py app\editor.py app\gui\__init__.py app\gui\main_window.py app\gui\tabs\generate.py app\gui\tabs\settings.py app\gui\widgets.py main.py tests\test_appconfig.py tests\test_packaging.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p test_appconfig.py` 通过;`python -m unittest discover -s tests -p test_packaging.py` 通过;`python -m unittest discover -s tests -p test_gui.py` 通过;`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过;`git diff --check` 通过。 +- 验证:`python -m py_compile app\appconfig.py app\diagnostics.py app\prompts.py app\editor.py app\gui\__init__.py app\gui\main_window.py app\gui\tabs\generate.py app\gui\tabs\settings.py app\gui\widgets.py main.py tests\test_appconfig.py tests\test_packaging.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p test_appconfig.py` 通过(14 tests);`python -m unittest discover -s tests -p test_packaging.py` 通过(3 tests);`python -m unittest discover -s tests -p test_gui.py` 通过(95 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过(217 tests);`git diff --check` 通过。 ## 【2026-07-07】T-538 修正 · 区分数据迁移冲突和目录不可写 @@ -1312,3 +1296,58 @@ - 文档:`docs/packaging.md`、`docs/03-tech-stack.md`、`docs/06-tasks.md`、`docs/current-state.md` 已改为发布整个 `dist/cmshopee` 文件夹,验证重点是不得带出 `data/` 和用户本地数据。 - 代码:`scripts/build_exe.ps1` 打包成功后会提示当前依赖布局;无 `_internal/` 时明确说明扁平结构正常,并提醒发布整个 `dist/cmshopee` 目录。 - 边界:未升级 PyInstaller,未改变 spec 产物布局,未改运行时路径、DB、AI、Excel、CDP 或 Shopee 流程。 + +## 【2026-07-07】T-540 文档 · 打包版本号与 release 产物命名统一 + +- 需求:参考 `D:\chengma\cmbot` 的打包版本控制方式,解决当前 `cmshopee` 没有统一版本源、发布目录不带版本号、GUI 标题和构建产物无法一致追踪的问题。 +- 文档:`docs/packaging.md` 新增“版本号与发布目录(T-540 方案)”,定义 `app/version.py` 为唯一版本源、`release\cmshopee-\` 发布目录、`cmshopee--portable.zip` 便携包、`version.txt` 和中文 README;`docs/06-tasks.md` 新增 T-540;`docs/current-state.md` 记录后续任务口径。 +- 决策:本阶段只做手动发版版本号一致性,不搬 `cmbot` 的 `Launcher.exe`、`manifest.json`、SHA-256 下载校验、强制更新或在线更新机制。 +- 边界:本轮只更新文档,未改代码、构建脚本、测试、运行时路径、DB、AI、Excel、CDP 或 Shopee 流程。 + +## 【2026-07-07】T-540 完成 · 打包版本号与 release 产物命名统一 + +- 状态:DONE +- 代码:新增 `app/version.py`,统一维护 `APP_NAME`、`APP_CODE_NAME`、`APP_VERSION` 和 `display_name()`;GUI 主窗口标题改为 `蝦皮圈優化助手 v`,PySide6 缺失提示继续读取统一品牌名。 +- 打包:`scripts/build_exe.ps1` 从 `app/version.py` 读取版本,最低支持 Python 3.7.9,低于 3.7.9 直接失败;PyInstaller 仍输出 `dist\cmshopee`,随后组装 `release\cmshopee-\`,写入 `version.txt`、中文 `README.txt`,并生成 `release\cmshopee--portable.zip`;`release/` 已加入 `.gitignore`。 +- 兼容:脚本源码保持 ASCII,中文构建提示和 README 文案用 UTF-8 base64 在运行时解码,避免 Windows PowerShell 5.1 误读 UTF-8 无 BOM 导致解析失败。 +- 测试:`tests/test_packaging.py` 覆盖版本源、release 命名、zip、`version.txt`、Python 3.7.9 最低版本校验和数据排除脚本逻辑;`tests/test_gui.py` 改为断言窗口标题读取统一版本源。 +- 边界:不做 Launcher、manifest、sha256 下载校验、在线更新、强制更新或自动替换程序目录;未改 DB schema、AI、Excel、CDP 或 Shopee 流程。 + +## 【2026-07-07】T-540 修正 · 打包兼容 Python 3.7.9 + +- 需求:用户确认当前项目在 Python 3.7.9 环境可运行,打包文档与脚本不应继续要求 Python 3.10+。 +- 代码:`scripts/build_exe.ps1` 的最低版本校验改为 Python 3.7.9;低于 3.7.9 才失败。 +- 文档:`docs/packaging.md`、`docs/03-tech-stack.md`、`docs/06-tasks.md`、`docs/current-state.md` 改为当前发布构建兼容 Python 3.7.9+,CI 继续使用 Python 3.11。 +- 测试:`tests/test_packaging.py` 改为断言构建脚本包含 Python 3.7.9 最低版本校验,并防止重新写回 Python 3.10+ 阻断。 +- 边界:只改打包版本要求、文档和测试;未改运行依赖版本、业务逻辑、DB、AI、Excel、CDP 或 Shopee 流程。 + +## 【2026-07-07】T-540 修正 · 打包固定使用 Python 3.10 + +- 需求:用户已安装 Python 3.10,正式打包需要固定使用 Python 3.10,避免 PATH 上的 Python 3.7.9 或其他版本参与发布构建。 +- 代码:`scripts/build_exe.ps1` 改为固定调用 Windows Python Launcher 的 `py -3.10`,读取到的版本必须是 `3.10.x`;`python -m PyInstaller` 改为 `py -3.10 -m PyInstaller`,并在 PyInstaller 退出码非 0 时直接失败。 +- 文档:`docs/packaging.md`、`docs/03-tech-stack.md`、`docs/06-tasks.md`、`docs/current-state.md` 改为正式打包固定 Python 3.10;安装打包依赖命令改为 `py -3.10 -m pip install -r requirements-build.txt`。 +- 测试:`tests/test_packaging.py` 改为断言构建脚本固定 `py -3.10`,且不再使用裸 `python -m PyInstaller` 或 3.7.9 最低版本口径。 +- 验证:`py -3.10 -m pip install -r requirements-build.txt` 成功;`py -3.10 -m compileall app main.py` 通过;`py -3.10 -m unittest discover -s tests` 通过(219 tests);`powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1` 成功,输出显示 `使用 Python 3.10: 3.10.11` 并生成 `release\cmshopee-0.1.0-portable.zip`。 +- 边界:只改正式打包解释器选择、文档和测试;未改运行依赖版本、业务逻辑、DB、AI、Excel、CDP 或 Shopee 流程。 + +## 【2026-07-07】T-541 文档 · 打包版窗口左上角显示不全 + +- 现象:运行打包后的 `dist/cmshopee/cmshopee.exe` / release 包时,Windows 10 虚拟机或小分辨率环境可能出现主窗口贴到屏幕左上角、左边和标题栏显示不全,用户看不到品牌标题且无法拖动窗口到中间。 +- 原因:`MainWindow.__init__()` 当前只 `resize(1180, 760)`,没有读取 `QApplication.primaryScreen().availableGeometry()` 限制初始尺寸、居中显示,也没有保证窗口 frame/title bar 完整落在可用屏幕内。 +- 文档:`docs/06-tasks.md` 新增 T-541;`docs/troubleshooting.md` 增加临时处理(`Alt+Space` → `M` → 方向键移动)和正式修复方案;`docs/packaging.md` 打包验收清单新增标题栏完整可见/可拖动检查;`docs/current-state.md` 记录 T-541 为下一步优先修复。 +- 方案:后续代码在主窗口启动时根据屏幕可用区域限制尺寸并居中,保证标题栏可见;小屏继续适配,不保存坏的历史窗口坐标。 +- 边界:本轮只更新文档,未改 GUI 代码、打包脚本、业务逻辑、DB、AI、Excel、CDP 或 Shopee 流程。 + +## 【2026-07-07】T-541 完成 · 打包版主窗口初始位置与小屏适配 + +- 状态:DONE +- 代码:`app/gui/main_window.py` 新增 `_fit_and_center_window()`,启动时读取 `QApplication.primaryScreen().availableGeometry()`,将主窗口尺寸限制在可用屏幕范围内并居中;首次 `showEvent` 后再应用一次,避免打包版在 Windows 10 VM/小分辨率环境标题栏出屏。 +- 文档:`docs/06-tasks.md` 将 T-541 标记为 DONE;`docs/troubleshooting.md`、`docs/packaging.md`、`docs/current-state.md` 更新为当前修复与打包验收口径。 +- 测试:`tests/test_gui.py` 新增小屏和大屏 mock geometry 单测,覆盖尺寸限制与居中位置;`scripts/build_exe.ps1` 已重新构建 release,确认 T-541 合入后仍可打包。 +- 边界:不保存/恢复窗口历史坐标;未改业务流程、DB、AI、Excel、CDP/Shopee、打包产物结构或用户数据路径。 + +## 【2026-07-07】T-540 修正 · 当前打包布局统一为 PyInstaller 6 + +- 现象:T-540 代码和 `requirements-build.txt` 已固定 PyInstaller 6.11.1,构建脚本也会校验 `_internal/`,但 `docs/packaging.md`、`docs/03-tech-stack.md`、`docs/06-tasks.md`、`docs/current-state.md` 仍有当前 PyInstaller 5.13.2 扁平 onedir 的表述。 +- 文档:已把当前打包口径统一为 PyInstaller 6.11.1,产物必须包含 `cmshopee.exe` + `_internal/`;用户升级说明改为整体覆盖新版程序文件、`_internal/`、`version.txt`、`README.txt`,并保留旧目录 `data/`。 +- 边界:只修正文档一致性;历史 T-538 流水仍保留当时 PyInstaller 5 的记录;未改业务流程、DB、AI、Excel、CDP/Shopee 或用户数据路径。 diff --git a/requirements-build.txt b/requirements-build.txt index 49c36dc..da6636c 100644 --- a/requirements-build.txt +++ b/requirements-build.txt @@ -1,2 +1,2 @@ -r requirements.txt -pyinstaller==5.13.2 +pyinstaller==6.11.1 diff --git a/scripts/build_exe.ps1 b/scripts/build_exe.ps1 index 0551092..4d3d5f4 100644 --- a/scripts/build_exe.ps1 +++ b/scripts/build_exe.ps1 @@ -1,20 +1,99 @@ $ErrorActionPreference = "Stop" $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -$repoRoot = Resolve-Path (Join-Path $scriptDir "..") +$repoRoot = (Resolve-Path (Join-Path $scriptDir "..")).Path Set-Location $repoRoot -$pythonVersion = python -c "import sys; print('%d.%d.%d' % sys.version_info[:3])" -Write-Host "Using Python: $pythonVersion" -if ([version]$pythonVersion -lt [version]"3.10.0") { - Write-Warning "Project target is Python 3.10+. Switch to a 3.10+ environment before official release builds." +function Read-VersionValue { + param( + [string]$Path, + [string]$Name + ) + + $pattern = '^\s*{0}\s*=\s*"([^"]+)"' -f [regex]::Escape($Name) + foreach ($line in Get-Content -LiteralPath $Path -Encoding UTF8) { + if ($line -match $pattern) { + return $Matches[1] + } + } + throw "Cannot find $Name in $Path" +} + +function Decode-Utf8Base64 { + param([string]$Text) + + return [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Text)) +} + +function Assert-InProject { + param([string]$Path) + + $resolvedRoot = (Resolve-Path -LiteralPath $repoRoot).Path + $rootPrefix = $resolvedRoot.TrimEnd("\") + "\" + if (Test-Path -LiteralPath $Path) { + $resolvedPath = (Resolve-Path -LiteralPath $Path).Path + if ($resolvedPath -ne $resolvedRoot -and -not $resolvedPath.StartsWith($rootPrefix, [System.StringComparison]::OrdinalIgnoreCase)) { + throw "Refusing to modify path outside project: $resolvedPath" + } + } else { + $fullPath = [System.IO.Path]::GetFullPath($Path) + if ($fullPath -ne $resolvedRoot -and -not $fullPath.StartsWith($rootPrefix, [System.StringComparison]::OrdinalIgnoreCase)) { + throw "Refusing to modify path outside project: $fullPath" + } + } +} + +function Assert-NoExcludedData { + param( + [string]$Root, + [string[]]$Excluded + ) + + $found = @() + foreach ($relative in $Excluded) { + $candidate = Join-Path $Root $relative + if (Test-Path -LiteralPath $candidate) { + $found += $relative + } + } + if ($found.Count -gt 0) { + throw "Build output contains local user data: $($found -join ', ')" + } +} + +$versionFile = Join-Path $repoRoot "app\version.py" +if (-not (Test-Path -LiteralPath $versionFile)) { + throw "Missing app\version.py" +} +$appName = Read-VersionValue -Path $versionFile -Name "APP_NAME" +$appCodeName = Read-VersionValue -Path $versionFile -Name "APP_CODE_NAME" +$appVersion = Read-VersionValue -Path $versionFile -Name "APP_VERSION" +if ($appCodeName -ne "cmshopee") { + throw "APP_CODE_NAME must be cmshopee, current value: $appCodeName" +} + +$pythonExe = "py" +$pythonArgs = @("-3.10") +$pythonVersionOutput = & $pythonExe @pythonArgs -c "import sys; print('%d.%d.%d' % sys.version_info[:3])" +if ($LASTEXITCODE -ne 0 -or -not $pythonVersionOutput) { + throw (Decode-Utf8Base64 "5pyq5om+5YiwIFB5dGhvbiAzLjEw44CC6K+35YWI5a6J6KOFIFB5dGhvbiAzLjEw77yM5bm256Gu6K6kIFdpbmRvd3MgUHl0aG9uIExhdW5jaGVyIOWPr+mAmui/hyBweSAtMy4xMCDosIPnlKjjgII=") +} +$pythonVersion = ($pythonVersionOutput | Select-Object -First 1).Trim() +Write-Host "$(Decode-Utf8Base64 "5L2/55SoIFB5dGhvbiAzLjEwOg==") $pythonVersion" +if (-not $pythonVersion.StartsWith("3.10.")) { + throw "$(Decode-Utf8Base64 "5b2T5YmN5omT5YyF6ISa5pys5Zu65a6a5L2/55SoIFB5dGhvbiAzLjEw77yM5b2T5YmN54mI5pys"): $pythonVersion" } $spec = Join-Path $repoRoot "cmshopee.spec" if (-not (Test-Path -LiteralPath $spec)) { throw "Missing cmshopee.spec" } -python -m PyInstaller --noconfirm --clean $spec +Write-Host "$(Decode-Utf8Base64 "5q2j5Zyo5p6E5bu6") $appName $appVersion" +Write-Host (Decode-Utf8Base64 "5omT5YyF5ZG95Luk5Zu65a6a5L2/55SoIHB5IC0zLjEwIC1tIFB5SW5zdGFsbGVy44CC") +& $pythonExe @pythonArgs -m PyInstaller --noconfirm --clean $spec +if ($LASTEXITCODE -ne 0) { + throw "$(Decode-Utf8Base64 "UHlJbnN0YWxsZXIg5omT5YyF5aSx6LSl77yM6YCA5Ye656CB"): $LASTEXITCODE" +} $distDir = Join-Path $repoRoot "dist\cmshopee" $exePath = Join-Path $distDir "cmshopee.exe" @@ -37,19 +116,51 @@ $excluded = @( "data" ) -$found = @() -foreach ($relative in $excluded) { - $candidate = Join-Path $distDir $relative - if (Test-Path -LiteralPath $candidate) { - $found += $relative +Assert-NoExcludedData -Root $distDir -Excluded $excluded + +$internalDir = Join-Path $distDir "_internal" +if (-not (Test-Path -LiteralPath $internalDir)) { + throw "Build output missing _internal directory. Expected PyInstaller 6.x onedir layout (cmshopee.exe + _internal). Ensure PyInstaller >= 6.0 is installed: py -3.10 -m pip install -r requirements-build.txt" +} +Write-Host "Verified onedir layout: _internal present" + +$releaseRoot = Join-Path $repoRoot "release" +$releaseName = "$appCodeName-$appVersion" +$releaseDir = Join-Path $releaseRoot $releaseName +$portableZip = Join-Path $releaseRoot "$releaseName-portable.zip" + +foreach ($path in @($releaseDir, $portableZip)) { + Assert-InProject -Path $path + if (Test-Path -LiteralPath $path) { + Remove-Item -LiteralPath $path -Recurse -Force } } -if ($found.Count -gt 0) { - throw "Build output contains local user data: $($found -join ', ')" -} +New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null +Get-ChildItem -LiteralPath $distDir -Force | Copy-Item -Destination $releaseDir -Recurse -Force +Set-Content -LiteralPath (Join-Path $releaseDir "version.txt") -Value $appVersion -Encoding ascii -NoNewline -Write-Host "Build complete: $exePath" -Write-Host "Do not copy local data into the release package." +$readme = @( + "$appName v$appVersion", + "", + (Decode-Utf8Base64 "5ZCv5Yqo5pa55byP77ya"), + (Decode-Utf8Base64 "5Y+M5Ye7IGNtc2hvcGVlLmV4ZSDlkK/liqjjgII="), + "", + (Decode-Utf8Base64 "5L2/55So6K+05piO77ya"), + (Decode-Utf8Base64 "LSDor7fmiormlbTkuKrmlofku7blpLnop6PljovliLDlj6/lhpnnm67lvZXvvIzkvovlpoIgRDpcY21zaG9wZWUg5oiW5qGM6Z2i44CC"), + (Decode-Utf8Base64 "LSDkuI3opoHmlL7liLAgQzpcUHJvZ3JhbSBGaWxlcyDmiJblhbbku5bpnIDopoHnrqHnkIblkZjmnYPpmZDlhpnlhaXnmoTnm67lvZXjgII="), + (Decode-Utf8Base64 "LSDpppbmrKHov5DooYzkvJrlnKjnqIvluo/lkIznuqfliJvlu7ogZGF0YVzvvIznlKjkuo7kv53lrZjotKblj7fjgIHku7vliqHjgIHlm77niYfjgIHml6Xlv5fjgIHphY3nva7lkoznmbvlvZXmgIHjgII="), + (Decode-Utf8Base64 "LSDljYfnuqfml7bopobnm5bmlrDniYjnqIvluo/mlofku7blkozkvp3otZbmlofku7bvvIzkvYblv4Xpobvkv53nlZnml6fnm67lvZXph4znmoQgZGF0YVzjgII="), + (Decode-Utf8Base64 "LSDkuI3opoHmioogZGF0YVwg5Y+R57uZ5YW25LuW5Lq677yM6YeM6Z2i5YyF5ZCr5pys5Zyw6YWN572u44CB5Lia5Yqh5pWw5o2u44CB5Zu+54mH5ZKM55m75b2V5oCB44CC") +) +Set-Content -LiteralPath (Join-Path $releaseDir "README.txt") -Value $readme -Encoding UTF8 + +Assert-NoExcludedData -Root $releaseDir -Excluded $excluded +Compress-Archive -Path (Join-Path $releaseDir "*") -DestinationPath $portableZip -Force + +Write-Host "$(Decode-Utf8Base64 "5p6E5bu65a6M5oiQOg==") $exePath" +Write-Host "$(Decode-Utf8Base64 "5Y+R5biD55uu5b2VOg==") $releaseDir" +Write-Host "$(Decode-Utf8Base64 "5L6/5pC65Y6L57yp5YyFOg==") $portableZip" +Write-Host (Decode-Utf8Base64 "5LiN6KaB5oqK5pys5ZywIGRhdGHjgIHphY3nva7jgIHmlbDmja7lupPjgIHlm77niYfjgIHml6Xlv5fmiJbnmbvlvZXmgIHlpI3liLbov5vlj5HluIPljIXjgII=") diff --git a/tests/test_gui.py b/tests/test_gui.py index b46b164..0f72856 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -157,7 +157,7 @@ class GuiTests(TempDirMixin, unittest.TestCase): window = MainWindow(config=self.make_config(temp_dir)) self.addCleanup(window.close) - self.assertEqual("蝦皮圈優化助手", window.windowTitle()) + self.assertEqual(gui.display_name(), window.windowTitle()) self.assertEqual(5, window.tabs.count()) self.assertEqual( TAB_TITLES, diff --git a/tests/test_packaging.py b/tests/test_packaging.py index b970f05..617a6a4 100644 --- a/tests/test_packaging.py +++ b/tests/test_packaging.py @@ -1,6 +1,7 @@ import unittest from _helpers import REPO_ROOT +from app import version class PackagingTests(unittest.TestCase): @@ -43,8 +44,43 @@ class PackagingTests(unittest.TestCase): ): self.assertIn(token, script) - self.assertIn("python -m PyInstaller --noconfirm --clean", script) + self.assertIn('$pythonExe = "py"', script) + self.assertIn('$pythonArgs = @("-3.10")', script) + self.assertIn("& $pythonExe @pythonArgs -m PyInstaller --noconfirm --clean", script) self.assertIn("cmshopee.spec", script) + self.assertIn("Decode-Utf8Base64", script) + + def test_release_version_source_is_single_place(self): + version_py = self.read_text("app/version.py") + + self.assertIn('APP_NAME = "蝦皮圈優化助手"', version_py) + self.assertIn('APP_CODE_NAME = "cmshopee"', version_py) + self.assertRegex(version.APP_VERSION, r"^\d+\.\d+\.\d+$") + self.assertEqual( + f"{version.APP_NAME} v{version.APP_VERSION}", + version.display_name(), + ) + + def test_build_script_assembles_versioned_release_package(self): + script = self.read_text("scripts/build_exe.ps1") + + self.assertIn('Read-VersionValue -Path $versionFile -Name "APP_VERSION"', script) + self.assertIn('$pythonExe = "py"', script) + self.assertIn('$pythonArgs = @("-3.10")', script) + self.assertIn( + 'Decode-Utf8Base64 "5pyq5om+5YiwIFB5dGhvbiAzLjEw44CC6K+35YWI5a6J6KOFIFB5dGhvbiAzLjEw77yM5bm256Gu6K6kIFdpbmRvd3MgUHl0aG9uIExhdW5jaGVyIOWPr+mAmui/hyBweSAtMy4xMCDosIPnlKjjgII="', + script, + ) + self.assertIn('$pythonVersion.StartsWith("3.10.")', script) + self.assertNotIn('python -m PyInstaller --noconfirm --clean', script) + self.assertNotIn("3.7.9", script) + self.assertIn('$releaseRoot = Join-Path $repoRoot "release"', script) + self.assertIn('$releaseName = "$appCodeName-$appVersion"', script) + self.assertIn('$portableZip = Join-Path $releaseRoot "$releaseName-portable.zip"', script) + self.assertIn('version.txt', script) + self.assertIn('README.txt', script) + self.assertIn('Compress-Archive', script) + self.assertIn('Assert-NoExcludedData -Root $releaseDir', script) if __name__ == "__main__":