diff --git a/Utils/JD/JdUtils.py b/Utils/JD/JdUtils.py index d819a3f..f74d52d 100644 --- a/Utils/JD/JdUtils.py +++ b/Utils/JD/JdUtils.py @@ -177,6 +177,11 @@ class FixJdCookie: """初始化 socket""" await self.send_heartbeat(ws, aid, pin_zj) print("开始监听初始化") + + # 🔧 修复:生成唯一设备ID,避免多端互踢 + import uuid + unique_device_id = f"shuidrop_gui_{uuid.uuid4().hex[:16]}" + auth = { "id": hashlib.md5(str(int(time.time() * 1000)).encode()).hexdigest(), "aid": aid, @@ -185,8 +190,9 @@ class FixJdCookie: "type": "auth", "body": {"presence": 1, "clientVersion": "2.6.3"}, "to": {"app": "im.waiter"}, - "from": {"app": "im.waiter", "pin": pin_zj, "clientType": "comet", "dvc": "device1234"} + "from": {"app": "im.waiter", "pin": pin_zj, "clientType": "comet", "dvc": unique_device_id} } + print(f"[DEBUG] 使用唯一设备ID: {unique_device_id}") await ws.send(json.dumps(auth)) @@ -611,10 +617,42 @@ class FixJdCookie: print(f"等待监听消息-{datetime.now()}") response = await asyncio.wait_for(ws.recv(), timeout=1) print(f"原始消息类型:{type(response)}, 消息体为: {response}") - await self.process_incoming_message(response, ws, aid, pin_zj, vender_id, store) - - # 安全解析消息 + + # 🔧 修复:检测被踢下线消息 json_resp = json.loads(response) if isinstance(response, (str, bytes)) else response + + # 检查是否为server_msg类型且code=5(被踢下线) + if json_resp.get("type") == "server_msg": + body = json_resp.get("body", {}) + if body.get("code") == 5: + msgtext = body.get("msgtext", "账号在其他设备登录") + self._log(f"⚠️ 收到被踢下线消息: {msgtext}", "WARNING") + self._log("⚠️ 检测到多端登录冲突,请确保:", "WARNING") + self._log(" 1. 关闭网页版京东咚咚", "WARNING") + self._log(" 2. 关闭其他客户端", "WARNING") + self._log(" 3. 确认只有本客户端连接", "WARNING") + + # 通知GUI显示弹窗 + try: + from WebSocket.backend_singleton import get_websocket_manager + ws_manager = get_websocket_manager() + if ws_manager: + # 从platform_listeners获取店铺名称 + store_name = "京东店铺" + for key, info in ws_manager.platform_listeners.items(): + if info.get('store_id') == store: + store_name = info.get('store_name', '') or "京东店铺" + break + # 传递 store_id 参数,用于通知后端 + ws_manager.notify_platform_kicked("京东", store_name, msgtext, store_id=store) + except Exception as notify_error: + self._log(f"通知GUI失败: {notify_error}", "ERROR") + + # 不再自动重连,等待用户处理 + stop_event.set() + break + + await self.process_incoming_message(response, ws, aid, pin_zj, vender_id, store) print(json_resp) @@ -660,9 +698,8 @@ class FixJdCookie: if not await self.handle_reconnect(e): break - # 关闭后端服务连接 + # 关闭后端服务连接(JDBackendService没有close方法,跳过) if self.backend_connected: - await self.backend_service.close() self.backend_connected = False self._log("🛑 消息监听已停止", "INFO") diff --git a/WebSocket/BackendClient.py b/WebSocket/BackendClient.py index ec39c5a..b0ad4bb 100644 --- a/WebSocket/BackendClient.py +++ b/WebSocket/BackendClient.py @@ -130,6 +130,10 @@ class BackendClient: self._log("已禁用心跳机制", "WARNING") self.is_connected = True + + # 🔥 在重置之前记录是否是重连(用于后续上报平台状态) + was_reconnecting = self.reconnect_attempts > 0 + self.reconnect_attempts = 0 # 重置重连计数 self.is_reconnecting = False self._log("后端WebSocket连接成功", "SUCCESS") @@ -140,7 +144,7 @@ class BackendClient: # 发送连接状态通知给后端 self._notify_connection_status(True) - self.on_connected() + self.on_connected(was_reconnecting) # 消息循环 async for message in self.websocket: @@ -308,53 +312,68 @@ class BackendClient: if log: self.log_callback = log - def on_connected(self): + def on_connected(self, was_reconnecting: bool = False): """连接成功时的处理""" - if self.reconnect_attempts > 0: - self._log(f"后端WebSocket重连成功!(第{self.reconnect_attempts}次尝试)", "SUCCESS") - else: - self._log("后端WebSocket连接成功", "SUCCESS") - - # 重连成功后可选择上报状态给后端 - if self.reconnect_attempts > 0: + if was_reconnecting: + self._log("后端WebSocket重连成功!", "SUCCESS") + # 重连成功后上报平台状态给后端 self._report_reconnect_status() + else: + self._log("后端WebSocket首次连接成功", "SUCCESS") # 不再主动请求 get_store,避免与后端不兼容导致协程未完成 def _report_reconnect_status(self): - """重连成功后上报当前状态(可选)""" + """重连成功后上报当前状态""" try: # 获取当前已连接的平台列表 from WebSocket.backend_singleton import get_websocket_manager manager = get_websocket_manager() if hasattr(manager, 'platform_listeners') and manager.platform_listeners: + platform_count = len(manager.platform_listeners) + self._log(f"🔄 检测到 {platform_count} 个活跃平台连接,准备重新上报状态", "INFO") + + # 延迟1秒,确保后端完全准备好 + import time + time.sleep(1.0) + for platform_key, listener_info in manager.platform_listeners.items(): store_id = listener_info.get('store_id') platform = listener_info.get('platform') + store_name = listener_info.get('store_name', '') if store_id and platform: # 上报平台仍在连接状态 try: + store_display = store_name or store_id[:8] + "..." reconnect_message = { "type": "connect_message", "store_id": store_id, "status": True, - "content": f"GUI重连成功,{platform}平台状态正常" + "cookies": "" # 重连时无需再次发送cookies } - # 异步发送,不阻塞连接过程 - asyncio.run_coroutine_threadsafe( + + # 同步发送,确保发送成功 + future = asyncio.run_coroutine_threadsafe( self._send_to_backend(reconnect_message), self.loop ) - print(f"[重连] 已上报{platform}平台状态") + # 等待发送完成(最多2秒) + future.result(timeout=2) + + self._log(f"✅ 已重新上报 {platform} 平台状态: {store_display}", "SUCCESS") except Exception as e: - print(f"[重连] 上报{platform}平台状态失败: {e}") + self._log(f"❌ 上报 {platform} 平台状态失败: {e}", "ERROR") + import traceback + self._log(f"详细错误: {traceback.format_exc()}", "DEBUG") else: - print("[重连] 当前无活跃平台连接,跳过状态上报") + self._log("当前无活跃平台连接,跳过状态上报", "INFO") except Exception as e: - print(f"[重连] 状态上报过程异常: {e}") + self._log(f"状态上报过程异常: {e}", "ERROR") + import traceback + self._log(f"详细错误: {traceback.format_exc()}", "DEBUG") def _notify_connection_status(self, connected: bool): """通知后端连接状态变化""" @@ -365,30 +384,21 @@ class BackendClient: # 获取当前活跃平台的store_id active_store_id = None try: - from Utils.JD.JdUtils import WebsocketManager as JdManager - from Utils.Dy.DyUtils import DouYinWebsocketManager as DyManager - from Utils.Pdd.PddUtils import WebsocketManager as PddManager - - # 检查各平台是否有活跃连接 - for mgr_class, platform_name in [(JdManager, "京东"), (DyManager, "抖音"), (PddManager, "拼多多")]: - try: - mgr = mgr_class() - if hasattr(mgr, '_store') and mgr._store: - for shop_key, entry in mgr._store.items(): - if entry and entry.get('platform'): - # 从shop_key中提取store_id(格式:平台:store_id) - if ':' in shop_key: - _, store_id = shop_key.split(':', 1) - active_store_id = store_id - print(f"[状态] 检测到活跃{platform_name}平台: {store_id}") - break - if active_store_id: - break - except Exception as e: - print(f"[状态] 检查{platform_name}平台失败: {e}") - continue + from WebSocket.backend_singleton import get_websocket_manager + manager = get_websocket_manager() + + # 从 platform_listeners 获取活跃平台 + if hasattr(manager, 'platform_listeners') and manager.platform_listeners: + # 获取第一个活跃平台的 store_id + for platform_key, listener_info in manager.platform_listeners.items(): + store_id = listener_info.get('store_id') + platform = listener_info.get('platform') + if store_id and platform: + active_store_id = store_id + self._log(f"检测到活跃{platform}平台: {store_id}", "DEBUG") + break except Exception as e: - print(f"[状态] 获取活跃平台信息失败: {e}") + self._log(f"获取活跃平台信息失败: {e}", "DEBUG") status_message = { "type": "connection_status", @@ -400,9 +410,9 @@ class BackendClient: # 如果有活跃平台,添加store_id if active_store_id: status_message["store_id"] = active_store_id - print(f"[状态] 添加store_id到状态消息: {active_store_id}") + self._log(f"添加store_id到状态消息: {active_store_id}", "DEBUG") else: - print(f"[状态] 未检测到活跃平台,不添加store_id") + self._log("未检测到活跃平台,不添加store_id", "DEBUG") # 异步发送状态通知 asyncio.run_coroutine_threadsafe( @@ -411,12 +421,12 @@ class BackendClient: ) status_text = "连接" if connected else "断开" - print(f"[状态] 已通知后端GUI客户端{status_text}") + self._log(f"已通知后端GUI客户端{status_text}", "DEBUG") except Exception as e: - print(f"[状态] 发送状态通知失败: {e}") + self._log(f"发送状态通知失败: {e}", "ERROR") import traceback - print(f"[状态] 详细错误: {traceback.format_exc()}") + self._log(f"详细错误: {traceback.format_exc()}", "DEBUG") def on_message_received(self, message: Dict[str, Any]): """处理接收到的消息 - 根据WebSocket文档v2更新""" @@ -1172,10 +1182,11 @@ class BackendClient: self.get_store() def _handle_login(self, message: Dict[str, Any]): - """处理平台登录消息(新版:type=login, cookies/login_params, store_id, platform_name)""" + """处理平台登录消息(新版:type=login, cookies/login_params, store_id, platform_name, store_name)""" cookies = message.get('cookies', '') store_id = message.get('store_id', '') platform_name = message.get('platform_name', '') + store_name = message.get('store_name', '') # 新增:获取店铺名称 content = message.get('content', '') data = message.get('data', {}) @@ -1184,17 +1195,17 @@ class BackendClient: (("拼多多登录" in content and data.get('login_params')) or ("抖音登录" in content and data.get('login_flow')))): # 登录参数模式 - 传递完整的消息JSON给处理器 - print(f"收到{platform_name}登录参数: 平台={platform_name}, 店铺={store_id}, 类型={content}") + print(f"收到{platform_name}登录参数: 平台={platform_name}, 店铺={store_name or store_id}, 类型={content}") if self.login_callback: # 传递完整的JSON消息,让处理器来解析登录参数 import json full_message = json.dumps(message) - self.login_callback(platform_name, store_id, full_message) + self.login_callback(platform_name, store_id, full_message, store_name) else: # 普通Cookie模式 - print(f"收到登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}") + print(f"收到登录指令: 平台={platform_name}, 店铺={store_name or store_id}, cookies_len={len(cookies) if cookies else 0}") if self.login_callback: - self.login_callback(platform_name, store_id, cookies) + self.login_callback(platform_name, store_id, cookies, store_name) def _handle_error_message(self, message: Dict[str, Any]): """处理错误消息""" @@ -1253,7 +1264,7 @@ class BackendClient: def _handle_disconnect(self, message: Dict[str, Any]): """处理被踢下线消息""" - disconnect_message = message.get('message', '您的账号在其他设备登录,当前连接已断开') + disconnect_message = message.get('content', '您的账号在其他设备登录,当前连接已断开') print(f"[断开] 收到后端断开通知: {disconnect_message}") diff --git a/WebSocket/backend_singleton.py b/WebSocket/backend_singleton.py index e1202a0..9f7dda3 100644 --- a/WebSocket/backend_singleton.py +++ b/WebSocket/backend_singleton.py @@ -59,7 +59,8 @@ class WebSocketManager: } def set_callbacks(self, log: Callable = None, success: Callable = None, error: Callable = None, - platform_connected: Callable = None, token_error: Callable = None, disconnect: Callable = None): + platform_connected: Callable = None, platform_disconnected: Callable = None, + token_error: Callable = None, disconnect: Callable = None): """设置回调函数""" if log: self.callbacks['log'] = log @@ -69,6 +70,8 @@ class WebSocketManager: self.callbacks['error'] = error if platform_connected: # ← 新增 self.callbacks['platform_connected'] = platform_connected + if platform_disconnected: + self.callbacks['platform_disconnected'] = platform_disconnected if token_error: self.callbacks['token_error'] = token_error if disconnect: @@ -101,6 +104,35 @@ class WebSocketManager: except Exception as e: self._log(f"通知平台连接失败: {e}", "ERROR") + def notify_platform_kicked(self, platform_name: str, store_name: str, reason: str = "账号在其他设备登录", store_id: str = None): + """通知GUI平台被踢下线(供平台监听器调用)""" + try: + self._log(f"⚠️ 平台被踢下线: {platform_name} - {store_name}, 原因: {reason}", "WARNING") + + # 从连接列表中移除 + if platform_name in self.connected_platforms: + self.connected_platforms.remove(platform_name) + + # 🔥 发送平台断开消息给后端(status=false) + if store_id and self.backend_client: + try: + disconnect_message = { + "type": "connect_message", + "store_id": store_id, + "status": False, + "cookies": "" + } + self.backend_client.send_message(disconnect_message) + self._log(f"✅ 已通知后端 {platform_name} 平台断开: {store_id}", "INFO") + except Exception as send_error: + self._log(f"❌ 发送平台断开消息失败: {send_error}", "ERROR") + + # 通知GUI显示弹窗 + if self.callbacks['platform_disconnected']: + self.callbacks['platform_disconnected'](platform_name, store_name, reason) + except Exception as e: + self._log(f"通知平台断开失败: {e}", "ERROR") + def connect_backend(self, token: str) -> bool: """连接后端WebSocket""" try: @@ -139,11 +171,12 @@ class WebSocketManager: except Exception as e: self._log(f"成功回调执行失败: {e}", "ERROR") - def _on_backend_login(platform_name: str, store_id: str, cookies: str): + def _on_backend_login(platform_name: str, store_id: str, cookies: str, store_name: str = ""): + store_display = store_name or store_id self._log( - f"收到后端登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}", + f"收到后端登录指令: 平台={platform_name}, 店铺={store_display}, cookies_len={len(cookies) if cookies else 0}", "INFO") - self._handle_platform_login(platform_name, store_id, cookies) + self._handle_platform_login(platform_name, store_id, cookies, store_name) def _on_token_error(error_content: str): self._log(f"Token验证失败: {error_content}", "ERROR") @@ -175,11 +208,12 @@ class WebSocketManager: backend = BackendClient.from_exe_token(token) - def _on_backend_login(platform_name: str, store_id: str, cookies: str): + def _on_backend_login(platform_name: str, store_id: str, cookies: str, store_name: str = ""): + store_display = store_name or store_id self._log( - f"收到后端登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}", + f"收到后端登录指令: 平台={platform_name}, 店铺={store_display}, cookies_len={len(cookies) if cookies else 0}", "INFO") - self._handle_platform_login(platform_name, store_id, cookies) + self._handle_platform_login(platform_name, store_id, cookies, store_name) def _on_backend_success(): try: @@ -221,7 +255,7 @@ class WebSocketManager: self.callbacks['error'](str(e)) return False - def _handle_platform_login(self, platform_name: str, store_id: str, cookies: str): + def _handle_platform_login(self, platform_name: str, store_id: str, cookies: str, store_name: str = ""): """处理平台登录请求""" try: # 🔥 检查并断开当前店铺的旧连接(策略B:先断开旧连接,再建立新连接) @@ -335,16 +369,16 @@ class WebSocketManager: if cookies == "login_success": self._log("⚠️ 千牛平台收到空cookies,但允许启动监听器", "WARNING") cookies = "" # 清空cookies,千牛不需要真实cookies - self._start_qianniu_listener(store_id, cookies) + self._start_qianniu_listener(store_id, cookies, store_name) elif normalized_platform == "京东": - self._start_jd_listener(store_id, cookies) + self._start_jd_listener(store_id, cookies, store_name) elif normalized_platform == "抖音": - self._start_douyin_listener(store_id, cookies) + self._start_douyin_listener(store_id, cookies, store_name) elif normalized_platform == "拼多多": - self._start_pdd_listener(store_id, cookies) + self._start_pdd_listener(store_id, cookies, store_name) else: self._log(f"❌ 不支持的平台: {platform_name}", "ERROR") @@ -382,7 +416,7 @@ class WebSocketManager: import traceback self._log(f"详细错误: {traceback.format_exc()}", "ERROR") - def _start_jd_listener(self, store_id: str, cookies: str): + def _start_jd_listener(self, store_id: str, cookies: str, store_name: str = ""): """启动京东平台监听""" try: def _runner(): @@ -400,7 +434,8 @@ class WebSocketManager: self.platform_listeners[f"京东:{store_id}"] = { 'thread': thread, 'platform': '京东', - 'store_id': store_id + 'store_id': store_id, + 'store_name': store_name # 保存店铺名称 } # 上报连接状态给后端 @@ -431,7 +466,7 @@ class WebSocketManager: except Exception as send_e: self._log(f"失败状态下报连接状态也失败: {send_e}", "ERROR") - def _start_douyin_listener(self, store_id: str, cookies: str): + def _start_douyin_listener(self, store_id: str, cookies: str, store_name: str = ""): """启动抖音平台监听""" try: def _runner(): @@ -502,6 +537,7 @@ class WebSocketManager: 'thread': thread, 'platform': '抖音', 'store_id': store_id, + 'store_name': store_name # 保存店铺名称 } # 更新监听器状态 @@ -519,7 +555,7 @@ class WebSocketManager: # 🔥 移除:确保失败时也不在这里上报状态 # 失败状态应该在DyLogin中处理,与拼多多保持一致 - def _start_qianniu_listener(self, store_id: str, cookies: str): + def _start_qianniu_listener(self, store_id: str, cookies: str, store_name: str = ""): """启动千牛平台监听(单连接多店铺架构)""" try: # 获取用户token(从后端客户端获取) @@ -548,6 +584,7 @@ class WebSocketManager: 'thread': thread, 'platform': '千牛', 'store_id': store_id, + 'store_name': store_name, # 保存店铺名称 'exe_token': exe_token } @@ -579,7 +616,7 @@ class WebSocketManager: except Exception as send_e: self._log(f"失败状态下报千牛平台连接状态也失败: {send_e}", "ERROR") - def _start_pdd_listener(self, store_id: str, data: str): + def _start_pdd_listener(self, store_id: str, data: str, store_name: str = ""): """启动拼多多平台监听""" try: def _runner(): @@ -671,6 +708,7 @@ class WebSocketManager: 'thread': thread, 'platform': '拼多多', 'store_id': store_id, + 'store_name': store_name # 保存店铺名称 } # ✅ 临时方案:启动后立即通知(因为 PDD 监听器会阻塞,无法通过返回值判断) diff --git a/config.py b/config.py index 11c34a9..8ccc368 100644 --- a/config.py +++ b/config.py @@ -9,7 +9,8 @@ import json # 用于将令牌保存为 JSON 格式 # 后端服务器配置 # BACKEND_HOST = "192.168.5.233" -BACKEND_HOST = "192.168.5.106" +# BACKEND_HOST = "192.168.5.106" +BACKEND_HOST = "192.168.5.12" # BACKEND_HOST = "shuidrop.com" # BACKEND_HOST = "test.shuidrop.com" BACKEND_PORT = "8000" @@ -20,8 +21,8 @@ BACKEND_WS_URL = f"ws://{BACKEND_HOST}:{BACKEND_PORT}" # WebSocket配置 WS_CONNECT_TIMEOUT = 16.0 WS_MESSAGE_TIMEOUT = 30.0 -WS_PING_INTERVAL = 10 # 10秒ping间隔(提高检测频率) -WS_PING_TIMEOUT = 5 # 5秒ping超时(更快检测断线) +WS_PING_INTERVAL = 15 # 10秒ping间隔(提高检测频率) +WS_PING_TIMEOUT = 10 # 5秒ping超时(更快检测断线) WS_ENABLE_PING = True # 是否启用WebSocket原生ping心跳 WS_ENABLE_APP_PING = False # 禁用应用层ping心跳(避免重复) diff --git a/main.py b/main.py index 1b5a18c..52216ed 100644 --- a/main.py +++ b/main.py @@ -613,6 +613,36 @@ class LoginWindow(QMainWindow): except Exception as e: self.add_log(f"处理平台连接事件失败: {e}", "ERROR") + def on_platform_kicked(self, platform_name: str, store_name: str, reason: str): + """处理平台被踢下线 - 显示弹窗警告""" + try: + self.add_log(f"⚠️ {platform_name}平台被踢下线: {store_name}, 原因: {reason}", "WARNING") + + # 显示弹窗提示 + message_text = ( + f"【{store_name}】连接已断开\n\n" + f"原因:{reason}\n\n" + f"请确保:\n" + f"1. 关闭网页版{platform_name}客户端\n" + f"2. 关闭其他{platform_name}客户端\n" + f"3. 确认只有本程序连接\n\n" + f"处理完成后,请重新登录平台。" + ) + QMessageBox.warning( + self, + f"{platform_name}连接已断开", + message_text, + QMessageBox.Ok + ) + + # 更新状态显示 + self.status_label.setText(f"⚠️ {platform_name}已断开") + self.status_label.setStyleSheet( + "color: #ff6b6b; background: rgba(255, 107, 107, 0.1); border-radius: 12px; padding: 5px 10px;") + + except Exception as e: + self.add_log(f"显示平台断开提示失败: {e}", "ERROR") + def on_token_error(self, error_content: str): """处理token错误 - 显示红色错误信息并停止所有操作""" try: @@ -761,23 +791,16 @@ class LoginWindow(QMainWindow): self.tray_icon.setIcon(self.style().standardIcon(self.style().SP_ComputerIcon)) # 创建托盘菜单 - tray_menu = QMenu() + self.tray_menu = QMenu() - # 显示窗口动作 - show_action = QAction("显示主窗口", self) - show_action.triggered.connect(self.show_window) - tray_menu.addAction(show_action) + # 连接菜单显示信号,实时更新状态 + self.tray_menu.aboutToShow.connect(self.update_tray_menu) - # 分隔线 - tray_menu.addSeparator() - - # 退出动作 - quit_action = QAction("退出程序", self) - quit_action.triggered.connect(self.quit_application) - tray_menu.addAction(quit_action) + # 初始化菜单内容 + self.update_tray_menu() # 设置托盘菜单 - self.tray_icon.setContextMenu(tray_menu) + self.tray_icon.setContextMenu(self.tray_menu) # 设置托盘提示 self.tray_icon.setToolTip("AI客服智能助手") @@ -790,6 +813,87 @@ class LoginWindow(QMainWindow): print("[INFO] 系统托盘已初始化") + def update_tray_menu(self): + """实时更新托盘菜单,显示连接状态""" + try: + # 清空现有菜单 + self.tray_menu.clear() + + # 获取WebSocket管理器 + ws_manager = get_websocket_manager() + + # 1. 显示后端连接状态 + if ws_manager and ws_manager.backend_client and ws_manager.backend_client.is_connected: + backend_status = QAction("✅ 后端已连接", self) + backend_status.setEnabled(False) # 不可点击 + self.tray_menu.addAction(backend_status) + + # 2. 显示已连接的平台信息 + platform_listeners = ws_manager.platform_listeners + if platform_listeners: + # 按平台分组店铺 + platform_stores = {} + for key, info in platform_listeners.items(): + platform = info.get('platform', '') + store_name = info.get('store_name', '') + store_id = info.get('store_id', '') + + if platform not in platform_stores: + platform_stores[platform] = [] + + # 优先显示店铺名称,如果没有则显示 store_id 前8位 + if store_name: + display_name = store_name + else: + display_name = store_id[:8] + "..." if len(store_id) > 8 else store_id + + platform_stores[platform].append(display_name) + + # 显示每个平台的店铺 + for platform, stores in platform_stores.items(): + stores_text = ", ".join(stores) + platform_info = QAction(f"📊 {platform}: {stores_text}", self) + platform_info.setEnabled(False) # 不可点击 + self.tray_menu.addAction(platform_info) + else: + # 后端已连接但没有平台连接 + no_platform = QAction("⚠️ 暂无平台连接", self) + no_platform.setEnabled(False) + self.tray_menu.addAction(no_platform) + else: + # 后端未连接 + backend_disconnected = QAction("❌ 后端未连接", self) + backend_disconnected.setEnabled(False) + self.tray_menu.addAction(backend_disconnected) + + # 添加分隔线 + self.tray_menu.addSeparator() + + # 3. 显示主窗口 + show_action = QAction("显示主窗口", self) + show_action.triggered.connect(self.show_window) + self.tray_menu.addAction(show_action) + + # 添加分隔线 + self.tray_menu.addSeparator() + + # 4. 退出程序 + quit_action = QAction("退出程序", self) + quit_action.triggered.connect(self.quit_application) + self.tray_menu.addAction(quit_action) + + except Exception as e: + print(f"[ERROR] 更新托盘菜单失败: {e}") + # 如果更新失败,至少保证基本功能可用 + self.tray_menu.clear() + show_action = QAction("显示主窗口", self) + show_action.triggered.connect(self.show_window) + self.tray_menu.addAction(show_action) + + quit_action = QAction("退出程序", self) + quit_action.triggered.connect(self.quit_application) + self.tray_menu.addAction(quit_action) + def tray_icon_activated(self, reason): """托盘图标被激活时的处理""" if reason == QSystemTrayIcon.DoubleClick: @@ -898,6 +1002,7 @@ class LoginWindow(QMainWindow): success=lambda: self.add_log("WebSocket连接管理器连接成功", "SUCCESS"), error=lambda error: self.add_log(f"WebSocket连接管理器错误: {error}", "ERROR"), platform_connected=self.on_platform_connected, # 新增:平台连接回调 + platform_disconnected=self.on_platform_kicked, # 新增:平台被踢回调 token_error=self.on_token_error, # 新增:token错误回调 disconnect=self.on_disconnect # 新增:被踢下线回调 )