feat: 添加应用详情窗口和更新检查功能
- 实现全新的应用详情窗口,包含统计信息、基本信息和描述展示 - 添加应用更新检查功能到CLI工具 - 优化版本列表页面的文件路径处理逻辑 - 升级GUI版本至Beta 0.4 - 增强公告详情页面的链接处理能力
This commit is contained in:
@@ -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):
|
||||
"""开发者信息窗口"""
|
||||
|
||||
Reference in New Issue
Block a user