diff --git a/pyqt5fluentdesign/__pycache__/app_detail_window.cpython-312.pyc b/pyqt5fluentdesign/__pycache__/app_detail_window.cpython-312.pyc index ec9d2b9..01dcbda 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/__pycache__/leonapp_gui.cpython-312.pyc b/pyqt5fluentdesign/__pycache__/leonapp_gui.cpython-312.pyc index a3c4e1d..46b4a5c 100644 Binary files a/pyqt5fluentdesign/__pycache__/leonapp_gui.cpython-312.pyc and b/pyqt5fluentdesign/__pycache__/leonapp_gui.cpython-312.pyc differ diff --git a/pyqt5fluentdesign/app_detail_window.py b/pyqt5fluentdesign/app_detail_window.py index 95230ab..af4a612 100644 --- a/pyqt5fluentdesign/app_detail_window.py +++ b/pyqt5fluentdesign/app_detail_window.py @@ -87,6 +87,9 @@ class AppDetailWindow(QMainWindow): self.app_id = app_id self.parent_window = parent + # 存储当前加载的本地缓存图片路径列表 + self.cached_image_paths = [] + # 设置窗口属性 self.setWindowTitle("应用详情") self.resize(850, 650) @@ -258,11 +261,13 @@ class AppDetailWindow(QMainWindow): result = self.api_client.make_request('getappinfo', {'id': self.app_id}) # 检查API调用是否成功 - if isinstance(result, dict) and 'success' in result and result['success']: - # 保存应用数据到实例属性 + if isinstance(result, dict) and 'data' in result and result['data'] is not None: + # 新API格式: {"status": "success", "data": {...}} + self.app_data = result['data'] + app_data = self.app_data + elif isinstance(result, dict) and 'success' in result and result['success']: + # 旧API格式兼容性支持 self.app_data = result['data'] - - # 为了保持向后兼容性,也保留局部变量 app_data = self.app_data else: # API调用失败,显示错误信息 @@ -287,7 +292,8 @@ class AppDetailWindow(QMainWindow): "created_at": "-", "downloads": "0", "rating": "0.0", - "category": "未知" + "category": "未知", + "images": [] } app_data = self.app_data @@ -363,6 +369,11 @@ class AppDetailWindow(QMainWindow): # 显示统计信息卡片 self.display_stats_card(app_data) + # 显示APP预览图片(如果有) + images = app_data.get('images', []) + if images and isinstance(images, list) and len(images) > 0: + self.display_app_images(images) + # 显示应用基本信息卡片 self.display_app_info_card(app_data) @@ -440,6 +451,286 @@ class AppDetailWindow(QMainWindow): self.scroll_layout.addWidget(stats_card) + def display_app_images(self, images): + """显示应用预览图片,使用QFluentWidgets的HorizontalFlipView组件""" + # 导入HorizontalFlipView组件 + from qfluentwidgets import HorizontalFlipView + import os + import requests + import uuid + from PyQt5.QtCore import Qt, QDir + from PyQt5.QtGui import QPixmap + from PyQt5.QtWidgets import QLabel + + # 打印传入的images数据 + logger.info(f"接收到的图片数据: {images}") + logger.info(f"图片数据类型: {type(images)}") + logger.info(f"图片数据长度: {len(images) if isinstance(images, list) else '不是列表'}") + + # 创建缓存文件夹 + cache_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cache') + if not os.path.exists(cache_dir): + os.makedirs(cache_dir) + logger.info(f"创建缓存文件夹: {cache_dir}") + + # 创建图片预览卡片 + images_card = CardWidget() + images_card.setObjectName("ImagesCard") + images_card.setStyleSheet(""" + #ImagesCard { + background-color: white; + border-radius: 12px; + border: 1px solid rgba(0, 0, 0, 0.05); + } + """) + + card_layout = QVBoxLayout(images_card) + card_layout.setContentsMargins(20, 20, 20, 20) + card_layout.setSpacing(15) + + # 卡片标题和图标 + title_layout = QHBoxLayout() + title_icon = QLabel() + title_icon.setPixmap(FluentIcon.PHOTO.icon().pixmap(18, 18)) + title_icon.setStyleSheet("color: #9C27B0;") + + 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) + + # 创建HorizontalFlipView组件 + flip_view = HorizontalFlipView(self) + flip_view.setFixedSize(800, 450) + flip_view.setBorderRadius(12) + + # 准备图片URL列表 + valid_image_urls = [] + base_domain = 'http://leonmmcoset.jjxmm.win:8010' + logger.info(f"使用的基础域名: {base_domain}") + + # 验证并添加有效的图片URL + if isinstance(images, list): + logger.info(f"开始处理{len(images)}个图片项") + for index, image_data in enumerate(images): + logger.info(f"处理第{index+1}个图片项: {image_data}") + logger.info(f"图片项类型: {type(image_data)}") + + try: + if isinstance(image_data, dict): + logger.info(f"图片字典键: {image_data.keys()}") + + if 'image_path' in image_data: + image_path = image_data['image_path'] + logger.info(f"image_path值: {image_path}") + logger.info(f"image_path类型: {type(image_path)}") + + # 确保image_path是字符串类型 + if not isinstance(image_path, str): + logger.warning(f"图片路径不是字符串类型: {type(image_path)}") + continue + + # 构建完整的图片URL + logger.info(f"图片路径是否以http开头: {image_path.startswith(('http://', 'https://'))}") + logger.info(f"图片路径是否以/开头: {image_path.startswith('/')}") + + if not image_path.startswith(('http://', 'https://')): + # 处理路径格式 + if image_path.startswith('/'): + image_path = image_path[1:] + logger.info(f"修正后的路径: {image_path}") + image_url = f'{base_domain}/{image_path}' + else: + image_url = image_path + + logger.info(f"构建的完整URL: {image_url}") + + # 检查URL是否有效 + if image_url and len(image_url) > 0: + valid_image_urls.append(image_url) + logger.info(f"添加有效图片URL: {image_url}") + else: + logger.warning(f"图片项中没有'image_path'键") + else: + logger.warning(f"图片项不是字典类型: {type(image_data)}") + except Exception as e: + logger.error(f"处理图片数据时出错: {str(e)}") + else: + logger.error(f"images参数不是列表类型: {type(images)}") + + logger.info(f"最终有效图片URL列表: {valid_image_urls}") + logger.info(f"有效图片数量: {len(valid_image_urls)}") + + # 添加图片,如果没有有效图片则显示默认提示 + if valid_image_urls: + # 清空之前的缓存记录 + self.cached_image_paths = [] + + # 下载并显示图片 + successful_images = 0 + for index, image_url in enumerate(valid_image_urls): + try: + logger.info(f"准备处理第{index+1}张图片: {image_url}") + + # 生成唯一的缓存文件名 + file_extension = os.path.splitext(image_url)[1] or '.png' + cache_filename = f"{uuid.uuid4()}{file_extension}" + cache_file_path = os.path.join(cache_dir, cache_filename) + + logger.info(f"准备下载到缓存文件: {cache_file_path}") + + # 下载图片 + response = requests.get(image_url, timeout=10) + if response.status_code == 200: + logger.info(f"成功下载图片,状态码: 200") + + # 保存图片到本地缓存 + with open(cache_file_path, 'wb') as f: + f.write(response.content) + logger.info(f"图片已保存到: {cache_file_path}") + + # 记录缓存文件路径 + self.cached_image_paths.append(cache_file_path) + + # 尝试从本地文件加载图片 + pixmap = QPixmap() + if pixmap.load(cache_file_path): + logger.info(f"成功从本地文件加载图片: {cache_file_path}") + + # 使用HorizontalFlipView的addImage方法添加本地文件路径 + flip_view.addImage(cache_file_path) + successful_images += 1 + logger.info(f"成功添加第{index+1}张图片到显示组件") + else: + logger.error(f"无法加载本地缓存图片: {cache_file_path}") + # 如果加载失败,删除缓存文件 + if os.path.exists(cache_file_path): + os.remove(cache_file_path) + logger.info(f"删除无效的缓存文件: {cache_file_path}") + else: + logger.error(f"下载图片失败,状态码: {response.status_code}") + except Exception as e: + logger.error(f"处理图片 {image_url} 时出错: {str(e)}") + + logger.info(f"成功加载图片数量: {successful_images}") + logger.info(f"当前缓存图片路径列表: {self.cached_image_paths}") + + # 如果没有成功加载任何图片,显示错误信息 + if successful_images == 0: + from qfluentwidgets import BodyLabel + logger.warning("没有成功加载任何图片,显示错误提示") + error_label = BodyLabel("无法加载预览图片") + error_label.setAlignment(Qt.AlignCenter) + error_label.setStyleSheet("color: #e74c3c; font-size: 16px;") + error_label.setFixedSize(800, 450) + + flip_view_layout = QHBoxLayout() + flip_view_layout.addStretch() + flip_view_layout.addWidget(error_label) + flip_view_layout.addStretch() + + card_layout.addLayout(flip_view_layout) + else: + # 监听当前页码改变信号 + flip_view.currentIndexChanged.connect(lambda index: logger.info(f"当前页面:{index}")) + + # 居中显示HorizontalFlipView + flip_view_layout = QHBoxLayout() + flip_view_layout.addStretch() + flip_view_layout.addWidget(flip_view) + flip_view_layout.addStretch() + + card_layout.addLayout(flip_view_layout) + else: + # 没有有效图片时显示提示信息 + from qfluentwidgets import BodyLabel + logger.warning("没有有效图片URL,显示无图片提示") + no_image_label = BodyLabel("暂无预览图片") + no_image_label.setAlignment(Qt.AlignCenter) + no_image_label.setStyleSheet("color: #999; font-size: 16px;") + no_image_label.setFixedSize(800, 450) + + # 替换flip_view为提示标签 + flip_view_layout = QHBoxLayout() + flip_view_layout.addStretch() + flip_view_layout.addWidget(no_image_label) + flip_view_layout.addStretch() + + # 添加到卡片布局 + card_layout.addLayout(flip_view_layout) + + # 添加到主滚动布局 + self.scroll_layout.addWidget(images_card) + + def cleanup_cached_images(self): + """清理缓存的图片文件""" + logger.info(f"开始清理缓存图片,共{len(self.cached_image_paths)}个文件") + + try: + # 首先清理记录在cached_image_paths中的文件 + for image_path in self.cached_image_paths: + try: + if os.path.exists(image_path): + os.remove(image_path) + logger.info(f"成功删除缓存图片: {image_path}") + else: + logger.warning(f"缓存图片不存在: {image_path}") + except Exception as e: + logger.error(f"删除缓存图片失败 {image_path}: {str(e)}") + + # 清空缓存路径列表 + self.cached_image_paths = [] + + # 额外清理整个cache文件夹中的所有图片文件 + cache_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cache') + if os.path.exists(cache_dir): + logger.info(f"开始清理整个缓存文件夹: {cache_dir}") + for filename in os.listdir(cache_dir): + file_path = os.path.join(cache_dir, filename) + try: + if os.path.isfile(file_path) and filename.endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp')): + os.remove(file_path) + logger.info(f"成功删除缓存文件夹中的文件: {filename}") + except Exception as e: + logger.error(f"删除缓存文件夹中的文件 {filename} 失败: {str(e)}") + + logger.info("缓存图片清理完成") + except Exception as e: + logger.error(f"缓存清理过程中出现错误: {str(e)}") + + def closeEvent(self, event): + """窗口关闭事件,清理缓存的图片""" + logger.info("窗口即将关闭,开始清理缓存图片") + self.cleanup_cached_images() + super().closeEvent(event) + + def __del__(self): + """析构函数,确保在对象被销毁时也能清理缓存""" + logger.info("对象即将销毁,执行最终缓存清理") + # 确保在应用意外退出时也能清理缓存 + try: + self.cleanup_cached_images() + except: + # 避免析构函数中抛出异常 + pass + + def _check_image_accessible(self, image_url): + """检查图片URL是否可访问""" + try: + # 发送HEAD请求检查图片是否可访问 + logger.info(f"检查图片URL可访问性: {image_url}") + response = requests.head(image_url, timeout=5) + logger.info(f"图片URL状态码: {response.status_code}") + return response.status_code == 200 + except Exception as e: + logger.error(f"检查图片URL可访问性失败: {str(e)}") + return False + + + def display_app_info_card(self, app_data): """显示应用基本信息卡片""" info_card = CardWidget() diff --git a/pyqt5fluentdesign/config/config.json b/pyqt5fluentdesign/config/config.json index 5c9892e..5444e8a 100644 --- a/pyqt5fluentdesign/config/config.json +++ b/pyqt5fluentdesign/config/config.json @@ -2,7 +2,7 @@ "General": { "AppOpenMethod": 1, "AutoOpenInstaller": false, - "MicaEffect": false + "MicaEffect": true }, "QFluentWidgets": { "ThemeColor": "#ff4169e1", diff --git a/pyqt5fluentdesign/leonapp_gui.py b/pyqt5fluentdesign/leonapp_gui.py index fbc3450..a1eaf1d 100644 --- a/pyqt5fluentdesign/leonapp_gui.py +++ b/pyqt5fluentdesign/leonapp_gui.py @@ -48,7 +48,7 @@ from qfluentwidgets import ( PrimaryPushButton, LineEdit, ComboBox, ProgressBar, TableWidget, ScrollArea, InfoBar, InfoBarPosition, NavigationInterface, NavigationItemPosition, FluentWindow, MSFluentWindow, FluentIcon, SimpleCardWidget, PrimaryPushSettingCard, - OptionsSettingCard, QConfig, ConfigItem, OptionsConfigItem, BoolValidator, OptionsValidator, qconfig, SplashScreen + OptionsSettingCard, QConfig, ConfigItem, OptionsConfigItem, BoolValidator, OptionsValidator, qconfig, SplashScreen, NavigationAvatarWidget ) from qfluentwidgets import FluentTranslator from app_detail_window import AppDetailWindow @@ -69,11 +69,7 @@ class AppConfig(QConfig): OptionsValidator([0, 1]) ) - # 云母效果 - mica_effect = OptionsConfigItem( - "General", "MicaEffect", True, - OptionsValidator([True, False]) - ) + # 配置项已删除云母效果 # 创建配置文件目录 config_dir = os.path.join(os.path.dirname(__file__), "config") @@ -2630,9 +2626,14 @@ class SettingsTab(QWidget): # 创建主布局 self.main_layout = QVBoxLayout(self) - # 创建滚动区域 + # 创建滚动区域并设置无样式 self.scroll_area = ScrollArea(self) self.scroll_area.setWidgetResizable(True) + # 设置无样式但保留内容显示 + self.scroll_area.setFrameShape(QFrame.NoFrame) + self.scroll_area.setStyleSheet("background-color: transparent; border: none;") + self.scroll_area.verticalScrollBar().setStyleSheet("QScrollBar:vertical { width: 0px; }") + self.scroll_area.horizontalScrollBar().setStyleSheet("QScrollBar:horizontal { height: 0px; }") # 创建滚动内容控件 self.scroll_content = QWidget() @@ -2682,25 +2683,9 @@ class SettingsTab(QWidget): general_layout.addWidget(self.app_open_method_option) - # 添加云母效果选项 - self.mica_effect_option = OptionsSettingCard( - icon=FluentIcon.INFO, - title="启用云母效果", - content="开启窗口的云母效果,提供更现代的视觉体验", - texts=["是", "否"], - configItem=app_config.mica_effect, - parent=general_card - ) + # 已删除云母效果选项 - # 连接信号 - self.mica_effect_option.optionChanged.connect(self.on_mica_effect_changed) - - general_layout.addWidget(self.mica_effect_option) - - # 连接信号 - self.mica_effect_option.optionChanged.connect(self.on_mica_effect_changed) - - # 已经在前面添加过了,这里不需要重复添加 + # 已经删除了云母效果选项 # 添加查看日志按钮 general_layout.addSpacing(20) @@ -2729,23 +2714,7 @@ class SettingsTab(QWidget): """应用打开方式设置变更处理""" app_config.app_open_method = index - def on_mica_effect_changed(self, index): - """云母效果设置变更处理""" - app_config.mica_effect = (index == 0) - # 显示提示,告知用户需要重启应用才能生效 - from qfluentwidgets import InfoBar, InfoBarPosition - from PyQt5.QtWidgets import QApplication - - # 创建Sweet Alert风格的提示 - InfoBar.warning( - title="需要重启应用", - content="云母效果设置已更新,需要重启应用才能生效。", - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.TOP_RIGHT, - duration=3000, - parent=self - ) + # 已删除云母效果处理方法 def show_logs(self): """显示应用日志""" @@ -2972,22 +2941,8 @@ class LeonAppGUI(MSFluentWindow): if os.path.exists(icon_path): self.setWindowIcon(QIcon(icon_path)) - # 根据配置启用云母效果 - from qfluentwidgets import isDarkTheme - if app_config.mica_effect: - # 优先尝试设置云母效果 - if hasattr(self, 'setMicaEffect'): - self.setMicaEffect(True) - # 如果没有直接的云母效果方法,尝试使用亚克力效果作为替代 - elif hasattr(self, 'setAcrylicEffect'): - self.setAcrylicEffect(True) - # 如果都没有,使用窗口透明度作为最后的替代 - elif hasattr(self, 'setWindowOpacity'): - self.setWindowOpacity(0.95) - else: - # 禁用效果,恢复为完全不透明 - if hasattr(self, 'setWindowOpacity'): - self.setWindowOpacity(1.0) + # 已删除云母效果相关代码 + # 保持窗口默认不透明状态 # 初始化UI self.init_ui()