"""采购工具固定双 Tab 原生窗口。""" from __future__ import annotations from PySide6.QtCore import QTimer, Qt, Slot from PySide6.QtGui import QCloseEvent from PySide6.QtWidgets import QMainWindow, QTabWidget from cmbuyer_client.localstate.models import ProfileSettings from cmbuyer_client.polling.coordinator import PollingCoordinator, PollingState, RecoveryStatus from .execution import ExecutionPage from .records import PurchaseRecordProvider from .settings import ProfileStore, SettingsPage class PurchaseToolWindow(QMainWindow): EXECUTION_PAGE_ID = "purchase-execution" SETTINGS_PAGE_ID = "settings" def __init__( self, *, store: ProfileStore, coordinator: PollingCoordinator, profile_settings: ProfileSettings | None, has_stored_device_token: bool, record_provider: PurchaseRecordProvider | None = None, parent=None, ) -> None: super().__init__(parent) self.setWindowTitle("采购工具") self.setAccessibleName("采购工具") self.setMinimumSize(720, 520) self.resize(1180, 760) self._coordinator = coordinator self._close_pending = False recovery = coordinator.recovery_status frozen = recovery is None or recovery.has_pending_claim or recovery.has_active_claim self.tabs = QTabWidget() self.tabs.setObjectName("mainTabs") self.tabs.setTabsClosable(False) self.tabs.setMovable(False) self.execution_page = ExecutionPage(coordinator, record_provider) self.execution_page.setProperty("pageId", self.EXECUTION_PAGE_ID) self.settings_page = SettingsPage( store, settings=profile_settings, has_stored_device_token=has_stored_device_token, identity_frozen=frozen, ) self.settings_page.setProperty("pageId", self.SETTINGS_PAGE_ID) self.tabs.addTab(self.execution_page, "采购执行") self.tabs.addTab(self.settings_page, "配置") self.tabs.setCurrentWidget(self.execution_page) self.setCentralWidget(self.tabs) self.settings_page.settings_saved.connect( lambda settings, has_token: self._coordinator.update_profile_settings(settings) ) coordinator.recovery_status_changed.connect(self._on_recovery_status) coordinator.configuration_freeze_changed.connect(self.settings_page.set_identity_frozen) coordinator.settled.connect(self._finish_pending_close) @Slot(object) def _on_recovery_status(self, raw: object) -> None: if not isinstance(raw, RecoveryStatus): self.settings_page.set_identity_frozen(True) return self.settings_page.set_identity_frozen(raw.has_pending_claim or raw.has_active_claim) def closeEvent(self, event: QCloseEvent) -> None: running = self._coordinator.state in ( PollingState.STARTING, PollingState.RECOVERING, PollingState.WAITING, PollingState.CLAIMING, PollingState.ACTIVE, ) or self._coordinator.operation_in_flight if running: # 不 terminate 飞行中的 QThread。先提升 stop latch,等待 HTTP 自身 # 超时和 DurableClientGateway 落库,再由 settled 重试关闭。 self._close_pending = True self._coordinator.stop() event.ignore() return event.accept() @Slot() def _finish_pending_close(self) -> None: if not self._close_pending: return self._close_pending = False QTimer.singleShot(0, self.close)