994 lines
32 KiB
Python
994 lines
32 KiB
Python
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()
|