feat(subscription): add membership access preflight
This commit is contained in:
+218
-1
@@ -2,6 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import QUrl
|
||||
from PySide6.QtGui import QDesktopServices
|
||||
|
||||
from .. import subscription
|
||||
from ..version import display_name
|
||||
from .tabs.accounts import AccountsTab
|
||||
from .tabs.apply import ApplyTab
|
||||
@@ -10,6 +14,7 @@ from .tabs.generate import GenerateTab
|
||||
from .tabs.product_suite import ProductSuiteTab
|
||||
from .tabs.settings import SettingsTab
|
||||
from .widgets import *
|
||||
from .workers import SubscriptionCheckWorker
|
||||
|
||||
|
||||
PREFERRED_WINDOW_SIZE = (1180, 760)
|
||||
@@ -106,6 +111,14 @@ class MainWindow(QMainWindow):
|
||||
self._settings_tab_index = TAB_TITLES.index("设置")
|
||||
self._last_tab_index = 0
|
||||
self._reverting_tab_change = False
|
||||
self._subscription_status = subscription.SubscriptionStatus(
|
||||
subscription.STATUS_UNAVAILABLE
|
||||
)
|
||||
self._subscription_worker = None
|
||||
self._subscription_thread = None
|
||||
self._subscription_request_token = 0
|
||||
self._subscription_prompt_key = ""
|
||||
self._subscription_closed = False
|
||||
self.tabs = QTabWidget()
|
||||
self.tabs.setObjectName("mainTabs")
|
||||
self.tabs.setStyleSheet(TAB_STYLE)
|
||||
@@ -115,7 +128,30 @@ class MainWindow(QMainWindow):
|
||||
settings_tab = self._settings_tab()
|
||||
if hasattr(settings_tab, "settingsSaved"):
|
||||
settings_tab.settingsSaved.connect(self._on_settings_saved)
|
||||
self.setCentralWidget(self.tabs)
|
||||
self._header_title_label = QLabel(display_name())
|
||||
self._header_title_label.setObjectName("appHeaderTitle")
|
||||
self._header_title_label.setStyleSheet(
|
||||
"font-size: 15px; font-weight: 600; color: #24292f;"
|
||||
)
|
||||
self._subscription_label = QLabel("会员:正在验证")
|
||||
self._subscription_label.setObjectName("subscriptionStatusLabel")
|
||||
self._subscription_label.setAccessibleName("会员状态")
|
||||
self._subscription_label.setStyleSheet("color: #0969da;")
|
||||
header = QWidget()
|
||||
header.setObjectName("appHeader")
|
||||
header_layout = QHBoxLayout(header)
|
||||
header_layout.setContentsMargins(18, 8, 18, 4)
|
||||
header_layout.setSpacing(10)
|
||||
header_layout.addWidget(self._header_title_label)
|
||||
header_layout.addStretch(1)
|
||||
header_layout.addWidget(self._subscription_label)
|
||||
central = QWidget()
|
||||
central_layout = QVBoxLayout(central)
|
||||
central_layout.setContentsMargins(0, 0, 0, 0)
|
||||
central_layout.setSpacing(0)
|
||||
central_layout.addWidget(header)
|
||||
central_layout.addWidget(self.tabs, 1)
|
||||
self.setCentralWidget(central)
|
||||
self.show_status("就绪", level="muted")
|
||||
if startup_status:
|
||||
self.show_status(startup_status, level="success")
|
||||
@@ -150,6 +186,7 @@ class MainWindow(QMainWindow):
|
||||
cover_prompts_dir=appconfig.cover_prompts_dir(self.config),
|
||||
open_accounts_callback=lambda: self.open_accounts_tab(),
|
||||
refresh_workflow_callback=lambda: self.refresh_task_tabs(),
|
||||
subscription_preflight_callback=self.ensure_subscription_for_new_submit,
|
||||
)
|
||||
if title == "③ 更新蝦皮":
|
||||
return ApplyTab(
|
||||
@@ -179,6 +216,7 @@ class MainWindow(QMainWindow):
|
||||
config=self.config,
|
||||
config_path=self.config_path,
|
||||
status_callback=self.show_status,
|
||||
subscription_preflight_callback=self.ensure_subscription_for_new_submit,
|
||||
)
|
||||
raise ValueError(f"未知主界面模块:{title}")
|
||||
|
||||
@@ -195,6 +233,182 @@ class MainWindow(QMainWindow):
|
||||
widget.refresh_gateway_state()
|
||||
label = "自定义网关" if str(backend) == "direct" else "默认网关"
|
||||
self.show_status("设置已保存,当前使用%s" % label, level="success")
|
||||
self.begin_subscription_check(show_prompt=False)
|
||||
|
||||
@property
|
||||
def subscription_status(self):
|
||||
return self._subscription_status
|
||||
|
||||
def begin_subscription_check(self, *, show_prompt=True):
|
||||
"""Refresh membership state without blocking the Qt GUI thread."""
|
||||
|
||||
self._subscription_request_token += 1
|
||||
token = self._subscription_request_token
|
||||
if self._subscription_worker is not None:
|
||||
self._subscription_worker.cancel()
|
||||
self._set_product_access(False)
|
||||
self._set_subscription_label("会员:正在验证", "info")
|
||||
worker = SubscriptionCheckWorker(
|
||||
config=self.config,
|
||||
cmhub_config_path=self.config.get("cmhub_config_path"),
|
||||
)
|
||||
worker.finished.connect(
|
||||
lambda payload, token=token, show_prompt=show_prompt: self._on_subscription_checked(
|
||||
token,
|
||||
payload,
|
||||
show_prompt=show_prompt,
|
||||
)
|
||||
)
|
||||
worker.cancelled.connect(
|
||||
lambda payload, token=token, show_prompt=show_prompt: self._on_subscription_checked(
|
||||
token,
|
||||
payload,
|
||||
show_prompt=show_prompt,
|
||||
)
|
||||
)
|
||||
thread = run_worker(worker, thread_name="SubscriptionCheckWorker", start=False)
|
||||
thread.finished.connect(lambda: self._forget_subscription_thread(thread))
|
||||
self._subscription_worker = worker
|
||||
self._subscription_thread = thread
|
||||
thread.start()
|
||||
|
||||
def ensure_subscription_for_new_submit(self, action_name="生成"):
|
||||
"""Return whether a new product request may start from the current UI."""
|
||||
|
||||
status = self._subscription_status
|
||||
if status.allows_product_workflows:
|
||||
return True
|
||||
if self._subscription_thread is not None:
|
||||
self.show_status("正在验证会员状态,请稍后再%s" % action_name, level="info")
|
||||
return False
|
||||
self.show_status("%s:%s" % (action_name, status.user_message), level="warning")
|
||||
self._show_subscription_access_prompt(status)
|
||||
return False
|
||||
|
||||
def _on_subscription_checked(self, token, payload, *, show_prompt=True):
|
||||
if self._subscription_closed or token != self._subscription_request_token:
|
||||
return
|
||||
result = payload.get("subscription") if isinstance(payload, dict) else None
|
||||
if not isinstance(result, subscription.SubscriptionStatus):
|
||||
result = subscription.SubscriptionStatus(subscription.STATUS_UNAVAILABLE)
|
||||
self._subscription_status = result
|
||||
self._apply_subscription_status(result, show_prompt=show_prompt)
|
||||
|
||||
def _forget_subscription_thread(self, thread):
|
||||
if self._subscription_thread is thread:
|
||||
self._subscription_thread = None
|
||||
self._subscription_worker = None
|
||||
|
||||
def _apply_subscription_status(self, status, *, show_prompt=True):
|
||||
self._subscription_status = status
|
||||
if status.state == subscription.STATUS_ACTIVE:
|
||||
expiry = subscription.format_expiry(status.expires_at)
|
||||
self._set_subscription_label(
|
||||
"%s · %s · 有效至%s" % (status.account_name, status.plan_name, expiry),
|
||||
"success",
|
||||
)
|
||||
elif status.state == subscription.STATUS_GRACE:
|
||||
expiry = subscription.format_expiry(status.grace_expires_at) or subscription.format_expiry(status.expires_at)
|
||||
self._set_subscription_label(
|
||||
"%s · %s · 宽限至%s" % (status.account_name, status.plan_name, expiry),
|
||||
"warning",
|
||||
)
|
||||
else:
|
||||
self._set_subscription_label("会员:%s" % status.user_message, self._subscription_level(status))
|
||||
|
||||
self._set_product_access(status.allows_product_workflows)
|
||||
if status.allows_product_workflows:
|
||||
if status.state == subscription.STATUS_LEGACY:
|
||||
self.show_status(status.user_message, level="muted")
|
||||
else:
|
||||
self.show_status("会员状态已验证", level="success")
|
||||
self._show_subscription_notice_once(status)
|
||||
return
|
||||
self.open_settings_tab()
|
||||
self.show_status(status.user_message, level=self._subscription_level(status))
|
||||
if show_prompt:
|
||||
self._show_subscription_access_prompt(status)
|
||||
|
||||
@staticmethod
|
||||
def _subscription_level(status):
|
||||
if status.state in {subscription.STATUS_UNAVAILABLE, subscription.STATUS_KEY_INVALID}:
|
||||
return "warning"
|
||||
return "danger"
|
||||
|
||||
def _set_subscription_label(self, text, level):
|
||||
color = _status_level_color(level)
|
||||
self._subscription_label.setText(str(text))
|
||||
self._subscription_label.setStyleSheet("color: %s;" % color)
|
||||
self._subscription_label.setToolTip(str(text))
|
||||
|
||||
def _set_product_access(self, enabled):
|
||||
for index, title in enumerate(TAB_TITLES):
|
||||
self.tabs.setTabEnabled(index, bool(enabled) or title == "设置")
|
||||
|
||||
def _show_subscription_access_prompt(self, status):
|
||||
if self._subscription_closed:
|
||||
return
|
||||
if status.state in {subscription.STATUS_ACTIVE, subscription.STATUS_GRACE, subscription.STATUS_LEGACY}:
|
||||
return
|
||||
prompt_key = "|".join((status.state, status.notice_id, status.manage_url))
|
||||
if prompt_key and prompt_key == self._subscription_prompt_key:
|
||||
return
|
||||
self._subscription_prompt_key = prompt_key
|
||||
box = QMessageBox(self)
|
||||
box.setIcon(QMessageBox.Warning)
|
||||
box.setWindowTitle("需要配置会员账号")
|
||||
box.setText(status.user_message)
|
||||
box.setInformativeText("请配置有效的 cmhub API Key,或前往线上会员中心开通、续费后重试。")
|
||||
settings_button = box.addButton("打开设置", QMessageBox.AcceptRole)
|
||||
manage_button = None
|
||||
if status.manage_url:
|
||||
manage_button = box.addButton("前往会员中心", QMessageBox.ActionRole)
|
||||
exit_button = box.addButton("退出", QMessageBox.RejectRole)
|
||||
box.setDefaultButton(settings_button)
|
||||
box.setEscapeButton(exit_button)
|
||||
box.exec()
|
||||
if box.clickedButton() is settings_button:
|
||||
self.open_settings_tab()
|
||||
elif manage_button is not None and box.clickedButton() is manage_button:
|
||||
QDesktopServices.openUrl(QUrl(status.manage_url))
|
||||
elif box.clickedButton() is exit_button:
|
||||
self.close()
|
||||
|
||||
def _show_subscription_notice_once(self, status):
|
||||
notice_id = str(status.notice_id or "").strip()
|
||||
if not notice_id or notice_id == appconfig.subscription_notice_id(self.config):
|
||||
return
|
||||
box = QMessageBox(self)
|
||||
box.setIcon(QMessageBox.Information)
|
||||
box.setWindowTitle("已启用会员订阅")
|
||||
expiry = subscription.format_expiry(
|
||||
status.grace_expires_at if status.state == subscription.STATUS_GRACE else status.expires_at
|
||||
)
|
||||
text = "已启用蝦皮圈会员订阅服务。当前账号:%s;套餐:%s。" % (
|
||||
status.account_name,
|
||||
status.plan_name,
|
||||
)
|
||||
if expiry:
|
||||
text += "当前有效至%s。" % expiry
|
||||
text += "请在到期前前往线上会员中心选择或续费套餐。"
|
||||
box.setText(text)
|
||||
manage_button = None
|
||||
if status.manage_url:
|
||||
manage_button = box.addButton("前往会员中心", QMessageBox.ActionRole)
|
||||
close_button = box.addButton("关闭", QMessageBox.AcceptRole)
|
||||
box.setDefaultButton(close_button)
|
||||
box.exec()
|
||||
if manage_button is not None and box.clickedButton() is manage_button:
|
||||
QDesktopServices.openUrl(QUrl(status.manage_url))
|
||||
try:
|
||||
saved = appconfig.save_subscription_notice_id(
|
||||
notice_id,
|
||||
path=self.config_path,
|
||||
)
|
||||
except Exception:
|
||||
return
|
||||
self.config.clear()
|
||||
self.config.update(saved)
|
||||
|
||||
def _on_tab_changed(self, index):
|
||||
if self._reverting_tab_change:
|
||||
@@ -239,6 +453,9 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self._confirm_leave_settings_tab():
|
||||
self._subscription_closed = True
|
||||
if self._subscription_worker is not None:
|
||||
self._subscription_worker.cancel()
|
||||
event.accept()
|
||||
return
|
||||
event.ignore()
|
||||
|
||||
Reference in New Issue
Block a user