This commit is contained in:
2025-06-07 23:43:57 +08:00
parent 12a583295d
commit 2e29f7a1f7
7 changed files with 3245 additions and 392 deletions

993
test/t.py Normal file
View File

@@ -0,0 +1,993 @@
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()

View File

@@ -1,10 +1,11 @@
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QStackedWidget, QHBoxLayout, QWidget
from PyQt5.QtWidgets import QApplication, QMainWindow, QStackedWidget, QHBoxLayout, QWidget, QMessageBox
from PyQt5.QtCore import Qt
from views.login import LoginPage
from views.nav import NavBar
from views.app_center import AppCenter
from views.home import Home
from views.word_to_pdf import WordToPDFPage
from views.app_center import AppCenter
from views.history import HistoryPage
from PyQt5.QtGui import QPalette, QColor
@@ -33,21 +34,29 @@ QLineEdit:focus {
outline: none;
}
/* 按钮通用样式 */
/* ===== 按钮全局样式 ===== */
QPushButton {
background-color: #4f46e5;
color: white;
border-radius: 6px;
padding: 8px 16px;
border: none;
min-height: 35px;
border-radius: 8px; /* 增加圆角程度 */
padding: 10px 16px;
min-height: 40px; /* 增加高度 */
font-weight: 500;
transition: all 0.2s ease;
border: none;
outline: none; /* 防止点击时出现虚线边框 */
}
QPushButton:hover {
background-color: #4338ca;
transform: translateY(-1px);
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
QPushButton:pressed {
background-color: #3730a3;
transform: translateY(0);
box-shadow: none;
}
QPushButton:disabled {
@@ -55,6 +64,94 @@ QPushButton:disabled {
color: #a0aec0;
}
/* 特殊按钮样式 */
QPushButton#navBtn {
text-align: left;
padding: 12px 25px;
color: #a0aec0;
border-radius: 8px;
margin: 5px 15px;
font-size: 14px;
background-color: transparent;
border: none;
min-height: auto; /* 覆盖全局最小高度 */
}
QPushButton#navBtn:hover {
background-color: rgba(79, 70, 229, 0.1);
color: #cbd5e0;
}
QPushButton#navBtn.active {
background-color: rgba(79, 70, 229, 0.15);
color: #818cf8;
font-weight: 500;
}
QPushButton#logoutBtn {
text-align: left;
padding: 12px 25px;
border-radius: 8px;
margin: 5px 15px;
font-size: 14px;
color: #feb2b2;
background-color: transparent;
border: none;
min-height: auto; /* 覆盖全局最小高度 */
}
QPushButton#logoutBtn:hover {
color: #f56565;
}
/* 操作按钮样式 */
QPushButton#actionBtn {
background-color: rgba(129, 140, 248, 0.1);
color: #818cf8;
border: 1px solid rgba(129, 140, 248, 0.2);
min-height: auto; /* 覆盖全局最小高度 */
}
QPushButton#actionBtn:hover {
background-color: rgba(129, 140, 248, 0.2);
}
QPushButton#retryBtn {
background-color: rgba(251, 113, 133, 0.1);
color: #fb7185;
border: 1px solid rgba(251, 113, 133, 0.2);
min-height: auto; /* 覆盖全局最小高度 */
}
QPushButton#retryBtn:hover {
background-color: rgba(251, 113, 133, 0.2);
}
QPushButton#deleteBtn {
background-color: rgba(120, 113, 108, 0.1);
color: #a8a29e;
border: 1px solid rgba(120, 113, 108, 0.2);
min-height: auto; /* 覆盖全局最小高度 */
}
QPushButton#deleteBtn:hover {
background-color: rgba(120, 113, 108, 0.2);
}
/* 卡片按钮样式 */
QPushButton#appCardBtn {
min-height: auto; /* 覆盖全局最小高度 */
min-width: 120px;
}
/* 登录页面按钮 */
QPushButton#loginBtn {
min-height: auto; /* 覆盖全局最小高度 */
min-width: 240px;
padding: 14px 30px;
font-size: 16px;
}
/* 列表控件 */
QListWidget {
background-color: #2d3748;
@@ -110,42 +207,64 @@ QLabel {
}
/* 菜单栏 */
QMenuBar {
/* 导航栏 */
#navBar {
background-color: #1a202c;
color: #e2e8f0;
border-bottom: 1px solid #2d3748;
border-right: 1px solid #2d3748;
}
QMenuBar::item {
background-color: transparent;
padding: 4px 10px;
}
QMenuBar::item:selected {
background-color: #4f46e5;
color: white;
}
QMenu {
/* 用户卡片 */
#userCard {
background-color: #2d3748;
border: 1px solid #4a5568;
color: #e2e8f0;
padding: 4px;
border-radius: 8px;
margin: 15px;
padding: 15px;
}
QMenu::item {
padding: 6px 30px 6px 20px;
/* 导航按钮 */
#navBtn {
text-align: left;
padding: 12px 25px;
color: #a0aec0;
border-radius: 8px;
margin: 5px 15px;
font-size: 14px;
background-color: transparent;
border: none;
}
QMenu::item:selected {
background-color: #4f46e5;
color: white;
#navBtn:hover {
background-color: rgba(79, 70, 229, 0.1);
color: #cbd5e0;
}
QMenu::separator {
height: 1px;
background: #4a5568;
margin: 4px 8px;
#navBtn.active {
background-color: rgba(79, 70, 229, 0.15);
color: #818cf8;
font-weight: 500;
}
/* 退出按钮 */
#logoutBtn {
text-align: left;
padding: 12px 25px;
border-radius: 8px;
margin: 5px 15px;
font-size: 14px;
color: #feb2b2;
background-color: transparent;
border: none;
}
#logoutBtn:hover {
color: #f56565;
}
/* 分割线 */
QFrame[frameShape="4"] { /* HLine */
background-color: #2d3748;
margin: 10px 15px;
max-height: 1px;
}
/* 进度条 */
@@ -251,6 +370,10 @@ class MainWindow(QMainWindow):
self.login_page = LoginPage()
self.login_page.login_success.connect(self.handle_login_success)
# 首页
self.home = Home()
self.home.app_selected.connect(self.handle_app_selected)
# 应用中心页
self.app_center = AppCenter()
self.app_center.app_selected.connect(self.handle_app_selected)
@@ -268,6 +391,7 @@ class MainWindow(QMainWindow):
# 添加页面
self.stacked_pages.addWidget(self.login_page)
self.stacked_pages.addWidget(self.app_center)
self.stacked_pages.addWidget(self.home)
self.stacked_pages.addWidget(self.word_to_pdf)
self.stacked_pages.addWidget(self.history_page)
self.stacked_pages.addWidget(self.settings_page)
@@ -285,7 +409,7 @@ class MainWindow(QMainWindow):
self.nav_bar.show()
# 设置默认激活页
self.nav_bar.set_active_nav("home")
self.stacked_pages.setCurrentWidget(self.app_center)
self.stacked_pages.setCurrentWidget(self.home)
def handle_logout(self):
self.username = None
@@ -298,6 +422,8 @@ class MainWindow(QMainWindow):
# 根据导航ID切换页面
if nav_id == "home":
self.stacked_pages.setCurrentWidget(self.home)
if nav_id == "app_center":
self.stacked_pages.setCurrentWidget(self.app_center)
elif nav_id == "history":
self.stacked_pages.setCurrentWidget(self.history_page)
@@ -305,9 +431,35 @@ class MainWindow(QMainWindow):
self.stacked_pages.setCurrentWidget(self.settings_page)
def handle_app_selected(self, app_id):
try:
if app_id == "word_to_pdf":
self.stacked_pages.setCurrentWidget(self.word_to_pdf)
# 后续可添加其他应用
elif app_id == "history":
# 注意这里假设历史记录页面尚未开发暂时显示word_to_pdf页面
# 实际开发中应替换为正确的历史记录页面
self.stacked_pages.setCurrentWidget(self.word_to_pdf)
else:
# 未开发的应用:弹出提示弹窗
self.show_development_popup(app_id)
except Exception as e:
# 捕获并打印异常信息,避免程序闪退
print(f"处理应用选择时发生错误: {e}")
# 可以选择显示错误弹窗
QMessageBox.critical(self, "错误", f"发生错误: {str(e)}", QMessageBox.Button.Ok)
def show_development_popup(self, app_id):
"""显示开发中弹窗"""
try:
msg_box = QMessageBox(self)
msg_box.setWindowTitle("功能开发中")
msg_box.setText(f"应用 '{app_id}' 正在开发中")
msg_box.setInformativeText("我们正在努力开发此功能,敬请期待!")
msg_box.setIcon(QMessageBox.Icon.Information)
msg_box.setStandardButtons(QMessageBox.Button.Ok)
msg_box.button(QMessageBox.Button.Ok).setText("知道了")
msg_box.exec()
except Exception as e:
print(f"显示弹窗时发生错误: {e}")
def main():

File diff suppressed because it is too large Load Diff

430
v1/views/home.py Normal file
View File

@@ -0,0 +1,430 @@
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'])

View File

@@ -1,7 +1,7 @@
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QLineEdit, QPushButton, QFrame, QCheckBox,
QSpacerItem, QSizePolicy)
from PyQt5.QtCore import Qt, pyqtSignal, QPropertyAnimation, QEasingCurve, QPoint, QSize
from PyQt5.QtCore import Qt, pyqtSignal, QPropertyAnimation, QEasingCurve, QPoint, QSize, QTimer, QPointF
from PyQt5.QtGui import QPixmap, QPainter, QLinearGradient, QColor, QIcon, QFont, QBrush
@@ -12,6 +12,15 @@ class LoginPage(QWidget):
super().__init__()
self.init_ui()
self.setup_animations()
self.gradient_angle = 0
self.particle_timer = QTimer(self)
self.particle_timer.timeout.connect(self.update_particles)
self.particle_timer.start(100) # 更新粒子位置的定时器
self.particles = self.create_particles()
# 添加回车键提交支持
self.username_input.returnPressed.connect(self.handle_login)
self.password_input.returnPressed.connect(self.handle_login)
def init_ui(self):
self.setObjectName("loginPage")
@@ -24,9 +33,87 @@ class LoginPage(QWidget):
self.setLayout(main_layout)
# 左侧装饰区 - 添加玻璃态效果
left_frame = QFrame()
left_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
left_frame.setObjectName("leftDecor")
self.left_frame = QWidget() # 改为QWidget以便自定义绘制
self.left_frame.setObjectName("leftDecor")
self.left_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# 在左侧装饰区添加艺术字体和版本信息
left_layout = QVBoxLayout(self.left_frame)
left_layout.setContentsMargins(40, 40, 40, 40)
left_layout.addStretch()
# 艺术字体标题
title_container = QFrame()
title_container.setObjectName("titleContainer")
title_container.setStyleSheet("background-color: rgba(0, 0, 0, 0.2); border-radius: 15px;")
title_layout = QVBoxLayout(title_container)
title_layout.setContentsMargins(30, 30, 30, 30)
app_title = QLabel("奶酪云工具箱")
app_title.setObjectName("appTitle")
app_title.setAlignment(Qt.AlignCenter)
app_title.setStyleSheet("""
#appTitle {
font-size: 56px;
font-weight: bold;
color: white;
font-family: 'SimHei', 'Microsoft YaHei', sans-serif;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5),
0 0 20px rgba(99, 102, 241, 0.8),
0 0 30px rgba(99, 102, 241, 0.6),
0 0 40px rgba(99, 102, 241, 0.4);
letter-spacing: 5px;
}
""")
subtitle = QLabel("高效 · 安全 · 云端协作")
subtitle.setObjectName("subtitle")
subtitle.setAlignment(Qt.AlignCenter)
subtitle.setStyleSheet("""
#subtitle {
font-size: 18px;
color: rgba(255, 255, 255, 0.85);
letter-spacing: 3px;
margin-top: 10px;
}
""")
title_layout.addWidget(app_title)
title_layout.addWidget(subtitle)
# 版本信息
version_info = QWidget()
version_layout = QVBoxLayout(version_info)
version_layout.setAlignment(Qt.AlignBottom | Qt.AlignCenter)
version_label = QLabel("版本 0.0.1")
version_label.setObjectName("versionLabel")
version_label.setAlignment(Qt.AlignCenter)
version_label.setStyleSheet("""
#versionLabel {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
}
""")
author_label = QLabel("作者:年糕崽崽")
author_label.setObjectName("authorLabel")
author_label.setAlignment(Qt.AlignCenter)
author_label.setStyleSheet("""
#authorLabel {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
margin-top: 5px;
}
""")
version_layout.addWidget(version_label)
version_layout.addWidget(author_label)
# 添加到左侧布局
left_layout.addWidget(title_container)
left_layout.addStretch()
left_layout.addWidget(version_info)
# 右侧登录区 - 深色背景
right_frame = QFrame()
@@ -40,7 +127,7 @@ class LoginPage(QWidget):
}
""")
main_layout.addWidget(left_frame, 3)
main_layout.addWidget(self.left_frame, 3)
main_layout.addWidget(right_frame, 1)
# 右侧登录区布局
@@ -363,6 +450,42 @@ class LoginPage(QWidget):
self.password_anim.setDuration(300)
self.password_anim.setEasingCurve(QEasingCurve.OutQuad)
def create_particles(self):
particles = []
for _ in range(30): # 创建30个粒子
size = 2 + 5 * (0.5) # 随机大小
x = 50 + 700 * (0.5) # 随机X位置
y = 50 + 500 * (0.5) # 随机Y位置
speed_x = -1 + 2 * (0.5) # X方向速度
speed_y = -1 + 2 * (0.5) # Y方向速度
color = QColor( # 随机蓝色调
int(100 + 155 * (0.5)),
int(180 + 75 * (0.5)),
int(255 - 50 * (0.5)),
int(50 + 100 * (0.5))
)
particles.append({
'x': x, 'y': y,
'size': size,
'speed_x': speed_x, 'speed_y': speed_y,
'color': color
})
return particles
def update_particles(self):
for p in self.particles:
# 更新粒子位置
p['x'] += p['speed_x']
p['y'] += p['speed_y']
# 边界反弹
if p['x'] <= 0 or p['x'] >= self.width():
p['speed_x'] *= -1
if p['y'] <= 0 or p['y'] >= self.height():
p['speed_y'] *= -1
self.left_frame.update() # 触发重绘
def toggle_password_visibility(self):
if self.password_input.echoMode() == QLineEdit.Password:
self.password_input.setEchoMode(QLineEdit.Normal)
@@ -441,7 +564,6 @@ class LoginPage(QWidget):
self.password_input.parent().setStyleSheet("#inputFrame { border: 1px solid #fc8181; }")
# 2秒后恢复
from PyQt5.QtCore import QTimer
QTimer.singleShot(2000, self.reset_styles)
def reset_styles(self):
@@ -480,3 +602,40 @@ class LoginPage(QWidget):
painter.drawEllipse(QPoint(250, 300), 50, 50)
super().paintEvent(event)
# 专门绘制左侧装饰区域的粒子效果
if self.left_frame.isVisible():
# 保存当前绘画状态
painter.save()
# 将绘画区域限制在左侧框架内
painter.translate(self.left_frame.pos())
painter.setClipRect(self.left_frame.rect())
# 绘制动态粒子
for p in self.particles:
painter.setBrush(QBrush(p['color']))
painter.setPen(Qt.NoPen)
painter.drawEllipse(QPointF(p['x'], p['y']), p['size'], p['size'])
# 绘制发光的光斑背景
gradient = QLinearGradient(0, 0, self.width(), self.height())
gradient.setColorAt(0, QColor(99, 102, 241, 20)) # 紫色
gradient.setColorAt(0.5, QColor(79, 70, 229, 40)) # 更亮的紫色
gradient.setColorAt(1, QColor(49, 46, 129, 20)) # 深紫色
painter.setBrush(QBrush(gradient))
painter.drawRect(0, 0, self.left_frame.width(), self.left_frame.height())
painter.restore()
# 使用示例
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
login_page = LoginPage()
login_page.login_success.connect(lambda username: print(f"登录成功: {username}"))
login_page.show()
sys.exit(app.exec_())

View File

@@ -1,6 +1,6 @@
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QSpacerItem, QSizePolicy)
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtCore import Qt, pyqtSignal, QSize
from PyQt5.QtGui import QPixmap, QIcon, QFont, QPainter, QBrush, QLinearGradient, QColor
@@ -10,7 +10,7 @@ class NavBar(QWidget):
def __init__(self, username):
super().__init__()
self.username = username or "访客" # 设置默认值,防止空字符串
self.username = username or "访客"
self.active_btn = None
self.init_ui()
@@ -80,51 +80,36 @@ class NavBar(QWidget):
layout.setContentsMargins(0, 20, 0, 20)
layout.setSpacing(0)
# 用户信息卡片 - 使用更现代的设计
# 用户信息卡片
user_frame = QFrame()
user_frame.setObjectName("userFrame")
user_layout = QHBoxLayout(user_frame)
user_layout.setContentsMargins(15, 15, 15, 15)
user_layout.setSpacing(15)
# 头像 - 使用渐变背景
# 头像
avatar = QLabel()
avatar.setFixedSize(50, 50)
# 绘制渐变背景头像
avatar_pixmap = QPixmap(50, 50)
avatar_pixmap.fill(Qt.transparent)
painter = QPainter(avatar_pixmap)
painter.setRenderHint(QPainter.Antialiasing)
brush = QBrush(Qt.SolidPattern)
gradient = QLinearGradient(0, 0, 50, 50)
gradient.setColorAt(0, QColor("#6366f1"))
gradient.setColorAt(1, QColor("#8b5cf6"))
painter.setBrush(gradient)
painter.drawEllipse(0, 0, 50, 50)
painter.end()
avatar.setPixmap(avatar_pixmap)
# 添加用户首字母
avatar_label = QLabel()
avatar_label.setFixedSize(50, 50)
avatar_label.setAlignment(Qt.AlignCenter)
avatar.setStyleSheet("""
background-color: #4f46e5;
border-radius: 25px;
color: white;
font-size: 18px;
font-weight: bold;
qproperty-alignment: 'AlignCenter';
""")
initial = self.username[0].upper() if self.username else "访"
avatar_label.setText(initial)
avatar_label.setStyleSheet("font-size: 18px; font-weight: bold; color: white;")
avatar.setText(initial)
# 用户信息容器
# 用户信息
user_info = QWidget()
user_info_layout = QVBoxLayout(user_info)
user_info_layout.setContentsMargins(0, 0, 0, 0)
user_info_layout.setSpacing(5)
# 用户名
user_name = QLabel(self.username)
user_name.setObjectName("userName")
# 状态标签
status = QLabel("在线")
status.setStyleSheet("font-size: 12px; color: #68d391;")
@@ -132,80 +117,33 @@ class NavBar(QWidget):
user_info_layout.addWidget(status)
user_layout.addWidget(avatar)
user_layout.addWidget(avatar_label)
user_layout.addWidget(user_info)
# 导航项
# 导航项 - 添加应用中心链接
nav_items = [
{"name": "首页", "icon": "home", "id": "home"},
{"name": "应用中心", "icon": "apps", "id": "app_center"},
{"name": "应用中心", "icon": "apps", "id": "app_center"}, # 新添加的应用中心链接
{"name": "使用记录", "icon": "history", "id": "history"},
{"name": "个人设置", "icon": "settings", "id": "settings"},
]
# 导航按钮 - 添加图标
# 导航按钮
self.nav_buttons = []
for item in nav_items:
btn = QPushButton()
btn = QPushButton(item["name"])
btn.setObjectName("navBtn")
btn.setProperty("nav_id", item["id"])
btn.setCursor(Qt.PointingHandCursor)
# 添加图标和文本
btn_layout = QHBoxLayout()
btn_layout.setSpacing(12)
btn_layout.setContentsMargins(0, 0, 0, 0)
icon = QLabel()
icon_pixmap = QPixmap(f":/icons/{item['icon']}.png").scaled(20, 20, Qt.KeepAspectRatio,
Qt.SmoothTransformation)
# 图标着色为当前文本颜色
painter = QPainter(icon_pixmap)
painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
painter.fillRect(icon_pixmap.rect(), QColor("#a0aec0"))
painter.end()
icon.setPixmap(icon_pixmap)
text = QLabel(item["name"])
btn_layout.addWidget(icon)
btn_layout.addWidget(text)
btn_layout.addStretch()
btn.setLayout(btn_layout)
btn.setIcon(QIcon(f":/icons/{item['icon']}.png"))
btn.setIconSize(QSize(20, 20))
btn.clicked.connect(lambda _, x=item["id"]: self.nav_item_clicked.emit(x))
self.nav_buttons.append(btn)
# 退出按钮
logout_btn = QPushButton()
logout_btn = QPushButton("退出登录")
logout_btn.setObjectName("logoutBtn")
# 添加图标和文本
logout_layout = QHBoxLayout()
logout_layout.setSpacing(12)
logout_layout.setContentsMargins(0, 0, 0, 0)
logout_icon = QLabel()
logout_pixmap = QPixmap(":/icons/logout.png").scaled(20, 20, Qt.KeepAspectRatio, Qt.SmoothTransformation)
# 图标着色
painter = QPainter(logout_pixmap)
painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
painter.fillRect(logout_pixmap.rect(), QColor("#feb2b2"))
painter.end()
logout_icon.setPixmap(logout_pixmap)
logout_text = QLabel("退出登录")
logout_layout.addWidget(logout_icon)
logout_layout.addWidget(logout_text)
logout_layout.addStretch()
logout_btn.setLayout(logout_layout)
logout_btn.setIcon(QIcon(":/icons/logout.png"))
logout_btn.setIconSize(QSize(20, 20))
logout_btn.clicked.connect(self.logout_clicked.emit)
# 组装布局
@@ -215,7 +153,7 @@ class NavBar(QWidget):
# 添加分割线
separator = QFrame()
separator.setFrameShape(QFrame.HLine)
separator.setStyleSheet("background-color: #2d3748; margin: 0 15px; height: 1px;")
separator.setStyleSheet("margin: 0 15px; height: 1px; background-color: #2d3748;")
layout.addWidget(separator)
layout.addSpacing(20)
@@ -239,7 +177,7 @@ class NavBar(QWidget):
# 底部添加分割线
separator_bottom = QFrame()
separator_bottom.setFrameShape(QFrame.HLine)
separator_bottom.setStyleSheet("background-color: #2d3748; margin: 0 15px; height: 1px;")
separator_bottom.setStyleSheet("margin: 0 15px; height: 1px; background-color: #2d3748;")
bottom_container_layout.addWidget(separator_bottom)
bottom_container_layout.addSpacing(15)
@@ -256,27 +194,11 @@ class NavBar(QWidget):
# 移除所有按钮的active类
for btn in self.nav_buttons:
btn.setProperty("class", "")
# 重新设置样式以应用变化
btn.setStyle(btn.style())
# 为活动按钮添加active类
for btn in self.nav_buttons:
if btn.property("nav_id") == nav_id:
btn.setProperty("class", "active")
# 更新图标颜色
icon_label = btn.layout().itemAt(0).widget()
active_pixmap = icon_label.pixmap()
# 改变图标颜色
painter = QPainter(active_pixmap)
painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
painter.fillRect(active_pixmap.rect(), QColor("#818cf8"))
painter.end()
icon_label.setPixmap(active_pixmap)
# 重新设置样式以应用变化
btn.setStyle(btn.style())
self.active_btn = btn

View File

@@ -5,8 +5,8 @@ import datetime
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QListWidget, QListWidgetItem,
QFileDialog, QProgressBar, QMessageBox, QSpacerItem)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize
from PyQt5.QtGui import QPixmap, QIcon, QDragEnterEvent, QDropEvent
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize, QTimer
from PyQt5.QtGui import QPixmap, QIcon, QDragEnterEvent, QDropEvent, QDragLeaveEvent
def generate_unique_filename(original_path):
@@ -67,6 +67,7 @@ class WordToPDFPage(QWidget):
self.converted_files = {}
self.conversion_thread = None
self.init_ui()
self.setStyleSheet(self.get_page_style())
def init_ui(self):
self.setObjectName("wordToPdfPage")
@@ -77,81 +78,143 @@ class WordToPDFPage(QWidget):
layout.setSpacing(20)
# 左侧面板 - 文件上传
left_panel = QFrame()
left_panel.setObjectName("leftPanel")
left_panel.setMinimumWidth(300)
left_panel = QWidget()
left_layout = QVBoxLayout(left_panel)
left_layout.setContentsMargins(0, 0, 0, 0)
left_layout.setSpacing(15)
# 上传区域
# 上传区域(增强拖拽效果)
upload_area = QFrame()
upload_area.setObjectName("uploadArea")
upload_area.setFixedHeight(200)
upload_area.setAcceptDrops(True)
upload_layout = QVBoxLayout(upload_area)
upload_layout.setAlignment(Qt.AlignCenter)
upload_icon = QLabel()
# 确保你有正确的图标路径
# upload_icon.setPixmap(QPixmap(":/icons/upload.png").scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation))
upload_icon.setPixmap(QPixmap(":/icons/upload.png").scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation))
upload_icon.setAlignment(Qt.AlignCenter)
upload_text = QLabel("拖拽文件或文件夹到此处\n或点击上传")
upload_text.setAlignment(Qt.AlignCenter)
upload_text.setWordWrap(True)
self.upload_text = QLabel("拖拽文件或文件夹到此处\n或点击上传")
self.upload_text.setAlignment(Qt.AlignCenter)
self.upload_text.setWordWrap(True)
upload_btn = QPushButton("选择文件或文件夹")
upload_btn.setObjectName("uploadBtn")
upload_btn.clicked.connect(self.handle_upload)
upload_layout.addWidget(upload_icon)
upload_layout.addWidget(upload_text)
upload_layout.addWidget(self.upload_text)
upload_layout.addWidget(upload_btn)
# 文件列表
file_list_frame = QFrame()
file_list_frame.setObjectName("fileListFrame")
file_list_layout = QVBoxLayout(file_list_frame)
file_list_layout.setContentsMargins(0, 0, 0, 0)
file_list_layout.setSpacing(5)
file_title = QLabel("待转换文件")
file_title.setObjectName("sectionTitle")
self.file_list = QListWidget()
self.file_list.setObjectName("fileList")
self.file_list.setSelectionMode(QListWidget.ExtendedSelection)
file_list_layout.addWidget(file_title)
file_list_layout.addWidget(self.file_list)
# 操作按钮
btn_layout = QHBoxLayout()
btn_layout.setSpacing(10)
convert_btn = QPushButton("开始转换")
convert_btn.setObjectName("convertBtn")
convert_btn.clicked.connect(self.start_conversion)
self.convert_btn = QPushButton("开始转换")
self.convert_btn.setObjectName("convertBtn")
self.convert_btn.clicked.connect(self.start_conversion)
delete_btn = QPushButton("删除选中")
delete_btn.setObjectName("deleteBtn")
delete_btn.clicked.connect(self.delete_selected)
btn_layout.addWidget(convert_btn)
btn_layout.addWidget(self.convert_btn)
btn_layout.addWidget(delete_btn)
# 组装左侧面板
left_layout.addWidget(upload_area)
left_layout.addWidget(self.file_list)
left_layout.addWidget(file_list_frame)
left_layout.addLayout(btn_layout)
# 右侧面板 - 转换结果
right_panel = QFrame()
right_panel.setObjectName("rightPanel")
right_panel = QWidget()
right_layout = QVBoxLayout(right_panel)
right_layout.setContentsMargins(0, 0, 0, 0)
right_layout.setSpacing(15)
# 进度
# 进度区域
progress_frame = QFrame()
progress_frame.setObjectName("progressFrame")
progress_layout = QVBoxLayout(progress_frame)
progress_layout.setContentsMargins(15, 15, 15, 15)
progress_layout.setSpacing(10)
progress_title = QLabel("转换进度")
progress_title.setObjectName("sectionTitle")
# 总体进度
total_layout = QHBoxLayout()
total_layout.setSpacing(10)
total_label = QLabel("总体进度:")
total_label.setObjectName("progressLabel")
self.total_progress = QProgressBar()
self.total_progress.setObjectName("totalProgress")
self.total_progress.setTextVisible(False)
total_layout.addWidget(total_label)
total_layout.addWidget(self.total_progress)
# 文件进度
file_layout = QHBoxLayout()
file_layout.setSpacing(10)
file_label = QLabel("当前文件:")
file_label.setObjectName("progressLabel")
self.file_progress = QProgressBar()
self.file_progress.setObjectName("fileProgress")
self.file_progress.setTextVisible(False)
file_layout.addWidget(file_label)
file_layout.addWidget(self.file_progress)
# 状态标签
self.status_label = QLabel("就绪")
self.status_label.setObjectName("statusLabel")
self.status_label.setAlignment(Qt.AlignCenter)
progress_layout.addWidget(progress_title)
progress_layout.addLayout(total_layout)
progress_layout.addLayout(file_layout)
progress_layout.addWidget(self.status_label)
# 结果列表
result_frame = QFrame()
result_frame.setObjectName("resultFrame")
result_layout = QVBoxLayout(result_frame)
result_layout.setContentsMargins(0, 0, 0, 0)
result_layout.setSpacing(5)
result_title = QLabel("转换结果")
result_title.setObjectName("sectionTitle")
self.result_list = QListWidget()
self.result_list.setObjectName("resultList")
self.result_list.setSelectionMode(QListWidget.ExtendedSelection)
result_layout.addWidget(result_title)
result_layout.addWidget(self.result_list)
# 下载按钮
self.download_btn = QPushButton("全部下载")
@@ -159,27 +222,28 @@ class WordToPDFPage(QWidget):
self.download_btn.setEnabled(False)
self.download_btn.clicked.connect(self.download_all)
# 日志
log_area = QFrame()
log_area.setObjectName("logArea")
log_layout = QVBoxLayout(log_area)
# 日志区域
log_frame = QFrame()
log_frame.setObjectName("logFrame")
log_layout = QVBoxLayout(log_frame)
log_layout.setContentsMargins(0, 0, 0, 0)
log_layout.setSpacing(5)
log_title = QLabel("转换日志")
log_title.setObjectName("logTitle")
log_title.setObjectName("sectionTitle")
self.log_content = QListWidget()
self.log_content.setObjectName("logContent")
self.log_content.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
log_layout.addWidget(log_title)
log_layout.addWidget(self.log_content)
# 组装右侧面板
right_layout.addWidget(self.total_progress)
right_layout.addWidget(self.file_progress)
right_layout.addWidget(self.result_list)
right_layout.addWidget(progress_frame)
right_layout.addWidget(result_frame)
right_layout.addWidget(self.download_btn)
right_layout.addWidget(log_area)
right_layout.addWidget(log_frame)
# 主布局
layout.addWidget(left_panel)
@@ -187,13 +251,178 @@ class WordToPDFPage(QWidget):
self.setLayout(layout)
def get_page_style(self):
return """
/* ===== Word转PDF页面样式 ===== */
#wordToPdfPage {
background-color: #1a202c;
}
/* 卡片样式 */
#uploadArea,
#fileListFrame,
#progressFrame,
#resultFrame,
#logFrame {
background-color: #2d3748;
border: 1px solid #4a5568;
border-radius: 8px;
padding: 15px;
}
/* 上传区域 */
#uploadArea {
border: 2px dashed #4a5568;
background-color: rgba(45, 55, 72, 0.5);
transition: all 0.3s ease;
}
#uploadArea:hover {
border-color: #818cf8;
background-color: rgba(129, 140, 248, 0.1);
}
#uploadArea[dragActive="true"] {
border-color: #4ade80;
background-color: rgba(74, 222, 128, 0.1);
}
/* 标题样式 */
#sectionTitle {
font-size: 16px;
font-weight: 600;
color: #e2e8f0;
margin-bottom: 10px;
}
/* 文件列表 */
#fileList, #resultList, #logContent {
background-color: rgba(26, 32, 44, 0.3);
border: 1px solid #4a5568;
border-radius: 6px;
min-height: 200px;
max-height: 250px;
font-size: 13px;
}
#fileList::item, #resultList::item, #logContent::item {
padding: 8px 12px;
border-bottom: 1px solid rgba(74, 85, 104, 0.5);
}
#fileList::item:selected,
#resultList::item:selected,
#logContent::item:selected {
background-color: rgba(129, 140, 248, 0.15);
}
/* 进度条 */
#totalProgress, #fileProgress {
height: 8px;
border-radius: 4px;
background-color: #2d3748;
}
#totalProgress::chunk {
background-color: #818cf8;
border-radius: 4px;
}
#fileProgress::chunk {
background-color: #4ade80;
border-radius: 4px;
}
#progressLabel {
color: #a0aec0;
font-size: 13px;
min-width: 70px;
}
#statusLabel {
font-size: 14px;
color: #e2e8f0;
margin-top: 10px;
padding: 8px;
border-radius: 6px;
background-color: rgba(26, 32, 44, 0.3);
}
/* 按钮样式 */
#uploadBtn, #convertBtn, #deleteBtn, #downloadBtn {
min-height: 40px;
font-weight: 500;
}
#uploadBtn {
min-width: 180px;
}
#convertBtn {
background-color: #4f46e5;
}
#convertBtn:disabled {
background-color: #4a5568;
}
#downloadBtn {
background-color: #10b981;
}
#downloadBtn:hover {
background-color: #059669;
}
#deleteBtn {
background-color: #ef4444;
}
#deleteBtn:hover {
background-color: #dc2626;
}
/* 日志样式 */
#logContent::item[type="info"] {
color: #a0aec0;
}
#logContent::item[type="success"] {
color: #34d399;
}
#logContent::item[type="warning"] {
color: #fbbf24;
}
#logContent::item[type="error"] {
color: #f87171;
}
"""
def dragEnterEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls():
event.acceptProposedAction()
# 高亮显示拖拽区域
self.findChild(QFrame, "uploadArea").setProperty("dragActive", True)
self.findChild(QFrame, "uploadArea").style().polish(self.findChild(QFrame, "uploadArea"))
self.upload_text.setText("释放文件以添加")
def dragLeaveEvent(self, event: QDragLeaveEvent):
# 恢复拖拽区域样式
self.findChild(QFrame, "uploadArea").setProperty("dragActive", False)
self.findChild(QFrame, "uploadArea").style().polish(self.findChild(QFrame, "uploadArea"))
self.upload_text.setText("拖拽文件或文件夹到此处\n或点击上传")
def dropEvent(self, event: QDropEvent):
# 恢复拖拽区域样式
self.findChild(QFrame, "uploadArea").setProperty("dragActive", False)
self.findChild(QFrame, "uploadArea").style().polish(self.findChild(QFrame, "uploadArea"))
self.upload_text.setText("拖拽文件或文件夹到此处\n或点击上传")
urls = event.mimeData().urls()
files = []
added_count = 0
for url in urls:
path = url.toLocalFile()
@@ -207,46 +436,65 @@ class WordToPDFPage(QWidget):
files.append(path)
if files:
self.add_files(files)
added_count = self.add_files(files)
self.add_log(f"添加了 {added_count} 个文件", "success")
else:
self.add_log("未找到有效的Word文件", "warning")
def handle_upload(self):
"""处理文件或文件夹上传"""
# 使用getExistingDirectory获取文件夹
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
options = QFileDialog.Options()
files, _ = QFileDialog.getOpenFileNames(
self, "选择Word文件", "", "Word Files (*.doc *.docx);;All Files (*)", options=options
)
if files:
added_count = self.add_files(files)
self.add_log(f"添加了 {added_count} 个文件", "success")
else:
folder = QFileDialog.getExistingDirectory(self, "选择包含Word文件的文件夹")
if folder:
files = []
for root, _, filenames in os.walk(folder):
for f in filenames:
if f.lower().endswith(('.doc', '.docx')):
files.append(os.path.join(root, f))
self.add_files(files)
else:
# 如果用户没有选择文件夹,则选择文件
files, _ = QFileDialog.getOpenFileNames(
self, "选择Word文件", "", "Word Files (*.doc *.docx);;All Files (*)"
)
if files:
self.add_files(files)
added_count = self.add_files(files)
self.add_log(f"从文件夹添加了 {added_count} 个文件", "success")
else:
self.add_log("文件夹中没有找到Word文件", "warning")
def add_files(self, files):
"""添加文件到列表,避免重复"""
added_count = 0
for file in files:
if file not in self.files and os.path.exists(file):
self.files.append(file)
item = QListWidgetItem(os.path.basename(file))
item.setData(Qt.UserRole, file)
self.file_list.addItem(item)
added_count += 1
return added_count
def delete_selected(self):
"""删除选中的文件"""
for item in self.file_list.selectedItems():
selected_items = self.file_list.selectedItems()
if not selected_items:
self.add_log("请先选择要删除的文件", "warning")
return
for item in selected_items:
self.files.remove(item.data(Qt.UserRole))
self.file_list.takeItem(self.file_list.row(item))
self.add_log(f"删除了 {len(selected_items)} 个文件", "info")
def start_conversion(self):
"""开始转换文件"""
if not self.files:
QMessageBox.warning(self, "警告", "请先添加要转换的文件!")
self.add_log("请先添加要转换的文件!", "warning")
return
# 重置状态
@@ -254,7 +502,18 @@ class WordToPDFPage(QWidget):
self.result_list.clear()
self.log_content.clear()
self.download_btn.setEnabled(False)
self.convert_btn.setEnabled(False)
# 重置进度条
self.total_progress.setValue(0)
self.file_progress.setValue(0)
self.status_label.setText("正在准备转换...")
# 添加开始日志
self.add_log("开始转换任务", "info")
self.add_log(f"{len(self.files)} 个文件待处理", "info")
# 创建并启动转换线程
self.conversion_thread = ConversionThread(self.files.copy())
self.conversion_thread.progress_updated.connect(self.update_progress)
self.conversion_thread.conversion_done.connect(self.handle_conversion_done)
@@ -266,10 +525,28 @@ class WordToPDFPage(QWidget):
self.total_progress.setMaximum(total)
self.total_progress.setValue(current)
self.file_progress.setMaximum(100)
# 重置文件进度为0
self.file_progress.setValue(0)
self.file_progress.setMaximum(100)
self.log_content.addItem(f"正在转换: {filename} ({current}/{total})")
# 更新状态
self.status_label.setText(f"正在转换: {os.path.basename(filename)}")
self.add_log(f"开始转换: {os.path.basename(filename)} ({current}/{total})", "info")
# 模拟文件进度动画
self.animate_file_progress()
def animate_file_progress(self):
"""模拟文件转换进度动画(实际应用中应由实际进度驱动)"""
self.file_progress_value = 0
def update_progress():
if self.file_progress_value < 100:
self.file_progress_value += 2
self.file_progress.setValue(self.file_progress_value)
QTimer.singleShot(50, update_progress)
update_progress()
def handle_conversion_done(self, file_path, success, message):
"""处理单个文件转换完成"""
@@ -291,28 +568,50 @@ class WordToPDFPage(QWidget):
item.setData(Qt.UserRole, pdf_path)
self.result_list.addItem(item)
self.log_content.addItem(f"✅ 转换成功: {basename} -> {os.path.basename(pdf_path)}")
self.add_log(f"✅ 转换成功: {basename} -> {os.path.basename(pdf_path)}", "success")
else:
# 失败时显示错误信息
self.log_content.addItem(f"❌ 转换失败: {basename} - {message}")
self.add_log(f"❌ 转换失败: {basename} - {message}", "error")
def handle_finished_all(self):
"""所有文件转换完成"""
self.log_content.addItem(f"🎉 全部转换完成! 共转换 {len(self.converted_files)} 个文件")
self.download_btn.setEnabled(True)
success_count = len(self.converted_files)
fail_count = len(self.files) - success_count
# 更新状态
self.status_label.setText(f"转换完成! 成功: {success_count}, 失败: {fail_count}")
self.total_progress.setValue(self.total_progress.maximum())
self.file_progress.setValue(100)
self.convert_btn.setEnabled(True)
self.download_btn.setEnabled(bool(self.converted_files))
# 添加完成日志
if success_count:
self.add_log(f"🎉 全部转换完成! 成功转换 {success_count} 个文件", "success")
if fail_count:
self.add_log(f"⚠️ 有 {fail_count} 个文件转换失败", "warning")
if self.converted_files:
self.add_log("点击'全部下载'按钮保存转换后的文件", "info")
def download_all(self):
"""下载所有转换后的文件"""
if not self.converted_files:
self.add_log("没有可下载的文件", "warning")
return
folder = QFileDialog.getExistingDirectory(self, "选择保存位置")
if not folder:
self.add_log("下载已取消", "info")
return
success = 0
errors = []
# 开始下载
self.status_label.setText("正在下载文件...")
self.add_log(f"开始下载 {len(self.converted_files)} 个文件到 {folder}", "info")
for pdf_path in self.converted_files.values():
try:
file_name = os.path.basename(pdf_path)
@@ -323,16 +622,35 @@ class WordToPDFPage(QWidget):
with open(destination, 'wb') as dest_file:
dest_file.write(src_file.read())
success += 1
self.add_log(f"已下载: {file_name}", "success")
except Exception as e:
errors.append(f"{os.path.basename(pdf_path)}: {str(e)}")
self.add_log(f"下载失败: {os.path.basename(pdf_path)} - {str(e)}", "error")
# 更新状态
self.status_label.setText(f"下载完成! 成功: {success}, 失败: {len(errors)}")
# 显示结果
if errors:
error_msg = "\n".join(errors[:10]) # 最多显示10个错误
if len(errors) > 10:
error_msg += f"\n...等共 {len(errors)} 个错误"
QMessageBox.warning(self, "部分文件下载失败", f"成功下载 {success} 个文件\n失败文件:\n{error_msg}")
self.add_log(f"⚠️ 部分文件下载失败 ({len(errors)} 个)", "warning")
else:
QMessageBox.information(self, "下载完成", f"已成功下载 {success} 个文件到 {folder}")
self.add_log(f"✅ 全部文件下载成功!", "success")
self.log_content.addItem(f"已下载 {success} 个文件到 {folder}")
self.add_log(f"文件已保存到: {folder}", "info")
def add_log(self, message, log_type="info"):
"""添加日志项"""
item = QListWidgetItem(f"[{datetime.datetime.now().strftime('%H:%M:%S')}] {message}")
item.setData(Qt.UserRole + 1, log_type) # 存储日志类型用于样式
self.log_content.addItem(item)
self.log_content.scrollToBottom()
def paintEvent(self, event):
"""为拖拽区域添加悬停效果"""
if self.underMouse():
self.findChild(QFrame, "uploadArea").setProperty("hover", True)
self.findChild(QFrame, "uploadArea").style().polish(self.findChild(QFrame, "uploadArea"))
else:
self.findChild(QFrame, "uploadArea").setProperty("hover", False)
self.findChild(QFrame, "uploadArea").style().polish(self.findChild(QFrame, "uploadArea"))
super().paintEvent(event)