优化UI
This commit is contained in:
993
test/t.py
Normal file
993
test/t.py
Normal 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()
|
||||
228
v1/main.py
228
v1/main.py
@@ -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):
|
||||
if app_id == "word_to_pdf":
|
||||
self.stacked_pages.setCurrentWidget(self.word_to_pdf)
|
||||
# 后续可添加其他应用
|
||||
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
430
v1/views/home.py
Normal 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'])
|
||||
@@ -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_())
|
||||
130
v1/views/nav.py
130
v1/views/nav.py
@@ -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类
|
||||
# 为活动按钮添加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
|
||||
@@ -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, "选择文件夹")
|
||||
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)
|
||||
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:
|
||||
# 如果用户没有选择文件夹,则选择文件
|
||||
files, _ = QFileDialog.getOpenFileNames(
|
||||
self, "选择Word文件", "", "Word Files (*.doc *.docx);;All Files (*)"
|
||||
)
|
||||
if files:
|
||||
self.add_files(files)
|
||||
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))
|
||||
|
||||
if 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)
|
||||
Reference in New Issue
Block a user