feat: 完成PySide6主窗口骨架
- 实现 MainWindow + 固定顺序五 Tab QTabWidget + 状态栏 - 新增 offscreen GUI 单元测试覆盖 Tab 顺序和状态栏切换 - 缺 PySide6 时提供明确启动错误,避免无声失败 - 更新任务看板、API 合约、当前状态和进度记录
This commit is contained in:
+76
-6
@@ -1,10 +1,80 @@
|
||||
"""GUI entry point placeholder.
|
||||
"""PySide6 GUI entry point."""
|
||||
|
||||
The real PySide6 main window is implemented by task T-104.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QMainWindow,
|
||||
QTabWidget,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
QT_IMPORT_ERROR = None
|
||||
except ModuleNotFoundError as exc:
|
||||
QApplication = None
|
||||
QMainWindow = object
|
||||
QTabWidget = None
|
||||
QVBoxLayout = None
|
||||
QWidget = object
|
||||
QT_IMPORT_ERROR = exc
|
||||
|
||||
|
||||
TAB_TITLES = [
|
||||
"① 导入采集",
|
||||
"② AI生成",
|
||||
"③ 更新shopee",
|
||||
"④ 账号管理",
|
||||
"⑤ 设置",
|
||||
]
|
||||
|
||||
|
||||
if QT_IMPORT_ERROR is None:
|
||||
class MainWindow(QMainWindow):
|
||||
"""Main application window with the fixed five-tab workflow."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("cmshopee")
|
||||
self.resize(1180, 760)
|
||||
self.tabs = QTabWidget()
|
||||
self.tabs.setObjectName("mainTabs")
|
||||
self.tabs.currentChanged.connect(self._on_tab_changed)
|
||||
for title in TAB_TITLES:
|
||||
self.tabs.addTab(self._build_placeholder_tab(title), title)
|
||||
self.setCentralWidget(self.tabs)
|
||||
self.statusBar().showMessage("就绪")
|
||||
|
||||
def _build_placeholder_tab(self, title):
|
||||
widget = QWidget()
|
||||
widget.setObjectName(title)
|
||||
layout = QVBoxLayout(widget)
|
||||
layout.setContentsMargins(18, 18, 18, 18)
|
||||
layout.addStretch(1)
|
||||
return widget
|
||||
|
||||
def _on_tab_changed(self, index):
|
||||
self.statusBar().showMessage(f"当前:{self.tabs.tabText(index)}")
|
||||
else:
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
raise RuntimeError("PySide6 未安装,无法启动 GUI")
|
||||
|
||||
|
||||
def _ensure_offscreen_for_headless_tests():
|
||||
if "PYTEST_CURRENT_TEST" in os.environ and "QT_QPA_PLATFORM" not in os.environ:
|
||||
os.environ["QT_QPA_PLATFORM"] = "offscreen"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print("cmshopee GUI 尚未实现:请继续执行 T-104 构建 PySide6 主窗口。")
|
||||
return 0
|
||||
|
||||
if QT_IMPORT_ERROR is not None:
|
||||
print("cmshopee GUI 无法启动:当前 Python 环境未安装 PySide6。")
|
||||
return 1
|
||||
_ensure_offscreen_for_headless_tests()
|
||||
app = QApplication.instance() or QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
return app.exec()
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@
|
||||
| T-101 | `config` 生成 slug + 创建 `chrome_user_data_dir/<slug>` | T-003 | 别名→唯一 slug;目录按需建;路径绝对化 | DONE |
|
||||
| T-102 | `app/chrome.py` 启动器:拼参数并启动、探测端口 | T-101, T-002 | 含三参数;端口就绪可探测 | DONE |
|
||||
| T-103 | 首次登录保活 + 登录检测 `is_logged_in` | T-102, T-001 | 关闭再启动免重登;登录/未登录判断准确 | DONE |
|
||||
| T-104 | PySide6 五 Tab 主窗口骨架(`QMainWindow` + `QTabWidget`,5 Tab 空壳) | T-002 | 五个 Tab 按顺序可切换;启动不阻塞;基础状态栏可用 | TODO |
|
||||
| T-104 | PySide6 五 Tab 主窗口骨架(`QMainWindow` + `QTabWidget`,5 Tab 空壳) | T-002 | 五个 Tab 按顺序可切换;启动不阻塞;基础状态栏可用 | DONE |
|
||||
| T-104b | PySide6 worker 基类与线程启动工具(`BaseWorker` + `QThread` 包装) | T-104 | signals: progress/log/row_updated/failed/finished/cancelled;取消标记可用;worker 不直接操作 QWidget | TODO |
|
||||
| T-105 | Tab④ 账号增删改(账号名/别名/端口/密码本地明文仅参考)+ 启动登录 + 检测登录 | T-104, T-101, T-103 | 增删改入库、建目录;密码字段 UI 打码;状态列刷新;不自动登录/填密码 | TODO |
|
||||
| T-106 | 可选:为账号生成桌面快捷方式 | T-102 | `.lnk` 目标含该账号参数;双击进对应账号 | TODO |
|
||||
|
||||
+9
-1
@@ -214,12 +214,20 @@ render_prompt(template_text, task) -> str
|
||||
- 生成封面时 `gen_cover` 的 prompt = `render_prompt(当前封面模板, task)`。
|
||||
- 模板与 `title_prompt.txt` 均为可手改的纯文本文件。
|
||||
|
||||
## gui / workers 模块(`app/gui.py` / `app/workers.py`,待建,PySide6)
|
||||
## gui 模块(`app/gui.py`,已建,PySide6)
|
||||
|
||||
```python
|
||||
# GUI 入口
|
||||
main() -> int # 创建 QApplication + MainWindow
|
||||
class MainWindow(QMainWindow) # QTabWidget: ①②③④⑤
|
||||
TAB_TITLES: list[str] # 固定 Tab 顺序
|
||||
```
|
||||
|
||||
`MainWindow` 目前只实现五 Tab 空壳与基础状态栏;业务控件按后续 Tab 任务逐步填充。缺 PySide6 时 `main()` 返回 1 并输出明确提示。
|
||||
|
||||
## workers 模块(`app/workers.py`,待建,PySide6)
|
||||
|
||||
```python
|
||||
|
||||
# worker 约定
|
||||
class BaseWorker(QObject):
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-06-27
|
||||
- 阶段: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 登录保活与检测。
|
||||
- 阶段: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-104 PySide6 主窗口骨架。
|
||||
- 技术栈:Python 3.10+,自研 CDP(websocket-client + requests),SQLite(sqlite3)+ `config.json` + openpyxl + AI(服务商待定),GUI PySide6 5 Tab(已定)。
|
||||
- 生产代码:已建立 `app/` 包 + 根入口 `main.py`;`app/cdp.py` 为已验证 CDP 底座;`app/editor.py` 已封装登录状态检测、标题/封面/采集/更新按钮能力;`app/appconfig.py` 已实现 `config.json` 默认值/读写/更新、AI 参数与端口读取,以及 `config/ai_models.json` 模型清单 CRUD/过滤/打码/测试连接;`app/db.py` 已实现 SQLite schema、连接 PRAGMA、批次/账号/任务与阶段写库函数;`app/config.py` 已实现账号 slug 与 user-data-dir 创建;`app/chrome.py` 已实现 Chrome 参数拼装、启动与 CDP 端口探测;`app/gui.py` 目前是入口占位,完整 PySide6 主窗口待 T-104。
|
||||
- 测试:`tests/` 已建立;T-006 后纯逻辑改动必须运行 `python -m unittest discover -s tests`,当前覆盖 appconfig/db/config/chrome/editor 登录检测,并对尚未实现的 app.excel/app.prompts 做契约占位 skip;CDP/Shopee 改动仍需测试商品手动验证。
|
||||
- 生产代码:已建立 `app/` 包 + 根入口 `main.py`;`app/cdp.py` 为已验证 CDP 底座;`app/editor.py` 已封装登录状态检测、标题/封面/采集/更新按钮能力;`app/appconfig.py` 已实现 `config.json` 默认值/读写/更新、AI 参数与端口读取,以及 `config/ai_models.json` 模型清单 CRUD/过滤/打码/测试连接;`app/db.py` 已实现 SQLite schema、连接 PRAGMA、批次/账号/任务与阶段写库函数;`app/config.py` 已实现账号 slug 与 user-data-dir 创建;`app/chrome.py` 已实现 Chrome 参数拼装、启动与 CDP 端口探测;`app/gui.py` 已实现 PySide6 `MainWindow` + 五 Tab 空壳 + 状态栏。
|
||||
- 测试:`tests/` 已建立;T-006 后纯逻辑改动必须运行 `python -m unittest discover -s tests`,当前覆盖 appconfig/db/config/chrome/editor 登录检测/gui 骨架,并对尚未实现的 app.excel/app.prompts 做契约占位 skip;CDP/Shopee 改动仍需测试商品手动验证。
|
||||
- 数据:`config.json`、`config/ai_models.json`、`cmshopee.db`、`chrome_user_data_dir/`、`images/` 已由 `.gitignore` 排除;`app/appconfig.py` 首次读取缺失的 `config.json` 时会在本地写默认配置,`app/db.py` 调用 `init_db()` 时会在本地创建 SQLite DB。
|
||||
|
||||
## 既定设计要点(文档已定)
|
||||
@@ -32,7 +32,7 @@
|
||||
| `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 转发排查记录 |
|
||||
| `app/__init__.py` / `app/__main__.py` / `main.py` | 已有 | 正式包与启动入口;`python main.py` / `python -m app` 可运行占位入口 |
|
||||
| `app/gui.py` | 已有 | GUI 占位入口;完整 PySide6 主窗口待 T-104 |
|
||||
| `app/gui.py` | 已有 | T-104 产出:PySide6 `QMainWindow` + `QTabWidget` 五 Tab 空壳 + 状态栏 |
|
||||
| `app/editor.py` | 已有 | T-001/T-103 产出:登录状态检测、打开商品页、读/写标题、读/下载封面、上传拖封面、更新按钮、apply_task |
|
||||
| `app/appconfig.py` | 已有 | T-002 产出:`config.json` 默认值、读写、更新、路径/端口/AI 参数读取;拒绝敏感字段写入 |
|
||||
| `app/db.py` | 已有 | T-003 产出:batches/accounts/tasks schema;WAL/busy_timeout/foreign_keys;账号/批次/任务与 set_* 阶段写库 |
|
||||
@@ -54,9 +54,9 @@
|
||||
|
||||
任务状态以 [`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(`app/db.py` + SQLite 建表)、T-004(本地数据 gitignore)、T-005(AI 模型清单后端)、T-006(单元测试基座)、T-101(账号 slug/user-data-dir)、T-102(Chrome 启动器)、T-103(登录保活与检测)。
|
||||
- 已完成:T-000(正式代码包结构)、T-001(`app/editor.py` 模块化)、T-002(`app/appconfig.py` + `config.json`)、T-003(`app/db.py` + SQLite 建表)、T-004(本地数据 gitignore)、T-005(AI 模型清单后端)、T-006(单元测试基座)、T-101(账号 slug/user-data-dir)、T-102(Chrome 启动器)、T-103(登录保活与检测)、T-104(PySide6 五 Tab 主窗口骨架)。
|
||||
- 正在进行:无。
|
||||
- 下一个可领取任务:**T-104(PySide6 五 Tab 主窗口骨架)**。
|
||||
- 下一个可领取任务:**T-104b(PySide6 worker 基类与线程启动工具)**。
|
||||
|
||||
## 当前可运行内容
|
||||
|
||||
@@ -82,6 +82,8 @@ py -3 -c "import tempfile; from app import config; d=tempfile.TemporaryDirectory
|
||||
# chrome 参数拼装 / 端口探测由 tests/test_chrome.py 覆盖
|
||||
python -m unittest discover -s tests
|
||||
|
||||
# GUI 骨架由 tests/test_gui.py 覆盖;默认 python 当前 PySide6=6.5.3,py -3 环境缺 PySide6 时该测试会 skip
|
||||
|
||||
# 当前入口占位
|
||||
python main.py
|
||||
py -3 -m app
|
||||
|
||||
@@ -321,6 +321,14 @@
|
||||
- 验证:`py -3 -m compileall app main.py tests` 通过;`py -3 -m unittest discover -s tests` 通过(22 tests,skipped=2);`python -m unittest discover -s tests` 通过(22 tests,skipped=2)。
|
||||
- 下一步:按任务看板领取 T-104。
|
||||
|
||||
## 【2026-06-27】T-104 PySide6 五 Tab 主窗口骨架
|
||||
|
||||
- 状态:DONE
|
||||
- 变更:将 `app/gui.py` 从占位入口升级为 PySide6 `MainWindow(QMainWindow)`,内含固定顺序 `QTabWidget` 五 Tab:①导入采集、②AI生成、③更新shopee、④账号管理、⑤设置;实现基础状态栏与 Tab 切换状态提示;新增 `tests/test_gui.py` 覆盖 Tab 顺序与状态栏;同步 `docs/06-tasks.md`、`docs/current-state.md`、`docs/api.md`。
|
||||
- 细节:Tab 内容保持空壳容器,不提前实现后续业务 UI;缺 PySide6 时 `main()` 返回 1 并输出明确提示,便于依赖诊断。
|
||||
- 验证:`py -3 -m compileall app main.py tests` 通过;`py -3 -m unittest discover -s tests` 通过(23 tests,skipped=3,其中 GUI 因 py -3 缺 PySide6 跳过);`python -m unittest discover -s tests` 通过(24 tests,skipped=2,实际 offscreen 创建 MainWindow);默认 `python` 的 PySide6 版本为 6.5.3。
|
||||
- 下一步:按任务看板领取 T-104b。
|
||||
|
||||
## 【2026-06-27】审查 · T-001~T-103(含 T-000/T-004/T-005/T-006)
|
||||
|
||||
- 状态:DONE(审查通过)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from app import gui
|
||||
|
||||
if gui.QT_IMPORT_ERROR is not None:
|
||||
raise unittest.SkipTest("PySide6 未安装")
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from app.gui import MainWindow, TAB_TITLES
|
||||
|
||||
|
||||
class GuiTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.app = QApplication.instance() or QApplication([])
|
||||
|
||||
def test_main_window_has_five_tabs_in_workflow_order(self):
|
||||
window = MainWindow()
|
||||
self.addCleanup(window.close)
|
||||
|
||||
self.assertEqual(5, window.tabs.count())
|
||||
self.assertEqual(
|
||||
TAB_TITLES,
|
||||
[window.tabs.tabText(index) for index in range(window.tabs.count())],
|
||||
)
|
||||
self.assertEqual("就绪", window.statusBar().currentMessage())
|
||||
|
||||
def test_tab_switch_updates_status_bar(self):
|
||||
window = MainWindow()
|
||||
self.addCleanup(window.close)
|
||||
|
||||
window.tabs.setCurrentIndex(2)
|
||||
|
||||
self.assertEqual("③ 更新shopee", window.tabs.tabText(window.tabs.currentIndex()))
|
||||
self.assertEqual("当前:③ 更新shopee", window.statusBar().currentMessage())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user