feat: 完成T-205账号就绪引导保护
CollectWorker 增加采集前账号就绪预检:无账号、匹配账号 CDP 端口未响应或未登录时返回 blocked,不进入逐条采集,也不写 skipped/failed。 CollectTab 收到 blocked 后弹窗汇总原因,并跳转/引导到④账号管理配置账号、启动对应 Chrome、人工登录;MainWindow 为①传入打开④的回调。 新增 GUI 测试覆盖无账号、Chrome 未启动、未登录和弹窗引导;同步 T-205 状态、api、current-state 与 progress。
This commit is contained in:
+119
-4
@@ -75,7 +75,7 @@ QTabBar::tab:hover:!selected {
|
||||
|
||||
|
||||
if QT_IMPORT_ERROR is None:
|
||||
from . import accounts, appconfig, db, editor, excel
|
||||
from . import accounts, appconfig, chrome, db, editor, excel
|
||||
from . import config as account_config
|
||||
|
||||
|
||||
@@ -196,11 +196,19 @@ if QT_IMPORT_ERROR is None:
|
||||
class CollectTab(QWidget):
|
||||
"""Tab 1: import Excel files and list imported tasks."""
|
||||
|
||||
def __init__(self, parent=None, db_path=None, config=None, status_callback=None):
|
||||
def __init__(
|
||||
self,
|
||||
parent=None,
|
||||
db_path=None,
|
||||
config=None,
|
||||
status_callback=None,
|
||||
open_accounts_callback=None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.config = appconfig.load_config() if config is None else config
|
||||
self.db_path = _database_path(db_path, self.config)
|
||||
self.status_callback = status_callback
|
||||
self.open_accounts_callback = open_accounts_callback
|
||||
self.current_batch_id = None
|
||||
self.has_import_result = False
|
||||
self.last_import_stats = None
|
||||
@@ -235,8 +243,8 @@ if QT_IMPORT_ERROR is None:
|
||||
summary_layout.addWidget(self.show_all_button)
|
||||
summary_layout.addWidget(self.show_unmatched_button)
|
||||
|
||||
self.model = TaskTableModel(self)
|
||||
self.table = QTableView()
|
||||
self.model = TaskTableModel(self.table)
|
||||
self.table.setModel(self.model)
|
||||
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
@@ -272,6 +280,16 @@ if QT_IMPORT_ERROR is None:
|
||||
QMessageBox.warning(self, "导入采集", str(message))
|
||||
self._set_status(str(message))
|
||||
|
||||
def _show_account_guide(self, message):
|
||||
full_message = (
|
||||
f"{message}\n\n"
|
||||
"请先到「④ 账号管理」配置账号、启动对应账号 Chrome,并确认已人工登录 Shopee。"
|
||||
)
|
||||
QMessageBox.warning(self, "账号未就绪", full_message)
|
||||
self._set_status(full_message.replace("\n", " "))
|
||||
if self.open_accounts_callback is not None:
|
||||
self.open_accounts_callback()
|
||||
|
||||
def _choose_excel_files(self):
|
||||
files, _selected_filter = QFileDialog.getOpenFileNames(
|
||||
self,
|
||||
@@ -433,6 +451,9 @@ if QT_IMPORT_ERROR is None:
|
||||
def _on_collect_finished(self, payload):
|
||||
self._set_collect_running(False)
|
||||
self.refresh_tasks()
|
||||
if payload.get("blocked"):
|
||||
self._show_collect_blocked(payload)
|
||||
return
|
||||
message = "采集完成:成功{collected},略过{skipped},失败{failed}".format(
|
||||
collected=payload.get("collected", 0),
|
||||
skipped=payload.get("skipped", 0),
|
||||
@@ -450,6 +471,36 @@ if QT_IMPORT_ERROR is None:
|
||||
return
|
||||
self._set_status(message)
|
||||
|
||||
def _show_collect_blocked(self, payload):
|
||||
lines = ["采集前检查未通过。"]
|
||||
if payload.get("no_accounts"):
|
||||
lines.append("当前没有配置账号。")
|
||||
not_running = payload.get("not_running") or []
|
||||
if not_running:
|
||||
lines.append(
|
||||
"以下账号 Chrome 未启动或调试端口不可访问:"
|
||||
+ "、".join(self._account_label(item) for item in not_running)
|
||||
)
|
||||
logged_out = payload.get("logged_out") or []
|
||||
if logged_out:
|
||||
lines.append(
|
||||
"以下账号未登录 Shopee:"
|
||||
+ "、".join(self._account_label(item) for item in logged_out)
|
||||
)
|
||||
self._show_account_guide("\n".join(lines))
|
||||
|
||||
def _account_label(self, item):
|
||||
if isinstance(item, dict):
|
||||
name = item.get("account_name") or item.get("alias") or ""
|
||||
alias = item.get("alias") or ""
|
||||
reason = item.get("reason")
|
||||
else:
|
||||
name = getattr(item, "account_name", "") or getattr(item, "alias", "")
|
||||
alias = getattr(item, "alias", "")
|
||||
reason = getattr(item, "reason", None)
|
||||
label = f"{name}({alias})" if alias and name != alias else (name or alias)
|
||||
return f"{label}: {reason}" if reason else label
|
||||
|
||||
def _on_collect_cancelled(self, payload):
|
||||
self._set_collect_running(False)
|
||||
self.refresh_tasks()
|
||||
@@ -641,11 +692,12 @@ if QT_IMPORT_ERROR is None:
|
||||
class CollectWorker(BaseWorker):
|
||||
"""Collect old title and cover for imported tasks."""
|
||||
|
||||
def __init__(self, tasks, db_path=None, config=None):
|
||||
def __init__(self, tasks, db_path=None, config=None, preflight=True):
|
||||
super().__init__()
|
||||
self.tasks = list(tasks)
|
||||
self.db_path = db_path
|
||||
self.config = config
|
||||
self.preflight = preflight
|
||||
|
||||
def execute(self):
|
||||
account_rows = accounts.list_accounts(path=self.db_path, config=self.config)
|
||||
@@ -664,6 +716,22 @@ if QT_IMPORT_ERROR is None:
|
||||
failed = 0
|
||||
done = 0
|
||||
|
||||
if self.preflight:
|
||||
blocked = self._preflight_block(eligible, account_rows, account_by_alias)
|
||||
if blocked:
|
||||
blocked.update(
|
||||
{
|
||||
"ok": False,
|
||||
"blocked": True,
|
||||
"total": total,
|
||||
"done": 0,
|
||||
"collected": 0,
|
||||
"skipped": 0,
|
||||
"failed": 0,
|
||||
}
|
||||
)
|
||||
return blocked
|
||||
|
||||
for task in eligible:
|
||||
if self.should_cancel():
|
||||
break
|
||||
@@ -732,6 +800,49 @@ if QT_IMPORT_ERROR is None:
|
||||
"failed": failed,
|
||||
}
|
||||
|
||||
def _preflight_block(self, eligible, account_rows, account_by_alias):
|
||||
if not account_rows:
|
||||
return {
|
||||
"reason": "NO_ACCOUNTS",
|
||||
"no_accounts": True,
|
||||
}
|
||||
required_accounts = []
|
||||
seen_aliases = set()
|
||||
for task in eligible:
|
||||
alias = str(task.alias).strip()
|
||||
account = account_by_alias.get(alias)
|
||||
if account is not None and alias not in seen_aliases:
|
||||
required_accounts.append(account)
|
||||
seen_aliases.add(alias)
|
||||
not_running = []
|
||||
logged_out = []
|
||||
for account in required_accounts:
|
||||
if not chrome.is_running(account.debug_port):
|
||||
not_running.append(self._account_payload(account, "CDP 端口未响应"))
|
||||
continue
|
||||
status = self._login_status(account)
|
||||
if not status.get("logged_in"):
|
||||
logged_out.append(
|
||||
self._account_payload(account, self._login_skip_reason(status))
|
||||
)
|
||||
if not_running or logged_out:
|
||||
return {
|
||||
"reason": "ACCOUNT_NOT_READY",
|
||||
"not_running": not_running,
|
||||
"logged_out": logged_out,
|
||||
}
|
||||
return None
|
||||
|
||||
def _account_payload(self, account, reason=None):
|
||||
payload = {
|
||||
"account_name": account.account_name,
|
||||
"alias": account.alias,
|
||||
"debug_port": account.debug_port,
|
||||
}
|
||||
if reason:
|
||||
payload["reason"] = reason
|
||||
return payload
|
||||
|
||||
def _emit_progress(self, done, total, collected, skipped, failed):
|
||||
self.progress.emit(
|
||||
{
|
||||
@@ -1088,6 +1199,7 @@ if QT_IMPORT_ERROR is None:
|
||||
db_path=self.db_path,
|
||||
config=self.config,
|
||||
status_callback=self.statusBar().showMessage,
|
||||
open_accounts_callback=lambda: self.open_accounts_tab(),
|
||||
)
|
||||
if title == "④ 账号管理":
|
||||
return AccountsTab(
|
||||
@@ -1104,6 +1216,9 @@ if QT_IMPORT_ERROR is None:
|
||||
|
||||
def _on_tab_changed(self, index):
|
||||
self.statusBar().showMessage(f"当前:{self.tabs.tabText(index)}")
|
||||
|
||||
def open_accounts_tab(self):
|
||||
self.tabs.setCurrentIndex(TAB_TITLES.index("④ 账号管理"))
|
||||
else:
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
|
||||
Reference in New Issue
Block a user