feat: 实现设置页设备管理界面 (#5)
This commit is contained in:
+298
-67
@@ -1,101 +1,332 @@
|
||||
"""设置页的界面结构。
|
||||
|
||||
分四组:Admin、Android 设备、自动化、安全与诊断。
|
||||
完整规范见 `docs/client/05-ui-specification.md` §8。
|
||||
本文件只负责显示设备信息和提供界面更新入口。按钮事件统一由
|
||||
``settings_ui_event.py`` 绑定;ADB、SQLite 和硬件信息读取不得放在这里。
|
||||
|
||||
改动本文件前必读 `client/AGENTS.md`。以下几条最容易踩:
|
||||
|
||||
- **认证状态只显示“已配置/未配置”或掩码值,绝不回显完整 token。**
|
||||
- 本文件只负责显示和输入转发,“测试连接”这类会联网、会连手机的动作
|
||||
放 `settings_ui_event.py`,且必须走后台线程。
|
||||
- 表单必须有**可见标签**,占位符不能当标签用(占位符一输入就没了,
|
||||
用户会忘记这个框是干嘛的)。
|
||||
- 用可滚动布局和 Fluent 设置卡片;MVP 采用显式“保存设置”按钮,
|
||||
不要在同一页混用即时保存。
|
||||
- 校验失败时保留用户已输入的内容,并把焦点移到第一个出错的字段,
|
||||
不要清空重来。
|
||||
- 图标用 `FluentIcon`,**不得用表情符号**。
|
||||
- 页面 `objectName` 固定为 `settingsPage`,不要改。
|
||||
|
||||
注意:设备连接**不是**独立的顶级页面,统一放在本页,
|
||||
见 `docs/client/01-requirements.md` §3。
|
||||
改动本文件前必读 ``client/AGENTS.md``。页面 ``objectName`` 固定为
|
||||
``settingsPage``,图标使用 ``FluentIcon``,不得使用表情符号。
|
||||
"""
|
||||
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QSizePolicy, QVBoxLayout, QWidget
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt, pyqtSignal
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QFormLayout,
|
||||
QHeaderView,
|
||||
QHBoxLayout,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from qfluentwidgets import (
|
||||
CaptionLabel,
|
||||
CardWidget,
|
||||
FluentIcon as FIF,
|
||||
LineEdit,
|
||||
PushButton,
|
||||
ScrollArea,
|
||||
SubtitleLabel,
|
||||
TableView,
|
||||
TitleLabel,
|
||||
)
|
||||
|
||||
|
||||
class DevicePage(QWidget):
|
||||
"""Android 设备连接页。"""
|
||||
@dataclass(frozen=True)
|
||||
class AndroidDeviceRow:
|
||||
"""一台 ADB 已发现设备的界面数据。"""
|
||||
|
||||
serial: str
|
||||
connection_type: str
|
||||
model: str = "—"
|
||||
android_version: str = "—"
|
||||
status: str = "device"
|
||||
|
||||
@property
|
||||
def status_text(self) -> str:
|
||||
return {
|
||||
"device": "已连接",
|
||||
"offline": "离线",
|
||||
"unauthorized": "未授权",
|
||||
}.get(self.status, self.status or "未知")
|
||||
|
||||
|
||||
class AndroidDeviceTableModel(QAbstractTableModel):
|
||||
"""Android 设备表格模型,使用序列号维护互斥勾选。"""
|
||||
|
||||
checkedDeviceChanged = pyqtSignal(str)
|
||||
|
||||
HEADERS = ("选择", "设备号", "连接方式", "型号", "Android 版本", "状态")
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setObjectName("devicePage")
|
||||
self._rows: list[AndroidDeviceRow] = []
|
||||
self._checked_serial = ""
|
||||
|
||||
self.addressInput = LineEdit(self)
|
||||
self.addressInput.setText("192.168.0.173:5555")
|
||||
self.addressInput.setPlaceholderText("IP:端口")
|
||||
self.addressInput.setClearButtonEnabled(True)
|
||||
self.addressInput.setAccessibleName("Android 设备地址")
|
||||
def rowCount(self, parent=QModelIndex()) -> int:
|
||||
return 0 if parent.isValid() else len(self._rows)
|
||||
|
||||
self.connectButton = PushButton(FIF.LINK, "连接设备", self)
|
||||
self.connectButton.setAccessibleName("连接 Android 设备")
|
||||
self.deviceStatusLabel = CaptionLabel("当前状态:未连接", self)
|
||||
def columnCount(self, parent=QModelIndex()) -> int:
|
||||
return 0 if parent.isValid() else len(self.HEADERS)
|
||||
|
||||
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))
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
||||
return self.HEADERS[section]
|
||||
return super().headerData(section, orientation, role)
|
||||
|
||||
connectionLayout = QHBoxLayout()
|
||||
connectionLayout.setSpacing(12)
|
||||
connectionLayout.addWidget(self.addressInput, 1)
|
||||
connectionLayout.addWidget(self.connectButton)
|
||||
cardLayout.addLayout(connectionLayout)
|
||||
cardLayout.addWidget(self.deviceStatusLabel)
|
||||
def data(self, index, role=Qt.DisplayRole):
|
||||
if not index.isValid() or not 0 <= index.row() < len(self._rows):
|
||||
return None
|
||||
|
||||
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)
|
||||
device = self._rows[index.row()]
|
||||
if index.column() == 0 and role == Qt.CheckStateRole:
|
||||
return Qt.Checked if device.serial == self._checked_serial else Qt.Unchecked
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
values = (
|
||||
"",
|
||||
device.serial,
|
||||
device.connection_type,
|
||||
device.model,
|
||||
device.android_version,
|
||||
device.status_text,
|
||||
)
|
||||
return values[index.column()]
|
||||
|
||||
if role == Qt.TextAlignmentRole:
|
||||
if index.column() in (0, 2, 4, 5):
|
||||
return int(Qt.AlignCenter)
|
||||
return int(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
|
||||
if role == Qt.ToolTipRole:
|
||||
if index.column() == 0 and device.status != "device":
|
||||
return "设备状态正常后才能选择"
|
||||
if index.column() == 1:
|
||||
return device.serial
|
||||
return None
|
||||
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return Qt.NoItemFlags
|
||||
|
||||
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
||||
if index.column() == 0 and self._rows[index.row()].status == "device":
|
||||
flags |= Qt.ItemIsUserCheckable
|
||||
return flags
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole) -> bool:
|
||||
if (
|
||||
not index.isValid()
|
||||
or index.column() != 0
|
||||
or role != Qt.CheckStateRole
|
||||
or self._rows[index.row()].status != "device"
|
||||
):
|
||||
return False
|
||||
|
||||
serial = self._rows[index.row()].serial
|
||||
checked_serial = serial if value == Qt.Checked else ""
|
||||
self.set_checked_serial(checked_serial)
|
||||
return True
|
||||
|
||||
def set_devices(self, devices: Iterable[AndroidDeviceRow]) -> None:
|
||||
"""替换设备列表,并按稳定序列号保留有效勾选。"""
|
||||
|
||||
rows = list(devices)
|
||||
valid_serials = {row.serial for row in rows if row.status == "device"}
|
||||
checked_serial = self._checked_serial
|
||||
|
||||
self.beginResetModel()
|
||||
self._rows = rows
|
||||
if checked_serial not in valid_serials:
|
||||
self._checked_serial = ""
|
||||
self.endResetModel()
|
||||
|
||||
if checked_serial != self._checked_serial:
|
||||
self.checkedDeviceChanged.emit(self._checked_serial)
|
||||
|
||||
def set_checked_serial(self, serial: str) -> None:
|
||||
"""勾选一台正常设备;传空字符串清除勾选。"""
|
||||
|
||||
if serial:
|
||||
selectable = any(
|
||||
row.serial == serial and row.status == "device" for row in self._rows
|
||||
)
|
||||
if not selectable:
|
||||
serial = ""
|
||||
|
||||
if serial == self._checked_serial:
|
||||
return
|
||||
|
||||
old_serial = self._checked_serial
|
||||
self._checked_serial = serial
|
||||
for candidate in (old_serial, serial):
|
||||
row = self.row_for_serial(candidate)
|
||||
if row >= 0:
|
||||
index = self.index(row, 0)
|
||||
self.dataChanged.emit(index, index, [Qt.CheckStateRole])
|
||||
self.checkedDeviceChanged.emit(serial)
|
||||
|
||||
@property
|
||||
def checked_serial(self) -> str:
|
||||
return self._checked_serial
|
||||
|
||||
def row_for_serial(self, serial: str) -> int:
|
||||
for row, device in enumerate(self._rows):
|
||||
if device.serial == serial:
|
||||
return row
|
||||
return -1
|
||||
|
||||
def device_for_serial(self, serial: str) -> Optional[AndroidDeviceRow]:
|
||||
row = self.row_for_serial(serial)
|
||||
return self._rows[row] if row >= 0 else None
|
||||
|
||||
|
||||
class SettingsPage(QWidget):
|
||||
"""后续设置项的扩展入口。"""
|
||||
"""设备管理与后续应用设置的统一页面。"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setObjectName("settingsPage")
|
||||
self._build_ui()
|
||||
|
||||
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)
|
||||
# 延迟到控件创建完成后绑定,避免事件层在构造过程中访问半成品页面。
|
||||
from .settings_ui_event import SettingsPageEventBinder
|
||||
|
||||
self.eventBinder = SettingsPageEventBinder(self)
|
||||
|
||||
def _build_ui(self) -> None:
|
||||
self.deviceIdInput = LineEdit(self)
|
||||
self.deviceIdInput.setText("待生成")
|
||||
self.deviceIdInput.setReadOnly(True)
|
||||
self.deviceIdInput.setAccessibleName("当前客户端设备号")
|
||||
|
||||
self.deviceNameInput = LineEdit(self)
|
||||
self.deviceNameInput.setPlaceholderText("请输入便于识别的设备名")
|
||||
self.deviceNameInput.setClearButtonEnabled(True)
|
||||
self.deviceNameInput.setMaxLength(50)
|
||||
self.deviceNameInput.setAccessibleName("当前客户端设备名")
|
||||
|
||||
self.currentDeviceCard = self._build_current_device_card()
|
||||
|
||||
self.addressInput = LineEdit(self)
|
||||
self.addressInput.setPlaceholderText("例如 192.168.0.173:5555")
|
||||
self.addressInput.setClearButtonEnabled(True)
|
||||
self.addressInput.setAccessibleName("Android 无线调试地址")
|
||||
|
||||
self.searchButton = PushButton(FIF.SEARCH, "搜索", self)
|
||||
self.connectButton = PushButton(FIF.LINK, "连接", self)
|
||||
self.saveButton = PushButton(FIF.SAVE, "保存", self)
|
||||
self.deleteButton = PushButton(FIF.DELETE, "删除", self)
|
||||
self.saveButton.setEnabled(False)
|
||||
self.deleteButton.setEnabled(False)
|
||||
|
||||
self.deviceTableModel = AndroidDeviceTableModel(self)
|
||||
self.deviceTable = TableView(self)
|
||||
self.deviceTable.setModel(self.deviceTableModel)
|
||||
self.deviceTable.setAccessibleName("已连接的 Android 设备")
|
||||
self.deviceTable.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.deviceTable.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.deviceTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
self.deviceTable.verticalHeader().hide()
|
||||
self.deviceTable.verticalHeader().setDefaultSectionSize(42)
|
||||
self.deviceTable.setMinimumHeight(250)
|
||||
self.deviceTable.setColumnWidth(0, 64)
|
||||
self.deviceTable.setColumnWidth(1, 210)
|
||||
self.deviceTable.setColumnWidth(2, 96)
|
||||
self.deviceTable.setColumnWidth(4, 112)
|
||||
header = self.deviceTable.horizontalHeader()
|
||||
header.setSectionResizeMode(0, QHeaderView.Fixed)
|
||||
header.setSectionResizeMode(1, QHeaderView.Interactive)
|
||||
header.setSectionResizeMode(2, QHeaderView.Fixed)
|
||||
header.setSectionResizeMode(3, QHeaderView.Stretch)
|
||||
header.setSectionResizeMode(4, QHeaderView.Fixed)
|
||||
header.setSectionResizeMode(5, QHeaderView.ResizeToContents)
|
||||
|
||||
self.deviceStatusLabel = CaptionLabel("当前使用设备:未选择", self)
|
||||
self.androidDeviceCard = self._build_android_device_card()
|
||||
|
||||
content = QWidget(self)
|
||||
content.setObjectName("settingsContent")
|
||||
contentLayout = QVBoxLayout(content)
|
||||
contentLayout.setContentsMargins(36, 30, 36, 36)
|
||||
contentLayout.setSpacing(16)
|
||||
contentLayout.addWidget(TitleLabel("设置", content))
|
||||
contentLayout.addWidget(self.currentDeviceCard)
|
||||
contentLayout.addWidget(self.androidDeviceCard)
|
||||
contentLayout.addStretch(1)
|
||||
|
||||
scrollArea = ScrollArea(self)
|
||||
scrollArea.setWidgetResizable(True)
|
||||
scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
scrollArea.setWidget(content)
|
||||
scrollArea.enableTransparentBackground()
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(36, 30, 36, 36)
|
||||
layout.setSpacing(16)
|
||||
layout.addWidget(TitleLabel("设置", self))
|
||||
layout.addWidget(card)
|
||||
layout.addStretch(1)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(scrollArea)
|
||||
|
||||
def _build_current_device_card(self) -> CardWidget:
|
||||
card = CardWidget(self)
|
||||
card.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
layout = QVBoxLayout(card)
|
||||
layout.setContentsMargins(24, 20, 24, 22)
|
||||
layout.setSpacing(12)
|
||||
layout.addWidget(SubtitleLabel("当前设备", card))
|
||||
|
||||
form = QFormLayout()
|
||||
form.setHorizontalSpacing(16)
|
||||
form.setVerticalSpacing(12)
|
||||
deviceIdLabel = CaptionLabel("设备号", card)
|
||||
deviceIdLabel.setBuddy(self.deviceIdInput)
|
||||
deviceNameLabel = CaptionLabel("设备名", card)
|
||||
deviceNameLabel.setBuddy(self.deviceNameInput)
|
||||
form.addRow(deviceIdLabel, self.deviceIdInput)
|
||||
form.addRow(deviceNameLabel, self.deviceNameInput)
|
||||
layout.addLayout(form)
|
||||
return card
|
||||
|
||||
def _build_android_device_card(self) -> CardWidget:
|
||||
card = CardWidget(self)
|
||||
layout = QVBoxLayout(card)
|
||||
layout.setContentsMargins(24, 20, 24, 22)
|
||||
layout.setSpacing(12)
|
||||
layout.addWidget(SubtitleLabel("Android 设备", card))
|
||||
|
||||
addressLayout = QHBoxLayout()
|
||||
addressLayout.setSpacing(12)
|
||||
addressLabel = CaptionLabel("无线地址", card)
|
||||
addressLabel.setBuddy(self.addressInput)
|
||||
addressLayout.addWidget(addressLabel)
|
||||
addressLayout.addWidget(self.addressInput, 1)
|
||||
addressLayout.addWidget(self.connectButton)
|
||||
layout.addLayout(addressLayout)
|
||||
|
||||
commandLayout = QHBoxLayout()
|
||||
commandLayout.setSpacing(10)
|
||||
commandLayout.addWidget(self.searchButton)
|
||||
commandLayout.addStretch(1)
|
||||
commandLayout.addWidget(self.saveButton)
|
||||
commandLayout.addWidget(self.deleteButton)
|
||||
layout.addLayout(commandLayout)
|
||||
layout.addWidget(self.deviceTable, 1)
|
||||
layout.addWidget(self.deviceStatusLabel)
|
||||
return card
|
||||
|
||||
def set_client_info(self, device_id: str, device_name: str) -> None:
|
||||
"""显示后续设备身份服务提供的当前客户端信息。"""
|
||||
|
||||
self.deviceIdInput.setText(device_id or "待生成")
|
||||
self.deviceNameInput.setText(device_name)
|
||||
|
||||
def set_android_devices(self, devices: Iterable[AndroidDeviceRow]) -> None:
|
||||
"""显示后续 ADB 服务返回的设备列表。"""
|
||||
|
||||
self.deviceTableModel.set_devices(devices)
|
||||
|
||||
def set_saved_android_device(self, serial: str) -> None:
|
||||
"""显示已经保存并实际用于自动化的 Android 设备。"""
|
||||
|
||||
self.deviceTableModel.set_checked_serial(serial)
|
||||
text = serial if serial else "未选择"
|
||||
self.deviceStatusLabel.setText(f"当前使用设备:{text}")
|
||||
|
||||
@@ -1,25 +1,92 @@
|
||||
"""设置页的事件绑定。
|
||||
|
||||
处理保存设置、测试 Admin 连接、测试设备连接等动作。
|
||||
|
||||
改动本文件前必读 `client/AGENTS.md`。以下几条最容易踩:
|
||||
|
||||
- **凭据(token、密码)不写进 SQLite 的 `app_settings`,也不写进日志。**
|
||||
存到 Windows 凭据管理器,见 `docs/client/06-quality-security.md` §7。
|
||||
`data/` 目录是明文的、设计上就是整个拷走的,拷一次等于泄露一次。
|
||||
- “测试连接”会联网、会连手机,**必须走后台线程**,
|
||||
用 `QObject` + `moveToThread`(模板见 `docs/client/02-architecture.md` §5.1)。
|
||||
在主线程里直接连,界面会卡住好几秒。
|
||||
- 非敏感设置存 `app_settings` 表,键名沿用 `<分组>.<名称>` 的写法,
|
||||
清单见 `docs/client/03-data-model.md` §6。
|
||||
- 涉及路径的设置(日志目录、Artifact 目录)一律基于 `data_dir()`,
|
||||
**不要硬编码,也不要用 `os.getcwd()`**——打包成 exe 后会失效,
|
||||
见 `docs/client/03-data-model.md` §2.2。
|
||||
- 保存前要校验:数量上限、价格偏差、轮询周期这些填错了会直接影响
|
||||
采购安全,不能存进去再说。
|
||||
- 测试结果用 `InfoBar` 反馈,失败时说清“哪一步失败、下一步做什么”,
|
||||
不要把原始异常堆栈贴到界面上。
|
||||
|
||||
注意:真实下单开关属于红线,默认关闭,不得在这里做成
|
||||
一个随手能打开的普通开关,见根目录 `AGENTS.md` 红线第 2 条。
|
||||
本文件把界面操作转换为轻量信号,不直接执行 ADB、SQLite 或硬件信息读取。
|
||||
后续应用服务应在后台线程处理阻塞操作,再通过页面更新方法返回结果。
|
||||
"""
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
|
||||
|
||||
|
||||
class SettingsPageEventBinder(QObject):
|
||||
"""绑定设备管理控件,并向应用层发出稳定事件。"""
|
||||
|
||||
searchRequested = pyqtSignal()
|
||||
connectRequested = pyqtSignal(str)
|
||||
saveRequested = pyqtSignal(str, str)
|
||||
deleteRequested = pyqtSignal(str)
|
||||
|
||||
def __init__(self, page):
|
||||
super().__init__(page)
|
||||
self._page = page
|
||||
self._busy = False
|
||||
|
||||
page.searchButton.clicked.connect(self._request_search)
|
||||
page.connectButton.clicked.connect(self._request_connect)
|
||||
page.saveButton.clicked.connect(self._request_save)
|
||||
page.deleteButton.clicked.connect(self._request_delete)
|
||||
page.addressInput.textChanged.connect(self._sync_button_state)
|
||||
page.deviceTableModel.checkedDeviceChanged.connect(self._sync_button_state)
|
||||
self._sync_button_state()
|
||||
|
||||
@pyqtSlot()
|
||||
def _request_search(self) -> None:
|
||||
if self._busy:
|
||||
return
|
||||
self.searchRequested.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def _request_connect(self) -> None:
|
||||
if self._busy:
|
||||
return
|
||||
|
||||
address = self._page.addressInput.text().strip()
|
||||
if not address:
|
||||
self._page.deviceStatusLabel.setText(
|
||||
"连接失败:请先填写无线调试地址,例如 192.168.0.173:5555"
|
||||
)
|
||||
self._page.addressInput.setFocus()
|
||||
return
|
||||
self.connectRequested.emit(address)
|
||||
|
||||
@pyqtSlot()
|
||||
def _request_save(self) -> None:
|
||||
if self._busy:
|
||||
return
|
||||
|
||||
serial = self._page.deviceTableModel.checked_serial
|
||||
if not serial:
|
||||
self._page.deviceStatusLabel.setText("保存失败:请先勾选一台已连接设备")
|
||||
self._page.deviceTable.setFocus()
|
||||
return
|
||||
|
||||
device_name = self._page.deviceNameInput.text().strip()
|
||||
self.saveRequested.emit(device_name, serial)
|
||||
|
||||
@pyqtSlot()
|
||||
def _request_delete(self) -> None:
|
||||
if self._busy:
|
||||
return
|
||||
|
||||
serial = self._page.deviceTableModel.checked_serial
|
||||
if not serial:
|
||||
self._page.deviceStatusLabel.setText("删除失败:请先勾选一台设备")
|
||||
self._page.deviceTable.setFocus()
|
||||
return
|
||||
self.deleteRequested.emit(serial)
|
||||
|
||||
@pyqtSlot()
|
||||
def _sync_button_state(self, *_args) -> None:
|
||||
serial = self._page.deviceTableModel.checked_serial
|
||||
has_address = bool(self._page.addressInput.text().strip())
|
||||
self._page.searchButton.setEnabled(not self._busy)
|
||||
self._page.connectButton.setEnabled(not self._busy and has_address)
|
||||
self._page.saveButton.setEnabled(not self._busy and bool(serial))
|
||||
self._page.deleteButton.setEnabled(not self._busy and bool(serial))
|
||||
|
||||
def set_busy(self, busy: bool, message: str = "") -> None:
|
||||
"""切换界面忙碌状态,防止用户重复提交设备命令。"""
|
||||
|
||||
self._busy = busy
|
||||
self._sync_button_state()
|
||||
if message:
|
||||
self._page.deviceStatusLabel.setText(message)
|
||||
|
||||
@@ -28,7 +28,7 @@ from qfluentwidgets import (
|
||||
)
|
||||
|
||||
from .pdd_ui import PDDTaskPage
|
||||
from .settings_ui import DevicePage, SettingsPage
|
||||
from .settings_ui import SettingsPage
|
||||
|
||||
|
||||
class MainWindow(FluentWindow):
|
||||
@@ -38,11 +38,9 @@ class MainWindow(FluentWindow):
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user