Files
nl-utils/v1/views/home.py
2025-06-08 23:55:24 +08:00

430 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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文件为一个拆分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'])