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

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):
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():
@@ -324,4 +476,4 @@ def main():
if __name__ == "__main__":
main()
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):
@@ -479,4 +601,41 @@ class LoginPage(QWidget):
painter.setBrush(QColor(6, 78, 59, 60))
painter.drawEllipse(QPoint(250, 300), 50, 50)
super().paintEvent(event)
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类
# 为活动按钮添加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, "选择文件夹")
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)