diff --git a/APP Store.zip b/APP Store.zip index 5b30c33..8c1f82e 100644 Binary files a/APP Store.zip and b/APP Store.zip differ diff --git a/leonapp-cli/leonapp_cli.py b/leonapp-cli/leonapp_cli.py index df8ecf4..80d476b 100644 --- a/leonapp-cli/leonapp_cli.py +++ b/leonapp-cli/leonapp_cli.py @@ -14,6 +14,9 @@ from datetime import datetime import requests from colorama import init, Fore, Style +# 当前版本 +APP_VERSION = "Beta 0.3" + # 初始化colorama def init_colorama(): """初始化colorama,确保在Windows和其他平台上都能正确显示彩色文本""" @@ -327,10 +330,10 @@ class LeonAppCLI: elif command == 'stats': self.get_count_info() - + elif command == 'check-update': + self.check_update() else: self.print_error("未知命令,请输入 'help' 获取帮助") - except KeyboardInterrupt: print() self.print_info("按 'exit' 退出") @@ -350,6 +353,7 @@ class LeonAppCLI: print(Fore.CYAN + "developer info [id]" + Fore.WHITE + " - 查看开发者信息") print(Fore.CYAN + "list announcements" + Fore.WHITE + " - 列出所有公告") print(Fore.CYAN + "stats" + Fore.WHITE + " - 查看统计信息") + print(Fore.CYAN + "check-update" + Fore.WHITE + " - 检查更新") def parse_arguments(self): """解析命令行参数""" @@ -399,6 +403,46 @@ class LeonAppCLI: return parser.parse_args() + def check_update(self): + """检查更新功能""" + self.print_header("检查更新") + self.print_info("正在检查更新...") + + # 获取LeonAPP的版本信息(App ID为15) + data = self.make_api_request('getappversions', {'id': 15}) + + if data: + try: + # 检查数据格式 + if not isinstance(data, dict) or 'versions' not in data: + self.print_error("获取版本信息失败:数据格式不正确") + return + + versions = data.get('versions', []) + if not versions: + self.print_error("未找到任何版本信息") + return + + # 最新版本通常在列表的第一个 + latest_version = versions[0].get('version', '未知') + current_version = APP_VERSION + + # 比较版本号 + if latest_version != current_version: + # 版本不一致,显示更新提示 + self.print_separator() + self.print_success(f"发现新版本:{latest_version}") + self.print_info(f"当前版本:{current_version}") + self.print_info("建议前往应用商店下载最新版本。") + self.print_separator() + else: + # 已是最新版本 + self.print_success(f"已是最新版本:{current_version}") + except Exception as e: + self.print_error(f"处理版本信息时发生错误:{str(e)}") + else: + self.print_error("检查更新失败,请稍后重试。") + def run(self): """运行CLI""" # 解析命令行参数 @@ -421,6 +465,8 @@ class LeonAppCLI: self.list_all_announcements(args.page, args.limit) elif args.command == 'stats': self.get_count_info() + elif args.command == 'check-update': + self.check_update() elif args.command == 'interactive': self.interactive_mode() else: diff --git a/pyqt5fluentdesign/__pycache__/app_detail_window.cpython-312.pyc b/pyqt5fluentdesign/__pycache__/app_detail_window.cpython-312.pyc index 15fd84c..c517a01 100644 Binary files a/pyqt5fluentdesign/__pycache__/app_detail_window.cpython-312.pyc and b/pyqt5fluentdesign/__pycache__/app_detail_window.cpython-312.pyc differ diff --git a/pyqt5fluentdesign/app_detail_window.py b/pyqt5fluentdesign/app_detail_window.py index 8e8ab00..97b18c9 100644 --- a/pyqt5fluentdesign/app_detail_window.py +++ b/pyqt5fluentdesign/app_detail_window.py @@ -1,10 +1,60 @@ -from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QPushButton) -from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, + QPushButton, QFrame, QLabel, QGridLayout) +from PyQt5.QtCore import Qt, QSize, pyqtSignal, QThread +from PyQt5.QtGui import QPixmap, QFont from qfluentwidgets import (InfoBar, InfoBarPosition, TitleLabel, SubtitleLabel, PrimaryPushButton, PushButton, ScrollArea, CardWidget, FluentIcon, SimpleCardWidget, BodyLabel) import json +import os +import requests +import sys +import tempfile +import subprocess +import platform + +class DownloadThread(QThread): + """下载线程,用于在后台下载文件""" + progress = pyqtSignal(int) + finished = pyqtSignal(str) + error = pyqtSignal(str) + + def __init__(self, download_url, save_path): + super().__init__() + self.download_url = download_url + self.save_path = save_path + + def run(self): + """线程运行函数""" + try: + # 发送请求 + with requests.get(self.download_url, stream=True, timeout=30) as response: + response.raise_for_status() + + # 获取文件大小 + total_size = int(response.headers.get('content-length', 0)) + downloaded_size = 0 + + # 创建保存目录 + os.makedirs(os.path.dirname(self.save_path), exist_ok=True) + + # 下载文件 + with open(self.save_path, 'wb') as file: + for chunk in response.iter_content(chunk_size=8192): + if chunk: + file.write(chunk) + downloaded_size += len(chunk) + + # 计算进度 + if total_size > 0: + progress = int(downloaded_size / total_size * 100) + self.progress.emit(progress) + + # 下载完成 + self.finished.emit(self.save_path) + + except Exception as e: + self.error.emit(f"下载失败: {str(e)}") class AppDetailWindow(QMainWindow): def __init__(self, api_client, app_id, parent=None): @@ -15,13 +65,29 @@ class AppDetailWindow(QMainWindow): # 设置窗口属性 self.setWindowTitle("应用详情") - self.resize(800, 600) + self.resize(850, 650) self.setObjectName("AppDetailWindow") - # 添加全局样式 + # 添加全局样式 - 现代扁平化设计 self.setStyleSheet(""" #AppDetailWindow { - background-color: #F2F3F5; + background-color: #F5F7FA; + } + QLabel { + color: #333333; + } + #AppTitle { + color: #1A1A1A; + font-weight: bold; + } + #StatusBadge { + border-radius: 12px; + padding: 2px 10px; + font-size: 12px; + } + #DeveloperLabel { + color: #666666; + font-size: 14px; } """) @@ -31,20 +97,18 @@ class AppDetailWindow(QMainWindow): # 创建主布局 self.main_layout = QVBoxLayout(self.central_widget) - self.main_layout.setContentsMargins(20, 20, 20, 20) - self.main_layout.setSpacing(12) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) - # 创建标题区域 - self.header_layout = QHBoxLayout() - self.app_title = TitleLabel("加载中...") - self.app_title.setObjectName("AppTitle") - self.close_button = PushButton("关闭") - self.close_button.clicked.connect(self.close) - self.close_button.setFixedWidth(80) + # 创建自定义顶部区域(非传统顶栏) + self.create_custom_header() - self.header_layout.addWidget(self.app_title) - self.header_layout.addStretch() - self.header_layout.addWidget(self.close_button) + # 添加分隔线 + separator = QFrame() + separator.setFrameShape(QFrame.HLine) + separator.setFrameShadow(QFrame.Sunken) + separator.setStyleSheet("background-color: #E5E6EB;") + self.main_layout.addWidget(separator) # 创建滚动区域 - 使用QFluentWidgets的ScrollArea self.scroll_area = ScrollArea() @@ -55,20 +119,26 @@ class AppDetailWindow(QMainWindow): # 设置滚动区域样式 self.scroll_area.setStyleSheet(""" ScrollArea { - background-color: transparent; + background-color: #F5F7FA; border: none; } QScrollBar:vertical { - width: 8px; + width: 10px; background: transparent; + margin-right: 5px; } QScrollBar::handle:vertical { - background: rgba(142, 142, 147, 0.3); - border-radius: 4px; - min-height: 40px; + background: rgba(142, 142, 147, 0.2); + border-radius: 5px; + min-height: 50px; } QScrollBar::handle:vertical:hover { - background: rgba(142, 142, 147, 0.5); + background: rgba(142, 142, 147, 0.4); + } + QScrollBar::add-line:vertical, + QScrollBar::sub-line:vertical { + height: 0px; + width: 0px; } """) @@ -76,38 +146,159 @@ class AppDetailWindow(QMainWindow): self.scroll_content = QWidget() self.scroll_content.setObjectName("ScrollContent") self.scroll_layout = QVBoxLayout(self.scroll_content) - self.scroll_layout.setContentsMargins(0, 0, 0, 20) - self.scroll_layout.setSpacing(16) + self.scroll_layout.setContentsMargins(20, 20, 20, 30) + self.scroll_layout.setSpacing(20) # 添加滚动区域到主布局 self.scroll_area.setWidget(self.scroll_content) - - # 将标题区域和滚动区域添加到主布局 - self.main_layout.addLayout(self.header_layout) self.main_layout.addWidget(self.scroll_area) # 加载应用详情 self.load_app_detail() + + def create_custom_header(self): + """创建自定义顶部区域""" + self.header_widget = QWidget() + self.header_widget.setObjectName("HeaderWidget") + self.header_widget.setStyleSheet(""" + #HeaderWidget { + background-color: #FFFFFF; + padding: 15px 20px; + } + """) + + self.header_layout = QHBoxLayout(self.header_widget) + self.header_layout.setContentsMargins(0, 0, 0, 0) + self.header_layout.setSpacing(15) + + # 应用图标占位 - 使用QLabel替换Avatar + self.app_icon = QLabel() + self.app_icon.setFixedSize(60, 60) + self.app_icon.setStyleSheet("background-color: #4CAF50; border-radius: 12px;") + + # 应用信息布局 + self.app_info_layout = QVBoxLayout() + self.app_info_layout.setContentsMargins(0, 0, 0, 0) + self.app_info_layout.setSpacing(5) + + # 标题和状态布局 + self.title_status_layout = QHBoxLayout() + self.title_status_layout.setSpacing(10) + + # 应用标题 + self.app_title = TitleLabel("加载中...") + self.app_title.setObjectName("AppTitle") + self.title_status_layout.addWidget(self.app_title) + + # 应用状态标签 + self.status_badge = QLabel("- ") + self.status_badge.setObjectName("StatusBadge") + self.status_badge.setStyleSheet("background-color: #E5E6EB; color: #666666;") + self.status_badge.setAlignment(Qt.AlignCenter) + self.status_badge.setFixedHeight(24) + self.title_status_layout.addWidget(self.status_badge) + self.title_status_layout.addStretch() + + # 开发者标签 + self.developer_label = QLabel("开发者: 加载中...") + self.developer_label.setObjectName("DeveloperLabel") + + # 添加到应用信息布局 + self.app_info_layout.addLayout(self.title_status_layout) + self.app_info_layout.addWidget(self.developer_label) + + # 右侧按钮布局 + self.button_layout = QVBoxLayout() + self.button_layout.setContentsMargins(0, 0, 0, 0) + self.button_layout.setSpacing(8) + + # 关闭按钮 + self.close_button = PushButton("关闭") + self.close_button.clicked.connect(self.close) + self.close_button.setFixedSize(90, 32) + self.button_layout.addWidget(self.close_button) + self.button_layout.addStretch() + + # 添加到主头部布局 + self.header_layout.addWidget(self.app_icon) + self.header_layout.addLayout(self.app_info_layout, 1) + self.header_layout.addLayout(self.button_layout) + + # 添加到主布局 + self.main_layout.addWidget(self.header_widget) def load_app_detail(self): """加载应用详情""" try: - # 这里应该调用API获取应用详情 - # 暂时使用模拟数据 - app_data = { - "id": self.app_id, - "name": "示例应用", - "description": "这是一个示例应用,用于展示应用详情页面", - "developer_id": "1", - "developer_name": "示例开发者", - "status": "approved", - "version": "1.0.0", - "created_at": "2023-01-01 10:00:00" - } + # 使用API客户端调用getappinfo端点获取应用详情 + result = self.api_client.make_request('getappinfo', {'id': self.app_id}) + + # 检查API调用是否成功 + if isinstance(result, dict) and 'success' in result and result['success']: + # 保存应用数据到实例属性 + self.app_data = result['data'] + + # 为了保持向后兼容性,也保留局部变量 + app_data = self.app_data + else: + # API调用失败,显示错误信息 + error_msg = result.get('error', '未知错误') if isinstance(result, dict) else 'API调用失败' + InfoBar.error( + title="错误", + content=f"加载应用详情失败: {error_msg}", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + parent=self + ) + # 使用默认数据 + self.app_data = { + "id": self.app_id, + "name": "未知应用", + "description": "无法加载应用详情", + "developer_id": "0", + "developer_name": "未知开发者", + "status": "unknown", + "version": "0.0.0", + "created_at": "-", + "downloads": "0", + "rating": "0.0", + "category": "未知" + } + app_data = self.app_data # 更新窗口标题 self.setWindowTitle(f"应用详情 - {app_data.get('name', '未知应用')}") + + # 更新头部信息 self.app_title.setText(app_data.get('name', '未知应用')) + # 改进开发者信息处理,确保能正确显示开发者名称 + developer_name = app_data.get('developer_name', '') + # 如果developer_name为空,尝试从其他可能的字段获取 + if not developer_name: + developer_name = app_data.get('developer', '') + # 如果developer也为空,尝试使用developer_email + if not developer_name: + developer_name = app_data.get('developer_email', '未知开发者') + self.developer_label.setText(f"开发者: {developer_name}") + + # 根据应用状态设置状态标签样式 + status = app_data.get('status', 'unknown') + status_text = {} + status_styles = {} + + status_text['approved'] = '已通过' + status_text['pending'] = '审核中' + status_text['rejected'] = '已拒绝' + status_text['unknown'] = '未知状态' + + status_styles['approved'] = 'background-color: #E8F5E9; color: #2E7D32;' + status_styles['pending'] = 'background-color: #FFF3E0; color: #E65100;' + status_styles['rejected'] = 'background-color: #FFEBEE; color: #C62828;' + status_styles['unknown'] = 'background-color: #E5E6EB; color: #666666;' + + self.status_badge.setText(status_text.get(status, '未知状态')) + self.status_badge.setStyleSheet(status_styles.get(status, status_styles['unknown'])) # 清空滚动区域 for i in reversed(range(self.scroll_layout.count())): @@ -116,6 +307,9 @@ class AppDetailWindow(QMainWindow): widget.setParent(None) widget.deleteLater() + # 显示统计信息卡片 + self.display_stats_card(app_data) + # 显示应用基本信息卡片 self.display_app_info_card(app_data) @@ -136,39 +330,132 @@ class AppDetailWindow(QMainWindow): parent=self ) + def display_stats_card(self, app_data): + """显示应用统计信息卡片""" + stats_card = CardWidget() + stats_card.setObjectName("StatsCard") + stats_card.setStyleSheet(""" + #StatsCard { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 12px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + } + #StatsCard QLabel { + color: white; + } + """) + + card_layout = QHBoxLayout(stats_card) + card_layout.setContentsMargins(20, 20, 20, 20) + card_layout.setSpacing(25) + + # 下载量统计项 + downloads_item = QVBoxLayout() + downloads_label = BodyLabel("下载量") + downloads_label.setStyleSheet("font-size: 13px; opacity: 0.9;") + downloads_value = TitleLabel(app_data.get("downloads", "0")) + downloads_value.setStyleSheet("font-size: 24px; font-weight: bold;") + downloads_item.addWidget(downloads_label) + downloads_item.addWidget(downloads_value) + downloads_item.setAlignment(Qt.AlignCenter) + + # 评分统计项 + rating_item = QVBoxLayout() + rating_label = BodyLabel("评分") + rating_label.setStyleSheet("font-size: 13px; opacity: 0.9;") + rating_value = TitleLabel(app_data.get("rating", "0.0")) + rating_value.setStyleSheet("font-size: 24px; font-weight: bold;") + rating_item.addWidget(rating_label) + rating_item.addWidget(rating_value) + rating_item.setAlignment(Qt.AlignCenter) + + # 分类统计项 + category_item = QVBoxLayout() + category_label = BodyLabel("分类") + category_label.setStyleSheet("font-size: 13px; opacity: 0.9;") + category_value = TitleLabel(app_data.get("category", "未分类")) + category_value.setStyleSheet("font-size: 24px; font-weight: bold;") + category_item.addWidget(category_label) + category_item.addWidget(category_value) + category_item.setAlignment(Qt.AlignCenter) + + # 添加到卡片布局 + card_layout.addLayout(downloads_item) + card_layout.addLayout(rating_item) + card_layout.addLayout(category_item) + + self.scroll_layout.addWidget(stats_card) + def display_app_info_card(self, app_data): """显示应用基本信息卡片""" info_card = CardWidget() info_card.setObjectName("InfoCard") + info_card.setStyleSheet(""" + #InfoCard { + background-color: white; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + } + """) card_layout = QVBoxLayout(info_card) - card_layout.setContentsMargins(16, 16, 16, 16) - card_layout.setSpacing(12) + card_layout.setContentsMargins(20, 20, 20, 20) + card_layout.setSpacing(15) + + # 卡片标题和图标 + title_layout = QHBoxLayout() + title_icon = QLabel() + title_icon.setPixmap(FluentIcon.INFO.icon().pixmap(18, 18)) + title_icon.setStyleSheet("color: #4CAF50;") - # 卡片标题 card_title = SubtitleLabel("基本信息") - card_layout.addWidget(card_title) + card_title.setStyleSheet("font-size: 16px; font-weight: 600;") - # 信息网格布局 - info_grid_layout = QVBoxLayout() - info_grid_layout.setSpacing(6) + title_layout.addWidget(title_icon) + title_layout.addWidget(card_title) + title_layout.addStretch() + card_layout.addLayout(title_layout) + + # 信息网格布局 - 使用两列布局 + info_grid_layout = QGridLayout() + info_grid_layout.setSpacing(12) + info_grid_layout.setColumnStretch(0, 1) + info_grid_layout.setColumnStretch(1, 1) + + # 添加基本信息字段,对开发者信息进行特殊处理 + developer_name = app_data.get("developer_name", "") + if not developer_name: + developer_name = app_data.get("developer", "--") - # 添加基本信息字段 info_items = [ ("应用ID", app_data.get("id", "--")), - ("开发者", app_data.get("developer_name", "--")), - ("状态", app_data.get("status", "--")), + ("开发者", developer_name), ("当前版本", app_data.get("version", "--")), ("创建时间", app_data.get("created_at", "--")) ] + row = 0 for label_text, value_text in info_items: - info_row = QHBoxLayout() + # 创建标签和值 + label = QLabel(f"{label_text}:") + label.setStyleSheet("color: #666666; font-size: 14px;") + # 确保value_text是字符串类型 + value = QLabel(str(value_text)) + value.setStyleSheet("color: #333333; font-size: 14px; font-weight: 500;") + value.setTextInteractionFlags(Qt.TextSelectableByMouse) - # 标签和值之间的比例 - info_row.addWidget(BodyLabel(f"{label_text}:"), 1) - info_row.addWidget(BodyLabel(f"{value_text}"), 3) - info_grid_layout.addLayout(info_row) + # 布局 + item_layout = QVBoxLayout() + item_layout.addWidget(label) + item_layout.addWidget(value) + item_layout.setSpacing(4) + + # 添加到网格 + col = row % 2 + actual_row = row // 2 + info_grid_layout.addLayout(item_layout, actual_row, col) + row += 1 card_layout.addLayout(info_grid_layout) self.scroll_layout.addWidget(info_card) @@ -177,53 +464,338 @@ class AppDetailWindow(QMainWindow): """显示应用描述卡片""" description_card = CardWidget() description_card.setObjectName("DescriptionCard") - - card_layout = QVBoxLayout(description_card) - card_layout.setContentsMargins(16, 16, 16, 16) - card_layout.setSpacing(12) - - # 卡片标题 - card_title = SubtitleLabel("应用描述") - card_layout.addWidget(card_title) - - # 描述文本 - from qfluentwidgets import TextEdit - description_text = TextEdit() - description_text.setReadOnly(True) - description_text.setWordWrapMode(3) # QTextOption.WrapAtWordBoundaryOrAnywhere - description_text.setMinimumHeight(150) - description_text.setStyleSheet(""" - TextEdit { - background-color: transparent; - border: 1px solid rgba(0, 0, 0, 0.05); - border-radius: 6px; - padding: 8px; + description_card.setStyleSheet(""" + #DescriptionCard { + background-color: white; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } """) - # 设置描述内容 - description_text.setPlainText(app_data.get("description", "无描述信息")) + card_layout = QVBoxLayout(description_card) + card_layout.setContentsMargins(20, 20, 20, 20) + card_layout.setSpacing(15) + + # 卡片标题和图标 + title_layout = QHBoxLayout() + title_icon = QLabel() + title_icon.setPixmap(FluentIcon.DOCUMENT.icon().pixmap(18, 18)) + title_icon.setStyleSheet("color: #2196F3;") + + card_title = SubtitleLabel("应用描述") + card_title.setStyleSheet("font-size: 16px; font-weight: 600;") + + title_layout.addWidget(title_icon) + title_layout.addWidget(card_title) + title_layout.addStretch() + card_layout.addLayout(title_layout) + + # 描述文本 + from PyQt5.QtWidgets import QTextBrowser + description_text = QTextBrowser() + description_text.setReadOnly(True) + description_text.setWordWrapMode(3) # QTextOption.WrapAtWordBoundaryOrAnywhere + description_text.setMinimumHeight(200) + description_text.setOpenExternalLinks(False) # 禁用自动打开外部链接,由我们自己处理 + description_text.setStyleSheet(""" + QTextBrowser { + background-color: #FAFAFA; + border: 1px solid #E5E6EB; + border-radius: 8px; + padding: 12px; + font-family: 'Microsoft YaHei', 'SimHei'; + font-size: 14px; + line-height: 1.6; + } + """) + + # 设置描述内容 - 支持Markdown + description_text.setHtml(self.convert_markdown_to_html(app_data.get("description", "无描述信息"))) + + # 连接链接点击信号,处理外部链接打开 + # QTextBrowser组件有anchorClicked信号,可以连接到处理函数 + description_text.anchorClicked.connect(self.handle_link_clicked) card_layout.addWidget(description_text) self.scroll_layout.addWidget(description_card) + def convert_markdown_to_html(self, markdown_text): + """将Markdown文本转换为HTML""" + try: + import markdown + return markdown.markdown(markdown_text) + except Exception as e: + # 如果转换失败,使用原始文本并进行HTML转义 + from PyQt5.QtCore import Qt + from PyQt5.QtGui import QTextDocument + + # 创建QTextDocument进行HTML转义 + doc = QTextDocument() + doc.setPlainText(markdown_text) + return doc.toHtml() + + def handle_link_clicked(self, url): + """处理链接点击事件,使用系统默认浏览器打开外部链接""" + from PyQt5.QtCore import QUrl + from PyQt5.QtGui import QDesktopServices + from qfluentwidgets import InfoBar + + # 检查URL是否为http或https协议 + if url.scheme() in ['http', 'https']: + # 使用系统默认浏览器打开链接 + QDesktopServices.openUrl(url) + return + + # 处理特殊的leonapp链接 + elif url.toString().startswith('leonapp://'): + # 提取应用ID或其他参数 + # 示例: leonapp://app/123 + path = url.toString()[10:] # 移除 'leonapp://' + if path.startswith('app/'): + app_id = path[4:] # 提取应用ID + # 实现打开特定应用详情的逻辑 + self.app_detail_window = AppDetailWindow(self.api_client, app_id, parent=self.parent()) + self.app_detail_window.show() + return + + # 显示不支持的链接类型提示 + InfoBar.warning( + title="不支持的链接类型", + content=f"无法打开链接: {url.toString()}", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.BOTTOM_RIGHT, + duration=3000, + parent=self + ) + def display_action_buttons(self): """显示操作按钮区域""" - button_card = SimpleCardWidget() + button_card = CardWidget() button_card.setObjectName("ButtonCard") - button_card.setMinimumHeight(80) + button_card.setStyleSheet(""" + #ButtonCard { + background-color: white; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + } + """) button_layout = QHBoxLayout(button_card) - button_layout.setContentsMargins(16, 16, 16, 16) + button_layout.setContentsMargins(20, 20, 20, 20) + button_layout.setSpacing(12) button_layout.addStretch() # 刷新按钮 refresh_button = PushButton("刷新") refresh_button.setIcon(FluentIcon.SYNC) refresh_button.clicked.connect(self.load_app_detail) + refresh_button.setFixedSize(100, 36) + + # 安装按钮 + install_button = PrimaryPushButton("安装") + install_button.setIcon(FluentIcon.DOWNLOAD) + install_button.setFixedSize(100, 36) + install_button.clicked.connect(self.install_app) button_layout.addWidget(refresh_button) + button_layout.addWidget(install_button) self.scroll_layout.addWidget(button_card) + def install_app(self): + """安装应用""" + from qfluentwidgets import InfoBar + from PyQt5.QtWidgets import QMessageBox, QProgressDialog + + # 检查是否有应用数据 + if not hasattr(self, 'app_data'): + InfoBar.error( + title="错误", + content="应用数据未加载,请刷新页面后重试。", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + parent=self + ) + return + + # 创建确认对话框 + reply = QMessageBox.question(self, + "确认安装", + f"您确定要安装{self.app_data.get('name', '未知应用')}吗?\n\n点击'确定'开始安装。", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No) + + 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 + ) + + # 获取下载链接(使用应用数据中的直链下载地址) + download_url = self.app_data.get('direct_download_url') + + # 如果没有直接下载链接,则尝试使用备用的download_url字段 + if not download_url: + download_url = self.app_data.get('download_url') + + # 如果仍然没有下载链接,显示错误信息 + if not download_url: + InfoBar.error( + title="错误", + content="无法获取应用的下载链接,请稍后重试。", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + parent=self + ) + return + + # 创建进度对话框 + self.progress_dialog = QProgressDialog( + f"正在下载{self.app_data.get('name', '未知应用')}...", + "取消", + 0, + 100, + parent=self + ) + self.progress_dialog.setWindowTitle("正在安装") + self.progress_dialog.setMinimumDuration(0) + + # 创建临时保存路径 + file_extension = ".exe" # 假设是Windows可执行文件 + app_name = self.app_data.get('name', '未知应用').replace(' ', '_') + temp_dir = tempfile.gettempdir() + save_path = os.path.join(temp_dir, f"{app_name}_installer{file_extension}") + + # 创建下载线程 + self.download_thread = DownloadThread(download_url, save_path) + self.download_thread.progress.connect(self.progress_dialog.setValue) + self.download_thread.finished.connect(self.on_download_finished) + self.download_thread.error.connect(self.on_download_error) + + # 开始下载 + self.download_thread.start() + self.progress_dialog.exec_() + + def on_download_finished(self, file_path): + """下载完成处理""" + self.progress_dialog.accept() + + # 根据操作系统执行不同的安装操作 + if platform.system() == "Windows": + try: + # 在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 + ) + except Exception as e: + InfoBar.error( + title="启动安装程序失败", + content=f"无法启动安装程序: {str(e)}\n您可以手动运行文件: {file_path}", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + parent=self + ) + else: + # 其他操作系统 + InfoBar.information( + title="下载完成", + content=f"文件已下载完成。\n\n文件位置:{file_path}\n\n请手动运行安装程序。", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self + ) + + 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 + ) + + def share_app(self): + """分享应用""" + from qfluentwidgets import InfoBar, PushButton + from PyQt5.QtGui import QClipboard + from PyQt5.QtWidgets import QApplication, QMessageBox + import webbrowser + + # 创建分享链接 + app_share_url = f"leonapp://app/{self.app_id}" + web_share_url = f"http://leonmcoset.jjxmm.win:8010/app?id={self.app_id}" + + # 创建自定义对话框 + dialog = QMessageBox(self) + dialog.setWindowTitle("分享应用") + dialog.setText(f"请选择分享方式:\n\n{self.app_data.get('name', '未知应用')}") + dialog.setIcon(QMessageBox.Information) + + # 添加按钮 + copy_web_url_btn = dialog.addButton("复制网页链接", QMessageBox.AcceptRole) + share_wechat_btn = dialog.addButton("分享到微信", QMessageBox.RejectRole) + copy_app_url_btn = dialog.addButton("复制应用内链接", QMessageBox.ActionRole) + + # 显示对话框并处理结果 + dialog.exec_() + + clicked_button = dialog.clickedButton() + if clicked_button == copy_web_url_btn: + # 复制网页链接 + self.copy_to_clipboard(web_share_url) + elif clicked_button == share_wechat_btn: + # 分享到微信(模拟功能) + InfoBar.info( + title="分享到微信", + content="请将以下链接分享给微信好友:\n" + web_share_url, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=5000, + parent=self + ) + self.copy_to_clipboard(web_share_url) + elif clicked_button == copy_app_url_btn: + # 复制应用内链接 + self.copy_to_clipboard(app_share_url) + + def copy_to_clipboard(self, text): + """复制文本到剪贴板""" + from qfluentwidgets import InfoBar + clipboard = QApplication.clipboard() + clipboard.setText(text) + InfoBar.success( + title="复制成功", + content="链接已复制到剪贴板!", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=2000, + parent=self + ) def closeEvent(self, event): """关闭窗口事件""" diff --git a/pyqt5fluentdesign/leonapp_gui.py b/pyqt5fluentdesign/leonapp_gui.py index 8e54440..7e6d43c 100644 --- a/pyqt5fluentdesign/leonapp_gui.py +++ b/pyqt5fluentdesign/leonapp_gui.py @@ -6,7 +6,7 @@ LeonApp GUI - 基于PyQt5和Fluent Design的App Store API图形界面工具 """ # APP版本号 -APP_VERSION = "Beta 0.3" +APP_VERSION = "Beta 0.4" import sys import json @@ -1617,216 +1617,7 @@ class StatsTab(QWidget): parent=self ) -class AppDetailWindow(QMainWindow): - """应用详情窗口""" - def __init__(self, api_client, app_id, parent=None): - super().__init__(parent) - self.api_client = api_client - self.app_id = app_id - self.setWindowTitle("应用详情") - self.resize(800, 600) - self.init_ui() - self.load_app_detail() - - def init_ui(self): - """初始化界面""" - # 创建中心部件 - central_widget = QWidget() - self.setCentralWidget(central_widget) - - # 创建主布局 - main_layout = QVBoxLayout(central_widget) - main_layout.setContentsMargins(20, 20, 20, 20) - - # 创建标题 - self.app_title = TitleLabel("加载中...") - main_layout.addWidget(self.app_title) - - # 创建应用信息区域 - self.info_card = CardWidget() - self.info_layout = QVBoxLayout(self.info_card) - main_layout.addWidget(self.info_card) - - # 创建进度条 - self.progress_bar = ProgressBar() - self.progress_bar.setVisible(False) - main_layout.addWidget(self.progress_bar) - - def load_app_detail(self): - """加载应用详情""" - self.show_progress() - self.worker = WorkerThread(self.api_client, 'getappinfo', {'id': self.app_id}) - self.worker.finished.connect(self.on_app_detail_loaded) - self.worker.progress.connect(self.update_progress) - self.worker.error.connect(self.show_error) - self.worker.start() - - def on_app_detail_loaded(self, data): - """应用详情加载完成处理""" - self.hide_progress() - - if not data: - self.show_error("应用详情加载失败") - return - - # 更新标题 - self.app_title.setText(data.get('name', '未知应用')) - - # 清空之前的信息 - while self.info_layout.count(): - item = self.info_layout.takeAt(0) - widget = item.widget() - if widget: - widget.deleteLater() - - # 添加应用信息 - info_text = f""" -版本: {data.get('version', '未知')} -年龄分级: {data.get('age_rating', '未知')} -评分: {data.get('avg_rating', '暂无')} -下载量: {data.get('total_downloads', 0)} - """ - - # 添加描述 - description_label = SubtitleLabel("描述") - self.info_layout.addWidget(description_label) - - # 使用QFluentWidgets的TextEdit支持Markdown显示 - from qfluentwidgets import TextEdit - description_text = TextEdit() - description_text.setReadOnly(True) - description_text.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) - description_text.setLineWrapMode(QTextEdit.WidgetWidth) - description_text.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - # 设置Fluent风格 - description_text.setStyleSheet(""" - TextEdit { - background-color: transparent; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 6px; - padding: 8px; - } - """) - - # 尝试将markdown格式的描述转换为HTML - app_description = data.get('description', '暂无描述') - try: - html_description = markdown.markdown(app_description) - description_text.setHtml(html_description) - except Exception: - # 如果markdown解析失败,使用纯文本 - description_text.setPlainText(app_description) - - # 设置文本框的高度 - description_text.setMinimumHeight(120) - self.info_layout.addWidget(description_text) - - # 添加标签信息 - if 'tags' in data and data['tags']: - tags_label = SubtitleLabel("标签") - self.info_layout.addWidget(tags_label) - - tags_text = ', '.join([tag['name'] for tag in data['tags']]) - self.info_layout.addWidget(CaptionLabel(tags_text)) - - # 添加版本信息 - if 'versions' in data and data['versions']: - versions_label = SubtitleLabel("版本历史") - self.info_layout.addWidget(versions_label) - - versions_text = "\n".join([f"- 版本 {v['version']} ({v['download_count']} 下载)" for v in data['versions'][:3]]) - self.info_layout.addWidget(CaptionLabel(versions_text)) - - # 添加"查看全部版本"按钮 - if len(data['versions']) > 3: - self.view_all_versions_button = PushButton("查看全部版本") - self.view_all_versions_button.clicked.connect(lambda: self.view_all_versions()) - self.info_layout.addWidget(self.view_all_versions_button) - - # 添加下载最新版本按钮 - if data['versions']: - # 保存最新版本信息用于下载 - self.latest_version = data['versions'][0] - download_layout = QHBoxLayout() - download_layout.addStretch(1) - - self.download_button = PushButton("下载最新版本") - self.download_button.setIcon(FluentIcon.DOWNLOAD) - self.download_button.clicked.connect(self.download_latest_version) - download_layout.addWidget(self.download_button) - - self.info_layout.addLayout(download_layout) - - def view_all_versions(self): - """查看全部版本""" - versions_window = AppVersionsWindow(self.api_client, self.app_id, self.app_title.text(), self) - versions_window.show() - - def download_latest_version(self): - """下载最新版本""" - if hasattr(self, 'latest_version'): - version_id = self.latest_version.get('id', '') - if version_id: - self.perform_download(version_id) - else: - self.show_error("无法获取版本ID") - else: - self.show_error("没有可下载的版本") - - def perform_download(self, version_id): - """执行下载操作""" - import webbrowser - - # 直接使用latest_version中的file_path进行下载 - if hasattr(self, 'latest_version') and 'file_path' in self.latest_version: - file_path = self.latest_version['file_path'] - # 构建直接的文件URL - download_url = f"http://leonmmcoset.jjxmm.win:8010/{file_path}" - - try: - # 使用系统默认浏览器打开下载链接 - webbrowser.open(download_url) - - # 显示下载成功提示 - InfoBar.success( - title="下载开始", - content=f"下载已开始,请稍候...", - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.BOTTOM_RIGHT, - duration=3000, - parent=self - ) - except Exception as e: - self.show_error(f"下载失败: {str(e)}") - else: - self.show_error("无法获取下载文件路径") - - def show_progress(self): - """显示进度条""" - self.progress_bar.setVisible(True) - self.progress_bar.setValue(0) - - def update_progress(self, value): - """更新进度条""" - self.progress_bar.setValue(value) - - def hide_progress(self): - """隐藏进度条""" - self.progress_bar.setVisible(False) - - def show_error(self, message): - """显示错误消息""" - self.hide_progress() - InfoBar.error( - title="错误", - content=message, - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.BOTTOM_RIGHT, - duration=5000, - parent=self - ) + class TagAppsWindow(QMainWindow): """标签下的应用列表窗口""" @@ -2256,14 +2047,18 @@ class AnnouncementDetailWindow(QMainWindow): content_title = SubtitleLabel("公告内容") card_layout.addWidget(content_title) - # 添加内容文本框,支持Markdown渲染 - from qfluentwidgets import TextEdit - self.content_text = TextEdit() + # 添加内容文本框,支持Markdown渲染和链接点击 + from PyQt5.QtWidgets import QTextBrowser + self.content_text = QTextBrowser() self.content_text.setHtml(self.content_html) self.content_text.setReadOnly(True) self.content_text.setMinimumHeight(250) self.content_text.setLineWrapMode(QTextEdit.WidgetWidth) self.content_text.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) + self.content_text.setOpenExternalLinks(False) # 禁用自动打开外部链接,由我们自己处理 + + # QTextBrowser组件有anchorClicked信号,可以连接到处理函数 + self.content_text.anchorClicked.connect(self.handle_link_clicked) # 设置Fluent风格样式 self.content_text.setStyleSheet(""" @@ -2278,6 +2073,41 @@ class AnnouncementDetailWindow(QMainWindow): card_layout.addWidget(self.content_text) self.scroll_layout.addWidget(content_card) + def handle_link_clicked(self, url): + """处理链接点击事件,使用系统默认浏览器打开外部链接""" + from PyQt5.QtCore import QUrl + from PyQt5.QtGui import QDesktopServices + + # 检查URL是否为http或https协议 + if url.scheme() in ['http', 'https']: + # 使用系统默认浏览器打开链接 + QDesktopServices.openUrl(url) + return + + # 处理特殊的leonapp链接 + elif url.toString().startswith('leonapp://'): + # 提取应用ID或其他参数 + # 示例: leonapp://app/123 + path = url.toString()[10:] # 移除 'leonapp://' + if path.startswith('app/'): + app_id = path[4:] # 提取应用ID + # 这里可以实现打开特定应用详情的逻辑 + from app_detail_window import AppDetailWindow + self.app_detail_window = AppDetailWindow(self.api_client, app_id, parent=self) + self.app_detail_window.show() + return + + # 显示不支持的链接类型提示 + InfoBar.warning( + title="不支持的链接类型", + content=f"无法打开链接: {url.toString()}", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.BOTTOM_RIGHT, + duration=3000, + parent=self + ) + def create_close_button(self, parent_layout): """创建关闭按钮""" button_card = SimpleCardWidget() @@ -2288,12 +2118,28 @@ class AnnouncementDetailWindow(QMainWindow): button_layout.setContentsMargins(16, 16, 16, 16) button_layout.addStretch() + # 关闭按钮 close_button = PushButton("关闭") close_button.setFixedWidth(100) close_button.clicked.connect(self.close) button_layout.addWidget(close_button) parent_layout.addWidget(button_card) + + def copy_to_clipboard(self, text): + """复制文本到剪贴板""" + from qfluentwidgets import InfoBar + clipboard = QApplication.clipboard() + clipboard.setText(text) + InfoBar.success( + title="复制成功", + content="链接已复制到剪贴板!", + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=2000, + parent=self + ) class DeveloperInfoWindow(QMainWindow): """开发者信息窗口""" diff --git a/version_list.php b/version_list.php index e35f536..de3fb08 100644 --- a/version_list.php +++ b/version_list.php @@ -141,29 +141,28 @@ while ($row = $result->fetch_assoc()) {
版本
发布日期:
-