diff --git a/admin/dashboard/templates/index.html b/admin/dashboard/templates/index.html
index 212d942..d59ef79 100644
--- a/admin/dashboard/templates/index.html
+++ b/admin/dashboard/templates/index.html
@@ -654,6 +654,9 @@
if (this.loginQrDialog.provider_stage === 'status_unavailable') {
return '登录异常';
}
+ if (this.loginQrDialog.provider_stage === 'login_finalizing') {
+ return '登录收口中';
+ }
if (this.loginQrDialog.provider_stage === 'scan_confirmed') {
return '已扫码待确认';
}
@@ -671,6 +674,9 @@
if (this.loginQrDialog.provider_stage === 'status_unavailable') {
return '864 登录异常';
}
+ if (this.loginQrDialog.provider_stage === 'login_finalizing') {
+ return '864 登录收口中';
+ }
if (this.loginQrDialog.provider_stage === 'scan_confirmed') {
return '864 扫码确认中';
}
@@ -701,6 +707,9 @@
if (this.loginQrDialog.provider_stage === 'status_unavailable') {
return '请查看当前错误提示';
}
+ if (this.loginQrDialog.provider_stage === 'login_finalizing') {
+ return '扫码成功,等待服务端完成登录';
+ }
if (this.loginQrDialog.provider_stage === 'scan_confirmed') {
return '已扫码,等待服务端确认';
}
diff --git a/wechat_ipad/providers/server_864/runtime.py b/wechat_ipad/providers/server_864/runtime.py
index 99fcd98..5ff7a44 100644
--- a/wechat_ipad/providers/server_864/runtime.py
+++ b/wechat_ipad/providers/server_864/runtime.py
@@ -223,22 +223,29 @@ class Server864RuntimeMixin:
raise RuntimeError(error_message) from e
if is_logged_in:
await self._safe_callback(
- on_login_qr_cleared,
+ on_login_qr_update,
{
+ "uuid": uuid,
+ "url": url,
+ "scan_url": scan_url,
+ "expires_in": effective_time if effective_time > 0 else None,
"status": "confirmed",
# 864 进入 `state=2` 后,已经拿到了 wxid / wxnewpass 等关键登录信息:
- # 1. 这说明扫码本身已经成功,不需要用户继续执行额外扫码动作;
- # 2. 这里把提示文案改得更明确,减少“是不是还要继续点验证链接”的疑惑;
- # 3. 后续若服务端初始化还要几秒,也归入“服务端完成登录”阶段,而不是继续归咎给扫码步骤。
+ # 1. 这说明扫码本身已经成功,但并不代表 ABOT 后续初始化已经全部完成;
+ # 2. 若这里直接关闭弹窗,后面的版本过低/初始化失败就无法同步给前端;
+ # 3. 因此先保持弹窗可见,把阶段切到“登录收口中”,等真正初始化完成后再清理。
"status_text": "扫码成功,等待服务端完成登录",
- "uuid": uuid,
+ "login_source": "fresh_qr",
"provider_name": "server_864",
- "provider_stage": "logged_in",
+ "provider_stage": "login_finalizing",
"connection_ready": True,
"login_required": False,
+ "raw_state": 2,
+ "login_qr_api": login_qr_api,
+ "login_way": login_way,
},
logger=logger,
- callback_name="on_login_qr_cleared",
+ callback_name="on_login_qr_update",
)
break
@@ -347,15 +354,43 @@ class Server864RuntimeMixin:
)
await asyncio.sleep(5)
- await self._wait_init_ready(logger=logger)
- await self._refresh_identity_from_profile(logger=logger)
- ipad_config["wxid"] = self.wxid
- ipad_config["login_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
- self._save_runtime_state(
- state_path=state_path,
- state_payload={"wxid": self.wxid, "login_time": ipad_config["login_time"]},
- logger=logger,
- )
+ try:
+ await self._wait_init_ready(logger=logger)
+ await self._refresh_identity_from_profile(logger=logger)
+ ipad_config["wxid"] = self.wxid
+ ipad_config["login_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
+ self._save_runtime_state(
+ state_path=state_path,
+ state_payload={"wxid": self.wxid, "login_time": ipad_config["login_time"]},
+ logger=logger,
+ )
+ except Exception as e:
+ error_message = self._normalize_login_runtime_message(str(e))
+ # 扫码成功后的收口阶段同样需要把异常同步给前端:
+ # 1. 这类错误往往发生在“state=2 之后但正式进入业务前”,最容易被误判成前端没刷新;
+ # 2. 例如当前用户遇到的“客户端版本过低”就是在这个阶段由服务端主动断开;
+ # 3. 因此这里明确把阶段标成 `status_unavailable`,让弹窗继续停留并展示真实原因。
+ await self._safe_callback(
+ on_login_qr_update,
+ {
+ "uuid": uuid,
+ "url": url,
+ "scan_url": scan_url,
+ "status": "unavailable",
+ "status_text": error_message,
+ "login_source": "fresh_qr",
+ "provider_name": "server_864",
+ "provider_stage": "status_unavailable",
+ "connection_ready": False,
+ "login_required": True,
+ "raw_state": 2,
+ "login_qr_api": login_qr_api,
+ "login_way": login_way,
+ },
+ logger=logger,
+ callback_name="on_login_qr_update",
+ )
+ raise RuntimeError(error_message) from e
async def _probe_login_stage(self) -> dict[str, Any]:
"""探测 864 当前登录阶段,供 Dashboard 展示更准确的运维状态。"""