feat(app_detail_window): 改进应用安装流程并添加错误处理

添加统一的错误弹窗函数,改进下载URL处理逻辑
使用本地install目录保存安装文件,添加下载完成后自动打开文件夹功能
替换InfoBar为QMessageBox实现更美观的弹窗提示
This commit is contained in:
2025-09-25 21:43:11 +08:00
parent 57e8fc877a
commit 4081f9a9db
4 changed files with 196 additions and 75 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ logs/
/pyqt5fluentdesign/output/
/pyqt5fluentdesign/logs/
/pyqt5fluentdesign/install/
# 忽略Windows系统文件
Thumbs.db

View File

@@ -1,6 +1,6 @@
# 这破代码有人能修复吗
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QFrame, QLabel, QGridLayout)
QPushButton, QFrame, QLabel, QGridLayout, QMessageBox)
from PyQt5.QtCore import Qt, QSize, pyqtSignal, QThread
from PyQt5.QtGui import QPixmap, QFont
from qfluentwidgets import (InfoBar, InfoBarPosition, TitleLabel, SubtitleLabel,
@@ -14,6 +14,25 @@ import tempfile
import subprocess
import platform
def show_error_dialog(title, message):
"""显示错误弹窗模拟Sweet Alert风格"""
# 创建错误消息框
msg_box = QMessageBox()
msg_box.setIcon(QMessageBox.Critical)
msg_box.setWindowTitle(title)
# 设置字体大小
font = QFont()
font.setPointSize(10)
msg_box.setFont(font)
# 设置消息内容
msg_box.setText(message)
msg_box.setStandardButtons(QMessageBox.Ok)
# 显示弹窗
msg_box.exec_()
class DownloadThread(QThread):
"""下载线程,用于在后台下载文件"""
progress = pyqtSignal(int)
@@ -652,34 +671,44 @@ class AppDetailWindow(QMainWindow):
# 检查是否有应用数据
if not hasattr(self, 'app_data'):
InfoBar.error(
title="错误",
content="应用数据未加载,请刷新页面后重试。",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
parent=self
)
show_error_dialog("错误", "应用数据未加载,请刷新页面后重试。")
return
# 创建确认对话框
reply = QMessageBox.question(self,
"确认安装",
f"您确定要安装{self.app_data.get('name', '未知应用')}吗?\n\n点击'确定'开始安装。",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
# 创建Sweet Alert风格的确认对话框
confirm_box = QMessageBox()
confirm_box.setWindowTitle("确认安装")
confirm_box.setIcon(QMessageBox.Question)
confirm_box.setText(f"您确定要安装{self.app_data.get('name', '未知应用')}吗?\n\n点击'确定'开始安装。")
# 设置字体大小
font = QFont()
font.setPointSize(10)
confirm_box.setFont(font)
# 添加按钮
confirm_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
confirm_box.setDefaultButton(QMessageBox.No)
# 显示对话框
reply = confirm_box.exec_()
if reply == QMessageBox.Yes:
# 显示开始安装的提示
InfoBar.success(
title="安装开始",
content=f"{self.app_data.get('name', '未知应用')}安装已开始,请稍候...",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
duration=3000,
parent=self
)
# 创建Sweet Alert风格的成功提示
success_box = QMessageBox()
success_box.setWindowTitle("安装开始")
success_box.setIcon(QMessageBox.Information)
success_box.setText(f"{self.app_data.get('name', '未知应用')}安装已开始,请稍候...")
# 设置字体大小
font = QFont()
font.setPointSize(10)
success_box.setFont(font)
# 添加按钮
success_box.setStandardButtons(QMessageBox.Ok)
# 显示对话框
success_box.exec_()
# 1. 首先尝试从应用数据中获取直接下载链接
download_url = self.app_data.get('direct_download_url')
@@ -688,30 +717,75 @@ class AppDetailWindow(QMainWindow):
if not download_url:
download_url = self.app_data.get('download_url')
# 3. 如果仍然没有下载链接尝试通过API获取下载链接
# 3. 根据新API格式从versions数组中获取最新版本的file_path
if not download_url and 'versions' in self.app_data and isinstance(self.app_data['versions'], list) and self.app_data['versions']:
# 假设versions数组中第一个版本是最新的
latest_version = self.app_data['versions'][0]
if isinstance(latest_version, dict) and 'file_path' in latest_version:
download_url = latest_version['file_path']
# 4. 如果仍然没有下载链接尝试通过API获取下载链接
if not download_url:
try:
# 使用app_id通过API获取下载链接
app_id = self.app_data.get('id')
if app_id:
download_result = self.api_client.make_request('getdownloadlink', {'id': app_id})
if isinstance(download_result, dict) and 'success' in download_result and download_result['success']:
if isinstance(download_result, dict):
# 检查是否是新API格式的响应
if 'data' in download_result:
if isinstance(download_result['data'], dict) and 'versions' in download_result['data'] and download_result['data']['versions']:
latest_version = download_result['data']['versions'][0]
if isinstance(latest_version, dict) and 'file_path' in latest_version:
download_url = latest_version['file_path']
# 兼容旧API格式
elif 'success' in download_result and download_result['success']:
download_url = download_result.get('data')
except Exception as e:
print(f"获取下载链接API调用失败: {str(e)}")
# 4. 如果仍然没有下载链接,显示错误信息
if not download_url:
InfoBar.error(
title="错误",
content="无法获取应用的下载链接,请稍后重试。\n可能原因API返回数据中缺少下载链接字段。",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
parent=self
)
show_error_dialog("错误", "无法获取应用的下载链接,请稍后重试。\n可能原因API返回数据中缺少下载链接字段。")
return
# 5. 更健壮的URL处理逻辑
from urllib.parse import urlparse, quote
import os
# 检查是否看起来像一个文件名(包含扩展名)
is_filename = False
if '.' in download_url:
# 获取最后一个点后的部分作为扩展名
extension = download_url.split('.')[-1].lower()
# 常见的应用安装包扩展名
common_extensions = ['exe', 'apk', 'dmg', 'pkg', 'msi', 'appx', 'deb', 'rpm']
if extension in common_extensions and len(extension) <= 5:
is_filename = True
# 如果是文件名或不包含协议处理为完整URL
if is_filename or not download_url.startswith(('http://', 'https://')):
# 使用基础域名
base_domain = 'leonmmcoset.jjxmm.win:8010'
# 对于所有情况统一使用直链形式不再使用download.php
# 对文件名/路径进行URL编码处理空格和特殊字符
if not download_url.startswith(('http://', 'https://')):
# 使用http协议和基础域名直接拼接文件名或路径
encoded_path = quote(download_url)
download_url = f'http://leonmmcoset.jjxmm.win:8010/{encoded_path.lstrip("/")}'
# 额外检查:确保主机名有效(包含点)
parsed_url = urlparse(download_url)
if '.' not in parsed_url.netloc:
# 如果主机名仍然无效完全重建URL
base_domain = 'leonmmcoset.jjxmm.win:8010'
path = parsed_url.path or parsed_url.netloc
# 对路径进行URL编码
encoded_path = quote(path)
# 使用http协议
download_url = f'http://leonmmcoset.jjxmm.win:8010/{encoded_path.lstrip("/")}'
# 创建进度对话框
self.progress_dialog = QProgressDialog(
f"正在下载{self.app_data.get('name', '未知应用')}...",
@@ -723,11 +797,31 @@ class AppDetailWindow(QMainWindow):
self.progress_dialog.setWindowTitle("正在安装")
self.progress_dialog.setMinimumDuration(0)
# 创建临时保存路径
# 创建保存路径 - 使用代码文件夹所在的install目录
file_extension = ".exe" # 假设是Windows可执行文件
app_name = self.app_data.get('name', '未知应用').replace(' ', '_')
# 获取当前脚本所在目录
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
# 创建install目录路径
install_dir = os.path.join(current_dir, 'install')
# 确保install目录存在
if not os.path.exists(install_dir):
try:
os.makedirs(install_dir)
except Exception as e:
# 如果创建目录失败,回退到临时目录
show_error_dialog("创建目录失败", f"无法创建安装目录: {str(e)}\n将使用临时目录")
temp_dir = tempfile.gettempdir()
save_path = os.path.join(temp_dir, f"{app_name}_installer{file_extension}")
else:
# 使用install目录
save_path = os.path.join(install_dir, f"{app_name}_installer{file_extension}")
else:
# 目录已存在,直接使用
save_path = os.path.join(install_dir, f"{app_name}_installer{file_extension}")
# 创建下载线程
self.download_thread = DownloadThread(download_url, save_path)
@@ -749,49 +843,75 @@ class AppDetailWindow(QMainWindow):
# 在Windows上直接运行可执行文件
subprocess.Popen([file_path], shell=True)
# 显示安装成功提示
InfoBar.success(
title="下载完成",
content=f"安装程序已启动,请按照向导完成安装。\n文件位置:{file_path}",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
duration=5000,
parent=self
)
# 创建Sweet Alert风格的下载完成提示
success_box = QMessageBox()
success_box.setWindowTitle("下载完成")
success_box.setIcon(QMessageBox.Information)
success_box.setText(f"安装程序已启动,请按照向导完成安装。\n文件位置:{file_path}")
# 设置字体大小
font = QFont()
font.setPointSize(10)
success_box.setFont(font)
# 添加按钮
success_box.setStandardButtons(QMessageBox.Ok)
# 显示对话框
success_box.exec_()
# 打开下载文件夹并选中文件
subprocess.Popen(f'explorer /select,"{file_path}"')
except Exception as e:
InfoBar.error(
title="启动安装程序失败",
content=f"无法启动安装程序: {str(e)}\n您可以手动运行文件: {file_path}",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
parent=self
)
show_error_dialog("启动安装程序失败", f"无法启动安装程序: {str(e)}\n您可以手动运行文件: {file_path}")
# 即使启动安装程序失败,也尝试打开下载文件夹
try:
subprocess.Popen(f'explorer /select,"{file_path}"')
except Exception as ex:
show_error_dialog("打开文件夹失败", f"无法打开下载文件夹: {str(ex)}")
else:
# 其他操作系统
InfoBar.information(
title="下载完成",
content=f"文件已下载完成。\n\n文件位置:{file_path}\n\n请手动运行安装程序。",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
duration=5000,
parent=self
)
info_box = QMessageBox()
info_box.setWindowTitle("下载完成")
info_box.setIcon(QMessageBox.Information)
info_box.setText(f"文件已下载完成。\n\n文件位置:{file_path}\n\n请手动运行安装程序。")
# 设置字体大小
font = QFont()
font.setPointSize(10)
info_box.setFont(font)
# 添加按钮
info_box.setStandardButtons(QMessageBox.Ok)
# 显示对话框
info_box.exec_()
# 打开下载文件夹
try:
import os
import subprocess
# 获取文件所在目录
folder_path = os.path.dirname(file_path)
if platform.system() == 'Windows':
# Windows下使用explorer打开文件夹并选中文件
subprocess.Popen(f'explorer /select,"{file_path}"')
elif platform.system() == 'Darwin':
# macOS下使用open命令打开文件夹
subprocess.Popen(['open', folder_path])
else:
# Linux下使用xdg-open打开文件夹
subprocess.Popen(['xdg-open', folder_path])
except Exception as e:
show_error_dialog("打开文件夹失败", f"无法打开下载文件夹: {str(e)}")
def on_download_error(self, error_msg):
"""下载错误处理"""
self.progress_dialog.reject()
InfoBar.error(
title="安装失败",
content=error_msg,
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
parent=self
)
# 使用Sweet Alert风格弹窗显示错误
show_error_dialog("安装失败", error_msg)
def share_app(self):
"""分享应用"""
@@ -802,7 +922,7 @@ class AppDetailWindow(QMainWindow):
# 创建分享链接
app_share_url = f"leonapp://app/{self.app_id}"
web_share_url = f"http://leonmcoset.jjxmm.win:8010/app?id={self.app_id}"
web_share_url = f"http://leonmmcoset.jjxmm.win:8010/app?id={self.app_id}"
# 创建自定义对话框
dialog = QMessageBox(self)