feat(app_detail_window): 添加应用预览图片功能并优化缓存管理

- 新增HorizontalFlipView组件显示应用预览图片
- 实现图片下载缓存机制和自动清理功能
- 支持多种API返回格式的图片数据解析
- 添加详细的日志记录和错误处理
- 移除不再使用的云母效果相关代码
This commit is contained in:
2025-09-28 19:19:36 +08:00
parent abdee75504
commit 1b18e4b189
5 changed files with 310 additions and 64 deletions

View File

@@ -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()

View File

@@ -2,7 +2,7 @@
"General": {
"AppOpenMethod": 1,
"AutoOpenInstaller": false,
"MicaEffect": false
"MicaEffect": true
},
"QFluentWidgets": {
"ThemeColor": "#ff4169e1",

View File

@@ -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()