import math import random from PyQt5.QtCore import Qt, QPropertyAnimation, QRectF, QPoint, QTimer, QParallelAnimationGroup, pyqtProperty, \ QEasingCurve, QSize, pyqtSignal, QPointF from PyQt5.QtGui import QPixmap, QIcon, QColor, QPainter, QLinearGradient from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame, QGridLayout, QSpacerItem, QSizePolicy, QScrollArea, QSizeGrip, QLineEdit) class AnimatedCard(QFrame): """具有动画效果的卡片控件""" def __init__(self, app_data, parent=None): super().__init__(parent) self.app_data = app_data self.start_color = QColor(app_data["color"][0]) self.end_color = QColor(app_data["color"][1]) self._angle = 0 self.hover_state = False self.init_animations() self.setMinimumSize(300, 320) self.setMaximumSize(300, 320) self.setCursor(Qt.PointingHandCursor) self.setStyleSheet(""" AnimatedCard { border-radius: 16px; border: 1px solid rgba(255, 255, 255, 0.1); transition: all 0.3s ease; } """) def init_animations(self): # 渐变角度动画 self.angle_animation = QPropertyAnimation(self, b"angle") self.angle_animation.setDuration(20000) # 20秒完成一轮 self.angle_animation.setStartValue(0) self.angle_animation.setEndValue(360) self.angle_animation.setLoopCount(-1) # 无限循环 self.angle_animation.start() # 悬停动画组 self.hover_animation = QPropertyAnimation(self, b"geometry") self.hover_animation.setDuration(300) self.hover_animation.setEasingCurve(QEasingCurve.OutBack) # 阴影动画 self.shadow_animation = QPropertyAnimation(self, b"styleSheet") self.shadow_animation.setDuration(300) # 并行动画组 self.animation_group = QParallelAnimationGroup(self) self.animation_group.addAnimation(self.hover_animation) self.animation_group.addAnimation(self.shadow_animation) def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) # 创建主渐变背景 rect = self.rect() # 添加动态渐变背景 if self.hover_state: # 悬停状态使用更亮的渐变 gradient = QLinearGradient(rect.topLeft(), rect.bottomRight()) gradient.setColorAt(0, self.start_color.lighter(120)) gradient.setColorAt(1, self.end_color.lighter(120)) else: # 普通状态 gradient = QLinearGradient(rect.topLeft(), rect.bottomRight()) gradient.setColorAt(0, self.start_color) gradient.setColorAt(1, self.end_color) # 添加动态旋转效果 cx = rect.center().x() cy = rect.center().y() radius = max(rect.width(), rect.height()) * 1.2 start_x = cx + radius * 0.5 * math.cos(math.radians(self.angle)) start_y = cy + radius * 0.5 * math.sin(math.radians(self.angle)) end_x = cx - radius * 0.5 * math.cos(math.radians(self.angle)) end_y = cy - radius * 0.5 * math.sin(math.radians(self.angle)) moving_gradient = QLinearGradient(start_x, start_y, end_x, end_y) moving_gradient.setColorAt(0, QColor(255, 255, 255, 30)) moving_gradient.setColorAt(1, QColor(255, 255, 255, 10)) # 绘制背景 painter.setBrush(gradient) painter.drawRoundedRect(rect, 16, 16) # 绘制动态效果层 painter.setBrush(moving_gradient) painter.drawRoundedRect(rect, 16, 16) # 绘制高光边框 if self.hover_state: border_gradient = QLinearGradient(rect.topLeft(), rect.topRight()) border_gradient.setColorAt(0, QColor(255, 255, 255, 100)) border_gradient.setColorAt(1, QColor(255, 255, 255, 30)) painter.setPen(QColor(255, 255, 255, 150)) painter.drawRoundedRect(rect.adjusted(1, 1, -1, -1), 16, 16) def enterEvent(self, event): self.hover_state = True self.animate_hover(True) self.update() super().enterEvent(event) def leaveEvent(self, event): self.hover_state = False self.animate_hover(False) self.update() super().leaveEvent(event) def animate_hover(self, hover): """执行悬停动画""" # 保存当前位置和大小 current_geometry = self.geometry() # 目标位置和大小 if hover: target_geometry = current_geometry.adjusted(-5, -5, 5, 5) shadow_value = 20 else: target_geometry = current_geometry.adjusted(5, 5, -5, -5) shadow_value = 5 # 设置位置动画 self.hover_animation.setStartValue(current_geometry) self.hover_animation.setEndValue(target_geometry) # 设置阴影动画 self.shadow_animation.setStartValue(self.styleSheet()) self.shadow_animation.setEndValue(f""" AnimatedCard {{ border-radius: 16px; border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 10px {shadow_value}px rgba(0, 0, 0, 0.3); transition: all 0.3s ease; }} """) # 开始动画 self.animation_group.start() @pyqtProperty(int) def angle(self): return self._angle @angle.setter def angle(self, value): self._angle = value self.update() class AppDetailWindow(QWidget): """应用详情窗口""" def __init__(self, app_data, parent=None): super().__init__(parent) self.app_data = app_data self.setObjectName("appDetail") self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup) self.setAttribute(Qt.WA_TranslucentBackground) self.init_ui() self.setStyleSheet(self.get_styles()) def init_ui(self): layout = QVBoxLayout(self) layout.setContentsMargins(20, 20, 20, 20) layout.setSpacing(0) # 标题栏 title_bar = QWidget() title_bar.setObjectName("titleBar") title_bar_layout = QHBoxLayout(title_bar) title_bar_layout.setContentsMargins(0, 0, 0, 0) title = QLabel(self.app_data["title"]) title.setObjectName("detailTitle") close_btn = QPushButton() close_btn.setObjectName("closeBtn") close_btn.setIcon(QIcon(":/icons/close.png")) close_btn.setIconSize(QSize(16, 16)) close_btn.clicked.connect(self.close) title_bar_layout.addWidget(title) title_bar_layout.addWidget(close_btn) # 内容区域 content = QWidget() content_layout = QVBoxLayout(content) content_layout.setContentsMargins(0, 15, 0, 0) # 应用信息 info_layout = QHBoxLayout() info_layout.setSpacing(20) # 左侧 - 图标和基本信息 left_panel = QWidget() left_layout = QVBoxLayout(left_panel) left_layout.setSpacing(15) # 图标 icon_frame = QFrame() icon_frame.setFixedSize(120, 120) icon_frame.setObjectName("detailIcon") icon_layout = QVBoxLayout(icon_frame) icon = QLabel() icon_pixmap = QPixmap(f":/icons/{self.app_data['icon']}.png").scaled(80, 80, Qt.KeepAspectRatio, Qt.SmoothTransformation) # 转换图标为白色 painter = QPainter(icon_pixmap) painter.setCompositionMode(QPainter.CompositionMode_SourceIn) painter.fillRect(icon_pixmap.rect(), Qt.white) painter.end() icon.setPixmap(icon_pixmap) icon.setAlignment(Qt.AlignCenter) icon_layout.addWidget(icon) # 作者信息 author_label = QLabel(f"开发者: {self.app_data['author']}") author_label.setObjectName("detailAuthor") # 版本信息 version_label = QLabel(f"版本: {self.app_data['version']}") version_label.setObjectName("detailVersion") # 上传时间 upload_label = QLabel(f"上传时间: {self.app_data['upload_time']}") upload_label.setObjectName("detailUpload") left_layout.addWidget(icon_frame, 0, Qt.AlignCenter) left_layout.addWidget(author_label) left_layout.addWidget(version_label) left_layout.addWidget(upload_label) left_layout.addStretch() # 右侧 - 描述和功能 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setSpacing(15) # 应用描述 desc_label = QLabel(self.app_data["desc"]) desc_label.setObjectName("detailDesc") desc_label.setWordWrap(True) # 功能列表 features_label = QLabel("功能特点:") features_label.setObjectName("detailSection") features_list = QWidget() features_layout = QVBoxLayout(features_list) features_layout.setSpacing(8) for feature in self.app_data["features"]: feature_item = QLabel(f"• {feature}") feature_item.setObjectName("featureItem") feature_item.setWordWrap(True) features_layout.addWidget(feature_item) # 操作按钮 btn_layout = QHBoxLayout() btn_layout.setSpacing(15) start_btn = QPushButton("立即使用") start_btn.setObjectName("detailStartBtn") start_btn.clicked.connect(lambda: self.parent().app_selected.emit(self.app_data["id"])) cancel_btn = QPushButton("返回") cancel_btn.setObjectName("detailCancelBtn") cancel_btn.clicked.connect(self.close) btn_layout.addWidget(start_btn) btn_layout.addWidget(cancel_btn) right_layout.addWidget(desc_label) right_layout.addWidget(features_label) right_layout.addWidget(features_list) right_layout.addStretch() right_layout.addLayout(btn_layout) info_layout.addWidget(left_panel) info_layout.addWidget(right_panel) content_layout.addLayout(info_layout) # 添加到主布局 layout.addWidget(title_bar) layout.addWidget(content) # 添加大小控制点 size_grip = QSizeGrip(self) size_grip.setFixedSize(16, 16) size_grip.setStyleSheet("background: transparent;") layout.addWidget(size_grip, 0, Qt.AlignBottom | Qt.AlignRight) # 设置窗口尺寸 self.resize(600, 500) def get_styles(self): return """ /* ===== 应用详情样式 ===== */ #appDetail { background-color: rgba(26, 32, 44, 0.95); border: 1px solid rgba(74, 85, 104, 0.7); border-radius: 12px; box-shadow: 0 15px 50px rgba(0, 0, 0, 0.5); } #titleBar { background-color: rgba(45, 55, 72, 0.7); border-top-left-radius: 12px; border-top-right-radius: 12px; padding: 15px 20px; } #detailTitle { font-size: 24px; font-weight: bold; color: #ffffff; } #closeBtn { background-color: transparent; border: none; padding: 5px; border-radius: 4px; } #closeBtn:hover { background-color: rgba(239, 68, 68, 0.2); } #detailIcon { background-color: rgba(255, 255, 255, 0.1); border-radius: 60px; } #detailAuthor, #detailVersion, #detailUpload { font-size: 16px; color: rgba(255, 255, 255, 0.9); padding: 5px 10px; border-radius: 6px; background-color: rgba(255, 255, 255, 0.05); } #detailDesc { font-size: 17px; color: rgba(255, 255, 255, 0.85); line-height: 1.6; } #detailSection { font-size: 19px; font-weight: 600; color: #ffffff; margin-top: 15px; } #featureItem { font-size: 15px; color: rgba(255, 255, 255, 0.8); padding-left: 10px; } #detailStartBtn { background-color: #4f46e5; color: white; border-radius: 8px; padding: 12px 30px; font-size: 16px; font-weight: 600; min-width: 140px; } #detailStartBtn:hover { background-color: #4338ca; } #detailCancelBtn { background-color: rgba(255, 255, 255, 0.1); color: rgba(255, 255, 255, 0.9); border-radius: 8px; padding: 12px 30px; font-size: 16px; min-width: 100px; } #detailCancelBtn:hover { background-color: rgba(255, 255, 255, 0.2); } """ class AppCenter(QWidget): """独立的应用中心页面""" app_selected = pyqtSignal(str) def __init__(self): super().__init__() self.setObjectName("Home") self.app_detail_window = None self.init_ui() self.setStyleSheet(self.get_styles()) def init_ui(self): # 主布局 main_layout = QVBoxLayout() main_layout.setContentsMargins(40, 30, 40, 30) main_layout.setSpacing(20) # 标题和搜索区域 header_layout = QHBoxLayout() title = QLabel("应用中心") title.setObjectName("pageTitle") # 搜索框 search_container = QFrame() search_container.setObjectName("searchContainer") search_layout = QHBoxLayout(search_container) search_layout.setContentsMargins(15, 5, 15, 5) self.search_input = QLineEdit() self.search_input.setPlaceholderText("搜索应用...") self.search_input.setObjectName("searchInput") self.search_input.textChanged.connect(self.filter_apps) # 连接信号到filter_apps方法 search_btn = QPushButton() search_btn.setObjectName("searchBtn") search_btn.setIcon(QIcon(":/icons/search.png")) search_btn.setIconSize(QSize(20, 20)) search_layout.addWidget(self.search_input) search_layout.addWidget(search_btn) header_layout.addWidget(title) header_layout.addStretch() header_layout.addWidget(search_container) main_layout.addLayout(header_layout) # 应用类别过滤 self.category_layout = QHBoxLayout() self.category_layout.setSpacing(15) categories = ["全部", "文档处理", "图像处理", "媒体工具", "其他"] for category in categories: btn = QPushButton(category) btn.setObjectName("categoryBtn") if category == "全部": btn.setProperty("active", True) btn.clicked.connect(lambda _, c=category: self.filter_by_category(c)) self.category_layout.addWidget(btn) self.category_layout.addStretch() main_layout.addLayout(self.category_layout) # 应用卡片容器 self.apps_container = QWidget() self.apps_layout = QGridLayout(self.apps_container) self.apps_layout.setSpacing(30) self.apps_layout.setAlignment(Qt.AlignTop) # 滚动区域 scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setWidget(self.apps_container) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll_area.setStyleSheet("border: none; background: transparent;") main_layout.addWidget(scroll_area) self.setLayout(main_layout) # 初始化应用数据 self.apps = self.get_apps_data() self.display_apps(self.apps) # 添加背景粒子效果 self.particles = [] self.init_particles() self.particle_timer = QTimer(self) self.particle_timer.timeout.connect(self.update_particles) self.particle_timer.start(30) # 30ms更新一次 def get_styles(self): return """ /* ===== 应用中心页面样式 ===== */ #Home { background-color: #1a202c; } #pageTitle { font-size: 28px; font-weight: bold; color: #ffffff; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); margin-bottom: 30px; } #searchContainer { background-color: rgba(45, 55, 72, 0.5); border-radius: 25px; border: 1px solid rgba(74, 85, 104, 0.5); } #searchInput { background-color: transparent; border: none; color: #e2e8f0; font-size: 15px; min-width: 250px; } #searchInput:focus { outline: none; } #searchBtn { background-color: transparent; border: none; border-radius: 15px; padding: 5px; } #searchBtn:hover { background-color: rgba(129, 140, 248, 0.2); } #categoryBtn { background-color: rgba(45, 55, 72, 0.5); color: #a0aec0; border-radius: 20px; padding: 8px 20px; font-size: 14px; border: none; transition: all 0.3s ease; } #categoryBtn:hover { background-color: rgba(79, 70, 229, 0.3); color: #e2e8f0; } #categoryBtn[active="true"] { background-color: #4f46e5; color: white; } /* 应用卡片样式 */ #appCardTitle { font-size: 22px; font-weight: 700; color: white; text-align: center; margin-top: 15px; letter-spacing: 0.5px; } #appCardDesc { font-size: 15px; color: rgba(255, 255, 255, 0.8); text-align: center; padding: 0 15px; line-height: 1.5; } #appCardAuthor { font-size: 14px; color: rgba(255, 255, 255, 0.7); text-align: center; padding: 0 15px; } #appCardDate { font-size: 13px; color: rgba(255, 255, 255, 0.6); text-align: center; padding: 0 15px; } #appCardBtn { background-color: rgba(255, 255, 255, 0.9); color: #1a202c; border-radius: 12px; padding: 10px 20px; font-weight: 600; border: none; min-width: 120px; transition: all 0.3s ease; font-size: 15px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } #appCardBtn:hover { background-color: white; transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); } #appCardIcon { background-color: rgba(255, 255, 255, 0.15); border-radius: 50%; transition: all 0.3s ease; } #detailBtn { background-color: transparent; border: none; color: rgba(255, 255, 255, 0.7); font-size: 13px; padding: 5px; text-decoration: underline; } #detailBtn:hover { color: white; } """ def get_apps_data(self): """获取应用数据""" return [ { "id": "word_to_pdf", "title": "Word转PDF", "desc": "批量将Word文档转换为PDF格式", "color": ("#6366F1", "#4F46E5"), "icon": "word_to_pdf", "author": "李明", "upload_time": "2023-06-15", "version": "1.2.0", "category": "文档处理", "features": [ "支持批量转换多个Word文件", "保持原始文档格式", "支持DOC和DOCX格式", "转换速度快,效率高" ] }, { "id": "ocr", "title": "OCR文字识别", "desc": "从图片中提取文字内容", "color": ("#4ADE80", "#22C55E"), "icon": "ocr", "author": "张伟", "upload_time": "2023-07-22", "version": "2.0.1", "category": "图像处理", "features": [ "支持多种图片格式", "识别准确率高", "支持多语言识别", "导出为可编辑文本" ] }, { "id": "image_convert", "title": "图片格式转换", "desc": "转换图像文件格式并优化", "color": ("#F97316", "#EA580C"), "icon": "image_convert", "author": "王芳", "upload_time": "2023-08-10", "version": "1.5.3", "category": "图像处理", "features": [ "支持JPEG、PNG、WEBP等格式", "批量转换功能", "图片压缩优化", "保持图片质量" ] }, { "id": "pdf_merge", "title": "PDF合并工具", "desc": "合并多个PDF文件为一个", "color": ("#8B5CF6", "#7C3AED"), "icon": "pdf_merge", "author": "刘强", "upload_time": "2023-09-05", "version": "1.1.0", "category": "文档处理", "features": [ "拖拽排序PDF文件", "自定义合并顺序", "保留原始文档质量", "支持大文件处理" ] }, { "id": "pdf_split", "title": "PDF拆分工具", "desc": "拆分PDF文件为多个文档", "color": ("#EC4899", "#DB2777"), "icon": "pdf_split", "author": "陈晓", "upload_time": "2023-09-18", "version": "1.0.5", "category": "文档处理", "features": [ "按页码拆分PDF", "按书签拆分PDF", "自定义拆分范围", "批量处理多个文件" ] }, { "id": "video_compress", "title": "视频压缩", "desc": "减小视频文件大小", "color": ("#3B82F6", "#2563EB"), "icon": "video_compress", "author": "赵雷", "upload_time": "2023-10-02", "version": "1.3.2", "category": "媒体工具", "features": [ "支持多种视频格式", "自定义压缩参数", "保持视频质量", "批量处理功能" ] }, { "id": "audio_extract", "title": "音频提取", "desc": "从视频中提取音频", "color": ("#06B6D4", "#0891B2"), "icon": "audio_extract", "author": "杨帆", "upload_time": "2023-10-15", "version": "1.0.3", "category": "媒体工具", "features": [ "支持多种视频格式", "提取高质量音频", "批量处理功能", "快速处理速度" ] }, { "id": "file_encrypt", "title": "文件加密", "desc": "保护您的敏感文件", "color": ("#F59E0B", "#D97706"), "icon": "file_encrypt", "author": "周涛", "upload_time": "2023-10-28", "version": "1.1.2", "category": "其他", "features": [ "AES-256加密算法", "支持多种文件类型", "密码保护", "批量加密功能" ] }, { "id": "text_summary", "title": "文本摘要", "desc": "自动提取文本核心内容", "color": ("#10B981", "#059669"), "icon": "text_summary", "author": "吴明", "upload_time": "2023-11-10", "version": "1.0.1", "category": "文本处理", "features": [ "智能提取关键信息", "支持多种语言", "可调节摘要长度", "高精度结果输出" ] } ] def display_apps(self, apps): """显示应用卡片""" # 清空现有卡片 for i in reversed(range(self.apps_layout.count())): widget = self.apps_layout.itemAt(i).widget() if widget: widget.deleteLater() # 添加新卡片 row, col = 0, 0 max_cols = 3 # 每行最多3个卡片 for app in apps: card = self.create_app_card(app) self.apps_layout.addWidget(card, row, col) col += 1 if col >= max_cols: col = 0 row += 1 # 如果最后一行不满,添加空白占位 if col != 0 and col < max_cols: for i in range(col, max_cols): spacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum) self.apps_layout.addItem(spacer, row, i) def create_app_card(self, app): """创建应用卡片""" card = AnimatedCard(app) card.setObjectName("appCard") layout = QVBoxLayout(card) layout.setContentsMargins(25, 25, 25, 20) layout.setSpacing(15) # 图标 icon_frame = QFrame() icon_frame.setFixedSize(90, 90) icon_frame.setObjectName("appCardIcon") icon_layout = QVBoxLayout(icon_frame) icon_layout.setContentsMargins(0, 0, 0, 0) icon = QLabel() icon_pixmap = QPixmap(f":/icons/{app['icon']}.png").scaled(56, 56, Qt.KeepAspectRatio, Qt.SmoothTransformation) # 转换图标为白色 painter = QPainter(icon_pixmap) painter.setCompositionMode(QPainter.CompositionMode_SourceIn) painter.fillRect(icon_pixmap.rect(), Qt.white) painter.end() icon.setPixmap(icon_pixmap) icon.setAlignment(Qt.AlignCenter) icon_layout.addWidget(icon) layout.addWidget(icon_frame, 0, Qt.AlignHCenter) # 标题 title = QLabel(app["title"]) title.setObjectName("appCardTitle") layout.addWidget(title) # 描述 desc = QLabel(app["desc"]) desc.setObjectName("appCardDesc") desc.setWordWrap(True) layout.addWidget(desc) # 作者信息 author = QLabel(f"开发者: {app['author']}") author.setObjectName("appCardAuthor") layout.addWidget(author) # 上传时间 date = QLabel(f"上传时间: {app['upload_time']}") date.setObjectName("appCardDate") layout.addWidget(date) # 添加伸缩空间保持底部对齐 layout.addStretch() # 按钮区域 btn_layout = QHBoxLayout() btn_layout.setSpacing(10) # 开始使用按钮 start_btn = QPushButton("开始使用") start_btn.setObjectName("appCardBtn") start_btn.setCursor(Qt.PointingHandCursor) start_btn.clicked.connect(lambda: self.app_selected.emit(app["id"])) # 详情按钮 detail_btn = QPushButton("详情") detail_btn.setObjectName("detailBtn") detail_btn.setCursor(Qt.PointingHandCursor) detail_btn.clicked.connect(lambda: self.show_app_detail(app)) btn_layout.addWidget(start_btn) btn_layout.addWidget(detail_btn) layout.addLayout(btn_layout) return card def init_particles(self): """初始化背景粒子""" for _ in range(20): particle = { 'x': random.randint(0, self.width()), 'y': random.randint(0, self.height()), 'size': random.randint(2, 6), 'speed': random.uniform(0.5, 2), 'color': random.choice([ QColor(79, 70, 229, 80), QColor(129, 140, 248, 60), QColor(59, 130, 246, 70), QColor(16, 185, 129, 60) ]) } self.particles.append(particle) def update_particles(self): """更新粒子位置""" if not self.isVisible(): return for particle in self.particles: particle['y'] += particle['speed'] if particle['y'] > self.height(): particle['y'] = 0 particle['x'] = random.randint(0, self.width()) self.update() def paintEvent(self, event): """绘制背景粒子效果""" super().paintEvent(event) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) # 绘制粒子 for particle in self.particles: painter.setBrush(particle['color']) painter.drawEllipse(QPointF(particle['x'], particle['y']), particle['size'], particle['size']) def filter_apps(self): """根据搜索关键词过滤应用""" keyword = self.search_input.text().strip().lower() if not keyword: self.display_apps(self.apps) return filtered_apps = [ app for app in self.apps if (keyword in app["title"].lower() or keyword in app["desc"].lower() or keyword in app["author"].lower() or keyword in app["category"].lower()) ] self.display_apps(filtered_apps) def filter_by_category(self, category): """根据类别过滤应用""" # 更新按钮状态 for i in range(self.category_layout.count()): btn = self.category_layout.itemAt(i).widget() if btn: btn.setProperty("active", btn.text() == category) # 重新应用样式 btn.style().unpolish(btn) btn.style().polish(btn) if category == "全部": self.display_apps(self.apps) return filtered_apps = [app for app in self.apps if app["category"] == category] self.display_apps(filtered_apps) def show_app_detail(self, app_data): """显示应用详情窗口""" # 关闭现有详情窗口 if self.app_detail_window: self.app_detail_window.close() self.app_detail_window = AppDetailWindow(app_data) self.app_detail_window.setParent(self) # 计算位置 - 居中显示 parent_rect = self.geometry() x = parent_rect.x() + (parent_rect.width() - self.app_detail_window.width()) // 2 y = parent_rect.y() + (parent_rect.height() - self.app_detail_window.height()) // 2 self.app_detail_window.move(x, y) self.app_detail_window.show()