diff --git a/test/t.py b/test/t.py new file mode 100644 index 0000000..7da8f7e --- /dev/null +++ b/test/t.py @@ -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() diff --git a/v1/main.py b/v1/main.py index d946a97..edd9e10 100644 --- a/v1/main.py +++ b/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(): @@ -324,4 +476,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/v1/views/app_center.py b/v1/views/app_center.py index f7a2f0e..e3f530a 100644 --- a/v1/views/app_center.py +++ b/v1/views/app_center.py @@ -1,216 +1,798 @@ import math - +import random +from PyQt5.QtCore import Qt, QPropertyAnimation, QPoint, QTimer, QParallelAnimationGroup, pyqtProperty, \ + QEasingCurve, QSize, pyqtSignal, QPointF, QByteArray +from PyQt5.QtGui import QPixmap, QIcon, QColor, QPainter, QLinearGradient +from PyQt5.QtSvg import QSvgRenderer, QSvgWidget from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QPushButton, QFrame, QGridLayout, QSpacerItem) -from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtGui import QPixmap, QIcon, QColor, QLinearGradient, QBrush, QPainter -from PyQt5.QtCore import QRectF + QPushButton, QFrame, QGridLayout, + QSizePolicy, QScrollArea, QSizeGrip, QLineEdit) -class GradientFrame(QFrame): - def __init__(self, start_color, end_color, angle=0, parent=None): +class AnimatedCard(QFrame): + """动画卡片控件,与Home页面一致""" + + 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 = angle # 0-360 degrees + 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) + 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) - gradient = QLinearGradient(0, 0, self.width(), self.height()) - if self.angle == 0: - gradient = QLinearGradient(0, 0, self.width(), 0) - elif self.angle == 90: - gradient = QLinearGradient(0, 0, 0, self.height()) - elif self.angle == 180: - gradient = QLinearGradient(self.width(), 0, 0, 0) - elif self.angle == 270: - gradient = QLinearGradient(0, self.height(), 0, 0) + # 创建主渐变背景 + 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: - # Angle between 0 and 360 - rad = self.angle * 3.14159 / 180.0 - cx = self.width() / 2 - cy = self.height() / 2 - radius = max(self.width(), self.height()) - start_x = cx + radius * 0.5 * math.cos(rad) - start_y = cy + radius * 0.5 * math.sin(rad) - end_x = cx - radius * 0.5 * math.cos(rad) - end_y = cy - radius * 0.5 * math.sin(rad) - gradient = QLinearGradient(start_x, start_y, end_x, end_y) + # 普通状态 + gradient = QLinearGradient(rect.topLeft(), rect.bottomRight()) + gradient.setColorAt(0, self.start_color) + gradient.setColorAt(1, self.end_color) - 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)) - painter.fillRect(self.rect(), QBrush(gradient)) + 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)) - super().paintEvent(event) + # 绘制背景 + 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 get_styles(self): + return """ + /* ===== 应用详情样式 ===== */ + #appDetail { + background-color: rgba(26, 32, 44, 0.98); /* 提高不透明度 */ + 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, 1); /* 完全不透明 */ + 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; + padding: 10px; + } + + #detailAuthor, + #detailVersion, + #detailUpload { + font-size: 16px; + color: rgba(255, 255, 255, 0.9); + padding: 8px 15px; + border-radius: 8px; + background-color: rgba(255, 255, 255, 0.05); + margin: 5px 0; + } + + #detailDesc { + font-size: 17px; + color: rgba(255, 255, 255, 0.85); + line-height: 1.6; + padding: 0 0 15px 0; + } + + #detailSection { + font-size: 19px; + font-weight: 600; + color: #ffffff; + margin-top: 15px; + margin-bottom: 10px; + } + + #featureItem { + font-size: 15px; + color: rgba(255, 255, 255, 0.8); + padding-left: 15px; + padding-bottom: 8px; + } + + #detailStartBtn { + background-color: #4f46e5; + color: white; + border-radius: 8px; + padding: 12px 30px; + font-size: 16px; + font-weight: 600; + min-width: 140px; + transition: background-color 0.3s; + } + + #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; + transition: background-color 0.3s; + } + + #detailCancelBtn:hover { + background-color: rgba(255, 255, 255, 0.2); + } + + /* 内容区域背景色 */ + QWidget#content { + background-color: rgba(26, 32, 44, 1); /* 完全不透明 */ + border-radius: 0 0 12px 12px; + padding: 20px; + } + """ + + def init_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(1, 1, 1, 1) # 减小边距使阴影更明显 + layout.setSpacing(0) + + # 标题栏 + title_bar = QWidget() + title_bar.setObjectName("titleBar") + title_bar_layout = QHBoxLayout(title_bar) + title_bar_layout.setContentsMargins(15, 5, 15, 5) + + 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.addStretch() + title_bar_layout.addWidget(close_btn) + + # 内容区域 + content = QWidget() + content.setObjectName("content") + content_layout = QVBoxLayout(content) + content_layout.setContentsMargins(20, 20, 20, 20) + + # 应用信息 + 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) + btn_layout.setContentsMargins(0, 20, 0, 0) # 添加上边距 + + 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.addStretch() + btn_layout.addWidget(start_btn) + btn_layout.addWidget(cancel_btn) + btn_layout.addStretch() + + 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(700, 550) class AppCenter(QWidget): + """应用中心页面,与Home页面卡片样式一致""" app_selected = pyqtSignal(str) def __init__(self): super().__init__() + self.setObjectName("AppCenterPage") + self.app_detail_window = None + self.current_category = "全部" self.init_ui() + self.setStyleSheet(self.get_styles()) + + # 启动加载动画 + self.start_loading_animation() + + # 初始化应用数据 - 模拟API请求延迟 + QTimer.singleShot(800, self.load_data) # 0.8秒后加载数据 + + # 添加背景粒子效果 + self.particles = [] + self.init_particles() + self.particle_timer = QTimer(self) + self.particle_timer.timeout.connect(self.update_particles) + self.particle_timer.start(30) def init_ui(self): - self.setObjectName("appCenter") - self.setStyleSheet(""" - #appCenter { - background-color: #1a202c; - } + # 主布局 + main_layout = QVBoxLayout() + main_layout.setContentsMargins(40, 30, 40, 30) + main_layout.setSpacing(20) - #pageTitle { - font-size: 28px; - font-weight: bold; - color: white; - margin-bottom: 30px; - } - - #appCard { - border-radius: 12px; - background-color: #2d3748; - border: 1px solid #4a5568; - transition: transform 0.2s, box-shadow 0.2s; - } - - #appCard:hover { - transform: translateY(-5px); - background-color: #3c4657; - box-shadow: 0 10px 20px rgba(0, 0, 0, 0.25); - } - - #appCardTitle { - font-size: 20px; - font-weight: bold; - color: white; - text-align: center; - margin-top: 15px; - } - - #appCardDesc { - font-size: 14px; - color: #cbd5e0; - text-align: center; - padding: 0 10px; - } - - #appCardBtn { - background-color: #4f46e5; - color: white; - border-radius: 6px; - padding: 10px 16px; - font-weight: 600; - border: none; - min-width: 120px; - } - - #appCardBtn:hover { - background-color: #4338ca; - } - """) - - layout = QVBoxLayout() - layout.setContentsMargins(40, 30, 40, 30) - layout.setSpacing(20) - - # 标题区域 - title_layout = QHBoxLayout() + # 标题和搜索区域 + header_layout = QHBoxLayout() title = QLabel("应用中心") title.setObjectName("pageTitle") - title_spacer = QSpacerItem(40, 20) + # 搜索框 + search_container = QFrame() + search_container.setObjectName("searchContainer") + search_layout = QHBoxLayout(search_container) + search_layout.setContentsMargins(15, 5, 15, 5) - title_layout.addWidget(title) - title_layout.addSpacerItem(title_spacer) + self.search_input = QLineEdit() + self.search_input.setPlaceholderText("搜索应用...") + self.search_input.setObjectName("searchInput") + self.search_input.textChanged.connect(self.filter_apps) - layout.addLayout(title_layout) + search_btn = QPushButton() + search_btn.setObjectName("searchBtn") + search_btn.setIcon(QIcon(":/icons/search.png")) + search_btn.setIconSize(QSize(20, 20)) - # 卡片容器 - grid_layout = QVBoxLayout() - grid_layout.setSpacing(20) + search_layout.addWidget(self.search_input) + search_layout.addWidget(search_btn) - # 应用卡片 - apps = [ - { - "id": "word_to_pdf", - "title": "Word转PDF", - "desc": "批量将Word文档转换为PDF格式", - "color": ("#6366F1", "#4F46E5") # 渐变开始和结束颜色 - }, - { - "id": "ocr", - "title": "OCR文字识别", - "desc": "从图片中提取文字内容", - "color": ("#4ADE80", "#22C55E") - }, - { - "id": "image_convert", - "title": "图片格式转换", - "desc": "转换图像文件格式并优化", - "color": ("#F97316", "#EA580C") - }, - { - "id": "coming_soon", - "title": "敬请期待", - "desc": "更多实用功能正在开发中", - "color": ("#8B5CF6", "#7C3AED") + 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.loading_container = QWidget() + self.loading_container.setVisible(True) + loading_layout = QVBoxLayout(self.loading_container) + loading_layout.setAlignment(Qt.AlignCenter) + + # 旋转SVG动画 + self.loading_label = QLabel() + self.loading_label.setObjectName("loadingLabel") + loading_layout.addWidget(self.loading_label, alignment=Qt.AlignCenter) + + # 加载中文本 + loading_text = QLabel("正在加载中...") + loading_text.setObjectName("loadingText") + loading_layout.addWidget(loading_text, alignment=Qt.AlignCenter) + + # 卡片容器(初始时隐藏) + self.apps_container = QWidget() + self.apps_container.setVisible(False) + self.apps_layout = QGridLayout(self.apps_container) + self.apps_layout.setSpacing(30) + self.apps_layout.setAlignment(Qt.AlignTop | Qt.AlignHCenter) + + # 滚动区域(包含加载容器和卡片容器) + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + + # 创建包装容器 + scroll_widget = QWidget() + scroll_layout = QVBoxLayout(scroll_widget) + scroll_layout.addWidget(self.loading_container) + scroll_layout.addWidget(self.apps_container) + scroll_layout.setContentsMargins(0, 0, 0, 0) + + scroll_area.setWidget(scroll_widget) + scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + scroll_area.setStyleSheet(""" + QScrollArea { + border: none; + background: transparent; } + QScrollBar:vertical { + border: none; + background: rgba(45, 55, 72, 0.3); + width: 10px; + margin: 0px 0px 0px 0px; + } + QScrollBar::handle:vertical { + background: rgba(74, 85, 104, 0.7); + min-height: 20px; + border-radius: 4px; + } + QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0px; + background: none; + } + """) + + main_layout.addWidget(scroll_area) + self.setLayout(main_layout) + + # 启动加载动画 + self.start_loading_animation() + + # 初始化应用数据 - 模拟API请求延迟 + QTimer.singleShot(800, self.load_data) # 0.8秒后加载数据 + + # 添加背景粒子效果 + self.particles = [] + self.init_particles() + self.particle_timer = QTimer(self) + self.particle_timer.timeout.connect(self.update_particles) + self.particle_timer.start(30) + + def start_loading_animation(self): + """启动加载动画""" + # 直接使用QSvgWidget加载SVG + self.loading_svg = QSvgWidget() + self.loading_svg.setFixedSize(80, 80) + self.loading_svg.setStyleSheet("background: transparent;") + + # 添加SVG内容 + svg_data = QByteArray(b''' + + + + + + + + + + + ''') + self.loading_svg.load(svg_data) + + # 添加到布局 + self.loading_label.setLayout(QVBoxLayout()) + self.loading_label.layout().setContentsMargins(0, 0, 0, 0) + self.loading_label.layout().addWidget(self.loading_svg, alignment=Qt.AlignCenter) + + # 添加样式类 + self.loading_label.setObjectName("loadingLabel") + + def load_data(self): + """模拟加载数据""" + self.apps = self.get_apps_data() + + # 隐藏加载动画,显示应用列表 + self.loading_container.setVisible(False) + self.apps_container.setVisible(True) + + # 计算列数并显示应用卡片 + cards_per_row = max(1, self.width() // 350) if self.width() > 0 else 3 + self.display_apps(self.apps, cards_per_row) + + # 重置动画状态 + if hasattr(self, 'loading_svg'): + self.loading_svg.deleteLater() + + def filter_apps(self): + """根据搜索关键词过滤应用""" + keyword = self.search_input.text().strip().lower() + + if not keyword and self.current_category == "全部": + self.display_apps(self.apps, + max(1, self.width() // 350) if self.width() > 0 else 3) + return + + filtered_apps = [] + for app in self.apps: + # 检查搜索条件 + match_search = (not keyword or + keyword in app["title"].lower() or + keyword in app["desc"].lower() or + keyword in app["author"].lower()) + + # 检查类别条件 + match_category = (self.current_category == "全部" or + app["category"] == self.current_category) + + if match_search and match_category: + filtered_apps.append(app) + + # 计算列数并显示过滤后的应用 + cards_per_row = max(1, self.width() // 350) if self.width() > 0 else 3 + self.display_apps(filtered_apps, cards_per_row) + + def filter_by_category(self, category): + """根据类别过滤应用""" + self.current_category = category + + # 更新按钮状态 + for i in range(self.category_layout.count()): + item = self.category_layout.itemAt(i) + if item and item.widget(): + btn = item.widget() + btn.setProperty("active", btn.text() == category) + # 重新应用样式 + btn.style().unpolish(btn) + btn.style().polish(btn) + + self.filter_apps() # 应用新的过滤 + + def display_apps(self, apps, cards_per_row=3): + """显示应用列表""" + # 清除当前内容 + for i in reversed(range(self.apps_layout.count())): + widget = self.apps_layout.itemAt(i).widget() + if widget: + widget.setParent(None) + widget.deleteLater() + + # 添加新卡片 - 使用栅格布局 + row, col = 0, 0 + for i, app in enumerate(apps): + card = self.create_app_card(app) + self.apps_layout.addWidget(card, row, col) + col += 1 + if col >= cards_per_row: + col = 0 + row += 1 + + # 如果没有应用显示提示信息 + if not apps: + no_result = QLabel("没有找到匹配的应用") + no_result.setObjectName("noResult") + no_result.setAlignment(Qt.AlignCenter) + self.apps_layout.addWidget(no_result, 0, 0, 1, cards_per_row) + + def show_app_detail(self, app_data): + """显示应用详情弹窗""" + if self.app_detail_window: + self.app_detail_window.close() + self.app_detail_window.deleteLater() + + self.app_detail_window = AppDetailWindow(app_data, self) + + # 计算位置(居中显示) + center_point = self.mapToGlobal(self.rect().center()) + self.app_detail_window.move(center_point - QPoint(self.app_detail_window.width() // 2, + self.app_detail_window.height() // 2)) + self.app_detail_window.show() + + def init_particles(self): + """初始化粒子效果""" + for _ in range(50): + particle = { + "pos": QPointF(random.randint(0, self.width()), random.randint(0, self.height())), + "size": random.randint(2, 5), + "speed": random.uniform(0.5, 2.0), + "color": random.choice([ + (66, 153, 225, 70), # 蓝色 + (72, 187, 120, 60), # 绿色 + (237, 137, 54, 60), # 橙色 + (224, 42, 94, 50) # 粉色 + ]) + } + self.particles.append(particle) + + def update_particles(self): + """更新粒子位置""" + for particle in self.particles: + # 添加随机偏移 + particle["pos"].setX(particle["pos"].x() + random.uniform(-particle["speed"], particle["speed"])) + particle["pos"].setY(particle["pos"].y() + particle["speed"]) + + # 如果粒子移出底部,重置到顶部 + if particle["pos"].y() > self.height(): + particle["pos"] = QPointF(random.randint(0, self.width()), -10) + + self.update() + + def paintEvent(self, event): + """绘制背景粒子效果""" + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + + # 绘制背景 + painter.fillRect(self.rect(), QColor(26, 32, 44)) + + # 绘制粒子 + for particle in self.particles: + r, g, b, a = particle["color"] + painter.setBrush(QColor(r, g, b, a)) + painter.setPen(Qt.NoPen) + painter.drawEllipse(particle["pos"], particle["size"], particle["size"]) + + def resizeEvent(self, event): + """窗口大小改变时调整粒子""" + # 移除超出边界的粒子 + self.particles = [ + p for p in self.particles + if 0 <= p["pos"].x() <= self.width() and 0 <= p["pos"].y() <= self.height() ] - # 每行显示2张卡片 - for i in range(0, len(apps), 2): - row_layout = QHBoxLayout() - row_layout.setSpacing(30) + # 补充新粒子 + while len(self.particles) < 50: + self.init_particles() - for j in range(2): - index = i + j - if index < len(apps): - row_layout.addWidget(self.create_app_card(apps[index])) - else: - # 添加空白占位符保持布局 - spacer = QSpacerItem(0, 0) - row_layout.addItem(spacer) - - grid_layout.addLayout(row_layout) - - layout.addLayout(grid_layout) - layout.addStretch() - - self.setLayout(layout) + super().resizeEvent(event) def create_app_card(self, app): - """创建应用卡片""" - card = GradientFrame(app["color"][0], app["color"][1], 45) + """创建应用卡片,完全使用Home页面样式""" + card = AnimatedCard(app['color'][0], app['color'][1]) card.setObjectName("appCard") - card.setFixedSize(300, 280) + # 使用垂直布局作为卡片的主布局 layout = QVBoxLayout(card) - layout.setContentsMargins(20, 20, 20, 20) + layout.setContentsMargins(25, 25, 25, 25) layout.setSpacing(15) - # 图标 + # 图标部分 icon_frame = QFrame() - icon_frame.setFixedSize(80, 80) - icon_frame.setStyleSheet("background-color: rgba(255, 255, 255, 0.2); border-radius: 40px;") + icon_frame.setFixedSize(90, 90) icon_frame.setObjectName("appCardIcon") icon_layout = QVBoxLayout(icon_frame) icon_layout.setContentsMargins(0, 0, 0, 0) - # 使用QSS设置图标颜色为白色 icon = QLabel() - icon_pixmap = QPixmap(f":/icons/{app['id']}.png").scaled(48, 48, - Qt.KeepAspectRatio, - Qt.SmoothTransformation) + icon_pixmap = QPixmap(f":/icons/{app['icon']}.png").scaled(56, 56, + Qt.KeepAspectRatio, + Qt.SmoothTransformation) + # 转换图标为白色 - if app["id"] != "coming_soon": - painter = QPainter(icon_pixmap) - painter.setCompositionMode(QPainter.CompositionMode_SourceIn) - painter.fillRect(icon_pixmap.rect(), Qt.white) - painter.end() + 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) @@ -221,6 +803,7 @@ class AppCenter(QWidget): # 标题 title = QLabel(app["title"]) title.setObjectName("appCardTitle") + title.setWordWrap(True) # 确保长标题可以换行 layout.addWidget(title) # 描述 @@ -230,29 +813,325 @@ class AppCenter(QWidget): layout.addWidget(desc) # 添加伸缩空间保持底部对齐 - spacer = QSpacerItem(20, 20) - layout.addItem(spacer) + layout.addStretch() - # 按钮 - btn_text = "开始使用" if app["id"] != "coming_soon" else "敬请期待" - btn = QPushButton(btn_text) - btn.setObjectName("appCardBtn") - btn.setCursor(Qt.PointingHandCursor) + # 按钮区域 - 使用水平布局并使按钮居中 + btn_container = QWidget() + btn_layout = QHBoxLayout(btn_container) + btn_layout.setContentsMargins(0, 0, 0, 0) + btn_layout.setSpacing(10) - if app["id"] == "coming_soon": - btn.setEnabled(False) - btn.setStyleSheet(""" - #appCardBtn:disabled { - background-color: rgba(255, 255, 255, 0.3); - color: rgba(255, 255, 255, 0.7); - } - """) - else: - btn.clicked.connect(lambda: self.app_selected.emit(app["id"])) + # 开始使用按钮 + start_btn = QPushButton("开始使用") + start_btn.setObjectName("appCardBtn") + start_btn.setCursor(Qt.PointingHandCursor) + start_btn.setMinimumWidth(100) # 设置最小宽度防止重叠 + start_btn.clicked.connect(lambda: self.app_selected.emit(app["id"])) - layout.addWidget(btn, 0, Qt.AlignHCenter) + # 详情按钮 + detail_btn = QPushButton("详情") + detail_btn.setObjectName("appCardBtn") + detail_btn.setCursor(Qt.PointingHandCursor) + detail_btn.setMinimumWidth(100) # 设置最小宽度防止重叠 + detail_btn.clicked.connect(lambda: self.show_app_detail(app)) - # 为卡片添加点击事件 - card.mousePressEvent = lambda e: self.app_selected.emit(app["id"]) if app["id"] != "coming_soon" else None + # 添加到按钮布局 + btn_layout.addWidget(start_btn) + btn_layout.addWidget(detail_btn) - return card \ No newline at end of file + # 将按钮布局添加到容器 + layout.addWidget(btn_container, 0, Qt.AlignHCenter) + + return card + + 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.2", + "category": "媒体工具", + "features": [ + "支持常见视频格式", + "无损音频提取", + "批量处理功能", + "转换速度快" + ] + }, + { + "id": "text_summary", + "title": "文本摘要", + "desc": "自动提取文章关键信息", + "color": ("#F59E0B", "#D97706"), + "icon": "text_summary", + "author": "周华", + "upload_time": "2023-10-28", + "version": "2.1.0", + "category": "文档处理", + "features": [ + "多种摘要算法可选", + "自定义摘要长度", + "支持多语言处理", + "处理结果可导出" + ] + }, + { + "id": "color_picker", + "title": "颜色提取器", + "desc": "从图像中提取配色方案", + "color": ("#EC4899", "#DB2777"), + "icon": "color_picker", + "author": "吴敏", + "upload_time": "2023-11-05", + "version": "1.0.0", + "category": "图像处理", + "features": [ + "自动分析主色调", + "提取多种配色方案", + "支持导出配色代码", + "直观的颜色展示" + ] + } + ] + + + def get_styles(self): + return """ + /* ===== 应用中心页面样式 ===== */ + #AppCenterPage { + 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(255, 255, 255, 0.15); + border-radius: 12px; + min-width: 300px; + } + + #searchInput { + background: transparent; + border: none; + color: white; + font-size: 16px; + padding: 8px 0; + } + + #searchInput::placeholder { + color: rgba(255, 255, 255, 0.5); + } + + #searchBtn { + background: transparent; + border: none; + padding: 0; + } + + #searchBtn:hover { + background-color: rgba(255, 255, 255, 0.1); + border-radius: 6px; + } + + #categoryBtn { + background-color: rgba(74, 85, 104, 0.5); + color: rgba(255, 255, 255, 0.8); + border-radius: 8px; + padding: 8px 20px; + font-size: 15px; + transition: all 0.3s ease; + } + + #categoryBtn[active="true"] { + background-color: #4f46e5; + color: white; + } + + #categoryBtn:hover { + background-color: rgba(74, 85, 104, 0.7); + } + + #noResult { + font-size: 20px; + color: rgba(255, 255, 255, 0.5); + padding: 50px; + } + + /* ===== 加载动画样式 ===== */ + #loadingText { + font-size: 24px; + color: rgba(255, 255, 255, 0.7); + margin-top: 20px; + text-align: center; + letter-spacing: 2px; + } + + #loadingLabel { + margin: 0; + padding: 0; + } + + /* ===== 应用卡片样式 ===== */ + #appCard { + border: 1px solid rgba(255, 255, 255, 0.1); + } + + #appCardTitle { + font-size: 22px; + font-weight: 700; + color: white; + text-align: center; + margin-top: 15px; + letter-spacing: 0.5px; + padding: 0 10px; /* 添加内边距防止文字溢出 */ + } + + #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.15); /* 半透明背景 */ + color: white; + border-radius: 12px; + padding: 10px 15px; /* 减少内边距 */ + font-weight: 500; + border: 1px solid rgba(255, 255, 255, 0.2); + min-width: 80px; /* 减小最小宽度 */ + transition: all 0.3s ease; + font-size: 14px; /* 减小字体大小 */ + } + + #appCardBtn:hover { + background-color: rgba(255, 255, 255, 0.25); + transform: translateY(-2px); + } + + #appCardIcon { + background-color: rgba(255, 255, 255, 0.15); + border-radius: 50%; + transition: all 0.3s ease; + } + + #appCardIcon:hover { + transform: scale(1.05); + } + """ \ No newline at end of file diff --git a/v1/views/home.py b/v1/views/home.py new file mode 100644 index 0000000..c60f22b --- /dev/null +++ b/v1/views/home.py @@ -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']) \ No newline at end of file diff --git a/v1/views/login.py b/v1/views/login.py index e8f2720..3f5291a 100644 --- a/v1/views/login.py +++ b/v1/views/login.py @@ -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) \ No newline at end of file + 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_()) \ No newline at end of file diff --git a/v1/views/nav.py b/v1/views/nav.py index e196113..3e3258b 100644 --- a/v1/views/nav.py +++ b/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 \ No newline at end of file diff --git a/v1/views/word_to_pdf.py b/v1/views/word_to_pdf.py index f4c7608..267a90b 100644 --- a/v1/views/word_to_pdf.py +++ b/v1/views/word_to_pdf.py @@ -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}") \ No newline at end of file + 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) \ No newline at end of file