feat: 完成Tab④账号管理
- 新增 accounts 服务层,封装账号 CRUD、目录创建、端口分配与登录检测 - 接入 PySide6 账号管理 Tab:表格、账号弹窗、启动登录、后台检测登录 - 密码仅本地保存并在 UI 打码,表格不展示明文;不自动登录或填密码 - 增加顶部 Tab 栏防误点样式,扩大点击区域并高亮当前 Tab - 补充账号服务和 GUI 单元测试,并同步架构、API、路由、任务和进度文档
This commit is contained in:
+237
@@ -0,0 +1,237 @@
|
||||
"""Account management services for the PySide6 accounts tab."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from . import appconfig, chrome, db, editor
|
||||
from . import config as account_config
|
||||
|
||||
|
||||
DEFAULT_REGION_HOST = editor.DEFAULT_REGION_HOST
|
||||
|
||||
|
||||
class AccountError(RuntimeError):
|
||||
"""Raised when account management cannot complete an operation."""
|
||||
|
||||
|
||||
def _now() -> str:
|
||||
return datetime.now().isoformat(timespec="seconds")
|
||||
|
||||
|
||||
def _db_path(path=None, config=None) -> str:
|
||||
return path or appconfig.db_path(config)
|
||||
|
||||
|
||||
def _trim(value) -> str:
|
||||
return str(value or "").strip()
|
||||
|
||||
|
||||
def _optional_text(value) -> Optional[str]:
|
||||
text = _trim(value)
|
||||
return text or None
|
||||
|
||||
|
||||
def _required_text(value, field_name: str) -> str:
|
||||
text = _trim(value)
|
||||
if not text:
|
||||
raise AccountError(f"{field_name}不能为空")
|
||||
return text
|
||||
|
||||
|
||||
def normalize_region_host(value=None) -> str:
|
||||
text = _trim(value) or DEFAULT_REGION_HOST
|
||||
if "://" in text:
|
||||
text = urlparse(text).netloc
|
||||
text = text.strip("/")
|
||||
if "/" in text:
|
||||
text = text.split("/", 1)[0]
|
||||
return text or DEFAULT_REGION_HOST
|
||||
|
||||
|
||||
def normalize_debug_port(value) -> int:
|
||||
try:
|
||||
port = int(value)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise AccountError("调试端口必须是数字") from exc
|
||||
if port <= 0 or port > 65535:
|
||||
raise AccountError("调试端口必须在 1-65535 范围内")
|
||||
return port
|
||||
|
||||
|
||||
def preview_user_data_dir(alias, config=None) -> str:
|
||||
slug = account_config.make_slug(alias)
|
||||
root = appconfig.user_data_root(config)
|
||||
return os.path.abspath(os.path.join(str(root), slug))
|
||||
|
||||
|
||||
def mask_password(password) -> str:
|
||||
return "******" if password else ""
|
||||
|
||||
|
||||
def next_debug_port(config=None, path=None) -> int:
|
||||
cfg = appconfig.load_config() if config is None else config
|
||||
start, end = appconfig.debug_port_range(cfg)
|
||||
database_path = _db_path(path, cfg)
|
||||
db.init_db(database_path)
|
||||
used = {int(account.debug_port) for account in db.list_accounts(path=database_path)}
|
||||
for port in range(start, end + 1):
|
||||
if port not in used:
|
||||
return port
|
||||
return end + 1
|
||||
|
||||
|
||||
def _assert_debug_port_available(port, database_path, ignore_alias=None) -> None:
|
||||
used_by = [
|
||||
account.alias
|
||||
for account in db.list_accounts(path=database_path)
|
||||
if int(account.debug_port) == int(port) and account.alias != ignore_alias
|
||||
]
|
||||
if used_by:
|
||||
raise AccountError(f"调试端口 {port} 已被账号 {used_by[0]} 使用")
|
||||
|
||||
|
||||
def list_accounts(path=None, config=None):
|
||||
database_path = _db_path(path, config)
|
||||
db.init_db(database_path)
|
||||
return db.list_accounts(path=database_path)
|
||||
|
||||
|
||||
def get_account(alias, path=None, config=None):
|
||||
database_path = _db_path(path, config)
|
||||
db.init_db(database_path)
|
||||
account = db.get_account_by_alias(alias, path=database_path)
|
||||
if account is None:
|
||||
raise AccountError(f"账号不存在: {alias}")
|
||||
return account
|
||||
|
||||
|
||||
def create_account(
|
||||
account_name,
|
||||
alias,
|
||||
region_host=None,
|
||||
debug_port=None,
|
||||
password=None,
|
||||
note=None,
|
||||
path=None,
|
||||
config=None,
|
||||
):
|
||||
cfg = appconfig.load_config() if config is None else config
|
||||
database_path = _db_path(path, cfg)
|
||||
db.init_db(database_path)
|
||||
|
||||
account_name = _required_text(account_name, "账号名")
|
||||
alias = _required_text(alias, "别名")
|
||||
region_host = normalize_region_host(region_host)
|
||||
port = normalize_debug_port(
|
||||
next_debug_port(config=cfg, path=database_path) if debug_port is None else debug_port
|
||||
)
|
||||
_assert_debug_port_available(port, database_path)
|
||||
slug = account_config.make_slug(alias)
|
||||
user_data_dir = account_config.ensure_user_data_dir(slug, config=cfg)
|
||||
|
||||
try:
|
||||
return db.add_account(
|
||||
account_name,
|
||||
alias,
|
||||
region_host,
|
||||
port,
|
||||
password=_optional_text(password),
|
||||
note=_optional_text(note),
|
||||
slug=slug,
|
||||
user_data_dir=user_data_dir,
|
||||
path=database_path,
|
||||
)
|
||||
except db.DbError as exc:
|
||||
raise AccountError(str(exc)) from exc
|
||||
|
||||
|
||||
def update_account(
|
||||
original_alias,
|
||||
account_name,
|
||||
alias,
|
||||
region_host=None,
|
||||
debug_port=None,
|
||||
password=None,
|
||||
note=None,
|
||||
path=None,
|
||||
config=None,
|
||||
):
|
||||
cfg = appconfig.load_config() if config is None else config
|
||||
database_path = _db_path(path, cfg)
|
||||
existing = get_account(original_alias, path=database_path, config=cfg)
|
||||
|
||||
account_name = _required_text(account_name, "账号名")
|
||||
alias = _required_text(alias, "别名")
|
||||
region_host = normalize_region_host(region_host)
|
||||
port = normalize_debug_port(debug_port)
|
||||
_assert_debug_port_available(port, database_path, ignore_alias=existing.alias)
|
||||
|
||||
if alias != existing.alias:
|
||||
slug = account_config.make_slug(alias)
|
||||
user_data_dir = account_config.ensure_user_data_dir(slug, config=cfg)
|
||||
else:
|
||||
slug = existing.slug
|
||||
user_data_dir = os.path.abspath(existing.user_data_dir)
|
||||
os.makedirs(user_data_dir, exist_ok=True)
|
||||
|
||||
try:
|
||||
db.update_account(
|
||||
original_alias,
|
||||
path=database_path,
|
||||
account_name=account_name,
|
||||
alias=alias,
|
||||
region_host=region_host,
|
||||
slug=slug,
|
||||
user_data_dir=user_data_dir,
|
||||
debug_port=port,
|
||||
password=_optional_text(password),
|
||||
note=_optional_text(note),
|
||||
)
|
||||
return get_account(alias, path=database_path, config=cfg)
|
||||
except db.DbError as exc:
|
||||
raise AccountError(str(exc)) from exc
|
||||
|
||||
|
||||
def delete_account(alias, path=None, config=None) -> None:
|
||||
database_path = _db_path(path, config)
|
||||
db.init_db(database_path)
|
||||
db.delete_account(alias, path=database_path)
|
||||
|
||||
|
||||
def resolve_account(account_or_alias, path=None, config=None):
|
||||
if isinstance(account_or_alias, str):
|
||||
return get_account(account_or_alias, path=path, config=config)
|
||||
return account_or_alias
|
||||
|
||||
|
||||
def launch_for_login(account_or_alias, path=None, config=None):
|
||||
account = resolve_account(account_or_alias, path=path, config=config)
|
||||
return chrome.launch_chrome(account, config=config)
|
||||
|
||||
|
||||
def detect_login(account_or_alias, timeout=8, path=None, config=None) -> dict:
|
||||
database_path = _db_path(path, config)
|
||||
account = resolve_account(account_or_alias, path=database_path, config=config)
|
||||
status = editor.login_status(account, timeout=timeout)
|
||||
if status.get("logged_in"):
|
||||
db.update_account(account.alias, path=database_path, last_login_at=_now())
|
||||
return status
|
||||
|
||||
|
||||
def login_status_text(status) -> str:
|
||||
if not status:
|
||||
return "未知"
|
||||
if status.get("logged_in"):
|
||||
return "已登录"
|
||||
reason = status.get("reason")
|
||||
if reason == "LOGIN_PAGE":
|
||||
return "未登录"
|
||||
if reason == "NO_SESSION_COOKIE":
|
||||
return "未登录"
|
||||
if reason:
|
||||
return f"未登录({reason})"
|
||||
return "未登录"
|
||||
@@ -19,6 +19,7 @@ DEFAULT_BUSY_TIMEOUT_MS = 5000
|
||||
VALID_BATCH_FIELDS = {"source_files_json", "status", "note"}
|
||||
VALID_ACCOUNT_FIELDS = {
|
||||
"account_name",
|
||||
"alias",
|
||||
"region_host",
|
||||
"slug",
|
||||
"user_data_dir",
|
||||
@@ -354,19 +355,24 @@ def add_account(
|
||||
return get_account_by_alias(alias, conn=database)
|
||||
|
||||
|
||||
def update_account(alias, path=None, conn=None, **fields) -> None:
|
||||
def update_account(account_alias, path=None, conn=None, **fields) -> None:
|
||||
_validate_fields(fields, VALID_ACCOUNT_FIELDS)
|
||||
if not fields:
|
||||
return
|
||||
fields["updated_at"] = _now()
|
||||
assignments = ", ".join(f"{field} = ?" for field in fields)
|
||||
params = list(fields.values()) + [alias]
|
||||
params = list(fields.values()) + [account_alias]
|
||||
with _connection(conn, path) as database:
|
||||
with database:
|
||||
database.execute(
|
||||
f"UPDATE accounts SET {assignments} WHERE alias = ?",
|
||||
params,
|
||||
)
|
||||
try:
|
||||
with database:
|
||||
database.execute(
|
||||
f"UPDATE accounts SET {assignments} WHERE alias = ?",
|
||||
params,
|
||||
)
|
||||
except sqlite3.IntegrityError as exc:
|
||||
raise DbError(
|
||||
f"账号别名或 slug 已存在: {fields.get('alias', account_alias)}"
|
||||
) from exc
|
||||
|
||||
|
||||
def delete_account(alias, path=None, conn=None) -> None:
|
||||
|
||||
+399
-3
@@ -7,8 +7,22 @@ import sys
|
||||
|
||||
try:
|
||||
from PySide6.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QApplication,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFormLayout,
|
||||
QHBoxLayout,
|
||||
QHeaderView,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QMessageBox,
|
||||
QPlainTextEdit,
|
||||
QPushButton,
|
||||
QSpinBox,
|
||||
QTableWidget,
|
||||
QTableWidgetItem,
|
||||
QTabWidget,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
@@ -31,24 +45,406 @@ TAB_TITLES = [
|
||||
"⑤ 设置",
|
||||
]
|
||||
|
||||
TAB_STYLE = """
|
||||
QTabWidget::pane {
|
||||
border-top: 1px solid #c9d1d9;
|
||||
}
|
||||
QTabBar::tab {
|
||||
min-width: 128px;
|
||||
min-height: 34px;
|
||||
padding: 8px 18px;
|
||||
margin-right: 8px;
|
||||
border: 1px solid #c9d1d9;
|
||||
border-bottom-color: #b8c0ca;
|
||||
background: #f4f6f8;
|
||||
color: #24292f;
|
||||
}
|
||||
QTabBar::tab:selected {
|
||||
background: #ffffff;
|
||||
border-color: #687785;
|
||||
border-bottom-color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
QTabBar::tab:hover:!selected {
|
||||
background: #eaf2ff;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
if QT_IMPORT_ERROR is None:
|
||||
from . import accounts, appconfig
|
||||
from . import config as account_config
|
||||
|
||||
|
||||
class AccountDialog(QDialog):
|
||||
"""Dialog for adding or editing one account."""
|
||||
|
||||
def __init__(self, parent=None, account=None, default_port=9222, config=None):
|
||||
super().__init__(parent)
|
||||
self._account = account
|
||||
self._config = config
|
||||
self.setWindowTitle("编辑账号" if account else "新增账号")
|
||||
|
||||
self.account_name_edit = QLineEdit()
|
||||
self.alias_edit = QLineEdit()
|
||||
self.region_host_edit = QLineEdit(accounts.DEFAULT_REGION_HOST)
|
||||
self.debug_port_spin = QSpinBox()
|
||||
self.debug_port_spin.setRange(1, 65535)
|
||||
self.debug_port_spin.setValue(int(default_port))
|
||||
self.password_edit = QLineEdit()
|
||||
self.password_edit.setEchoMode(QLineEdit.Password)
|
||||
self.note_edit = QPlainTextEdit()
|
||||
self.note_edit.setMaximumHeight(76)
|
||||
self.slug_edit = QLineEdit()
|
||||
self.slug_edit.setReadOnly(True)
|
||||
self.user_data_dir_edit = QLineEdit()
|
||||
self.user_data_dir_edit.setReadOnly(True)
|
||||
|
||||
if account is not None:
|
||||
self.account_name_edit.setText(account.account_name)
|
||||
self.alias_edit.setText(account.alias)
|
||||
self.region_host_edit.setText(account.region_host)
|
||||
self.debug_port_spin.setValue(int(account.debug_port))
|
||||
self.password_edit.setText(account.password or "")
|
||||
self.note_edit.setPlainText(account.note or "")
|
||||
self.slug_edit.setText(account.slug)
|
||||
self.user_data_dir_edit.setText(account.user_data_dir)
|
||||
|
||||
form = QFormLayout()
|
||||
form.addRow("账号名", self.account_name_edit)
|
||||
form.addRow("别名", self.alias_edit)
|
||||
form.addRow("地区", self.region_host_edit)
|
||||
form.addRow("调试端口", self.debug_port_spin)
|
||||
form.addRow("密码", self.password_edit)
|
||||
form.addRow("备注", self.note_edit)
|
||||
form.addRow("slug", self.slug_edit)
|
||||
form.addRow("数据目录", self.user_data_dir_edit)
|
||||
|
||||
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
buttons.accepted.connect(self.accept)
|
||||
buttons.rejected.connect(self.reject)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addLayout(form)
|
||||
layout.addWidget(buttons)
|
||||
|
||||
self.alias_edit.textChanged.connect(self._update_path_preview)
|
||||
self._update_path_preview()
|
||||
|
||||
def _update_path_preview(self):
|
||||
alias = self.alias_edit.text().strip()
|
||||
if not alias:
|
||||
self.slug_edit.clear()
|
||||
self.user_data_dir_edit.clear()
|
||||
return
|
||||
try:
|
||||
slug = account_config.make_slug(alias)
|
||||
self.slug_edit.setText(slug)
|
||||
if self._account is not None and alias == self._account.alias:
|
||||
self.user_data_dir_edit.setText(self._account.user_data_dir)
|
||||
else:
|
||||
self.user_data_dir_edit.setText(
|
||||
accounts.preview_user_data_dir(alias, config=self._config)
|
||||
)
|
||||
except Exception:
|
||||
self.slug_edit.clear()
|
||||
self.user_data_dir_edit.clear()
|
||||
|
||||
def values(self):
|
||||
return {
|
||||
"account_name": self.account_name_edit.text().strip(),
|
||||
"alias": self.alias_edit.text().strip(),
|
||||
"region_host": self.region_host_edit.text().strip(),
|
||||
"debug_port": self.debug_port_spin.value(),
|
||||
"password": self.password_edit.text(),
|
||||
"note": self.note_edit.toPlainText().strip(),
|
||||
}
|
||||
|
||||
|
||||
from .workers import BaseWorker, run_worker
|
||||
|
||||
|
||||
class AccountLoginCheckWorker(BaseWorker):
|
||||
def __init__(self, account, db_path=None, config=None, timeout=8):
|
||||
super().__init__()
|
||||
self.account = account
|
||||
self.db_path = db_path
|
||||
self.config = config
|
||||
self.timeout = timeout
|
||||
|
||||
def execute(self):
|
||||
status = accounts.detect_login(
|
||||
self.account,
|
||||
timeout=self.timeout,
|
||||
path=self.db_path,
|
||||
config=self.config,
|
||||
)
|
||||
self.row_updated.emit(self.account.id, status)
|
||||
return {"alias": self.account.alias, "status": status}
|
||||
|
||||
|
||||
class AccountsTab(QWidget):
|
||||
COLUMNS = ["账号名", "别名", "地区", "端口", "登录状态", "备注"]
|
||||
|
||||
def __init__(self, parent=None, db_path=None, config=None, status_callback=None):
|
||||
super().__init__(parent)
|
||||
self.db_path = db_path
|
||||
self.config = appconfig.load_config() if config is None else config
|
||||
self.status_callback = status_callback
|
||||
self.account_rows = []
|
||||
self.login_statuses = {}
|
||||
self.threads = []
|
||||
|
||||
self.table = QTableWidget(0, len(self.COLUMNS))
|
||||
self.table.setHorizontalHeaderLabels(self.COLUMNS)
|
||||
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
self.table.verticalHeader().setVisible(False)
|
||||
|
||||
self.add_button = QPushButton("新增")
|
||||
self.edit_button = QPushButton("编辑")
|
||||
self.delete_button = QPushButton("删除")
|
||||
self.launch_button = QPushButton("启动登录")
|
||||
self.check_button = QPushButton("检测登录")
|
||||
|
||||
toolbar = QHBoxLayout()
|
||||
for button in (
|
||||
self.add_button,
|
||||
self.edit_button,
|
||||
self.delete_button,
|
||||
self.launch_button,
|
||||
self.check_button,
|
||||
):
|
||||
toolbar.addWidget(button)
|
||||
toolbar.addStretch(1)
|
||||
|
||||
self.empty_label = QLabel("")
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(18, 18, 18, 18)
|
||||
layout.addLayout(toolbar)
|
||||
layout.addWidget(self.table, 1)
|
||||
layout.addWidget(self.empty_label)
|
||||
|
||||
self.add_button.clicked.connect(self.add_account)
|
||||
self.edit_button.clicked.connect(self.edit_account)
|
||||
self.delete_button.clicked.connect(self.delete_account)
|
||||
self.launch_button.clicked.connect(self.launch_login)
|
||||
self.check_button.clicked.connect(self.check_login)
|
||||
self.table.itemSelectionChanged.connect(self._update_button_state)
|
||||
self.table.doubleClicked.connect(self.edit_account)
|
||||
|
||||
self.refresh_accounts()
|
||||
|
||||
def _set_status(self, message):
|
||||
if self.status_callback is not None:
|
||||
self.status_callback(message)
|
||||
|
||||
def _selected_account(self):
|
||||
selected = self.table.selectionModel().selectedRows()
|
||||
if not selected:
|
||||
return None
|
||||
row = selected[0].row()
|
||||
if row < 0 or row >= len(self.account_rows):
|
||||
return None
|
||||
return self.account_rows[row]
|
||||
|
||||
def _update_button_state(self):
|
||||
has_selection = self._selected_account() is not None
|
||||
for button in (
|
||||
self.edit_button,
|
||||
self.delete_button,
|
||||
self.launch_button,
|
||||
self.check_button,
|
||||
):
|
||||
button.setEnabled(has_selection)
|
||||
|
||||
def refresh_accounts(self):
|
||||
try:
|
||||
self.account_rows = accounts.list_accounts(
|
||||
path=self.db_path,
|
||||
config=self.config,
|
||||
)
|
||||
except Exception as exc:
|
||||
self.account_rows = []
|
||||
self._set_status(f"账号读取失败:{exc}")
|
||||
|
||||
self.table.setRowCount(len(self.account_rows))
|
||||
for row, account in enumerate(self.account_rows):
|
||||
status = self.login_statuses.get(account.alias, "未知")
|
||||
values = [
|
||||
account.account_name,
|
||||
account.alias,
|
||||
account.region_host,
|
||||
str(account.debug_port),
|
||||
status,
|
||||
account.note or "",
|
||||
]
|
||||
for column, value in enumerate(values):
|
||||
item = QTableWidgetItem(value)
|
||||
self.table.setItem(row, column, item)
|
||||
self.empty_label.setText("" if self.account_rows else "暂无账号")
|
||||
self._update_button_state()
|
||||
|
||||
def _show_error(self, message):
|
||||
QMessageBox.warning(self, "账号管理", str(message))
|
||||
self._set_status(str(message))
|
||||
|
||||
def add_account(self, checked=False):
|
||||
try:
|
||||
default_port = accounts.next_debug_port(
|
||||
path=self.db_path,
|
||||
config=self.config,
|
||||
)
|
||||
except Exception:
|
||||
default_port = appconfig.default_debug_port(self.config)
|
||||
dialog = AccountDialog(self, default_port=default_port, config=self.config)
|
||||
if dialog.exec() != QDialog.Accepted:
|
||||
return
|
||||
try:
|
||||
accounts.create_account(
|
||||
path=self.db_path,
|
||||
config=self.config,
|
||||
**dialog.values(),
|
||||
)
|
||||
except Exception as exc:
|
||||
self._show_error(exc)
|
||||
return
|
||||
self.refresh_accounts()
|
||||
self._set_status("账号已新增")
|
||||
|
||||
def edit_account(self, checked=False):
|
||||
account = self._selected_account()
|
||||
if account is None:
|
||||
return
|
||||
dialog = AccountDialog(
|
||||
self,
|
||||
account=account,
|
||||
default_port=account.debug_port,
|
||||
config=self.config,
|
||||
)
|
||||
if dialog.exec() != QDialog.Accepted:
|
||||
return
|
||||
try:
|
||||
updated = accounts.update_account(
|
||||
account.alias,
|
||||
path=self.db_path,
|
||||
config=self.config,
|
||||
**dialog.values(),
|
||||
)
|
||||
except Exception as exc:
|
||||
self._show_error(exc)
|
||||
return
|
||||
if updated.alias != account.alias:
|
||||
self.login_statuses.pop(account.alias, None)
|
||||
self.refresh_accounts()
|
||||
self._set_status("账号已保存")
|
||||
|
||||
def delete_account(self, checked=False):
|
||||
account = self._selected_account()
|
||||
if account is None:
|
||||
return
|
||||
answer = QMessageBox.question(
|
||||
self,
|
||||
"删除账号",
|
||||
f"确认删除账号「{account.alias}」?",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No,
|
||||
)
|
||||
if answer != QMessageBox.Yes:
|
||||
return
|
||||
try:
|
||||
accounts.delete_account(account.alias, path=self.db_path, config=self.config)
|
||||
except Exception as exc:
|
||||
self._show_error(exc)
|
||||
return
|
||||
self.login_statuses.pop(account.alias, None)
|
||||
self.refresh_accounts()
|
||||
self._set_status("账号已删除")
|
||||
|
||||
def launch_login(self, checked=False):
|
||||
account = self._selected_account()
|
||||
if account is None:
|
||||
return
|
||||
try:
|
||||
accounts.launch_for_login(account, config=self.config)
|
||||
except Exception as exc:
|
||||
self._show_error(exc)
|
||||
return
|
||||
self.login_statuses[account.alias] = "已启动"
|
||||
self.refresh_accounts()
|
||||
self._set_status("Chrome 已启动,请人工登录")
|
||||
|
||||
def check_login(self, checked=False):
|
||||
account = self._selected_account()
|
||||
if account is None:
|
||||
return
|
||||
self.login_statuses[account.alias] = "检测中"
|
||||
self.refresh_accounts()
|
||||
worker = AccountLoginCheckWorker(
|
||||
account,
|
||||
db_path=self.db_path,
|
||||
config=self.config,
|
||||
)
|
||||
worker.finished.connect(self._on_login_check_finished)
|
||||
worker.failed.connect(
|
||||
lambda _task_id, error, alias=account.alias: self._on_login_check_failed(
|
||||
alias,
|
||||
error,
|
||||
)
|
||||
)
|
||||
thread = run_worker(worker, start=False)
|
||||
thread.finished.connect(lambda: self._forget_thread(thread))
|
||||
self.threads.append(thread)
|
||||
thread.start()
|
||||
|
||||
def _forget_thread(self, thread):
|
||||
if thread in self.threads:
|
||||
self.threads.remove(thread)
|
||||
|
||||
def _on_login_check_finished(self, payload):
|
||||
if payload.get("ok") is False and not payload.get("alias"):
|
||||
return
|
||||
alias = payload.get("alias")
|
||||
status = payload.get("status") or {}
|
||||
if alias:
|
||||
self.login_statuses[alias] = accounts.login_status_text(status)
|
||||
self.refresh_accounts()
|
||||
self._set_status("登录状态已刷新")
|
||||
|
||||
def _on_login_check_failed(self, alias, error):
|
||||
self.login_statuses[alias] = "检测失败"
|
||||
self.refresh_accounts()
|
||||
self._set_status(f"登录检测失败:{error}")
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
"""Main application window with the fixed five-tab workflow."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, db_path=None, config=None):
|
||||
super().__init__()
|
||||
self.db_path = db_path
|
||||
self.config = appconfig.load_config() if config is None else config
|
||||
self.setWindowTitle("cmshopee")
|
||||
self.resize(1180, 760)
|
||||
self.tabs = QTabWidget()
|
||||
self.tabs.setObjectName("mainTabs")
|
||||
self.tabs.setStyleSheet(TAB_STYLE)
|
||||
self.tabs.currentChanged.connect(self._on_tab_changed)
|
||||
for title in TAB_TITLES:
|
||||
self.tabs.addTab(self._build_placeholder_tab(title), title)
|
||||
self.tabs.addTab(self._build_tab(title), title)
|
||||
self.setCentralWidget(self.tabs)
|
||||
self.statusBar().showMessage("就绪")
|
||||
|
||||
def _build_placeholder_tab(self, title):
|
||||
def _build_tab(self, title):
|
||||
if title == "④ 账号管理":
|
||||
return AccountsTab(
|
||||
db_path=self.db_path,
|
||||
config=self.config,
|
||||
status_callback=self.statusBar().showMessage,
|
||||
)
|
||||
widget = QWidget()
|
||||
widget.setObjectName(title)
|
||||
layout = QVBoxLayout(widget)
|
||||
|
||||
Reference in New Issue
Block a user