From 5916de4b92529377ca323b442862fee57b2c9854 Mon Sep 17 00:00:00 2001 From: chengma Date: Thu, 6 Aug 2026 12:06:16 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=8B=86=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=20UI=20=E6=A8=A1=E5=9D=97=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pdd_ui.py | 81 ++++++++++++++++++++++++++++++++++++++ client/src/settings_ui.py | 79 +++++++++++++++++++++++++++++++++++++ client/src/ui_main.py | 83 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 client/src/ui_main.py diff --git a/client/src/pdd_ui.py b/client/src/pdd_ui.py index e2041a2..7c9c499 100644 --- a/client/src/pdd_ui.py +++ b/client/src/pdd_ui.py @@ -24,3 +24,84 @@ 注意:本页唯一会产生外部后果(真的去操作手机、可能下单)的命令是 “开始自动获取”。其余操作全部只读本地数据库。 """ + +from PyQt5.QtWidgets import ( + QGridLayout, + QHBoxLayout, + QSizePolicy, + QVBoxLayout, + QWidget, +) +from qfluentwidgets import ( + CaptionLabel, + CardWidget, + FluentIcon as FIF, + LineEdit, + PrimaryPushButton, + SubtitleLabel, + TitleLabel, +) + + +class PDDTaskPage(QWidget): + """商品与规格配置页。""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("pddTaskPage") + + self.goodsInput = LineEdit(self) + self.goodsInput.setPlaceholderText("输入拼多多商品链接或商品 ID") + self.goodsInput.setClearButtonEnabled(True) + self.goodsInput.setAccessibleName("商品链接或商品 ID") + + self.colorInput = LineEdit(self) + self.colorInput.setPlaceholderText("例如:黑色") + self.colorInput.setClearButtonEnabled(True) + self.colorInput.setAccessibleName("目标颜色") + + self.sizeInput = LineEdit(self) + self.sizeInput.setPlaceholderText("例如:L") + self.sizeInput.setClearButtonEnabled(True) + self.sizeInput.setAccessibleName("目标尺码") + + self.startButton = PrimaryPushButton(FIF.PLAY, "开始任务", self) + self.startButton.setAccessibleName("开始自动购买任务") + self.statusLabel = CaptionLabel("等待启动", self) + + card = CardWidget(self) + card.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + cardLayout = QVBoxLayout(card) + cardLayout.setContentsMargins(24, 20, 24, 22) + cardLayout.setSpacing(12) + cardLayout.addWidget(SubtitleLabel("创建任务", card)) + cardLayout.addWidget(CaptionLabel("商品链接 / ID", card)) + cardLayout.addWidget(self.goodsInput) + + specLayout = QGridLayout() + specLayout.setHorizontalSpacing(16) + specLayout.setVerticalSpacing(8) + specLayout.addWidget(CaptionLabel("颜色", card), 0, 0) + specLayout.addWidget(CaptionLabel("尺码", card), 0, 1) + specLayout.addWidget(self.colorInput, 1, 0) + specLayout.addWidget(self.sizeInput, 1, 1) + specLayout.setColumnStretch(0, 1) + specLayout.setColumnStretch(1, 1) + cardLayout.addLayout(specLayout) + + actionLayout = QHBoxLayout() + actionLayout.setContentsMargins(0, 6, 0, 0) + actionLayout.addWidget(self.statusLabel) + actionLayout.addStretch(1) + actionLayout.addWidget(self.startButton) + cardLayout.addLayout(actionLayout) + + layout = QVBoxLayout(self) + layout.setContentsMargins(36, 30, 36, 36) + layout.setSpacing(16) + layout.addWidget(TitleLabel("pdd任务", self)) + # description = BodyLabel("配置商品与目标规格,然后启动自动化任务。", self) + # description.setWordWrap(True) + # layout.addWidget(description) + layout.addWidget(card) + layout.addStretch(1) diff --git a/client/src/settings_ui.py b/client/src/settings_ui.py index d479c47..249d291 100644 --- a/client/src/settings_ui.py +++ b/client/src/settings_ui.py @@ -20,3 +20,82 @@ 注意:设备连接**不是**独立的顶级页面,统一放在本页, 见 `docs/client/01-requirements.md` §3。 """ + +from PyQt5.QtWidgets import QHBoxLayout, QSizePolicy, QVBoxLayout, QWidget +from qfluentwidgets import ( + CaptionLabel, + CardWidget, + FluentIcon as FIF, + LineEdit, + PushButton, + SubtitleLabel, + TitleLabel, +) + + +class DevicePage(QWidget): + """Android 设备连接页。""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("devicePage") + + self.addressInput = LineEdit(self) + self.addressInput.setText("192.168.0.173:5555") + self.addressInput.setPlaceholderText("IP:端口") + self.addressInput.setClearButtonEnabled(True) + self.addressInput.setAccessibleName("Android 设备地址") + + self.connectButton = PushButton(FIF.LINK, "连接设备", self) + self.connectButton.setAccessibleName("连接 Android 设备") + self.deviceStatusLabel = CaptionLabel("当前状态:未连接", self) + + card = CardWidget(self) + card.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + cardLayout = QVBoxLayout(card) + cardLayout.setContentsMargins(24, 20, 24, 22) + cardLayout.setSpacing(12) + cardLayout.addWidget(SubtitleLabel("无线调试设备", card)) + cardLayout.addWidget(CaptionLabel("设备地址", card)) + + connectionLayout = QHBoxLayout() + connectionLayout.setSpacing(12) + connectionLayout.addWidget(self.addressInput, 1) + connectionLayout.addWidget(self.connectButton) + cardLayout.addLayout(connectionLayout) + cardLayout.addWidget(self.deviceStatusLabel) + + layout = QVBoxLayout(self) + layout.setContentsMargins(36, 30, 36, 36) + layout.setSpacing(16) + layout.addWidget(TitleLabel("设备管理", self)) + # description = BodyLabel("管理 uiautomator2 使用的 Android 设备连接。", self) + # description.setWordWrap(True) + # layout.addWidget(description) + layout.addWidget(card) + layout.addStretch(1) + + +class SettingsPage(QWidget): + """后续设置项的扩展入口。""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("settingsPage") + + card = CardWidget(self) + card.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + cardLayout = QVBoxLayout(card) + cardLayout.setContentsMargins(24, 20, 24, 22) + cardLayout.setSpacing(8) + cardLayout.addWidget(SubtitleLabel("应用设置", card)) + # hint = BodyLabel("主题、日志和任务策略可以在这里继续扩展。", card) + # hint.setWordWrap(True) + # cardLayout.addWidget(hint) + + layout = QVBoxLayout(self) + layout.setContentsMargins(36, 30, 36, 36) + layout.setSpacing(16) + layout.addWidget(TitleLabel("设置", self)) + layout.addWidget(card) + layout.addStretch(1) diff --git a/client/src/ui_main.py b/client/src/ui_main.py new file mode 100644 index 0000000..dc067fa --- /dev/null +++ b/client/src/ui_main.py @@ -0,0 +1,83 @@ +"""应用与主窗口装配。 + +本文件只负责把窗口、导航和页面拼起来,不写业务逻辑。 + +改动本文件前必读 `client/AGENTS.md`。以下几条最容易踩: + +- 图标一律用 `FluentIcon`,**不得使用表情符号充当图标**。 +- 不得在这里发起 Admin 请求、SQLite 长查询或 uiautomator2 调用, + 这些会卡死 Qt 主线程。业务事件放到各页面的 `*_ui_event.py`。 +- 页面的 `objectName` 必须唯一且稳定,改名会破坏导航。 +- 用布局和伸缩项排版,**不要用固定坐标**。 +- 不要用全局 QSS 覆盖 Fluent 控件;局部样式必须保留悬停、按下、 + 焦点和禁用四种状态。 +- 需要文件路径时走 `data_dir()` / `app_dir()`,不要硬编码, + 见 `docs/client/03-data-model.md` §2.2。 +""" + +import sys + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QApplication +from qfluentwidgets import ( + FluentIcon as FIF, + FluentWindow, + NavigationItemPosition, + Theme, + setTheme, +) + +from .pdd_ui import PDDTaskPage +from .settings_ui import DevicePage, SettingsPage + + +class MainWindow(FluentWindow): + """应用主窗口。""" + + def __init__(self): + super().__init__() + + self.pddTaskPage = PDDTaskPage(self) + self.devicePage = DevicePage(self) + self.settingsPage = SettingsPage(self) + + self.addSubInterface(self.pddTaskPage, FIF.HOME, "pdd") + self.addSubInterface(self.devicePage, FIF.IOT, "设备") + self.addSubInterface( + self.settingsPage, + FIF.SETTING, + "设置", + NavigationItemPosition.BOTTOM, + ) + + self.setWindowTitle("采集采购工具") + self.setMinimumSize(820, 560) + self.resize(1040, 680) + self._moveToScreenCenter() + + def _moveToScreenCenter(self): + screen = QApplication.primaryScreen() + if screen is None: + return + + frame = self.frameGeometry() + frame.moveCenter(screen.availableGeometry().center()) + self.move(frame.topLeft()) + + +def ui_main(): + """启动桌面应用。""" + + QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) + QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) + + app = QApplication(sys.argv) + setTheme(Theme.AUTO) + + window = MainWindow() + window.show() + return app.exec_() + + +if __name__ == "__main__": + sys.exit(ui_main())