[patch] 优化PDD的js环境补充 优化配置文件心跳环境检测 优化打包环境补充逻辑

This commit is contained in:
2025-10-15 16:26:44 +08:00
parent e15c1db49b
commit 737737a6c1
5 changed files with 3688 additions and 3536 deletions

View File

@@ -135,6 +135,8 @@ jobs:
- name: Build production executable - name: Build production executable
if: success() if: success()
shell: powershell shell: powershell
env:
PYTHONIOENCODING: utf-8
run: | run: |
Write-Host "=========================================="; Write-Host "==========================================";
Write-Host "Step 4.5: Build production executable"; Write-Host "Step 4.5: Build production executable";

File diff suppressed because it is too large Load Diff

268
config.py
View File

@@ -1,135 +1,135 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
项目配置文件 项目配置文件
统一管理所有配置参数,避免硬编码 统一管理所有配置参数,避免硬编码
""" """
# 用户访问令牌(默认空字符串) # 用户访问令牌(默认空字符串)
import os # 用于路径与目录操作(写入用户配置目录) import os # 用于路径与目录操作(写入用户配置目录)
import json # 用于将令牌保存为 JSON 格式 import json # 用于将令牌保存为 JSON 格式
# 后端服务器配置 # 后端服务器配置
# BACKEND_HOST = "192.168.5.233" # BACKEND_HOST = "192.168.5.233"
BACKEND_HOST = "192.168.5.12" BACKEND_HOST = "192.168.5.12"
# BACKEND_HOST = "shuidrop.com" # BACKEND_HOST = "shuidrop.com"
# BACKEND_HOST = "test.shuidrop.com" # BACKEND_HOST = "test.shuidrop.com"
BACKEND_PORT = "8000" BACKEND_PORT = "8000"
# BACKEND_PORT = "" # BACKEND_PORT = ""
BACKEND_WS_URL = f"ws://{BACKEND_HOST}:{BACKEND_PORT}" BACKEND_WS_URL = f"ws://{BACKEND_HOST}:{BACKEND_PORT}"
# BACKEND_WS_URL = f"wss://{BACKEND_HOST}" # BACKEND_WS_URL = f"wss://{BACKEND_HOST}"
# WebSocket配置 # WebSocket配置
WS_CONNECT_TIMEOUT = 16.0 WS_CONNECT_TIMEOUT = 16.0
WS_MESSAGE_TIMEOUT = 30.0 WS_MESSAGE_TIMEOUT = 60.0 # 增加消息超时适应AI处理时间
WS_PING_INTERVAL = 10 # 10秒ping间隔提高检测频率 WS_PING_INTERVAL = 20 # 20秒ping间隔避免AI处理期间频繁心跳
WS_PING_TIMEOUT = 5 # 5秒ping超时更快检测断线 WS_PING_TIMEOUT = 10 # 10秒ping超时给予更多响应时间
WS_ENABLE_PING = True # 是否启用WebSocket原生ping心跳 WS_ENABLE_PING = True # 是否启用WebSocket原生ping心跳
WS_ENABLE_APP_PING = False # 禁用应用层ping心跳避免重复 WS_ENABLE_APP_PING = False # 禁用应用层ping心跳避免重复
# AI处理超时配置 # AI处理超时配置
AI_PROCESS_TIMEOUT = 30 # AI处理超时时间 AI_PROCESS_TIMEOUT = 45 # AI处理超时时间- 增加以适应复杂查询
AI_LONG_PROCESS_THRESHOLD = 10 # AI长时间处理阈值 AI_LONG_PROCESS_THRESHOLD = 15 # AI长时间处理阈值
# 内存管理配置 # 内存管理配置
MAX_PENDING_REPLIES = 100 MAX_PENDING_REPLIES = 100
CLEANUP_INTERVAL = 300 # 5分钟 CLEANUP_INTERVAL = 300 # 5分钟
FUTURE_TIMEOUT = 300 # 5分钟 FUTURE_TIMEOUT = 300 # 5分钟
# 终端日志配置(简化) # 终端日志配置(简化)
LOG_LEVEL = "INFO" LOG_LEVEL = "INFO"
VERSION = "1.0" VERSION = "1.0"
# GUI配置 # GUI配置
WINDOW_TITLE = "AI回复连接入口-V1.0" WINDOW_TITLE = "AI回复连接入口-V1.0"
# 应用版本号(用于版本检查) # 应用版本号(用于版本检查)
APP_VERSION = "1.5.30" APP_VERSION = "1.5.30"
# 平台特定配置 # 平台特定配置
PLATFORMS = { PLATFORMS = {
"JD": { "JD": {
"name": "京东", "name": "京东",
"ws_url": "wss://dongdong.jd.com/workbench/websocket" "ws_url": "wss://dongdong.jd.com/workbench/websocket"
}, },
"DOUYIN": { "DOUYIN": {
"name": "抖音", "name": "抖音",
"ws_url": None # 动态获取 "ws_url": None # 动态获取
}, },
"QIANNIU": { "QIANNIU": {
"name": "千牛(淘宝)", "name": "千牛(淘宝)",
"ws_url": "ws://127.0.0.1:3030" "ws_url": "ws://127.0.0.1:3030"
}, },
"PDD": { "PDD": {
"name": "拼多多", "name": "拼多多",
"ws_url": None # 动态获取 "ws_url": None # 动态获取
} }
} }
def get_backend_ws_url(platform: str, store_id: str) -> str: def get_backend_ws_url(platform: str, store_id: str) -> str:
"""获取旧版后端WebSocket URL按店铺建连接保留兼容""" """获取旧版后端WebSocket URL按店铺建连接保留兼容"""
return f"{BACKEND_WS_URL}/ws/platform/{platform.lower()}/{store_id}/" return f"{BACKEND_WS_URL}/ws/platform/{platform.lower()}/{store_id}/"
def get_gui_ws_url(exe_token: str) -> str: def get_gui_ws_url(exe_token: str) -> str:
"""获取新版单连接GUI专用WebSocket URL按用户token建一条连接""" """获取新版单连接GUI专用WebSocket URL按用户token建一条连接"""
return f"{BACKEND_WS_URL}/ws/gui/{exe_token}/" return f"{BACKEND_WS_URL}/ws/gui/{exe_token}/"
def get_config(): def get_config():
"""获取所有配置""" """获取所有配置"""
return { return {
'backend_host': BACKEND_HOST, 'backend_host': BACKEND_HOST,
'backend_port': BACKEND_PORT, 'backend_port': BACKEND_PORT,
'backend_ws_url': BACKEND_WS_URL, 'backend_ws_url': BACKEND_WS_URL,
'ws_connect_timeout': WS_CONNECT_TIMEOUT, 'ws_connect_timeout': WS_CONNECT_TIMEOUT,
'ws_message_timeout': WS_MESSAGE_TIMEOUT, 'ws_message_timeout': WS_MESSAGE_TIMEOUT,
'max_pending_replies': MAX_PENDING_REPLIES, 'max_pending_replies': MAX_PENDING_REPLIES,
'cleanup_interval': CLEANUP_INTERVAL, 'cleanup_interval': CLEANUP_INTERVAL,
'platforms': PLATFORMS 'platforms': PLATFORMS
} }
APP_NAME = "ShuidropGUI" # 应用名称(作为配置目录名) APP_NAME = "ShuidropGUI" # 应用名称(作为配置目录名)
API_TOKEN = 'sd_acF0TisgfFOtsBm4ytqb17MQbcxuX9Vp' # 默认回退令牌(仅当未找到外部配置时使用) API_TOKEN = 'sd_acF0TisgfFOtsBm4ytqb17MQbcxuX9Vp' # 默认回退令牌(仅当未找到外部配置时使用)
def _get_config_paths(): def _get_config_paths():
"""返回(配置目录, 配置文件路径),位于 %APPDATA%/ShuidropGUI/config.json""" """返回(配置目录, 配置文件路径),位于 %APPDATA%/ShuidropGUI/config.json"""
base_dir = os.getenv('APPDATA') or os.path.expanduser('~') # 优先使用 APPDATA其次使用用户主目录 base_dir = os.getenv('APPDATA') or os.path.expanduser('~') # 优先使用 APPDATA其次使用用户主目录
cfg_dir = os.path.join(base_dir, APP_NAME) # 组合配置目录路径 cfg_dir = os.path.join(base_dir, APP_NAME) # 组合配置目录路径
cfg_file = os.path.join(cfg_dir, 'config.json') # 组合配置文件路径 cfg_file = os.path.join(cfg_dir, 'config.json') # 组合配置文件路径
return cfg_dir, cfg_file return cfg_dir, cfg_file
def get_saved_token() -> str: def get_saved_token() -> str:
"""优先从外部 JSON 配置读取令牌,不存在时回退到内置 API_TOKEN""" """优先从外部 JSON 配置读取令牌,不存在时回退到内置 API_TOKEN"""
try: try:
cfg_dir, cfg_file = _get_config_paths() # 获取目录与文件路径 cfg_dir, cfg_file = _get_config_paths() # 获取目录与文件路径
if os.path.exists(cfg_file): # 如果配置文件存在 if os.path.exists(cfg_file): # 如果配置文件存在
with open(cfg_file, 'r', encoding='utf-8') as f: # 以 UTF-8 读取 with open(cfg_file, 'r', encoding='utf-8') as f: # 以 UTF-8 读取
data = json.load(f) # 解析 JSON 内容 data = json.load(f) # 解析 JSON 内容
token = data.get('token', '') # 读取 token 字段 token = data.get('token', '') # 读取 token 字段
if token: # 如果有效 if token: # 如果有效
return token # 返回读取到的令牌 return token # 返回读取到的令牌
except Exception: except Exception:
pass # 读取失败时静默回退 pass # 读取失败时静默回退
return API_TOKEN # 回退为内置的默认值 return API_TOKEN # 回退为内置的默认值
def set_saved_token(new_token: str) -> bool: def set_saved_token(new_token: str) -> bool:
"""将访问令牌写入外部 JSON 配置,并更新内存中的值 """将访问令牌写入外部 JSON 配置,并更新内存中的值
- new_token: 新的访问令牌字符串 - new_token: 新的访问令牌字符串
返回: True 表示写入成功False 表示失败 返回: True 表示写入成功False 表示失败
""" """
try: try:
cfg_dir, cfg_file = _get_config_paths() cfg_dir, cfg_file = _get_config_paths()
os.makedirs(cfg_dir, exist_ok=True) os.makedirs(cfg_dir, exist_ok=True)
data = {'token': new_token} data = {'token': new_token}
with open(cfg_file, 'w', encoding='utf-8') as f: with open(cfg_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2) json.dump(data, f, ensure_ascii=False, indent=2)
# 同步更新内存变量,保证运行期可立即生效 # 同步更新内存变量,保证运行期可立即生效
global API_TOKEN global API_TOKEN
API_TOKEN = new_token API_TOKEN = new_token
return True return True
except Exception as e: except Exception as e:
# 发生异常时打印提示并返回失败 # 发生异常时打印提示并返回失败
print(f"写入令牌失败: {e}") print(f"写入令牌失败: {e}")
return False return False

272
main.py
View File

@@ -62,10 +62,9 @@ class LoginWindow(QMainWindow):
self.disconnect_signals = DisconnectSignals() self.disconnect_signals = DisconnectSignals()
self.disconnect_signals.disconnected.connect(self._show_disconnect_dialog) self.disconnect_signals.disconnected.connect(self._show_disconnect_dialog)
# 横幅动画相关 # 横幅相关
self.promo_banner = None self.promo_banner = None
self.banner_animation = None self.banner_shadow = None # 阴影效果引用
self.banner_color_index = 0
self.initUI() self.initUI()
@@ -177,60 +176,94 @@ class LoginWindow(QMainWindow):
print("[INFO] 窗口已自适应内容大小") print("[INFO] 窗口已自适应内容大小")
def create_promo_banner(self, layout): def create_promo_banner(self, layout):
"""创建宣传横幅(带动态渐变效果)""" """创建宣传横幅(使用图片,带悬停动画效果)"""
try: try:
# 创建横幅容器(最小化高度) # 创建横幅容器
banner_frame = QFrame() banner_frame = QFrame()
banner_frame.setObjectName("promoBanner") banner_frame.setObjectName("promoBanner")
banner_frame.setCursor(Qt.PointingHandCursor) # 鼠标变手型 banner_frame.setCursor(Qt.PointingHandCursor) # 鼠标变手型
banner_frame.setMinimumHeight(65) # 最小高度
banner_frame.setMaximumHeight(65) # 固定高度
# 保存横幅引用,用于动画 # 保存横幅引用,用于动画
self.promo_banner = banner_frame self.promo_banner = banner_frame
# 横幅布局(最小内边距) # 使用图片标签
banner_image = QLabel()
banner_image.setObjectName("bannerImage")
# 加载横幅图片
from windows_taskbar_fix import get_resource_path
image_path = get_resource_path("static/hengfu.jpg")
if os.path.exists(image_path):
from PyQt5.QtGui import QPixmap
pixmap = QPixmap(image_path)
# 设置固定的横幅尺寸
target_width = 400 # 固定宽度
target_height = 70 # 固定高度调整为70px更紧凑
# 缩放图片以适应目标尺寸
scaled_pixmap = pixmap.scaled(
target_width,
target_height,
Qt.IgnoreAspectRatio, # 忽略宽高比,拉伸填充
Qt.SmoothTransformation # 平滑缩放,提高图片质量
)
# 设置图片
banner_image.setPixmap(scaled_pixmap)
banner_image.setScaledContents(False)
banner_frame.setFixedHeight(target_height)
banner_image.setFixedSize(target_width, target_height)
# 🔧 使用CSS圆角来处理边角更简单稳定
banner_image.setStyleSheet("""
QLabel#bannerImage {
border-radius: 12px;
background-color: transparent;
}
""")
print(f"[INFO] 横幅图片已加载: {image_path}, 尺寸: {target_width}x{target_height}")
else:
# 图片不存在时的备用方案
banner_image.setText("🌟 限时优惠活动 - 点击了解详情 →")
banner_image.setAlignment(Qt.AlignCenter)
banner_image.setStyleSheet("""
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
stop:0 #667eea, stop:1 #764ba2);
color: white;
font-size: 12px;
font-weight: bold;
border-radius: 12px;
padding: 15px;
""")
banner_frame.setFixedHeight(60)
print(f"[WARNING] 横幅图片未找到: {image_path},使用备用样式")
# 创建布局
banner_layout = QHBoxLayout() banner_layout = QHBoxLayout()
banner_layout.setContentsMargins(10, 6, 10, 6) # 最小内边距 banner_layout.setContentsMargins(0, 0, 0, 0)
banner_layout.setSpacing(8) # 最小间距 banner_layout.setSpacing(0)
banner_frame.setLayout(banner_layout) banner_frame.setLayout(banner_layout)
banner_layout.addWidget(banner_image)
# 左侧图标(最小化) # 设置圆角和边框
icon_label = QLabel("🎁") banner_frame.setStyleSheet("""
icon_label.setFont(QFont('Microsoft YaHei', 22)) # 更小的图标字体 QFrame#promoBanner {
icon_label.setAlignment(Qt.AlignCenter) border-radius: 12px;
icon_label.setFixedSize(45, 45) # 更小的图标尺寸 background: transparent;
banner_layout.addWidget(icon_label) }
""")
# 右侧文字区域 # 初始阴影效果
text_layout = QVBoxLayout() self.banner_shadow = QGraphicsDropShadowEffect()
text_layout.setSpacing(1) # 最小文字间距 self.banner_shadow.setBlurRadius(15)
self.banner_shadow.setXOffset(0)
# 标题 self.banner_shadow.setYOffset(3)
title_label = QLabel("🌟 限时优惠活动进行中") self.banner_shadow.setColor(QColor(0, 0, 0, 60))
title_label.setFont(QFont('Microsoft YaHei', 10, QFont.Bold)) # 更小字体 banner_frame.setGraphicsEffect(self.banner_shadow)
title_label.setStyleSheet("color: white;")
text_layout.addWidget(title_label)
# 副标题
subtitle_label = QLabel("立即访问官网了解更多优惠详情 →")
subtitle_label.setFont(QFont('Microsoft YaHei', 8)) # 更小字体
subtitle_label.setStyleSheet("color: rgba(255, 255, 255, 0.9);")
text_layout.addWidget(subtitle_label)
banner_layout.addLayout(text_layout)
banner_layout.addStretch()
# 初始样式(紫色渐变背景)
self.update_banner_style(0)
# 添加阴影效果(更轻量)
shadow = QGraphicsDropShadowEffect()
shadow.setBlurRadius(10) # 减小模糊半径
shadow.setXOffset(0)
shadow.setYOffset(2) # 减小偏移
shadow.setColor(QColor(0, 0, 0, 40)) # 降低不透明度
banner_frame.setGraphicsEffect(shadow)
# 点击事件 - 跳转官网 # 点击事件 - 跳转官网
def open_official_website(): def open_official_website():
@@ -244,88 +277,82 @@ class LoginWindow(QMainWindow):
banner_frame.mousePressEvent = lambda event: open_official_website() banner_frame.mousePressEvent = lambda event: open_official_website()
# 🎯 添加鼠标悬停事件(动态效果)
def on_enter(event):
"""鼠标进入时的动画效果"""
try:
# 增强阴影效果
self.banner_shadow.setBlurRadius(25)
self.banner_shadow.setYOffset(5)
self.banner_shadow.setColor(QColor(0, 0, 0, 100))
# 轻微放大动画
animation = QPropertyAnimation(banner_frame, b"geometry")
original_rect = banner_frame.geometry()
expanded_rect = QRect(
original_rect.x() - 3,
original_rect.y() - 2,
original_rect.width() + 6,
original_rect.height() + 4
)
animation.setDuration(200)
animation.setStartValue(original_rect)
animation.setEndValue(expanded_rect)
animation.setEasingCurve(QEasingCurve.OutCubic)
animation.start()
# 保存动画引用避免被垃圾回收
banner_frame.hover_animation = animation
except Exception as e:
print(f"[DEBUG] 悬停动画错误: {e}")
def on_leave(event):
"""鼠标离开时的动画效果"""
try:
# 恢复阴影效果
self.banner_shadow.setBlurRadius(15)
self.banner_shadow.setYOffset(3)
self.banner_shadow.setColor(QColor(0, 0, 0, 60))
# 恢复原始大小
animation = QPropertyAnimation(banner_frame, b"geometry")
current_rect = banner_frame.geometry()
original_rect = QRect(
current_rect.x() + 3,
current_rect.y() + 2,
current_rect.width() - 6,
current_rect.height() - 4
)
animation.setDuration(200)
animation.setStartValue(current_rect)
animation.setEndValue(original_rect)
animation.setEasingCurve(QEasingCurve.InCubic)
animation.start()
# 保存动画引用避免被垃圾回收
banner_frame.leave_animation = animation
except Exception as e:
print(f"[DEBUG] 离开动画错误: {e}")
# 安装事件过滤器
banner_frame.enterEvent = on_enter
banner_frame.leaveEvent = on_leave
# 添加到主布局 # 添加到主布局
layout.addWidget(banner_frame) layout.addWidget(banner_frame)
# 启动颜色渐变动画 print("[INFO] 宣传横幅已创建(使用图片,带悬停动画)")
self.start_banner_animation()
print("[INFO] 宣传横幅已创建(带动态渐变效果)")
except Exception as e: except Exception as e:
print(f"[WARNING] 创建宣传横幅失败: {e}") print(f"[WARNING] 创建宣传横幅失败: {e}")
def update_banner_style(self, color_index): # 横幅颜色动画相关方法已移除(改用图片横幅)
"""更新横幅颜色样式"""
if not self.promo_banner:
return
# 定义多组渐变颜色方案(呼吸效果)
color_schemes = [
# 紫色(原色)
{
'normal': ('667eea', '764ba2'),
'hover': ('778ff5', '865cb3')
},
# 深紫色(变暗)
{
'normal': ('5a6dc8', '6a3f8f'),
'hover': ('6b7ed9', '7b50a0')
},
# 亮紫色(变亮)
{
'normal': ('7890fc', '8658b4'),
'hover': ('89a1ff', '9769c5')
},
# 紫色(原色)- 循环
{
'normal': ('667eea', '764ba2'),
'hover': ('778ff5', '865cb3')
}
]
scheme = color_schemes[color_index % len(color_schemes)]
style = f"""
QFrame#promoBanner {{
background: qlineargradient(
x1:0, y1:0, x2:1, y2:0,
stop:0 #{scheme['normal'][0]},
stop:1 #{scheme['normal'][1]}
);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.2);
}}
QFrame#promoBanner:hover {{
background: qlineargradient(
x1:0, y1:0, x2:1, y2:0,
stop:0 #{scheme['hover'][0]},
stop:1 #{scheme['hover'][1]}
);
}}
"""
self.promo_banner.setStyleSheet(style)
def start_banner_animation(self):
"""启动横幅呼吸动画"""
try:
# 创建定时器每2秒切换一次颜色
self.banner_animation = QTimer(self)
self.banner_animation.timeout.connect(self.animate_banner_color)
self.banner_animation.start(2000) # 2秒间隔
print("[INFO] 横幅呼吸动画已启动")
except Exception as e:
print(f"[WARNING] 启动横幅动画失败: {e}")
def animate_banner_color(self):
"""动画:循环改变横幅颜色"""
try:
self.banner_color_index = (self.banner_color_index + 1) % 4
self.update_banner_style(self.banner_color_index)
except Exception as e:
print(f"[WARNING] 横幅颜色动画失败: {e}")
def apply_modern_styles(self): def apply_modern_styles(self):
"""应用现代化简约样式 - 精致美化版""" """应用现代化简约样式 - 精致美化版"""
@@ -779,11 +806,6 @@ class LoginWindow(QMainWindow):
"""真正退出应用程序""" """真正退出应用程序"""
print("[INFO] 正在退出应用程序...") print("[INFO] 正在退出应用程序...")
# 停止横幅动画
if hasattr(self, 'banner_animation') and self.banner_animation:
self.banner_animation.stop()
print("[INFO] 横幅动画已停止")
# 执行原来的关闭逻辑 # 执行原来的关闭逻辑
try: try:
# 使用 WebSocket 管理器断开所有连接 # 使用 WebSocket 管理器断开所有连接

View File

@@ -87,11 +87,14 @@ def build_with_command():
'--hidden-import=WebSocket.backend_singleton', '--hidden-import=WebSocket.backend_singleton',
'--hidden-import=WebSocket.BackendClient', '--hidden-import=WebSocket.BackendClient',
'--hidden-import=windows_taskbar_fix', '--hidden-import=windows_taskbar_fix',
'--hidden-import=py_mini_racer', # PDD平台内置JavaScript引擎
'--hidden-import=py_mini_racer.py_mini_racer',
'--collect-all=py_mini_racer', # 🔧 收集所有PyMiniRacer文件包括DLL
'main.py' 'main.py'
] ]
try: try:
print(f"执行命令: {' '.join(cmd[:5])}... ({len(cmd)}个参数)") print(f"go to compile: {' '.join(cmd[:5])}... (all{len(cmd)} args)")
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8') result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
if result.returncode == 0: if result.returncode == 0:
@@ -335,6 +338,18 @@ def main():
print("ERROR: Verification phase failed") print("ERROR: Verification phase failed")
return False return False
print("Verification phase completed") print("Verification phase completed")
# 🔧 修复PyMiniRacer DLL
print("\nPyMiniRacer DLL fix phase started...")
try:
import fix_pyminiracer_dll
if fix_pyminiracer_dll.auto_fix_after_build():
print("PyMiniRacer DLL fix completed")
else:
print("WARNING: PyMiniRacer DLL fix failed - PDD platform may not work without Node.js")
except Exception as e:
print(f"WARNING: PyMiniRacer DLL fix error: {e}")
print(" PDD platform may need Node.js environment")
print("\n" + "=" * 60) print("\n" + "=" * 60)
print("Build completed successfully!") print("Build completed successfully!")