Files
nl-utils/v1/views/home.py
2025-06-07 23:43:57 +08:00

430 lines
14 KiB
Python

import math
import random
from PyQt5.QtCore import Qt, QPropertyAnimation, QRectF, QPoint, QTimer, QParallelAnimationGroup, pyqtSignal, QPointF, \
pyqtProperty, QEasingCurve
from PyQt5.QtGui import QPixmap, QIcon, QColor, QPainter, QLinearGradient, QBrush, QRadialGradient, QConicalGradient
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QGridLayout, QSpacerItem, QSizePolicy)
class AnimatedCard(QFrame):
"""具有动画效果的卡片控件"""
def __init__(self, start_color, end_color, parent=None):
super().__init__(parent)
self.start_color = QColor(start_color)
self.end_color = QColor(end_color)
self.angle = 0
self.hover_state = False
self.init_animations()
self.setMinimumSize(300, 280)
self.setMaximumSize(300, 280)
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"shadow")
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 Home(QWidget):
app_selected = pyqtSignal(str)
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.setObjectName("appCenter")
self.setStyleSheet(self.get_styles())
layout = QVBoxLayout()
layout.setContentsMargins(40, 30, 40, 30)
layout.setSpacing(20)
# 标题区域
title_layout = QHBoxLayout()
title = QLabel("首页")
title.setObjectName("pageTitle")
title_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
title_layout.addWidget(title)
title_layout.addSpacerItem(title_spacer)
layout.addLayout(title_layout)
# 卡片容器
grid_layout = QVBoxLayout()
grid_layout.setSpacing(30)
# 应用卡片
apps = [
{
"id": "word_to_pdf",
"title": "Word转PDF",
"desc": "批量将Word文档转换为PDF格式",
"color": ("#6366F1", "#4F46E5"), # 紫色
"icon": "word_to_pdf"
},
{
"id": "ocr",
"title": "OCR文字识别",
"desc": "从图片中提取文字内容",
"color": ("#4ADE80", "#22C55E"), # 绿色
"icon": "ocr"
},
{
"id": "image_convert",
"title": "图片格式转换",
"desc": "转换图像文件格式并优化",
"color": ("#F97316", "#EA580C"), # 橙色
"icon": "image_convert"
},
{
"id": "pdf_merge",
"title": "PDF合并工具",
"desc": "合并多个PDF文件为一个",
"color": ("#8B5CF6", "#7C3AED"), # 紫色
"icon": "pdf_merge"
},
{
"id": "pdf_split",
"title": "PDF拆分工具",
"desc": "拆分PDF文件为多个文档",
"color": ("#EC4899", "#DB2777"), # 粉色
"icon": "pdf_split"
},
{
"id": "video_compress",
"title": "视频压缩",
"desc": "减小视频文件大小",
"color": ("#3B82F6", "#2563EB"), # 蓝色
"icon": "video_compress"
}
]
# 每行显示3张卡片
for i in range(0, len(apps), 3):
row_layout = QHBoxLayout()
row_layout.setSpacing(30)
row_layout.setAlignment(Qt.AlignCenter)
for j in range(3):
index = i + j
if index < len(apps):
row_layout.addWidget(self.create_app_card(apps[index]))
else:
# 添加空白占位符保持布局
spacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)
row_layout.addSpacerItem(spacer)
grid_layout.addLayout(row_layout)
layout.addLayout(grid_layout)
layout.addStretch()
self.setLayout(layout)
# 添加背景粒子效果
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 """
/* ===== 应用中心样式 ===== */
#appCenter {
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;
}
#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;
}
#appCardBtn {
background-color: rgba(255, 255, 255, 0.9);
color: #1a202c;
border-radius: 12px;
padding: 12px 24px;
font-weight: 600;
border: none;
min-width: 140px;
transition: all 0.3s ease;
font-size: 16px;
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;
}
"""
def create_app_card(self, app):
"""创建应用卡片"""
card = AnimatedCard(app["color"][0], app["color"][1])
card.setObjectName("appCard")
layout = QVBoxLayout(card)
layout.setContentsMargins(25, 25, 25, 25)
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)
# 添加伸缩空间保持底部对齐
layout.addStretch()
# 按钮
btn_text = "开始使用" if app["id"] != "coming_soon" else "敬请期待"
btn = QPushButton(btn_text)
btn.setObjectName("appCardBtn")
btn.setCursor(Qt.PointingHandCursor)
if app["id"] == "coming_soon":
btn.setEnabled(False)
btn.setStyleSheet("""
#appCardBtn:disabled {
background-color: rgba(255, 255, 255, 0.3);
color: rgba(26, 32, 44, 0.7);
}
""")
else:
btn.clicked.connect(lambda: self.app_selected.emit(app["id"]))
layout.addWidget(btn, 0, Qt.AlignHCenter)
# 为卡片添加点击事件
card.mousePressEvent = lambda e: self.app_selected.emit(app["id"]) if app["id"] != "coming_soon" else None
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):
"""更新粒子位置"""
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'])