[patch] 优化更新提示逻辑 与 样式细节

This commit is contained in:
2025-10-30 16:54:24 +08:00
parent 5440aa7d6d
commit 8575de6ea5
2 changed files with 459 additions and 34 deletions

344
custom_update_dialog.py Normal file
View File

@@ -0,0 +1,344 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
自定义版本更新对话框
美观的布局和用户体验
"""
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QTextEdit, QFrame)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont, QCursor
class UpdateNotificationDialog(QDialog):
"""
版本更新提示对话框
布局:
- 标题区
- 更新内容区
- 下载地址区(可复制)
- 按钮区(两行,美观布局)
"""
# 用户选择(返回值)
CHOICE_UPDATE_NOW = 1 # 立即更新
CHOICE_UPDATE_LATER = 2 # 稍后更新
CHOICE_IGNORE = 3 # 忽略此版本
def __init__(self, version, download_url, update_content="", parent=None):
super().__init__(parent)
self.version = version
self.download_url = download_url
self.update_content = update_content
self.user_choice = None
self.init_ui()
def init_ui(self):
"""初始化UI"""
self.setWindowTitle("版本更新")
self.setFixedWidth(550)
self.setMinimumHeight(400)
# 禁用关闭按钮(只能通过按钮关闭)
self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | Qt.CustomizeWindowHint)
# 主布局
layout = QVBoxLayout()
layout.setSpacing(15)
layout.setContentsMargins(25, 20, 25, 20)
# ============ 标题区 ============
title_label = QLabel(f"🔔 发现新版本 v{self.version}")
title_label.setFont(QFont('Microsoft YaHei', 14, QFont.Bold))
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("color: #2c3e50; margin-bottom: 5px;")
layout.addWidget(title_label)
# 分隔线
line1 = QFrame()
line1.setFrameShape(QFrame.HLine)
line1.setStyleSheet("background: #ddd; max-height: 1px;")
layout.addWidget(line1)
# ============ 更新内容区(可选)============
# 🔥 只有在有更新内容时才显示
if self.update_content and self.update_content.strip():
content_label = QLabel("📝 更新内容:")
content_label.setFont(QFont('Microsoft YaHei', 10, QFont.Bold))
content_label.setStyleSheet("color: #34495e;")
layout.addWidget(content_label)
# 更新内容文本
content_text = QTextEdit()
content_text.setReadOnly(True)
content_text.setMaximumHeight(100)
content_text.setStyleSheet("""
QTextEdit {
border: 1px solid #ddd;
border-radius: 6px;
padding: 8px;
background: #f8f9fa;
font-size: 11px;
color: #555;
}
""")
content_text.setText(self.update_content)
layout.addWidget(content_text)
# ============ 下载地址区 ============
url_label = QLabel("📥 下载地址:")
url_label.setFont(QFont('Microsoft YaHei', 10, QFont.Bold))
url_label.setStyleSheet("color: #34495e; margin-top: 5px;")
layout.addWidget(url_label)
# 下载地址文本框(可选择复制)
self.url_text = QTextEdit()
self.url_text.setReadOnly(True)
self.url_text.setMaximumHeight(60)
self.url_text.setText(self.download_url)
self.url_text.setStyleSheet("""
QTextEdit {
border: 2px solid #4285f4;
border-radius: 6px;
padding: 8px;
background: #e3f2fd;
font-size: 10px;
color: #1565c0;
font-family: 'Consolas', 'Courier New', monospace;
}
""")
# 全选文本,方便复制
self.url_text.selectAll()
layout.addWidget(self.url_text)
# 复制提示
copy_hint = QLabel("💡 提示:上方地址已自动选中,按 Ctrl+C 即可复制")
copy_hint.setFont(QFont('Microsoft YaHei', 8))
copy_hint.setStyleSheet("color: #7f8c8d;")
layout.addWidget(copy_hint)
# 分隔线
line2 = QFrame()
line2.setFrameShape(QFrame.HLine)
line2.setStyleSheet("background: #ddd; max-height: 1px; margin-top: 10px;")
layout.addWidget(line2)
# ============ 操作说明区 ============
hint_label = QLabel(
"• 在线自动更新:自动下载并安装新版本\n"
"• 稍后更新:保留提示在状态栏,可随时点击更新\n"
"• 忽略此版本:本次运行不再提示此版本"
)
hint_label.setFont(QFont('Microsoft YaHei', 9))
hint_label.setStyleSheet("color: #666; margin: 5px 0;")
layout.addWidget(hint_label)
# ============ 按钮区(两行布局)============
# 第一行:主要操作
button_row1 = QHBoxLayout()
button_row1.setSpacing(10)
self.update_now_btn = QPushButton("🚀 在线自动更新")
self.update_now_btn.setFont(QFont('Microsoft YaHei', 11, QFont.Bold))
self.update_now_btn.setMinimumHeight(42)
self.update_now_btn.setCursor(QCursor(Qt.PointingHandCursor))
self.update_now_btn.clicked.connect(self.on_update_now)
self.update_later_btn = QPushButton("⏰ 稍后更新")
self.update_later_btn.setFont(QFont('Microsoft YaHei', 11))
self.update_later_btn.setMinimumHeight(42)
self.update_later_btn.setCursor(QCursor(Qt.PointingHandCursor))
self.update_later_btn.clicked.connect(self.on_update_later)
button_row1.addWidget(self.update_now_btn)
button_row1.addWidget(self.update_later_btn)
layout.addLayout(button_row1)
# 第二行:次要操作
button_row2 = QHBoxLayout()
button_row2.setSpacing(10)
self.ignore_btn = QPushButton("🚫 忽略此版本")
self.ignore_btn.setFont(QFont('Microsoft YaHei', 10))
self.ignore_btn.setMinimumHeight(38)
self.ignore_btn.setCursor(QCursor(Qt.PointingHandCursor))
self.ignore_btn.clicked.connect(self.on_ignore)
self.copy_btn = QPushButton("📋 复制下载地址")
self.copy_btn.setFont(QFont('Microsoft YaHei', 10))
self.copy_btn.setMinimumHeight(38)
self.copy_btn.setCursor(QCursor(Qt.PointingHandCursor))
self.copy_btn.clicked.connect(self.on_copy_url)
button_row2.addWidget(self.ignore_btn)
button_row2.addWidget(self.copy_btn)
layout.addLayout(button_row2)
self.setLayout(layout)
# 应用样式
self.apply_styles()
def apply_styles(self):
"""应用现代化样式"""
self.setStyleSheet("""
QDialog {
background: qlineargradient(
x1:0, y1:0, x2:0, y2:1,
stop:0 #f8fafb,
stop:1 #e8ecef
);
}
/* 主要操作按钮(立即更新、稍后更新)*/
QPushButton#updateNowBtn {
background: qlineargradient(
x1:0, y1:0, x2:0, y2:1,
stop:0 #4285f4,
stop:1 #1565c0
);
border: none;
color: white;
border-radius: 8px;
font-weight: bold;
}
QPushButton#updateNowBtn:hover {
background: qlineargradient(
x1:0, y1:0, x2:0, y2:1,
stop:0 #5294f5,
stop:1 #1e74c1
);
}
QPushButton#updateLaterBtn {
background: qlineargradient(
x1:0, y1:0, x2:0, y2:1,
stop:0 #ff9800,
stop:1 #f57c00
);
border: none;
color: white;
border-radius: 8px;
}
QPushButton#updateLaterBtn:hover {
background: qlineargradient(
x1:0, y1:0, x2:0, y2:1,
stop:0 #ffa726,
stop:1 #fb8c00
);
}
/* 次要操作按钮(忽略、复制)*/
QPushButton {
background: qlineargradient(
x1:0, y1:0, x2:0, y2:1,
stop:0 #f5f5f5,
stop:1 #e0e0e0
);
border: 2px solid #ccc;
border-radius: 8px;
color: #555;
}
QPushButton:hover {
background: qlineargradient(
x1:0, y1:0, x2:0, y2:1,
stop:0 #e8e8e8,
stop:1 #d0d0d0
);
border: 2px solid #999;
}
""")
# 设置按钮对象名以应用特定样式
self.update_now_btn.setObjectName("updateNowBtn")
self.update_later_btn.setObjectName("updateLaterBtn")
def on_update_now(self):
"""立即更新"""
self.user_choice = self.CHOICE_UPDATE_NOW
self.accept()
def on_update_later(self):
"""稍后更新"""
self.user_choice = self.CHOICE_UPDATE_LATER
self.accept()
def on_ignore(self):
"""忽略此版本"""
self.user_choice = self.CHOICE_IGNORE
self.accept()
def on_copy_url(self):
"""复制下载地址"""
from PyQt5.QtWidgets import QApplication, QMessageBox
clipboard = QApplication.clipboard()
clipboard.setText(self.download_url)
# 显示复制成功提示(不关闭主对话框)
QMessageBox.information(
self,
"复制成功",
f"✅ 下载地址已复制到剪贴板!\n\n"
f"您可以直接粘贴(Ctrl+V)到浏览器下载。",
QMessageBox.Ok
)
def get_user_choice(self):
"""获取用户选择"""
return self.user_choice
if __name__ == "__main__":
# 测试对话框
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
print("=" * 60)
print("测试更新对话框")
print("=" * 60)
print("\n场景1有更新内容")
dialog = UpdateNotificationDialog(
version="1.5.64",
download_url="https://shuidrop-chat-server.ks3-cn-guangzhou.ksyuncs.com/installers/ShuiDi_AI_Assistant_Setup_v1.5.64.exe",
update_content="• 修复了DY图片/视频上传问题\n• 优化了自动更新流程\n• 添加了圆形进度条"
)
dialog.exec_()
choice = dialog.get_user_choice()
if choice == UpdateNotificationDialog.CHOICE_UPDATE_NOW:
print("\n用户选择:在线自动更新")
elif choice == UpdateNotificationDialog.CHOICE_UPDATE_LATER:
print("\n用户选择:稍后更新")
elif choice == UpdateNotificationDialog.CHOICE_IGNORE:
print("\n用户选择:忽略此版本")
else:
print("\n用户关闭了对话框")
# 测试场景2无更新内容
print("\n" + "=" * 60)
print("场景2无更新内容应该隐藏更新内容区域")
print("=" * 60)
dialog2 = UpdateNotificationDialog(
version="1.5.65",
download_url="https://shuidrop-chat-server.ks3-cn-guangzhou.ksyuncs.com/installers/ShuiDi_AI_Assistant_Setup_v1.5.65.exe",
update_content="" # 空内容
)
dialog2.exec_()
print("\n测试完成!")
sys.exit(0)

149
main.py
View File

@@ -84,6 +84,10 @@ class LoginWindow(QMainWindow):
# 横幅相关
self.promo_banner = None
self.banner_shadow = None # 阴影效果引用
# 🔥 版本更新提示状态管理
self.notified_version = None # 已提示过的版本号(本次运行期间)
self.pending_update_info = None # 待更新的版本信息 {version, url, content}
self.initUI()
@@ -167,6 +171,11 @@ class LoginWindow(QMainWindow):
self.status_label.setAlignment(Qt.AlignCenter)
self.status_label.setFont(QFont('Microsoft YaHei', 9))
self.status_label.setStyleSheet("color: #95a5a6; margin-top: 2px;")
self.status_label.setCursor(Qt.ArrowCursor) # 默认鼠标样式
# 🔥 让状态标签可点击(用于更新提示)
self.status_label.mousePressEvent = self.on_status_label_clicked
main_layout.addWidget(self.status_label)
# 添加最小间距(为横幅留出位置)
@@ -1203,6 +1212,53 @@ class LoginWindow(QMainWindow):
self.quit_application()
event.accept()
def on_status_label_clicked(self, event):
"""状态标签被点击时的处理"""
try:
# 如果有待更新信息,点击打开更新对话框
if self.pending_update_info:
self.add_log("📱 用户点击状态标签,打开更新对话框", "INFO")
self.show_update_dialog_manual()
except Exception as e:
self.add_log(f"处理状态标签点击失败: {e}", "ERROR")
def show_update_dialog_manual(self):
"""手动打开更新对话框(用户点击状态标签时)"""
if not self.pending_update_info:
return
version = self.pending_update_info['version']
url = self.pending_update_info['url']
content = self.pending_update_info.get('content', '')
self.add_log(f"显示更新对话框: v{version}", "INFO")
self._show_update_dialog_internal(version, url, content)
def update_status_label_for_pending_update(self, latest_version, download_url):
"""更新状态标签显示待更新信息"""
try:
# 保存待更新信息
update_content = self._get_update_content(latest_version)
self.pending_update_info = {
'version': latest_version,
'url': download_url,
'content': update_content
}
# 更新状态标签
self.status_label.setText(f"🔔 新版本 v{latest_version} 可用(点击更新)")
self.status_label.setStyleSheet(
"color: #ff9800; background: rgba(255, 152, 0, 0.1); "
"border-radius: 12px; padding: 5px 10px; font-weight: bold; "
"cursor: pointer;" # 鼠标变手型
)
self.status_label.setCursor(Qt.PointingHandCursor) # 手型光标
self.add_log(f"✅ 状态标签已更新为待更新状态: v{latest_version}", "INFO")
except Exception as e:
self.add_log(f"更新状态标签失败: {e}", "ERROR")
def setup_version_checker(self):
"""设置版本检查器的GUI回调"""
try:
@@ -1233,8 +1289,18 @@ class LoginWindow(QMainWindow):
"""显示更新对话框(信号槽函数,始终在主线程中执行)"""
try:
self.add_log(f"🎯 主线程收到更新信号: v{latest_version}", "INFO")
# 🔥 修改调用新的trigger_update方法支持自动更新
# 🔥 检查是否已经提示过这个版本
if self.notified_version == latest_version:
self.add_log(f" 版本 {latest_version} 已提示过,本次运行不再弹窗", "INFO")
# 只更新状态标签,不弹窗
self.update_status_label_for_pending_update(latest_version, download_url)
return
# 🔥 首次提示这个版本,显示对话框
self.add_log(f"🔔 首次提示版本 {latest_version},显示更新对话框", "INFO")
self.trigger_update(download_url, latest_version)
except Exception as e:
self.add_log(f"❌ 显示更新对话框失败: {e}", "ERROR")
import traceback
@@ -1325,7 +1391,23 @@ class LoginWindow(QMainWindow):
return ""
def trigger_update(self, download_url, latest_version):
"""触发更新下载(支持自动更新和手动下载"""
"""触发更新提示(首次检测到新版本时调用"""
try:
# 🔥 获取更新内容(如果没有则传空字符串,对话框会自动隐藏该区域)
update_content = self._get_update_content(latest_version)
if not update_content:
update_content = "" # 确保是空字符串而非None
# 调用内部方法显示对话框
self._show_update_dialog_internal(latest_version, download_url, update_content)
except Exception as e:
self.add_log(f"触发更新失败: {e}", "ERROR")
import traceback
self.add_log(f"详细错误: {traceback.format_exc()}", "ERROR")
def _show_update_dialog_internal(self, latest_version, download_url, update_content):
"""内部方法:显示更新对话框(使用自定义对话框)"""
try:
# 检查下载地址是否有效
if not download_url or download_url.strip() == "":
@@ -1338,46 +1420,45 @@ class LoginWindow(QMainWindow):
)
return
# 获取更新内容
update_content = self._get_update_content(latest_version)
if update_content:
content_preview = update_content[:100] + "..." if len(update_content) > 100 else update_content
else:
content_preview = "(暂无更新说明)"
# 🔥 使用自定义对话框(美观布局)
from custom_update_dialog import UpdateNotificationDialog
# 🔥 新增:弹出三选一对话框(自动更新/手动下载/取消)
msg_box = QMessageBox(self)
msg_box.setWindowTitle("版本更新")
msg_box.setText(f"发现新版本 v{latest_version},是否更新?")
msg_box.setInformativeText(
f"更新内容:\n{content_preview}\n\n"
f"推荐使用「自动更新」,程序将自动下载并安装新版本。"
dialog = UpdateNotificationDialog(
version=latest_version,
download_url=download_url,
update_content=update_content,
parent=self
)
msg_box.setIcon(QMessageBox.Information)
# 添加三个按钮
auto_button = msg_box.addButton("自动更新", QMessageBox.YesRole)
manual_button = msg_box.addButton("手动下载", QMessageBox.NoRole)
cancel_button = msg_box.addButton("取消", QMessageBox.RejectRole)
msg_box.setDefaultButton(auto_button)
dialog.exec_()
choice = dialog.get_user_choice()
msg_box.exec_()
clicked_button = msg_box.clickedButton()
if clicked_button == auto_button:
# 🔥 自动更新:下载并安装
self.add_log("用户选择自动更新", "INFO")
# 处理用户选择
if choice == UpdateNotificationDialog.CHOICE_UPDATE_NOW:
# 🔥 立即更新:启动自动更新流程
self.add_log("用户选择立即更新", "INFO")
self.notified_version = latest_version # 标记已提示
self.start_auto_update(download_url, latest_version)
elif clicked_button == manual_button:
# 🔥 手动下载:打开浏览器
self.add_log("用户选择手动下载", "INFO")
self.start_manual_download(download_url, latest_version)
elif choice == UpdateNotificationDialog.CHOICE_UPDATE_LATER:
# 🔥 稍后更新:更新状态标签,保留提示
self.add_log("用户选择稍后更新", "INFO")
self.notified_version = latest_version # 标记已提示(避免重复弹窗)
self.update_status_label_for_pending_update(latest_version, download_url)
elif choice == UpdateNotificationDialog.CHOICE_IGNORE:
# 🔥 忽略此版本:标记已提示,清除待更新信息
self.add_log(f"用户忽略版本 {latest_version}", "INFO")
self.notified_version = latest_version # 标记已提示
self.pending_update_info = None # 清除待更新信息
else:
# 取消更新
self.add_log("用户取消更新", "INFO")
# 用户直接关闭对话框(等同于稍后更新
self.add_log("用户关闭更新对话框", "INFO")
self.notified_version = latest_version
self.update_status_label_for_pending_update(latest_version, download_url)
except Exception as e:
self.add_log(f"更新失败: {e}", "ERROR")
self.add_log(f"显示更新对话框失败: {e}", "ERROR")
import traceback
self.add_log(f"详细错误: {traceback.format_exc()}", "ERROR")